@agimon-ai/doompi 0.0.1-alpha.56 → 0.0.1-alpha.58

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
- const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./syncRegistration.cjs"),n=require("./syncState.cjs"),r=require("./bootstrapLocator.cjs");let i=require("node:path");i=e.__toESM(i,1);let a=require("node:fs");a=e.__toESM(a,1);let o=require("node:os");o=e.__toESM(o,1);function s(e){let s=e.homeDirectory??o.default.homedir(),c=[],l,u;try{u=t.readSyncRegistration(e.repoRoot,s),l=u?n.readSyncState(e.repoRoot,s):void 0}catch{l=void 0,u=void 0}if(!l||!u)return{fresh:!1,reasons:[`never-synced`]};let d;try{d=n.computeInputsHash(e.repoRoot,l.selection,s)}catch{c.push(`configuration-changed`)}d!==void 0&&d!==l.inputsHash&&c.push(`configuration-changed`),l.webSourcesHash!==void 0&&n.computeWebSourcesHash(l.resolved)!==l.webSourcesHash&&c.push(`code-changed`);try{r.readBootstrapStatus(e.repoRoot,e.expectedBootstrapEntry,s).fresh||c.push(`runtime-stale`)}catch{c.push(`runtime-stale`)}return u.webDirectory!==null&&(!a.default.existsSync(i.default.join(u.webDirectory,`index.html`))||!a.default.existsSync(i.default.join(i.default.dirname(u.webDirectory),`plugins`,`composition.js`))||!a.default.existsSync(i.default.join(i.default.dirname(u.webDirectory),`plugins`,`manifest.json`)))&&c.push(`cockpit-bundle-missing`),a.default.existsSync(u.apiDirectory)||c.push(`package-apis-missing`),{fresh:c.length===0,reasons:c,recordedInputsHash:l.inputsHash,...d===void 0?{}:{currentInputsHash:d}}}function c(e){if(e.fresh)return`the repository is synced`;let t={"never-synced":`it has never been synced`,"configuration-changed":`its configuration changed since the last sync`,"code-changed":`its cockpit sources changed since the last sync`,"runtime-stale":`its precompiled runtime is out of date`,"cockpit-bundle-missing":`the cockpit bundle is missing`,"package-apis-missing":`the package API routes are missing`};return e.reasons.map(e=>t[e]).join(`, `)}exports.describeSyncDrift=c,exports.readSyncDrift=s;
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./syncRegistration.cjs"),n=require("./syncState.cjs"),r=require("./bootstrapLocator.cjs");let i=require("node:path");i=e.__toESM(i,1);let a=require("node:fs");a=e.__toESM(a,1);let o=require("node:os");o=e.__toESM(o,1);function s(e){let s=e.homeDirectory??o.default.homedir(),c=[],l,u;try{u=t.readSyncRegistration(e.repoRoot,s),l=u?n.readSyncState(e.repoRoot,s):void 0}catch{l=void 0,u=void 0}if(!l||!u)return{fresh:!1,reasons:[`never-synced`]};let d;try{d=n.computeInputsHash(e.repoRoot,l.selection,s)}catch{c.push(`configuration-changed`)}d!==void 0&&d!==l.inputsHash&&c.push(`configuration-changed`),l.webSourcesHash!==void 0&&n.computeWebSourcesHash(l.resolved)!==l.webSourcesHash&&c.push(`code-changed`);try{r.readBootstrapStatus(e.repoRoot,e.expectedBootstrapEntry,s).fresh||c.push(`runtime-stale`)}catch{c.push(`runtime-stale`)}return(u.webDirectory===null&&e.requireWebBundle||u.webDirectory!==null&&(!a.default.existsSync(i.default.join(u.webDirectory,`index.html`))||!a.default.existsSync(i.default.join(i.default.dirname(u.webDirectory),`plugins`,`composition.js`))||!a.default.existsSync(i.default.join(i.default.dirname(u.webDirectory),`plugins`,`manifest.json`))))&&c.push(`cockpit-bundle-missing`),a.default.existsSync(u.apiDirectory)||c.push(`package-apis-missing`),{fresh:c.length===0,reasons:c,recordedInputsHash:l.inputsHash,...d===void 0?{}:{currentInputsHash:d}}}function c(e){if(e.fresh)return`the repository is synced`;let t={"never-synced":`it has never been synced`,"configuration-changed":`its configuration changed since the last sync`,"code-changed":`its cockpit sources changed since the last sync`,"runtime-stale":`its precompiled runtime is out of date`,"cockpit-bundle-missing":`the cockpit bundle is missing`,"package-apis-missing":`the package API routes are missing`};return e.reasons.map(e=>t[e]).join(`, `)}exports.describeSyncDrift=c,exports.readSyncDrift=s;
2
2
  //# sourceMappingURL=syncDrift.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"syncDrift.cjs","names":["os","readSyncRegistration","readSyncState","computeInputsHash","computeWebSourcesHash","readBootstrapStatus","fs","path"],"sources":["../../../src/adapters/syncDrift.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { readBootstrapStatus } from './bootstrapLocator.ts';\nimport { computeInputsHash, computeWebSourcesHash, readSyncState, type SyncState } from './syncState.ts';\nimport { readSyncRegistration } from './syncRegistration.ts';\n\nexport type SyncDriftReason =\n | 'never-synced'\n | 'configuration-changed'\n | 'code-changed'\n | 'runtime-stale'\n | 'cockpit-bundle-missing'\n | 'package-apis-missing';\n\nexport interface SyncDrift {\n /** True when nothing needs syncing before a session starts. */\n fresh: boolean;\n reasons: SyncDriftReason[];\n /** The recorded inputs hash, for a caller that wants to log what moved. */\n recordedInputsHash?: string;\n currentInputsHash?: string;\n}\n\nexport interface ReadSyncDriftOptions {\n repoRoot: string;\n /** Launcher-owned Doom entry used to build this repository's generated bootstrap. */\n expectedBootstrapEntry?: string;\n homeDirectory?: string;\n}\n\n/**\n * Whether this repository is synced for the composition it would launch.\n *\n * Sync produces three things a session or the cockpit reads later: the\n * resolved composition, the plugin bundle the browser loads, and the package\n * API routes each host mounts. A session started against any of them stale\n * runs, but quietly wrong: the cockpit shows no plugin surfaces and package\n * APIs answer nothing, with nothing in the log to say why.\n *\n * The hash is the one sync already records. It covers every `.doom` document\n * in both the repository and the home directory, the MCP configuration, the\n * plugin catalogue, and each profile's persona files, so an edit to any of\n * them reads as drift without this having to know what changed.\n */\nexport function readSyncDrift(options: ReadSyncDriftOptions): SyncDrift {\n const homeDirectory = options.homeDirectory ?? os.homedir();\n const reasons: SyncDriftReason[] = [];\n\n let state: SyncState | undefined;\n let registration: ReturnType<typeof readSyncRegistration>;\n try {\n registration = readSyncRegistration(options.repoRoot, homeDirectory);\n state = registration ? readSyncState(options.repoRoot, homeDirectory) : undefined;\n } catch {\n // Unreadable or invalid registration is indistinguishable from absent for\n // this purpose: the next session needs a sync before it can be trusted.\n state = undefined;\n registration = undefined;\n }\n if (!state || !registration) return { fresh: false, reasons: ['never-synced'] };\n\n let currentInputsHash: string | undefined;\n try {\n currentInputsHash = computeInputsHash(options.repoRoot, state.selection, homeDirectory);\n } catch {\n // A malformed document cannot be hashed, and syncing is what reports it.\n reasons.push('configuration-changed');\n }\n if (currentInputsHash !== undefined && currentInputsHash !== state.inputsHash) {\n reasons.push('configuration-changed');\n }\n // Cockpit sources are compiled straight from each package's web/ folder, so a\n // rebuilt plugin surface is a real change that no configuration hash sees. A\n // state recorded before this was tracked has nothing to compare and is left\n // alone rather than being called stale on sight.\n if (state.webSourcesHash !== undefined && computeWebSourcesHash(state.resolved) !== state.webSourcesHash) {\n reasons.push('code-changed');\n }\n // The same question the `--check` path asks, so the cockpit and the CLI\n // cannot disagree about whether one repository is synced.\n try {\n if (!readBootstrapStatus(options.repoRoot, options.expectedBootstrapEntry, homeDirectory).fresh) {\n reasons.push('runtime-stale');\n }\n } catch {\n // An unreadable bootstrap record is exactly as unusable as a stale one, and\n // syncing is what reports the underlying cause.\n reasons.push('runtime-stale');\n }\n if (\n registration.webDirectory !== null &&\n (!fs.existsSync(path.join(registration.webDirectory, 'index.html')) ||\n !fs.existsSync(path.join(path.dirname(registration.webDirectory), 'plugins', 'composition.js')) ||\n !fs.existsSync(path.join(path.dirname(registration.webDirectory), 'plugins', 'manifest.json')))\n ) {\n reasons.push('cockpit-bundle-missing');\n }\n if (!fs.existsSync(registration.apiDirectory)) reasons.push('package-apis-missing');\n\n return {\n fresh: reasons.length === 0,\n reasons,\n recordedInputsHash: state.inputsHash,\n ...(currentInputsHash === undefined ? {} : { currentInputsHash }),\n };\n}\n\n/** A one-line account of what drifted, for a log the person running the hub reads. */\nexport function describeSyncDrift(drift: SyncDrift): string {\n if (drift.fresh) return 'the repository is synced';\n const detail: Record<SyncDriftReason, string> = {\n 'never-synced': 'it has never been synced',\n 'configuration-changed': 'its configuration changed since the last sync',\n 'code-changed': 'its cockpit sources changed since the last sync',\n 'runtime-stale': 'its precompiled runtime is out of date',\n 'cockpit-bundle-missing': 'the cockpit bundle is missing',\n 'package-apis-missing': 'the package API routes are missing',\n };\n return drift.reasons.map((reason) => detail[reason]).join(', ');\n}\n"],"mappings":"6RA6CA,SAAgB,EAAc,EAA0C,CACtE,IAAM,EAAgB,EAAQ,eAAiBA,EAAAA,QAAG,QAAQ,EACpD,EAA6B,CAAC,EAEhC,EACA,EACJ,GAAI,CACF,EAAeC,EAAAA,qBAAqB,EAAQ,SAAU,CAAa,EACnE,EAAQ,EAAeC,EAAAA,cAAc,EAAQ,SAAU,CAAa,EAAI,IAAA,EAC1E,MAAQ,CAGN,EAAQ,IAAA,GACR,EAAe,IAAA,EACjB,CACA,GAAI,CAAC,GAAS,CAAC,EAAc,MAAO,CAAE,MAAO,GAAO,QAAS,CAAC,cAAc,CAAE,EAE9E,IAAI,EACJ,GAAI,CACF,EAAoBC,EAAAA,kBAAkB,EAAQ,SAAU,EAAM,UAAW,CAAa,CACxF,MAAQ,CAEN,EAAQ,KAAK,uBAAuB,CACtC,CACI,IAAsB,IAAA,IAAa,IAAsB,EAAM,YACjE,EAAQ,KAAK,uBAAuB,EAMlC,EAAM,iBAAmB,IAAA,IAAaC,EAAAA,sBAAsB,EAAM,QAAQ,IAAM,EAAM,gBACxF,EAAQ,KAAK,cAAc,EAI7B,GAAI,CACGC,EAAAA,oBAAoB,EAAQ,SAAU,EAAQ,uBAAwB,CAAa,CAAC,CAAC,OACxF,EAAQ,KAAK,eAAe,CAEhC,MAAQ,CAGN,EAAQ,KAAK,eAAe,CAC9B,CAWA,OATE,EAAa,eAAiB,OAC7B,CAACC,EAAAA,QAAG,WAAWC,EAAAA,QAAK,KAAK,EAAa,aAAc,YAAY,CAAC,GAChE,CAACD,EAAAA,QAAG,WAAWC,EAAAA,QAAK,KAAKA,EAAAA,QAAK,QAAQ,EAAa,YAAY,EAAG,UAAW,gBAAgB,CAAC,GAC9F,CAACD,EAAAA,QAAG,WAAWC,EAAAA,QAAK,KAAKA,EAAAA,QAAK,QAAQ,EAAa,YAAY,EAAG,UAAW,eAAe,CAAC,IAE/F,EAAQ,KAAK,wBAAwB,EAElCD,EAAAA,QAAG,WAAW,EAAa,YAAY,GAAG,EAAQ,KAAK,sBAAsB,EAE3E,CACL,MAAO,EAAQ,SAAW,EAC1B,UACA,mBAAoB,EAAM,WAC1B,GAAI,IAAsB,IAAA,GAAY,CAAC,EAAI,CAAE,mBAAkB,CACjE,CACF,CAGA,SAAgB,EAAkB,EAA0B,CAC1D,GAAI,EAAM,MAAO,MAAO,2BACxB,IAAM,EAA0C,CAC9C,eAAgB,2BAChB,wBAAyB,gDACzB,eAAgB,kDAChB,gBAAiB,yCACjB,yBAA0B,gCAC1B,uBAAwB,oCAC1B,EACA,OAAO,EAAM,QAAQ,IAAK,GAAW,EAAO,EAAO,CAAC,CAAC,KAAK,IAAI,CAChE"}
1
+ {"version":3,"file":"syncDrift.cjs","names":["os","readSyncRegistration","readSyncState","computeInputsHash","computeWebSourcesHash","readBootstrapStatus","fs","path"],"sources":["../../../src/adapters/syncDrift.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { readBootstrapStatus } from './bootstrapLocator.ts';\nimport { computeInputsHash, computeWebSourcesHash, readSyncState, type SyncState } from './syncState.ts';\nimport { readSyncRegistration } from './syncRegistration.ts';\n\nexport type SyncDriftReason =\n | 'never-synced'\n | 'configuration-changed'\n | 'code-changed'\n | 'runtime-stale'\n | 'cockpit-bundle-missing'\n | 'package-apis-missing';\n\nexport interface SyncDrift {\n /** True when nothing needs syncing before a session starts. */\n fresh: boolean;\n reasons: SyncDriftReason[];\n /** The recorded inputs hash, for a caller that wants to log what moved. */\n recordedInputsHash?: string;\n currentInputsHash?: string;\n}\n\nexport interface ReadSyncDriftOptions {\n repoRoot: string;\n /** Launcher-owned Doom entry used to build this repository's generated bootstrap. */\n expectedBootstrapEntry?: string;\n homeDirectory?: string;\n /** Web hosts cannot reuse a CLI-only generation without plugin artifacts. */\n requireWebBundle?: boolean;\n}\n\n/**\n * Whether this repository is synced for the composition it would launch.\n *\n * Sync produces three things a session or the cockpit reads later: the\n * resolved composition, the plugin bundle the browser loads, and the package\n * API routes each host mounts. A session started against any of them stale\n * runs, but quietly wrong: the cockpit shows no plugin surfaces and package\n * APIs answer nothing, with nothing in the log to say why.\n *\n * The hash is the one sync already records. It covers every `.doom` document\n * in both the repository and the home directory, the MCP configuration, the\n * plugin catalogue, and each profile's persona files, so an edit to any of\n * them reads as drift without this having to know what changed.\n */\nexport function readSyncDrift(options: ReadSyncDriftOptions): SyncDrift {\n const homeDirectory = options.homeDirectory ?? os.homedir();\n const reasons: SyncDriftReason[] = [];\n\n let state: SyncState | undefined;\n let registration: ReturnType<typeof readSyncRegistration>;\n try {\n registration = readSyncRegistration(options.repoRoot, homeDirectory);\n state = registration ? readSyncState(options.repoRoot, homeDirectory) : undefined;\n } catch {\n // Unreadable or invalid registration is indistinguishable from absent for\n // this purpose: the next session needs a sync before it can be trusted.\n state = undefined;\n registration = undefined;\n }\n if (!state || !registration) return { fresh: false, reasons: ['never-synced'] };\n\n let currentInputsHash: string | undefined;\n try {\n currentInputsHash = computeInputsHash(options.repoRoot, state.selection, homeDirectory);\n } catch {\n // A malformed document cannot be hashed, and syncing is what reports it.\n reasons.push('configuration-changed');\n }\n if (currentInputsHash !== undefined && currentInputsHash !== state.inputsHash) {\n reasons.push('configuration-changed');\n }\n // Cockpit sources are compiled straight from each package's web/ folder, so a\n // rebuilt plugin surface is a real change that no configuration hash sees. A\n // state recorded before this was tracked has nothing to compare and is left\n // alone rather than being called stale on sight.\n if (state.webSourcesHash !== undefined && computeWebSourcesHash(state.resolved) !== state.webSourcesHash) {\n reasons.push('code-changed');\n }\n // The same question the `--check` path asks, so the cockpit and the CLI\n // cannot disagree about whether one repository is synced.\n try {\n if (!readBootstrapStatus(options.repoRoot, options.expectedBootstrapEntry, homeDirectory).fresh) {\n reasons.push('runtime-stale');\n }\n } catch {\n // An unreadable bootstrap record is exactly as unusable as a stale one, and\n // syncing is what reports the underlying cause.\n reasons.push('runtime-stale');\n }\n if (\n (registration.webDirectory === null && options.requireWebBundle) ||\n (registration.webDirectory !== null &&\n (!fs.existsSync(path.join(registration.webDirectory, 'index.html')) ||\n !fs.existsSync(path.join(path.dirname(registration.webDirectory), 'plugins', 'composition.js')) ||\n !fs.existsSync(path.join(path.dirname(registration.webDirectory), 'plugins', 'manifest.json'))))\n ) {\n reasons.push('cockpit-bundle-missing');\n }\n if (!fs.existsSync(registration.apiDirectory)) reasons.push('package-apis-missing');\n\n return {\n fresh: reasons.length === 0,\n reasons,\n recordedInputsHash: state.inputsHash,\n ...(currentInputsHash === undefined ? {} : { currentInputsHash }),\n };\n}\n\n/** A one-line account of what drifted, for a log the person running the hub reads. */\nexport function describeSyncDrift(drift: SyncDrift): string {\n if (drift.fresh) return 'the repository is synced';\n const detail: Record<SyncDriftReason, string> = {\n 'never-synced': 'it has never been synced',\n 'configuration-changed': 'its configuration changed since the last sync',\n 'code-changed': 'its cockpit sources changed since the last sync',\n 'runtime-stale': 'its precompiled runtime is out of date',\n 'cockpit-bundle-missing': 'the cockpit bundle is missing',\n 'package-apis-missing': 'the package API routes are missing',\n };\n return drift.reasons.map((reason) => detail[reason]).join(', ');\n}\n"],"mappings":"6RA+CA,SAAgB,EAAc,EAA0C,CACtE,IAAM,EAAgB,EAAQ,eAAiBA,EAAAA,QAAG,QAAQ,EACpD,EAA6B,CAAC,EAEhC,EACA,EACJ,GAAI,CACF,EAAeC,EAAAA,qBAAqB,EAAQ,SAAU,CAAa,EACnE,EAAQ,EAAeC,EAAAA,cAAc,EAAQ,SAAU,CAAa,EAAI,IAAA,EAC1E,MAAQ,CAGN,EAAQ,IAAA,GACR,EAAe,IAAA,EACjB,CACA,GAAI,CAAC,GAAS,CAAC,EAAc,MAAO,CAAE,MAAO,GAAO,QAAS,CAAC,cAAc,CAAE,EAE9E,IAAI,EACJ,GAAI,CACF,EAAoBC,EAAAA,kBAAkB,EAAQ,SAAU,EAAM,UAAW,CAAa,CACxF,MAAQ,CAEN,EAAQ,KAAK,uBAAuB,CACtC,CACI,IAAsB,IAAA,IAAa,IAAsB,EAAM,YACjE,EAAQ,KAAK,uBAAuB,EAMlC,EAAM,iBAAmB,IAAA,IAAaC,EAAAA,sBAAsB,EAAM,QAAQ,IAAM,EAAM,gBACxF,EAAQ,KAAK,cAAc,EAI7B,GAAI,CACGC,EAAAA,oBAAoB,EAAQ,SAAU,EAAQ,uBAAwB,CAAa,CAAC,CAAC,OACxF,EAAQ,KAAK,eAAe,CAEhC,MAAQ,CAGN,EAAQ,KAAK,eAAe,CAC9B,CAYA,OAVG,EAAa,eAAiB,MAAQ,EAAQ,kBAC9C,EAAa,eAAiB,OAC5B,CAACC,EAAAA,QAAG,WAAWC,EAAAA,QAAK,KAAK,EAAa,aAAc,YAAY,CAAC,GAChE,CAACD,EAAAA,QAAG,WAAWC,EAAAA,QAAK,KAAKA,EAAAA,QAAK,QAAQ,EAAa,YAAY,EAAG,UAAW,gBAAgB,CAAC,GAC9F,CAACD,EAAAA,QAAG,WAAWC,EAAAA,QAAK,KAAKA,EAAAA,QAAK,QAAQ,EAAa,YAAY,EAAG,UAAW,eAAe,CAAC,KAEjG,EAAQ,KAAK,wBAAwB,EAElCD,EAAAA,QAAG,WAAW,EAAa,YAAY,GAAG,EAAQ,KAAK,sBAAsB,EAE3E,CACL,MAAO,EAAQ,SAAW,EAC1B,UACA,mBAAoB,EAAM,WAC1B,GAAI,IAAsB,IAAA,GAAY,CAAC,EAAI,CAAE,mBAAkB,CACjE,CACF,CAGA,SAAgB,EAAkB,EAA0B,CAC1D,GAAI,EAAM,MAAO,MAAO,2BACxB,IAAM,EAA0C,CAC9C,eAAgB,2BAChB,wBAAyB,gDACzB,eAAgB,kDAChB,gBAAiB,yCACjB,yBAA0B,gCAC1B,uBAAwB,oCAC1B,EACA,OAAO,EAAM,QAAQ,IAAK,GAAW,EAAO,EAAO,CAAC,CAAC,KAAK,IAAI,CAChE"}
@@ -13,6 +13,8 @@ interface ReadSyncDriftOptions {
13
13
  /** Launcher-owned Doom entry used to build this repository's generated bootstrap. */
14
14
  expectedBootstrapEntry?: string;
15
15
  homeDirectory?: string;
16
+ /** Web hosts cannot reuse a CLI-only generation without plugin artifacts. */
17
+ requireWebBundle?: boolean;
16
18
  }
17
19
  /**
18
20
  * Whether this repository is synced for the composition it would launch.
@@ -1 +1 @@
1
- {"version":3,"file":"syncDrift.d.cts","names":[],"sources":["../../../src/adapters/syncDrift.ts"],"mappings":";KAOY;UAQK;;EAEf;EACA,SAAS;;EAET;EACA;;UAGe;EACf;;EAEA;EACA;;;;;;;;;;;;;;;;iBAiBc,cAAc,SAAS,uBAAuB;;iBAgE9C,kBAAkB,OAAO"}
1
+ {"version":3,"file":"syncDrift.d.cts","names":[],"sources":["../../../src/adapters/syncDrift.ts"],"mappings":";KAOY;UAQK;;EAEf;EACA,SAAS;;EAET;EACA;;UAGe;EACf;;EAEA;EACA;;EAEA;;;;;;;;;;;;;;;;iBAiBc,cAAc,SAAS,uBAAuB;;iBAiE9C,kBAAkB,OAAO"}
@@ -13,6 +13,8 @@ interface ReadSyncDriftOptions {
13
13
  /** Launcher-owned Doom entry used to build this repository's generated bootstrap. */
14
14
  expectedBootstrapEntry?: string;
15
15
  homeDirectory?: string;
16
+ /** Web hosts cannot reuse a CLI-only generation without plugin artifacts. */
17
+ requireWebBundle?: boolean;
16
18
  }
17
19
  /**
18
20
  * Whether this repository is synced for the composition it would launch.
@@ -1 +1 @@
1
- {"version":3,"file":"syncDrift.d.mts","names":[],"sources":["../../../src/adapters/syncDrift.ts"],"mappings":";KAOY;UAQK;;EAEf;EACA,SAAS;;EAET;EACA;;UAGe;EACf;;EAEA;EACA;;;;;;;;;;;;;;;;iBAiBc,cAAc,SAAS,uBAAuB;;iBAgE9C,kBAAkB,OAAO"}
1
+ {"version":3,"file":"syncDrift.d.mts","names":[],"sources":["../../../src/adapters/syncDrift.ts"],"mappings":";KAOY;UAQK;;EAEf;EACA,SAAS;;EAET;EACA;;UAGe;EACf;;EAEA;EACA;;EAEA;;;;;;;;;;;;;;;;iBAiBc,cAAc,SAAS,uBAAuB;;iBAiE9C,kBAAkB,OAAO"}
@@ -1,2 +1,2 @@
1
- import{readSyncRegistration as e}from"./syncRegistration.mjs";import{computeInputsHash as t,computeWebSourcesHash as n,readSyncState as r}from"./syncState.mjs";import{readBootstrapStatus as i}from"./bootstrapLocator.mjs";import a from"node:path";import o from"node:fs";import s from"node:os";function c(c){let l=c.homeDirectory??s.homedir(),u=[],d,f;try{f=e(c.repoRoot,l),d=f?r(c.repoRoot,l):void 0}catch{d=void 0,f=void 0}if(!d||!f)return{fresh:!1,reasons:[`never-synced`]};let p;try{p=t(c.repoRoot,d.selection,l)}catch{u.push(`configuration-changed`)}p!==void 0&&p!==d.inputsHash&&u.push(`configuration-changed`),d.webSourcesHash!==void 0&&n(d.resolved)!==d.webSourcesHash&&u.push(`code-changed`);try{i(c.repoRoot,c.expectedBootstrapEntry,l).fresh||u.push(`runtime-stale`)}catch{u.push(`runtime-stale`)}return f.webDirectory!==null&&(!o.existsSync(a.join(f.webDirectory,`index.html`))||!o.existsSync(a.join(a.dirname(f.webDirectory),`plugins`,`composition.js`))||!o.existsSync(a.join(a.dirname(f.webDirectory),`plugins`,`manifest.json`)))&&u.push(`cockpit-bundle-missing`),o.existsSync(f.apiDirectory)||u.push(`package-apis-missing`),{fresh:u.length===0,reasons:u,recordedInputsHash:d.inputsHash,...p===void 0?{}:{currentInputsHash:p}}}function l(e){if(e.fresh)return`the repository is synced`;let t={"never-synced":`it has never been synced`,"configuration-changed":`its configuration changed since the last sync`,"code-changed":`its cockpit sources changed since the last sync`,"runtime-stale":`its precompiled runtime is out of date`,"cockpit-bundle-missing":`the cockpit bundle is missing`,"package-apis-missing":`the package API routes are missing`};return e.reasons.map(e=>t[e]).join(`, `)}export{l as describeSyncDrift,c as readSyncDrift};
1
+ import{readSyncRegistration as e}from"./syncRegistration.mjs";import{computeInputsHash as t,computeWebSourcesHash as n,readSyncState as r}from"./syncState.mjs";import{readBootstrapStatus as i}from"./bootstrapLocator.mjs";import a from"node:path";import o from"node:fs";import s from"node:os";function c(c){let l=c.homeDirectory??s.homedir(),u=[],d,f;try{f=e(c.repoRoot,l),d=f?r(c.repoRoot,l):void 0}catch{d=void 0,f=void 0}if(!d||!f)return{fresh:!1,reasons:[`never-synced`]};let p;try{p=t(c.repoRoot,d.selection,l)}catch{u.push(`configuration-changed`)}p!==void 0&&p!==d.inputsHash&&u.push(`configuration-changed`),d.webSourcesHash!==void 0&&n(d.resolved)!==d.webSourcesHash&&u.push(`code-changed`);try{i(c.repoRoot,c.expectedBootstrapEntry,l).fresh||u.push(`runtime-stale`)}catch{u.push(`runtime-stale`)}return(f.webDirectory===null&&c.requireWebBundle||f.webDirectory!==null&&(!o.existsSync(a.join(f.webDirectory,`index.html`))||!o.existsSync(a.join(a.dirname(f.webDirectory),`plugins`,`composition.js`))||!o.existsSync(a.join(a.dirname(f.webDirectory),`plugins`,`manifest.json`))))&&u.push(`cockpit-bundle-missing`),o.existsSync(f.apiDirectory)||u.push(`package-apis-missing`),{fresh:u.length===0,reasons:u,recordedInputsHash:d.inputsHash,...p===void 0?{}:{currentInputsHash:p}}}function l(e){if(e.fresh)return`the repository is synced`;let t={"never-synced":`it has never been synced`,"configuration-changed":`its configuration changed since the last sync`,"code-changed":`its cockpit sources changed since the last sync`,"runtime-stale":`its precompiled runtime is out of date`,"cockpit-bundle-missing":`the cockpit bundle is missing`,"package-apis-missing":`the package API routes are missing`};return e.reasons.map(e=>t[e]).join(`, `)}export{l as describeSyncDrift,c as readSyncDrift};
2
2
  //# sourceMappingURL=syncDrift.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"syncDrift.mjs","names":[],"sources":["../../../src/adapters/syncDrift.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { readBootstrapStatus } from './bootstrapLocator.ts';\nimport { computeInputsHash, computeWebSourcesHash, readSyncState, type SyncState } from './syncState.ts';\nimport { readSyncRegistration } from './syncRegistration.ts';\n\nexport type SyncDriftReason =\n | 'never-synced'\n | 'configuration-changed'\n | 'code-changed'\n | 'runtime-stale'\n | 'cockpit-bundle-missing'\n | 'package-apis-missing';\n\nexport interface SyncDrift {\n /** True when nothing needs syncing before a session starts. */\n fresh: boolean;\n reasons: SyncDriftReason[];\n /** The recorded inputs hash, for a caller that wants to log what moved. */\n recordedInputsHash?: string;\n currentInputsHash?: string;\n}\n\nexport interface ReadSyncDriftOptions {\n repoRoot: string;\n /** Launcher-owned Doom entry used to build this repository's generated bootstrap. */\n expectedBootstrapEntry?: string;\n homeDirectory?: string;\n}\n\n/**\n * Whether this repository is synced for the composition it would launch.\n *\n * Sync produces three things a session or the cockpit reads later: the\n * resolved composition, the plugin bundle the browser loads, and the package\n * API routes each host mounts. A session started against any of them stale\n * runs, but quietly wrong: the cockpit shows no plugin surfaces and package\n * APIs answer nothing, with nothing in the log to say why.\n *\n * The hash is the one sync already records. It covers every `.doom` document\n * in both the repository and the home directory, the MCP configuration, the\n * plugin catalogue, and each profile's persona files, so an edit to any of\n * them reads as drift without this having to know what changed.\n */\nexport function readSyncDrift(options: ReadSyncDriftOptions): SyncDrift {\n const homeDirectory = options.homeDirectory ?? os.homedir();\n const reasons: SyncDriftReason[] = [];\n\n let state: SyncState | undefined;\n let registration: ReturnType<typeof readSyncRegistration>;\n try {\n registration = readSyncRegistration(options.repoRoot, homeDirectory);\n state = registration ? readSyncState(options.repoRoot, homeDirectory) : undefined;\n } catch {\n // Unreadable or invalid registration is indistinguishable from absent for\n // this purpose: the next session needs a sync before it can be trusted.\n state = undefined;\n registration = undefined;\n }\n if (!state || !registration) return { fresh: false, reasons: ['never-synced'] };\n\n let currentInputsHash: string | undefined;\n try {\n currentInputsHash = computeInputsHash(options.repoRoot, state.selection, homeDirectory);\n } catch {\n // A malformed document cannot be hashed, and syncing is what reports it.\n reasons.push('configuration-changed');\n }\n if (currentInputsHash !== undefined && currentInputsHash !== state.inputsHash) {\n reasons.push('configuration-changed');\n }\n // Cockpit sources are compiled straight from each package's web/ folder, so a\n // rebuilt plugin surface is a real change that no configuration hash sees. A\n // state recorded before this was tracked has nothing to compare and is left\n // alone rather than being called stale on sight.\n if (state.webSourcesHash !== undefined && computeWebSourcesHash(state.resolved) !== state.webSourcesHash) {\n reasons.push('code-changed');\n }\n // The same question the `--check` path asks, so the cockpit and the CLI\n // cannot disagree about whether one repository is synced.\n try {\n if (!readBootstrapStatus(options.repoRoot, options.expectedBootstrapEntry, homeDirectory).fresh) {\n reasons.push('runtime-stale');\n }\n } catch {\n // An unreadable bootstrap record is exactly as unusable as a stale one, and\n // syncing is what reports the underlying cause.\n reasons.push('runtime-stale');\n }\n if (\n registration.webDirectory !== null &&\n (!fs.existsSync(path.join(registration.webDirectory, 'index.html')) ||\n !fs.existsSync(path.join(path.dirname(registration.webDirectory), 'plugins', 'composition.js')) ||\n !fs.existsSync(path.join(path.dirname(registration.webDirectory), 'plugins', 'manifest.json')))\n ) {\n reasons.push('cockpit-bundle-missing');\n }\n if (!fs.existsSync(registration.apiDirectory)) reasons.push('package-apis-missing');\n\n return {\n fresh: reasons.length === 0,\n reasons,\n recordedInputsHash: state.inputsHash,\n ...(currentInputsHash === undefined ? {} : { currentInputsHash }),\n };\n}\n\n/** A one-line account of what drifted, for a log the person running the hub reads. */\nexport function describeSyncDrift(drift: SyncDrift): string {\n if (drift.fresh) return 'the repository is synced';\n const detail: Record<SyncDriftReason, string> = {\n 'never-synced': 'it has never been synced',\n 'configuration-changed': 'its configuration changed since the last sync',\n 'code-changed': 'its cockpit sources changed since the last sync',\n 'runtime-stale': 'its precompiled runtime is out of date',\n 'cockpit-bundle-missing': 'the cockpit bundle is missing',\n 'package-apis-missing': 'the package API routes are missing',\n };\n return drift.reasons.map((reason) => detail[reason]).join(', ');\n}\n"],"mappings":"oSA6CA,SAAgB,EAAc,EAA0C,CACtE,IAAM,EAAgB,EAAQ,eAAiB,EAAG,QAAQ,EACpD,EAA6B,CAAC,EAEhC,EACA,EACJ,GAAI,CACF,EAAe,EAAqB,EAAQ,SAAU,CAAa,EACnE,EAAQ,EAAe,EAAc,EAAQ,SAAU,CAAa,EAAI,IAAA,EAC1E,MAAQ,CAGN,EAAQ,IAAA,GACR,EAAe,IAAA,EACjB,CACA,GAAI,CAAC,GAAS,CAAC,EAAc,MAAO,CAAE,MAAO,GAAO,QAAS,CAAC,cAAc,CAAE,EAE9E,IAAI,EACJ,GAAI,CACF,EAAoB,EAAkB,EAAQ,SAAU,EAAM,UAAW,CAAa,CACxF,MAAQ,CAEN,EAAQ,KAAK,uBAAuB,CACtC,CACI,IAAsB,IAAA,IAAa,IAAsB,EAAM,YACjE,EAAQ,KAAK,uBAAuB,EAMlC,EAAM,iBAAmB,IAAA,IAAa,EAAsB,EAAM,QAAQ,IAAM,EAAM,gBACxF,EAAQ,KAAK,cAAc,EAI7B,GAAI,CACG,EAAoB,EAAQ,SAAU,EAAQ,uBAAwB,CAAa,CAAC,CAAC,OACxF,EAAQ,KAAK,eAAe,CAEhC,MAAQ,CAGN,EAAQ,KAAK,eAAe,CAC9B,CAWA,OATE,EAAa,eAAiB,OAC7B,CAAC,EAAG,WAAW,EAAK,KAAK,EAAa,aAAc,YAAY,CAAC,GAChE,CAAC,EAAG,WAAW,EAAK,KAAK,EAAK,QAAQ,EAAa,YAAY,EAAG,UAAW,gBAAgB,CAAC,GAC9F,CAAC,EAAG,WAAW,EAAK,KAAK,EAAK,QAAQ,EAAa,YAAY,EAAG,UAAW,eAAe,CAAC,IAE/F,EAAQ,KAAK,wBAAwB,EAElC,EAAG,WAAW,EAAa,YAAY,GAAG,EAAQ,KAAK,sBAAsB,EAE3E,CACL,MAAO,EAAQ,SAAW,EAC1B,UACA,mBAAoB,EAAM,WAC1B,GAAI,IAAsB,IAAA,GAAY,CAAC,EAAI,CAAE,mBAAkB,CACjE,CACF,CAGA,SAAgB,EAAkB,EAA0B,CAC1D,GAAI,EAAM,MAAO,MAAO,2BACxB,IAAM,EAA0C,CAC9C,eAAgB,2BAChB,wBAAyB,gDACzB,eAAgB,kDAChB,gBAAiB,yCACjB,yBAA0B,gCAC1B,uBAAwB,oCAC1B,EACA,OAAO,EAAM,QAAQ,IAAK,GAAW,EAAO,EAAO,CAAC,CAAC,KAAK,IAAI,CAChE"}
1
+ {"version":3,"file":"syncDrift.mjs","names":[],"sources":["../../../src/adapters/syncDrift.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { readBootstrapStatus } from './bootstrapLocator.ts';\nimport { computeInputsHash, computeWebSourcesHash, readSyncState, type SyncState } from './syncState.ts';\nimport { readSyncRegistration } from './syncRegistration.ts';\n\nexport type SyncDriftReason =\n | 'never-synced'\n | 'configuration-changed'\n | 'code-changed'\n | 'runtime-stale'\n | 'cockpit-bundle-missing'\n | 'package-apis-missing';\n\nexport interface SyncDrift {\n /** True when nothing needs syncing before a session starts. */\n fresh: boolean;\n reasons: SyncDriftReason[];\n /** The recorded inputs hash, for a caller that wants to log what moved. */\n recordedInputsHash?: string;\n currentInputsHash?: string;\n}\n\nexport interface ReadSyncDriftOptions {\n repoRoot: string;\n /** Launcher-owned Doom entry used to build this repository's generated bootstrap. */\n expectedBootstrapEntry?: string;\n homeDirectory?: string;\n /** Web hosts cannot reuse a CLI-only generation without plugin artifacts. */\n requireWebBundle?: boolean;\n}\n\n/**\n * Whether this repository is synced for the composition it would launch.\n *\n * Sync produces three things a session or the cockpit reads later: the\n * resolved composition, the plugin bundle the browser loads, and the package\n * API routes each host mounts. A session started against any of them stale\n * runs, but quietly wrong: the cockpit shows no plugin surfaces and package\n * APIs answer nothing, with nothing in the log to say why.\n *\n * The hash is the one sync already records. It covers every `.doom` document\n * in both the repository and the home directory, the MCP configuration, the\n * plugin catalogue, and each profile's persona files, so an edit to any of\n * them reads as drift without this having to know what changed.\n */\nexport function readSyncDrift(options: ReadSyncDriftOptions): SyncDrift {\n const homeDirectory = options.homeDirectory ?? os.homedir();\n const reasons: SyncDriftReason[] = [];\n\n let state: SyncState | undefined;\n let registration: ReturnType<typeof readSyncRegistration>;\n try {\n registration = readSyncRegistration(options.repoRoot, homeDirectory);\n state = registration ? readSyncState(options.repoRoot, homeDirectory) : undefined;\n } catch {\n // Unreadable or invalid registration is indistinguishable from absent for\n // this purpose: the next session needs a sync before it can be trusted.\n state = undefined;\n registration = undefined;\n }\n if (!state || !registration) return { fresh: false, reasons: ['never-synced'] };\n\n let currentInputsHash: string | undefined;\n try {\n currentInputsHash = computeInputsHash(options.repoRoot, state.selection, homeDirectory);\n } catch {\n // A malformed document cannot be hashed, and syncing is what reports it.\n reasons.push('configuration-changed');\n }\n if (currentInputsHash !== undefined && currentInputsHash !== state.inputsHash) {\n reasons.push('configuration-changed');\n }\n // Cockpit sources are compiled straight from each package's web/ folder, so a\n // rebuilt plugin surface is a real change that no configuration hash sees. A\n // state recorded before this was tracked has nothing to compare and is left\n // alone rather than being called stale on sight.\n if (state.webSourcesHash !== undefined && computeWebSourcesHash(state.resolved) !== state.webSourcesHash) {\n reasons.push('code-changed');\n }\n // The same question the `--check` path asks, so the cockpit and the CLI\n // cannot disagree about whether one repository is synced.\n try {\n if (!readBootstrapStatus(options.repoRoot, options.expectedBootstrapEntry, homeDirectory).fresh) {\n reasons.push('runtime-stale');\n }\n } catch {\n // An unreadable bootstrap record is exactly as unusable as a stale one, and\n // syncing is what reports the underlying cause.\n reasons.push('runtime-stale');\n }\n if (\n (registration.webDirectory === null && options.requireWebBundle) ||\n (registration.webDirectory !== null &&\n (!fs.existsSync(path.join(registration.webDirectory, 'index.html')) ||\n !fs.existsSync(path.join(path.dirname(registration.webDirectory), 'plugins', 'composition.js')) ||\n !fs.existsSync(path.join(path.dirname(registration.webDirectory), 'plugins', 'manifest.json'))))\n ) {\n reasons.push('cockpit-bundle-missing');\n }\n if (!fs.existsSync(registration.apiDirectory)) reasons.push('package-apis-missing');\n\n return {\n fresh: reasons.length === 0,\n reasons,\n recordedInputsHash: state.inputsHash,\n ...(currentInputsHash === undefined ? {} : { currentInputsHash }),\n };\n}\n\n/** A one-line account of what drifted, for a log the person running the hub reads. */\nexport function describeSyncDrift(drift: SyncDrift): string {\n if (drift.fresh) return 'the repository is synced';\n const detail: Record<SyncDriftReason, string> = {\n 'never-synced': 'it has never been synced',\n 'configuration-changed': 'its configuration changed since the last sync',\n 'code-changed': 'its cockpit sources changed since the last sync',\n 'runtime-stale': 'its precompiled runtime is out of date',\n 'cockpit-bundle-missing': 'the cockpit bundle is missing',\n 'package-apis-missing': 'the package API routes are missing',\n };\n return drift.reasons.map((reason) => detail[reason]).join(', ');\n}\n"],"mappings":"oSA+CA,SAAgB,EAAc,EAA0C,CACtE,IAAM,EAAgB,EAAQ,eAAiB,EAAG,QAAQ,EACpD,EAA6B,CAAC,EAEhC,EACA,EACJ,GAAI,CACF,EAAe,EAAqB,EAAQ,SAAU,CAAa,EACnE,EAAQ,EAAe,EAAc,EAAQ,SAAU,CAAa,EAAI,IAAA,EAC1E,MAAQ,CAGN,EAAQ,IAAA,GACR,EAAe,IAAA,EACjB,CACA,GAAI,CAAC,GAAS,CAAC,EAAc,MAAO,CAAE,MAAO,GAAO,QAAS,CAAC,cAAc,CAAE,EAE9E,IAAI,EACJ,GAAI,CACF,EAAoB,EAAkB,EAAQ,SAAU,EAAM,UAAW,CAAa,CACxF,MAAQ,CAEN,EAAQ,KAAK,uBAAuB,CACtC,CACI,IAAsB,IAAA,IAAa,IAAsB,EAAM,YACjE,EAAQ,KAAK,uBAAuB,EAMlC,EAAM,iBAAmB,IAAA,IAAa,EAAsB,EAAM,QAAQ,IAAM,EAAM,gBACxF,EAAQ,KAAK,cAAc,EAI7B,GAAI,CACG,EAAoB,EAAQ,SAAU,EAAQ,uBAAwB,CAAa,CAAC,CAAC,OACxF,EAAQ,KAAK,eAAe,CAEhC,MAAQ,CAGN,EAAQ,KAAK,eAAe,CAC9B,CAYA,OAVG,EAAa,eAAiB,MAAQ,EAAQ,kBAC9C,EAAa,eAAiB,OAC5B,CAAC,EAAG,WAAW,EAAK,KAAK,EAAa,aAAc,YAAY,CAAC,GAChE,CAAC,EAAG,WAAW,EAAK,KAAK,EAAK,QAAQ,EAAa,YAAY,EAAG,UAAW,gBAAgB,CAAC,GAC9F,CAAC,EAAG,WAAW,EAAK,KAAK,EAAK,QAAQ,EAAa,YAAY,EAAG,UAAW,eAAe,CAAC,KAEjG,EAAQ,KAAK,wBAAwB,EAElC,EAAG,WAAW,EAAa,YAAY,GAAG,EAAQ,KAAK,sBAAsB,EAE3E,CACL,MAAO,EAAQ,SAAW,EAC1B,UACA,mBAAoB,EAAM,WAC1B,GAAI,IAAsB,IAAA,GAAY,CAAC,EAAI,CAAE,mBAAkB,CACjE,CACF,CAGA,SAAgB,EAAkB,EAA0B,CAC1D,GAAI,EAAM,MAAO,MAAO,2BACxB,IAAM,EAA0C,CAC9C,eAAgB,2BAChB,wBAAyB,gDACzB,eAAgB,kDAChB,gBAAiB,yCACjB,yBAA0B,gCAC1B,uBAAwB,oCAC1B,EACA,OAAO,EAAM,QAAQ,IAAK,GAAW,EAAO,EAAO,CAAC,CAAC,KAAK,IAAI,CAChE"}
@@ -4,7 +4,7 @@ const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../adapters/r
4
4
  `)}`)}if(c){let e=(0,D.loadMajorModesConfig)(y,g),t=h.missingLayerPackageSpecifiers(e,Object.keys(e.layers),a.createLayerResolvers(y));if(t.length>0)return s.write(`doompi sync is out of date:\n${t.map(e=>` configured package is not installed: ${e}`).join(`
5
5
  `)}\n`),1;let r;try{r=o.readLocatedSyncState(y,g)}catch(e){let t=e instanceof Error?e.message:String(e);return s.write(`doompi sync is out of date:\n ${t}\n`),1}let i=U(y,w.options,g),c=G(y,O,r?.state,n,this.settingsMode,i);return c.length===0?(s.write(`doompi sync is up to date
6
6
  `),0):(s.write(`doompi sync is out of date:\n${c.map(e=>` ${e}`).join(`
7
- `)}\n`),1)}if(!p&&v.readSyncDrift({repoRoot:y,homeDirectory:g}).fresh)return s.write(`doompi sync is already up to date
8
- `),0;let A=new x.SyncProgress(s),j=this.lockHeld?void 0:await r.acquireSyncLocationLock(r.resolveSyncLocation(y,g)),P;try{if(!p&&v.readSyncDrift({repoRoot:y,homeDirectory:g}).fresh)return s.write(`doompi sync is already up to date
9
- `),0;P=await this.stage(y,w.options,n,g,A)}finally{await j?.()}return K(y,s),s.write(q(P,this.settingsMode===`embedded`?`dpi`:`pi`)),0}async stage(e,t,n,s,c){let l=r.resolveSyncLocation(e,s),u=`${Date.now().toString(36)}-${C.default.randomUUID()}`,d=r.syncGenerationDirectory(l,u);await w.default.promises.mkdir(l.generationsDirectory,{recursive:!0,mode:448}),await w.default.promises.mkdir(d,{mode:448});try{let e=c.start(I,`resolving the matrix and staging resources`),r=await m.buildHarnessContext({...t,repoRoot:l.root,homeDirectory:s,cwd:l.root,resourceDirectory:d});await h.ensureLayerPackages({repoRoot:l.root,config:r.majorModesConfig,layers:Object.keys(r.majorModesConfig.layers),environment:n}),e(`${String(r.resources.skillCount)} skills, ${String(r.resources.agentCount)} agents`);let p=H(t),v=a.createLayerResolvers(l.root),x=o.recordResolvedEntries(r.majorModesConfig,v),C=U(l.root,t,s),w=f.piAgentDirectory(n,s),T=S.default.join(f.piThemeDirectory(w),`${A.DEFAULT_THEME_NAME}.json`),E=this.settingsMode===`persisted`?T:r.defaultThemePath,D={version:13,root:l.root,identity:l.identity,inputsHash:o.computeInputsHash(l.root,p,s),webSourcesHash:o.computeWebSourcesHash(x),compositionFingerprint:C,selection:p,env:B(r.environment),fileState:{profileEnvironment:(0,j.loadHarnessState)(r.environment).state.profileEnvironment,pluginHooks:r.resources.pluginHooks,mcpProjection:r.resources.mcpProjection},resolved:x,baseline:{mcpConfigPath:r.resources.mcpConfigPath,personaFile:r.environment.DOOMPI_PERSONA_FILE,themePath:E,themeName:A.DEFAULT_THEME_NAME}},O=c.start(`runtime`,`precompiling the mode bundles`),k=await _.buildSyncedRuntime(l.root,n,s,{state:D,directory:d});O(`${String(Object.keys(k.bundles).length)} mode bundles`);let M=await o.writeSyncState(l.root,k.state,s,S.default.join(d,`state.json`)),N=c.start(`web`,`bundling the web cockpit plugins`),P=await b.syncWebBundle({repoRoot:l.root,resolvedEntries:k.state.resolved,environment:n,outputDirectory:S.default.join(d,`web-bundle`),onNotice:e=>c.line(`web`,e)});if(P.status===`failed`)throw Error(`Cockpit bundle failed: ${P.reason}`);N(P.status===`bundled`?`cockpit bundled with plugins: ${P.pluginIds.join(`, `)}`:P.reason);let F=c.start(`api`,`generating the package API routes`),L=y.syncApiRoutes({resolvedEntries:k.state.resolved,outputDirectory:S.default.join(d,`api`),onNotice:e=>c.line(`api`,e)});F(`routes written to ${L.directory}`),i.publishSyncRegistration(l.root,{version:1,root:l.root,identity:l.identity,generation:u,generationRoot:d,statePath:M,stateSha256:i.syncStateSha256(M),webDirectory:P.status===`bundled`?P.assetsDir:null,apiDirectory:L.directory,package:Y(l.root)},s),X(l.generationsDirectory,u,e=>c.line(I,e));let R=this.settingsMode===`persisted`?g.writeProjectPiSettings(l.root):void 0;return{statePath:M,...R?{projectSettingsPath:R}:{},selection:p,mcpServers:k.state.baseline.mcpConfigPath?o.readMcpServerNames(k.state.baseline.mcpConfigPath):[],skillCount:r.resources.skillCount,agentCount:r.resources.agentCount}}catch(e){throw await w.default.promises.rm(d,{recursive:!0,force:!0}),e}}};exports.SyncCommand=Q,exports.collectDrift=G,exports.formatSyncResult=q,exports.recordedEnvironment=B,exports.selectionCompositionFingerprint=U,exports.selectionEnvironment=V,exports.toSelection=H;
7
+ `)}\n`),1)}let A={repoRoot:y,homeDirectory:g,requireWebBundle:!!n.DOOMPI_WEB_PACKAGE_ROOT};if(!p&&v.readSyncDrift(A).fresh)return s.write(`doompi sync is already up to date
8
+ `),0;let j=new x.SyncProgress(s),P=this.lockHeld?void 0:await r.acquireSyncLocationLock(r.resolveSyncLocation(y,g)),F;try{if(!p&&v.readSyncDrift(A).fresh)return s.write(`doompi sync is already up to date
9
+ `),0;F=await this.stage(y,w.options,n,g,j)}finally{await P?.()}return K(y,s),s.write(q(F,this.settingsMode===`embedded`?`dpi`:`pi`)),0}async stage(e,t,n,s,c){let l=r.resolveSyncLocation(e,s),u=`${Date.now().toString(36)}-${C.default.randomUUID()}`,d=r.syncGenerationDirectory(l,u);await w.default.promises.mkdir(l.generationsDirectory,{recursive:!0,mode:448}),await w.default.promises.mkdir(d,{mode:448});try{let e=c.start(I,`resolving the matrix and staging resources`),r=await m.buildHarnessContext({...t,repoRoot:l.root,homeDirectory:s,cwd:l.root,resourceDirectory:d});await h.ensureLayerPackages({repoRoot:l.root,config:r.majorModesConfig,layers:Object.keys(r.majorModesConfig.layers),environment:n}),e(`${String(r.resources.skillCount)} skills, ${String(r.resources.agentCount)} agents`);let p=H(t),v=a.createLayerResolvers(l.root),x=o.recordResolvedEntries(r.majorModesConfig,v),C=U(l.root,t,s),w=f.piAgentDirectory(n,s),T=S.default.join(f.piThemeDirectory(w),`${A.DEFAULT_THEME_NAME}.json`),E=this.settingsMode===`persisted`?T:r.defaultThemePath,D={version:13,root:l.root,identity:l.identity,inputsHash:o.computeInputsHash(l.root,p,s),webSourcesHash:o.computeWebSourcesHash(x),compositionFingerprint:C,selection:p,env:B(r.environment),fileState:{profileEnvironment:(0,j.loadHarnessState)(r.environment).state.profileEnvironment,pluginHooks:r.resources.pluginHooks,mcpProjection:r.resources.mcpProjection},resolved:x,baseline:{mcpConfigPath:r.resources.mcpConfigPath,personaFile:r.environment.DOOMPI_PERSONA_FILE,themePath:E,themeName:A.DEFAULT_THEME_NAME}},O=c.start(`runtime`,`precompiling the mode bundles`),k=await _.buildSyncedRuntime(l.root,n,s,{state:D,directory:d});O(`${String(Object.keys(k.bundles).length)} mode bundles`);let M=await o.writeSyncState(l.root,k.state,s,S.default.join(d,`state.json`)),N=c.start(`web`,`bundling the web cockpit plugins`),P=await b.syncWebBundle({repoRoot:l.root,resolvedEntries:k.state.resolved,environment:n,outputDirectory:S.default.join(d,`web-bundle`),onNotice:e=>c.line(`web`,e)});if(P.status===`failed`)throw Error(`Cockpit bundle failed: ${P.reason}`);N(P.status===`bundled`?`cockpit bundled with plugins: ${P.pluginIds.join(`, `)}`:P.reason);let F=c.start(`api`,`generating the package API routes`),L=y.syncApiRoutes({resolvedEntries:k.state.resolved,outputDirectory:S.default.join(d,`api`),onNotice:e=>c.line(`api`,e)});F(`routes written to ${L.directory}`),i.publishSyncRegistration(l.root,{version:1,root:l.root,identity:l.identity,generation:u,generationRoot:d,statePath:M,stateSha256:i.syncStateSha256(M),webDirectory:P.status===`bundled`?P.assetsDir:null,apiDirectory:L.directory,package:Y(l.root)},s),X(l.generationsDirectory,u,e=>c.line(I,e));let R=this.settingsMode===`persisted`?g.writeProjectPiSettings(l.root):void 0;return{statePath:M,...R?{projectSettingsPath:R}:{},selection:p,mcpServers:k.state.baseline.mcpConfigPath?o.readMcpServerNames(k.state.baseline.mcpConfigPath):[],skillCount:r.resources.skillCount,agentCount:r.resources.agentCount}}catch(e){throw await w.default.promises.rm(d,{recursive:!0,force:!0}),e}}};exports.SyncCommand=Q,exports.collectDrift=G,exports.formatSyncResult=q,exports.recordedEnvironment=B,exports.selectionCompositionFingerprint=U,exports.selectionEnvironment=V,exports.toSelection=H;
10
10
  //# sourceMappingURL=syncCommand.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"syncCommand.cjs","names":["path","HARNESS_STATE_POINTER","loadDoomConfig","DOOMPI_MAJOR_MODE_ENV","DOOMPI_PROFILE_ENV","DOOMPI_DOMAINS_ENV","os","loadMajorModesConfig","createLayerResolvers","resolveExtensionComposition","PERSONA_ENTRY","filterHookDisabledLayers","resolveLayers","piThemeDirectory","DEFAULT_THEME_NAME","readPiSettings","mergePiSettings","serializePiSettings","piExtensionAliasIsCurrent","DEFAULT_THEME","fs","syncStateRootMatches","computeInputsHash","recordResolvedEntries","readBootstrapStatus","piAgentDirectory","projectRegistersDoom","DUPLICATE_REGISTRATION_DRIFT","spawnSync","createRequire","DOOM_PACKAGE_NAME","doomPiPackageRoot","resolveDoomConfigurationRoot","loadDomains","parseHarnessArgs","piExtensionDispatcherIsUpgradeable","piExtensionDispatcherVersion","missingLayerPackageSpecifiers","readLocatedSyncState","readSyncDrift","SyncProgress","acquireSyncLocationLock","resolveSyncLocation","crypto","syncGenerationDirectory","buildHarnessContext","ensureLayerPackages","computeWebSourcesHash","loadHarnessState","buildSyncedRuntime","writeSyncState","syncWebBundle","syncApiRoutes","syncStateSha256","writeProjectPiSettings","readMcpServerNames"],"sources":["../../../src/commands/syncCommand.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { filterHookDisabledLayers, resolveLayers } from '@agimon-ai/doompi-config/majorModes';\nimport { buildHarnessContext } from '../adapters/harnessContext.ts';\nimport { ensureLayerPackages, missingLayerPackageSpecifiers } from '../adapters/layerPackageInstaller.ts';\nimport { readBootstrapStatus } from '../adapters/bootstrapLocator.ts';\nimport { DOOM_PACKAGE_NAME } from '../adapters/doomPackage.ts';\nimport { doomPiPackageRoot, piExtensionAliasIsCurrent, writePiExtensionAlias } from '../adapters/piExtensionAlias.ts';\nimport {\n PI_DISPATCHER_VERSION,\n piExtensionDispatcherIsUpgradeable,\n piExtensionDispatcherVersion,\n} from '../adapters/piExtensionDispatcher.ts';\nimport {\n mergePiSettings,\n piAgentDirectory,\n piThemeDirectory,\n readPiSettings,\n serializePiSettings,\n} from '../adapters/piSettings.ts';\nimport {\n DUPLICATE_REGISTRATION_DRIFT,\n projectRegistersDoom,\n writeProjectPiSettings,\n} from '../adapters/projectPiSettings.ts';\nimport { buildSyncedRuntime } from '../adapters/syncedRuntimeBuilder.ts';\nimport { acquireSyncLocationLock, resolveSyncLocation, syncGenerationDirectory } from '../adapters/syncLocation.ts';\nimport {\n publishSyncRegistration,\n SYNC_REGISTRATION_VERSION,\n syncStateSha256,\n type SyncPackageRegistration,\n} from '../adapters/syncRegistration.ts';\nimport { readSyncDrift } from '../adapters/syncDrift.ts';\nimport { syncApiRoutes } from '../adapters/apiRoutesSync.ts';\nimport { syncWebBundle } from '../adapters/webBundleSync.ts';\nimport {\n computeInputsHash,\n computeWebSourcesHash,\n readLocatedSyncState,\n readMcpServerNames,\n recordResolvedEntries,\n SYNC_STATE_VERSION,\n type SyncSelection,\n type SyncState,\n syncStateRootMatches,\n writeSyncState,\n} from '../adapters/syncState.ts';\nimport { HARNESS_STATE_POINTER, loadHarnessState } from '../adapters/config/harnessState';\nimport { loadDomains } from '@agimon-ai/doompi-config/domains';\nimport { loadMajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { DEFAULT_THEME, DEFAULT_THEME_NAME } from '@agimon-ai/doompi-ui/theme';\nimport { loadDoomConfig } from '../services/config/projectTrust';\nimport { createLayerResolvers, PERSONA_ENTRY, resolveExtensionComposition } from '../services/extensionAssembler.ts';\nimport type { HarnessOptions } from '../types/interfaces/harness';\nimport { resolveDoomConfigurationRoot } from '../adapters/repository/repository';\nimport { DOOMPI_DOMAINS_ENV, DOOMPI_MAJOR_MODE_ENV, DOOMPI_PROFILE_ENV } from './cli/matrixOptions.ts';\nimport { parseHarnessArgs } from './cli/options.ts';\nimport { SyncProgress, type SyncProgressOutput } from './syncPresenter.ts';\n\n/**\n * `doom-pi sync`: resolve the matrix once and write it where plain Pi finds it.\n *\n * The doom-emacs split. Everything that needs a real Node process (module\n * resolution, staging skills and agents, generating the MCP config) happens\n * here, and the doom-pi extension then only reads what this produced. The\n * launcher is untouched and keeps resolving the same matrix per run.\n */\n\nconst SYNC_COMMAND = 'sync';\nconst CHECK_OPTION = '--check';\n/** Republishes even when nothing drifted, for a generation suspected of being damaged. */\nconst FORCE_OPTION = '--force';\nconst HARNESS_ROOT_ENV = 'DOOMPI_ROOT';\nconst PERSONA_FILE_ENV = 'DOOMPI_PERSONA_FILE';\nconst HOOK_EMITTER = path.join('tools', 'harness', 'emit-hooks.mjs');\nconst NONE = '(none)';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\n/** Published generations kept behind the current one, so a running hub mid-read survives a prune. */\nconst RETAINED_GENERATIONS = 1;\nconst SYNC_LABEL = 'sync';\nconst RUNTIME_LABEL = 'runtime';\nconst WEB_LABEL = 'web';\nconst API_LABEL = 'api';\n\n/**\n * Harness variables worth recording, by prefix or exact name.\n *\n * An allowlist rather than the whole environment: the state file is a snapshot\n * of resolved configuration, and dumping `process.env` into it would write\n * every credential the sync happened to run with onto disk.\n */\nconst RECORDED_PREFIXES = ['DOOMPI_'];\nconst RECORDED_KEYS = ['CLAUDE_PROJECT_DIR', 'CODEX_REPO_ROOT', 'ORIGINAL_REPO_PATH', 'MCP_UI_VIEWER'];\n/**\n * Launcher-only values a synced session must not inherit.\n *\n * The child extension list is recomposed on every load, and the subagent binary\n * points at `pi.sh`, which a session started as plain `pi` should not shell out\n * to: Doom Team resolves Pi's own CLI when the variable is absent.\n */\nconst EXCLUDED_KEYS = new Set([\n 'DOOMPI_CHILD_EXTENSIONS',\n 'DOOMPI_COMPOSED',\n 'DOOMPI_MUTE',\n 'DOOMPI_TEMP_DIR',\n // A pointer to the syncing process's own state file. Recording it would hand\n // every later session a path to a state that died with this one.\n HARNESS_STATE_POINTER,\n 'PI_SUBAGENT_PI_BINARY',\n]);\n\ntype SyncOutput = SyncProgressOutput;\n\nexport type SyncSettingsMode = 'persisted' | 'embedded';\n\nexport interface SyncCommandOptions {\n settingsMode?: SyncSettingsMode;\n /** Test/embedding override; normal CLI execution uses the process home. */\n homeDirectory?: string;\n /** Internal pipeline seam when the caller owns the worktree lock. */\n lockHeld?: boolean;\n}\n\nexport interface SyncResult {\n statePath: string;\n /** Omitted when DPI supplies the integration as a process-local overlay. */\n settingsPath?: string;\n /** Set only when the repository still carried its own DoomPi registration. */\n projectSettingsPath?: string;\n selection: SyncSelection;\n mcpServers: string[];\n skillCount: number;\n agentCount: number;\n}\n\nexport function recordedEnvironment(environment: NodeJS.ProcessEnv): Record<string, string> {\n const recorded: Record<string, string> = {};\n for (const [key, value] of Object.entries(environment)) {\n if (value === undefined || EXCLUDED_KEYS.has(key)) continue;\n if (RECORDED_KEYS.includes(key) || RECORDED_PREFIXES.some((prefix) => key.startsWith(prefix))) {\n recorded[key] = value;\n }\n }\n return recorded;\n}\n\n/**\n * Layers the repository's declared selection under the usual resolution.\n *\n * `.doom/config.yaml` holds what the repository selects by default, the way\n * init.el does for doom-emacs. Seeding the environment the parser reads keeps\n * the precedence the launcher already documents: an explicit flag wins, then an\n * exported variable, then the declared default.\n */\nexport function selectionEnvironment(repoRoot: string, environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const { selection } = loadDoomConfig(repoRoot);\n if (!selection) return environment;\n return {\n ...environment,\n ...(selection.majorMode && !environment[DOOMPI_MAJOR_MODE_ENV]\n ? { [DOOMPI_MAJOR_MODE_ENV]: selection.majorMode }\n : {}),\n ...(selection.profile && !environment[DOOMPI_PROFILE_ENV] ? { [DOOMPI_PROFILE_ENV]: selection.profile } : {}),\n ...(selection.domains && environment[DOOMPI_DOMAINS_ENV] === undefined\n ? { [DOOMPI_DOMAINS_ENV]: selection.domains.join(',') }\n : {}),\n };\n}\n\nexport function toSelection(\n options: Pick<HarnessOptions, 'majorMode' | 'domains' | 'profile' | 'preset'>,\n): SyncSelection {\n return {\n majorMode: options.majorMode,\n domains: options.domains,\n profile: options.profile,\n preset: options.preset,\n };\n}\n\nexport function selectionCompositionFingerprint(\n repoRoot: string,\n options: Pick<HarnessOptions, 'agents' | 'hooks' | 'majorMode' | 'mcp' | 'preset'>,\n homeDirectory: string = os.homedir(),\n): string {\n const majorModesConfig = loadMajorModesConfig(repoRoot, homeDirectory);\n const resolvers = createLayerResolvers(repoRoot);\n return resolveExtensionComposition({\n agents: options.agents,\n autoStop: false,\n mute: false,\n preset: options.preset,\n personaEntry: resolvers.packageEntry(PERSONA_ENTRY),\n majorMode: options.majorMode,\n layers: filterHookDisabledLayers(\n majorModesConfig,\n resolveLayers(majorModesConfig, options.majorMode),\n options.hooks,\n ),\n majorModesConfig,\n resolvers,\n }).fingerprint;\n}\n\n/** Settings, dispatcher and theme differences an init would fix, independent of sync state. */\nfunction piIntegrationDrift(agentDirectory: string): string[] {\n const drift: string[] = [];\n const themePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n const settings = readPiSettings(agentDirectory);\n const merged = mergePiSettings(settings, agentDirectory, { themePath, themeName: DEFAULT_THEME_NAME });\n if (serializePiSettings(merged) !== serializePiSettings(settings)) {\n drift.push('Pi user settings are out of date; run doompi init');\n }\n if (!piExtensionAliasIsCurrent(agentDirectory)) drift.push('Pi user dispatcher is out of date; run doompi init');\n const expectedTheme = `${JSON.stringify(DEFAULT_THEME, null, 2)}\\n`;\n if (!fs.existsSync(themePath) || fs.readFileSync(themePath, 'utf8') !== expectedTheme) {\n drift.push('Pi user theme is out of date; run doompi init');\n }\n return drift;\n}\n\n/** Differences between what a sync would produce and what is on disk. */\nexport function collectDrift(\n repoRoot: string,\n selection: SyncSelection,\n state: SyncState | undefined,\n environment: NodeJS.ProcessEnv = process.env,\n settingsMode: SyncSettingsMode = 'persisted',\n expectedCompositionFingerprint?: string,\n): string[] {\n if (!state) return ['no sync state: run doompi sync'];\n const drift: string[] = [];\n if (!syncStateRootMatches(repoRoot, state.root)) drift.push('sync state belongs to a different repository');\n const recorded = state.selection;\n if (\n recorded.majorMode !== selection.majorMode ||\n recorded.profile !== selection.profile ||\n recorded.preset !== selection.preset ||\n recorded.domains.join(',') !== selection.domains.join(',')\n ) {\n drift.push('selection changed since the last sync');\n }\n // Hashed against the recorded selection, not the requested one, so a\n // selection change is reported once rather than as two findings.\n if (computeInputsHash(repoRoot, recorded, environment.HOME ?? os.homedir()) !== state.inputsHash) {\n drift.push('.doom configuration changed');\n }\n // Re-resolving is what catches a dependency upgrade moving a package, which\n // the inputs hash deliberately does not read.\n if (\n JSON.stringify(\n recordResolvedEntries(\n loadMajorModesConfig(repoRoot, environment.HOME ?? os.homedir()),\n createLayerResolvers(repoRoot),\n ),\n ) !== JSON.stringify(state.resolved)\n ) {\n drift.push('resolved extension paths changed');\n }\n if (expectedCompositionFingerprint && state.compositionFingerprint !== expectedCompositionFingerprint) {\n drift.push('extension composition changed');\n }\n try {\n if (!readBootstrapStatus(repoRoot, undefined, environment.HOME ?? os.homedir()).fresh) {\n drift.push('precompiled runtime is missing or stale');\n }\n } catch {\n drift.push('precompiled runtime is missing or stale');\n }\n\n if (settingsMode === 'persisted') {\n const agentDirectory = piAgentDirectory(environment);\n const themePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n drift.push(...piIntegrationDrift(agentDirectory));\n if (projectRegistersDoom(repoRoot)) drift.push(DUPLICATE_REGISTRATION_DRIFT);\n if (state.baseline.themePath !== themePath || state.baseline.themeName !== DEFAULT_THEME_NAME) {\n drift.push('synced theme location is out of date');\n }\n }\n return drift;\n}\n\n/** Regenerates the hook files the other frontends read before any harness code runs. */\nfunction emitFrontendHooks(repoRoot: string, output: SyncOutput): void {\n const emitter = path.join(repoRoot, HOOK_EMITTER);\n if (!fs.existsSync(emitter)) return;\n const result = spawnSync(process.execPath, [emitter, '--write'], { cwd: repoRoot, encoding: 'utf8' });\n if (result.status === 0) {\n output.write('hooks: regenerated for Claude Code and Codex\\n');\n return;\n }\n output.write(`hooks: emit-hooks failed (${result.stderr?.trim() || `exit ${String(result.status)}`})\\n`);\n}\n\nexport function formatSyncResult(result: SyncResult, runner = 'pi'): string {\n const { selection } = result;\n return [\n `mode: ${selection.majorMode}`,\n `domains: ${selection.domains.join(', ') || NONE}`,\n `profile: ${selection.profile ?? NONE}`,\n `skills: ${result.skillCount}`,\n `agents: ${result.agentCount}`,\n `mcp: ${result.mcpServers.join(', ') || NONE}`,\n `state: ${result.statePath}`,\n ...(result.settingsPath ? [`settings: ${result.settingsPath}`] : []),\n ...(result.projectSettingsPath\n ? [`project: removed duplicate registration from ${result.projectSettingsPath}`]\n : []),\n '',\n `Run ${runner} from the repository root to use it.`,\n '',\n ].join('\\n');\n}\n\n/**\n * The DoomPi package a repository pins for itself, if it pins one.\n *\n * Extensions are version-coupled to the harness that loads them, so the\n * registration must name the copy the repository resolves rather than whichever\n * copy happened to run sync. A globally installed DoomPi syncing a repository\n * that pins its own would otherwise record itself, and the dispatcher would\n * then load the wrong harness for every session in that repository.\n */\nfunction repositoryPackageRoot(repoRoot: string): string | undefined {\n try {\n return path.dirname(\n createRequire(path.join(repoRoot, 'package.json')).resolve(`${DOOM_PACKAGE_NAME}/package.json`),\n );\n } catch {\n // Not pinned here, which is normal; the executing package stands in.\n return undefined;\n }\n}\n\nfunction packageRegistrationFor(repoRoot: string): SyncPackageRegistration {\n const root = fs.realpathSync(repositoryPackageRoot(repoRoot) ?? doomPiPackageRoot());\n const manifestPath = path.join(root, 'package.json');\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as {\n version?: unknown;\n pi?: { extensions?: unknown };\n };\n const version = manifest.version;\n const extensions = manifest.pi?.extensions;\n const extension = Array.isArray(extensions) ? extensions.find((value) => typeof value === 'string') : undefined;\n if (typeof version !== 'string' || typeof extension !== 'string') {\n throw new Error(`Installed DoomPi package at ${root} has no versioned Pi extension entry`);\n }\n return {\n root,\n version,\n manifestPath,\n entry: fs.realpathSync(path.resolve(root, extension)),\n };\n}\n\n/**\n * Removes generations the published one replaced.\n *\n * Each generation holds a full cockpit bundle and runtime, so keeping every one\n * ever built grows without bound. One superseded generation is retained because\n * a hub that resolved its assets a moment ago may still be reading them; older\n * ones have no reader left. Ordered by directory name, whose leading timestamp\n * makes publication order recoverable without reading each state file.\n */\nfunction pruneSupersededGenerations(\n generationsDirectory: string,\n published: string,\n onNotice: (message: string) => void,\n): void {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(generationsDirectory, { withFileTypes: true });\n } catch (error) {\n // Nothing to prune is indistinguishable from an unreadable directory here,\n // and neither is worth failing a sync that already published.\n onNotice(`could not list generations to prune: ${describeError(error)}`);\n return;\n }\n const superseded = entries\n .filter((entry) => entry.isDirectory() && entry.name !== published)\n .map((entry) => entry.name)\n .sort();\n const removable = superseded.slice(0, Math.max(0, superseded.length - RETAINED_GENERATIONS));\n for (const name of removable) {\n try {\n fs.rmSync(path.join(generationsDirectory, name), { recursive: true, force: true });\n } catch (error) {\n // A generation still held open elsewhere stays; the next sync retries it.\n onNotice(`could not remove superseded generation ${name}: ${describeError(error)}`);\n }\n }\n if (removable.length > 0) onNotice(`pruned ${String(removable.length)} superseded generation(s)`);\n}\n\nfunction describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** Resolves the matrix, stages it into home-scoped worktree storage, and publishes one generation. */\nexport class SyncCommand {\n readonly name = SYNC_COMMAND;\n private readonly settingsMode: SyncSettingsMode;\n private readonly homeDirectory: string | undefined;\n private readonly lockHeld: boolean;\n\n constructor(options: SyncCommandOptions = {}) {\n this.settingsMode = options.settingsMode ?? 'persisted';\n this.homeDirectory = options.homeDirectory;\n this.lockHeld = options.lockHeld ?? false;\n }\n\n matches(args: string[]): boolean {\n return args[0] === this.name;\n }\n\n async execute(\n args: string[],\n environment: NodeJS.ProcessEnv = process.env,\n currentDirectory = process.cwd(),\n output: SyncOutput = process.stdout,\n ): Promise<number> {\n const check = args.includes(CHECK_OPTION);\n const force = args.includes(FORCE_OPTION);\n const rest = args.slice(1).filter((argument) => argument !== CHECK_OPTION && argument !== FORCE_OPTION);\n const homeDirectory = this.homeDirectory ?? environment.HOME ?? os.homedir();\n const inheritedRoot = environment[HARNESS_ROOT_ENV];\n const repoRoot = inheritedRoot\n ? path.resolve(inheritedRoot)\n : resolveDoomConfigurationRoot(currentDirectory, homeDirectory);\n const defaultMajorMode = loadMajorModesConfig(repoRoot, homeDirectory).defaultMajorMode;\n const defaultDomains = loadDomains(repoRoot, homeDirectory).defaultDomains;\n const parsed = parseHarnessArgs(\n rest,\n selectionEnvironment(repoRoot, environment),\n currentDirectory,\n defaultMajorMode,\n defaultDomains,\n );\n const selection = toSelection(parsed.options);\n const agentDirectory = piAgentDirectory(environment, homeDirectory);\n if (this.settingsMode === 'persisted' && !check) {\n if (piExtensionDispatcherIsUpgradeable(agentDirectory)) {\n const previousVersion = piExtensionDispatcherVersion(agentDirectory);\n writePiExtensionAlias(agentDirectory);\n output.write(\n `repair: upgraded Pi user dispatcher from protocol ${String(previousVersion)} to ${String(PI_DISPATCHER_VERSION)}\\n`,\n );\n }\n const drift = piIntegrationDrift(agentDirectory);\n if (drift.length > 0) {\n throw new Error(`DoomPi Pi integration is not ready:\\n${drift.map((entry) => ` ${entry}`).join('\\n')}`);\n }\n }\n if (check) {\n const majorModesConfig = loadMajorModesConfig(repoRoot, homeDirectory);\n const missingPackages = missingLayerPackageSpecifiers(\n majorModesConfig,\n Object.keys(majorModesConfig.layers),\n createLayerResolvers(repoRoot),\n );\n if (missingPackages.length > 0) {\n output.write(\n `doompi sync is out of date:\\n${missingPackages\n .map((specifier) => ` configured package is not installed: ${specifier}`)\n .join('\\n')}\\n`,\n );\n return 1;\n }\n let located: ReturnType<typeof readLocatedSyncState>;\n try {\n located = readLocatedSyncState(repoRoot, homeDirectory);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n output.write(`doompi sync is out of date:\\n ${detail}\\n`);\n return 1;\n }\n const expectedCompositionFingerprint = selectionCompositionFingerprint(repoRoot, parsed.options, homeDirectory);\n const drift = collectDrift(\n repoRoot,\n selection,\n located?.state,\n environment,\n this.settingsMode,\n expectedCompositionFingerprint,\n );\n if (drift.length === 0) {\n output.write('doompi sync is up to date\\n');\n return 0;\n }\n output.write(`doompi sync is out of date:\\n${drift.map((entry) => ` ${entry}`).join('\\n')}\\n`);\n return 1;\n }\n\n // Publishing an identical generation is not a no-op: it moves the\n // registration, so every attached cockpit reloads and the previous\n // generation becomes garbage. Same inputs, same published result.\n if (!force && readSyncDrift({ repoRoot, homeDirectory }).fresh) {\n output.write('doompi sync is already up to date\\n');\n return 0;\n }\n\n const progress = new SyncProgress(output);\n const releaseLock = this.lockHeld\n ? undefined\n : await acquireSyncLocationLock(resolveSyncLocation(repoRoot, homeDirectory));\n let result: SyncResult;\n try {\n // A concurrent publisher may have resolved the drift while this command\n // waited for the lock. Avoid moving the registration for no change.\n if (!force && readSyncDrift({ repoRoot, homeDirectory }).fresh) {\n output.write('doompi sync is already up to date\\n');\n return 0;\n }\n result = await this.stage(repoRoot, parsed.options, environment, homeDirectory, progress);\n } finally {\n await releaseLock?.();\n }\n emitFrontendHooks(repoRoot, output);\n output.write(formatSyncResult(result, this.settingsMode === 'embedded' ? 'dpi' : 'pi'));\n return 0;\n }\n\n private async stage(\n repoRoot: string,\n options: Omit<HarnessOptions, 'repoRoot'>,\n environment: NodeJS.ProcessEnv,\n homeDirectory: string,\n progress: SyncProgress,\n ): Promise<SyncResult> {\n const location = resolveSyncLocation(repoRoot, homeDirectory);\n const generation = `${Date.now().toString(36)}-${crypto.randomUUID()}`;\n const directory = syncGenerationDirectory(location, generation);\n await fs.promises.mkdir(location.generationsDirectory, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });\n // The leaf is created without `recursive`, so an existing path is an error\n // rather than something to adopt: the cockpit signs and serves whatever the\n // published generation holds, and sync must only ever publish bytes it\n // wrote itself into a directory it just created.\n await fs.promises.mkdir(directory, { mode: PRIVATE_DIRECTORY_MODE });\n\n try {\n const staged = progress.start(SYNC_LABEL, 'resolving the matrix and staging resources');\n const context = await buildHarnessContext({\n ...options,\n repoRoot: location.root,\n homeDirectory,\n cwd: location.root,\n resourceDirectory: directory,\n });\n await ensureLayerPackages({\n repoRoot: location.root,\n config: context.majorModesConfig,\n layers: Object.keys(context.majorModesConfig.layers),\n environment,\n });\n staged(`${String(context.resources.skillCount)} skills, ${String(context.resources.agentCount)} agents`);\n const selection = toSelection(options);\n const resolvers = createLayerResolvers(location.root);\n const resolved = recordResolvedEntries(context.majorModesConfig, resolvers);\n const compositionFingerprint = selectionCompositionFingerprint(location.root, options, homeDirectory);\n const agentDirectory = piAgentDirectory(environment, homeDirectory);\n const persistedThemePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n const themePath = this.settingsMode === 'persisted' ? persistedThemePath : context.defaultThemePath;\n const state: SyncState = {\n version: SYNC_STATE_VERSION,\n root: location.root,\n identity: location.identity,\n inputsHash: computeInputsHash(location.root, selection, homeDirectory),\n webSourcesHash: computeWebSourcesHash(resolved),\n compositionFingerprint,\n selection,\n env: recordedEnvironment(context.environment),\n fileState: {\n profileEnvironment: loadHarnessState(context.environment).state.profileEnvironment,\n pluginHooks: context.resources.pluginHooks,\n mcpProjection: context.resources.mcpProjection,\n },\n resolved,\n baseline: {\n mcpConfigPath: context.resources.mcpConfigPath,\n personaFile: context.environment[PERSONA_FILE_ENV],\n themePath,\n themeName: DEFAULT_THEME_NAME,\n },\n };\n\n const precompiled = progress.start(RUNTIME_LABEL, 'precompiling the mode bundles');\n const synced = await buildSyncedRuntime(location.root, environment, homeDirectory, { state, directory });\n precompiled(`${String(Object.keys(synced.bundles).length)} mode bundles`);\n const statePath = await writeSyncState(\n location.root,\n synced.state,\n homeDirectory,\n path.join(directory, 'state.json'),\n );\n\n const webProgress = progress.start(WEB_LABEL, 'bundling the web cockpit plugins');\n const web = await syncWebBundle({\n repoRoot: location.root,\n resolvedEntries: synced.state.resolved,\n environment,\n outputDirectory: path.join(directory, 'web-bundle'),\n onNotice: (message) => progress.line(WEB_LABEL, message),\n });\n if (web.status === 'failed') throw new Error(`Cockpit bundle failed: ${web.reason}`);\n webProgress(web.status === 'bundled' ? `cockpit bundled with plugins: ${web.pluginIds.join(', ')}` : web.reason);\n\n const apiProgress = progress.start(API_LABEL, 'generating the package API routes');\n const api = syncApiRoutes({\n resolvedEntries: synced.state.resolved,\n outputDirectory: path.join(directory, 'api'),\n onNotice: (message) => progress.line(API_LABEL, message),\n });\n apiProgress(`routes written to ${api.directory}`);\n\n publishSyncRegistration(\n location.root,\n {\n version: SYNC_REGISTRATION_VERSION,\n root: location.root,\n identity: location.identity,\n generation,\n generationRoot: directory,\n statePath,\n stateSha256: syncStateSha256(statePath),\n webDirectory: web.status === 'bundled' ? web.assetsDir : null,\n apiDirectory: api.directory,\n package: packageRegistrationFor(location.root),\n },\n homeDirectory,\n );\n pruneSupersededGenerations(location.generationsDirectory, generation, (message) =>\n progress.line(SYNC_LABEL, message),\n );\n const projectSettingsPath = this.settingsMode === 'persisted' ? writeProjectPiSettings(location.root) : undefined;\n\n return {\n statePath,\n ...(projectSettingsPath ? { projectSettingsPath } : {}),\n selection,\n mcpServers: synced.state.baseline.mcpConfigPath ? readMcpServerNames(synced.state.baseline.mcpConfigPath) : [],\n skillCount: context.resources.skillCount,\n agentCount: context.resources.agentCount,\n };\n } catch (error) {\n await fs.promises.rm(directory, { recursive: true, force: true });\n throw error;\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"syncCommand.cjs","names":["path","HARNESS_STATE_POINTER","loadDoomConfig","DOOMPI_MAJOR_MODE_ENV","DOOMPI_PROFILE_ENV","DOOMPI_DOMAINS_ENV","os","loadMajorModesConfig","createLayerResolvers","resolveExtensionComposition","PERSONA_ENTRY","filterHookDisabledLayers","resolveLayers","piThemeDirectory","DEFAULT_THEME_NAME","readPiSettings","mergePiSettings","serializePiSettings","piExtensionAliasIsCurrent","DEFAULT_THEME","fs","syncStateRootMatches","computeInputsHash","recordResolvedEntries","readBootstrapStatus","piAgentDirectory","projectRegistersDoom","DUPLICATE_REGISTRATION_DRIFT","spawnSync","createRequire","DOOM_PACKAGE_NAME","doomPiPackageRoot","resolveDoomConfigurationRoot","loadDomains","parseHarnessArgs","piExtensionDispatcherIsUpgradeable","piExtensionDispatcherVersion","missingLayerPackageSpecifiers","readLocatedSyncState","readSyncDrift","SyncProgress","acquireSyncLocationLock","resolveSyncLocation","crypto","syncGenerationDirectory","buildHarnessContext","ensureLayerPackages","computeWebSourcesHash","loadHarnessState","buildSyncedRuntime","writeSyncState","syncWebBundle","syncApiRoutes","syncStateSha256","writeProjectPiSettings","readMcpServerNames"],"sources":["../../../src/commands/syncCommand.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { filterHookDisabledLayers, resolveLayers } from '@agimon-ai/doompi-config/majorModes';\nimport { buildHarnessContext } from '../adapters/harnessContext.ts';\nimport { ensureLayerPackages, missingLayerPackageSpecifiers } from '../adapters/layerPackageInstaller.ts';\nimport { readBootstrapStatus } from '../adapters/bootstrapLocator.ts';\nimport { DOOM_PACKAGE_NAME } from '../adapters/doomPackage.ts';\nimport { doomPiPackageRoot, piExtensionAliasIsCurrent, writePiExtensionAlias } from '../adapters/piExtensionAlias.ts';\nimport {\n PI_DISPATCHER_VERSION,\n piExtensionDispatcherIsUpgradeable,\n piExtensionDispatcherVersion,\n} from '../adapters/piExtensionDispatcher.ts';\nimport {\n mergePiSettings,\n piAgentDirectory,\n piThemeDirectory,\n readPiSettings,\n serializePiSettings,\n} from '../adapters/piSettings.ts';\nimport {\n DUPLICATE_REGISTRATION_DRIFT,\n projectRegistersDoom,\n writeProjectPiSettings,\n} from '../adapters/projectPiSettings.ts';\nimport { buildSyncedRuntime } from '../adapters/syncedRuntimeBuilder.ts';\nimport { acquireSyncLocationLock, resolveSyncLocation, syncGenerationDirectory } from '../adapters/syncLocation.ts';\nimport {\n publishSyncRegistration,\n SYNC_REGISTRATION_VERSION,\n syncStateSha256,\n type SyncPackageRegistration,\n} from '../adapters/syncRegistration.ts';\nimport { readSyncDrift } from '../adapters/syncDrift.ts';\nimport { syncApiRoutes } from '../adapters/apiRoutesSync.ts';\nimport { syncWebBundle } from '../adapters/webBundleSync.ts';\nimport {\n computeInputsHash,\n computeWebSourcesHash,\n readLocatedSyncState,\n readMcpServerNames,\n recordResolvedEntries,\n SYNC_STATE_VERSION,\n type SyncSelection,\n type SyncState,\n syncStateRootMatches,\n writeSyncState,\n} from '../adapters/syncState.ts';\nimport { HARNESS_STATE_POINTER, loadHarnessState } from '../adapters/config/harnessState';\nimport { loadDomains } from '@agimon-ai/doompi-config/domains';\nimport { loadMajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { DEFAULT_THEME, DEFAULT_THEME_NAME } from '@agimon-ai/doompi-ui/theme';\nimport { loadDoomConfig } from '../services/config/projectTrust';\nimport { createLayerResolvers, PERSONA_ENTRY, resolveExtensionComposition } from '../services/extensionAssembler.ts';\nimport type { HarnessOptions } from '../types/interfaces/harness';\nimport { resolveDoomConfigurationRoot } from '../adapters/repository/repository';\nimport { DOOMPI_DOMAINS_ENV, DOOMPI_MAJOR_MODE_ENV, DOOMPI_PROFILE_ENV } from './cli/matrixOptions.ts';\nimport { parseHarnessArgs } from './cli/options.ts';\nimport { SyncProgress, type SyncProgressOutput } from './syncPresenter.ts';\n\n/**\n * `doom-pi sync`: resolve the matrix once and write it where plain Pi finds it.\n *\n * The doom-emacs split. Everything that needs a real Node process (module\n * resolution, staging skills and agents, generating the MCP config) happens\n * here, and the doom-pi extension then only reads what this produced. The\n * launcher is untouched and keeps resolving the same matrix per run.\n */\n\nconst SYNC_COMMAND = 'sync';\nconst CHECK_OPTION = '--check';\n/** Republishes even when nothing drifted, for a generation suspected of being damaged. */\nconst FORCE_OPTION = '--force';\nconst HARNESS_ROOT_ENV = 'DOOMPI_ROOT';\nconst PERSONA_FILE_ENV = 'DOOMPI_PERSONA_FILE';\nconst HOOK_EMITTER = path.join('tools', 'harness', 'emit-hooks.mjs');\nconst NONE = '(none)';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\n/** Published generations kept behind the current one, so a running hub mid-read survives a prune. */\nconst RETAINED_GENERATIONS = 1;\nconst SYNC_LABEL = 'sync';\nconst RUNTIME_LABEL = 'runtime';\nconst WEB_LABEL = 'web';\nconst API_LABEL = 'api';\n\n/**\n * Harness variables worth recording, by prefix or exact name.\n *\n * An allowlist rather than the whole environment: the state file is a snapshot\n * of resolved configuration, and dumping `process.env` into it would write\n * every credential the sync happened to run with onto disk.\n */\nconst RECORDED_PREFIXES = ['DOOMPI_'];\nconst RECORDED_KEYS = ['CLAUDE_PROJECT_DIR', 'CODEX_REPO_ROOT', 'ORIGINAL_REPO_PATH', 'MCP_UI_VIEWER'];\n/**\n * Launcher-only values a synced session must not inherit.\n *\n * The child extension list is recomposed on every load, and the subagent binary\n * points at `pi.sh`, which a session started as plain `pi` should not shell out\n * to: Doom Team resolves Pi's own CLI when the variable is absent.\n */\nconst EXCLUDED_KEYS = new Set([\n 'DOOMPI_CHILD_EXTENSIONS',\n 'DOOMPI_COMPOSED',\n 'DOOMPI_MUTE',\n 'DOOMPI_TEMP_DIR',\n // A pointer to the syncing process's own state file. Recording it would hand\n // every later session a path to a state that died with this one.\n HARNESS_STATE_POINTER,\n 'PI_SUBAGENT_PI_BINARY',\n]);\n\ntype SyncOutput = SyncProgressOutput;\n\nexport type SyncSettingsMode = 'persisted' | 'embedded';\n\nexport interface SyncCommandOptions {\n settingsMode?: SyncSettingsMode;\n /** Test/embedding override; normal CLI execution uses the process home. */\n homeDirectory?: string;\n /** Internal pipeline seam when the caller owns the worktree lock. */\n lockHeld?: boolean;\n}\n\nexport interface SyncResult {\n statePath: string;\n /** Omitted when DPI supplies the integration as a process-local overlay. */\n settingsPath?: string;\n /** Set only when the repository still carried its own DoomPi registration. */\n projectSettingsPath?: string;\n selection: SyncSelection;\n mcpServers: string[];\n skillCount: number;\n agentCount: number;\n}\n\nexport function recordedEnvironment(environment: NodeJS.ProcessEnv): Record<string, string> {\n const recorded: Record<string, string> = {};\n for (const [key, value] of Object.entries(environment)) {\n if (value === undefined || EXCLUDED_KEYS.has(key)) continue;\n if (RECORDED_KEYS.includes(key) || RECORDED_PREFIXES.some((prefix) => key.startsWith(prefix))) {\n recorded[key] = value;\n }\n }\n return recorded;\n}\n\n/**\n * Layers the repository's declared selection under the usual resolution.\n *\n * `.doom/config.yaml` holds what the repository selects by default, the way\n * init.el does for doom-emacs. Seeding the environment the parser reads keeps\n * the precedence the launcher already documents: an explicit flag wins, then an\n * exported variable, then the declared default.\n */\nexport function selectionEnvironment(repoRoot: string, environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const { selection } = loadDoomConfig(repoRoot);\n if (!selection) return environment;\n return {\n ...environment,\n ...(selection.majorMode && !environment[DOOMPI_MAJOR_MODE_ENV]\n ? { [DOOMPI_MAJOR_MODE_ENV]: selection.majorMode }\n : {}),\n ...(selection.profile && !environment[DOOMPI_PROFILE_ENV] ? { [DOOMPI_PROFILE_ENV]: selection.profile } : {}),\n ...(selection.domains && environment[DOOMPI_DOMAINS_ENV] === undefined\n ? { [DOOMPI_DOMAINS_ENV]: selection.domains.join(',') }\n : {}),\n };\n}\n\nexport function toSelection(\n options: Pick<HarnessOptions, 'majorMode' | 'domains' | 'profile' | 'preset'>,\n): SyncSelection {\n return {\n majorMode: options.majorMode,\n domains: options.domains,\n profile: options.profile,\n preset: options.preset,\n };\n}\n\nexport function selectionCompositionFingerprint(\n repoRoot: string,\n options: Pick<HarnessOptions, 'agents' | 'hooks' | 'majorMode' | 'mcp' | 'preset'>,\n homeDirectory: string = os.homedir(),\n): string {\n const majorModesConfig = loadMajorModesConfig(repoRoot, homeDirectory);\n const resolvers = createLayerResolvers(repoRoot);\n return resolveExtensionComposition({\n agents: options.agents,\n autoStop: false,\n mute: false,\n preset: options.preset,\n personaEntry: resolvers.packageEntry(PERSONA_ENTRY),\n majorMode: options.majorMode,\n layers: filterHookDisabledLayers(\n majorModesConfig,\n resolveLayers(majorModesConfig, options.majorMode),\n options.hooks,\n ),\n majorModesConfig,\n resolvers,\n }).fingerprint;\n}\n\n/** Settings, dispatcher and theme differences an init would fix, independent of sync state. */\nfunction piIntegrationDrift(agentDirectory: string): string[] {\n const drift: string[] = [];\n const themePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n const settings = readPiSettings(agentDirectory);\n const merged = mergePiSettings(settings, agentDirectory, { themePath, themeName: DEFAULT_THEME_NAME });\n if (serializePiSettings(merged) !== serializePiSettings(settings)) {\n drift.push('Pi user settings are out of date; run doompi init');\n }\n if (!piExtensionAliasIsCurrent(agentDirectory)) drift.push('Pi user dispatcher is out of date; run doompi init');\n const expectedTheme = `${JSON.stringify(DEFAULT_THEME, null, 2)}\\n`;\n if (!fs.existsSync(themePath) || fs.readFileSync(themePath, 'utf8') !== expectedTheme) {\n drift.push('Pi user theme is out of date; run doompi init');\n }\n return drift;\n}\n\n/** Differences between what a sync would produce and what is on disk. */\nexport function collectDrift(\n repoRoot: string,\n selection: SyncSelection,\n state: SyncState | undefined,\n environment: NodeJS.ProcessEnv = process.env,\n settingsMode: SyncSettingsMode = 'persisted',\n expectedCompositionFingerprint?: string,\n): string[] {\n if (!state) return ['no sync state: run doompi sync'];\n const drift: string[] = [];\n if (!syncStateRootMatches(repoRoot, state.root)) drift.push('sync state belongs to a different repository');\n const recorded = state.selection;\n if (\n recorded.majorMode !== selection.majorMode ||\n recorded.profile !== selection.profile ||\n recorded.preset !== selection.preset ||\n recorded.domains.join(',') !== selection.domains.join(',')\n ) {\n drift.push('selection changed since the last sync');\n }\n // Hashed against the recorded selection, not the requested one, so a\n // selection change is reported once rather than as two findings.\n if (computeInputsHash(repoRoot, recorded, environment.HOME ?? os.homedir()) !== state.inputsHash) {\n drift.push('.doom configuration changed');\n }\n // Re-resolving is what catches a dependency upgrade moving a package, which\n // the inputs hash deliberately does not read.\n if (\n JSON.stringify(\n recordResolvedEntries(\n loadMajorModesConfig(repoRoot, environment.HOME ?? os.homedir()),\n createLayerResolvers(repoRoot),\n ),\n ) !== JSON.stringify(state.resolved)\n ) {\n drift.push('resolved extension paths changed');\n }\n if (expectedCompositionFingerprint && state.compositionFingerprint !== expectedCompositionFingerprint) {\n drift.push('extension composition changed');\n }\n try {\n if (!readBootstrapStatus(repoRoot, undefined, environment.HOME ?? os.homedir()).fresh) {\n drift.push('precompiled runtime is missing or stale');\n }\n } catch {\n drift.push('precompiled runtime is missing or stale');\n }\n\n if (settingsMode === 'persisted') {\n const agentDirectory = piAgentDirectory(environment);\n const themePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n drift.push(...piIntegrationDrift(agentDirectory));\n if (projectRegistersDoom(repoRoot)) drift.push(DUPLICATE_REGISTRATION_DRIFT);\n if (state.baseline.themePath !== themePath || state.baseline.themeName !== DEFAULT_THEME_NAME) {\n drift.push('synced theme location is out of date');\n }\n }\n return drift;\n}\n\n/** Regenerates the hook files the other frontends read before any harness code runs. */\nfunction emitFrontendHooks(repoRoot: string, output: SyncOutput): void {\n const emitter = path.join(repoRoot, HOOK_EMITTER);\n if (!fs.existsSync(emitter)) return;\n const result = spawnSync(process.execPath, [emitter, '--write'], { cwd: repoRoot, encoding: 'utf8' });\n if (result.status === 0) {\n output.write('hooks: regenerated for Claude Code and Codex\\n');\n return;\n }\n output.write(`hooks: emit-hooks failed (${result.stderr?.trim() || `exit ${String(result.status)}`})\\n`);\n}\n\nexport function formatSyncResult(result: SyncResult, runner = 'pi'): string {\n const { selection } = result;\n return [\n `mode: ${selection.majorMode}`,\n `domains: ${selection.domains.join(', ') || NONE}`,\n `profile: ${selection.profile ?? NONE}`,\n `skills: ${result.skillCount}`,\n `agents: ${result.agentCount}`,\n `mcp: ${result.mcpServers.join(', ') || NONE}`,\n `state: ${result.statePath}`,\n ...(result.settingsPath ? [`settings: ${result.settingsPath}`] : []),\n ...(result.projectSettingsPath\n ? [`project: removed duplicate registration from ${result.projectSettingsPath}`]\n : []),\n '',\n `Run ${runner} from the repository root to use it.`,\n '',\n ].join('\\n');\n}\n\n/**\n * The DoomPi package a repository pins for itself, if it pins one.\n *\n * Extensions are version-coupled to the harness that loads them, so the\n * registration must name the copy the repository resolves rather than whichever\n * copy happened to run sync. A globally installed DoomPi syncing a repository\n * that pins its own would otherwise record itself, and the dispatcher would\n * then load the wrong harness for every session in that repository.\n */\nfunction repositoryPackageRoot(repoRoot: string): string | undefined {\n try {\n return path.dirname(\n createRequire(path.join(repoRoot, 'package.json')).resolve(`${DOOM_PACKAGE_NAME}/package.json`),\n );\n } catch {\n // Not pinned here, which is normal; the executing package stands in.\n return undefined;\n }\n}\n\nfunction packageRegistrationFor(repoRoot: string): SyncPackageRegistration {\n const root = fs.realpathSync(repositoryPackageRoot(repoRoot) ?? doomPiPackageRoot());\n const manifestPath = path.join(root, 'package.json');\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as {\n version?: unknown;\n pi?: { extensions?: unknown };\n };\n const version = manifest.version;\n const extensions = manifest.pi?.extensions;\n const extension = Array.isArray(extensions) ? extensions.find((value) => typeof value === 'string') : undefined;\n if (typeof version !== 'string' || typeof extension !== 'string') {\n throw new Error(`Installed DoomPi package at ${root} has no versioned Pi extension entry`);\n }\n return {\n root,\n version,\n manifestPath,\n entry: fs.realpathSync(path.resolve(root, extension)),\n };\n}\n\n/**\n * Removes generations the published one replaced.\n *\n * Each generation holds a full cockpit bundle and runtime, so keeping every one\n * ever built grows without bound. One superseded generation is retained because\n * a hub that resolved its assets a moment ago may still be reading them; older\n * ones have no reader left. Ordered by directory name, whose leading timestamp\n * makes publication order recoverable without reading each state file.\n */\nfunction pruneSupersededGenerations(\n generationsDirectory: string,\n published: string,\n onNotice: (message: string) => void,\n): void {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(generationsDirectory, { withFileTypes: true });\n } catch (error) {\n // Nothing to prune is indistinguishable from an unreadable directory here,\n // and neither is worth failing a sync that already published.\n onNotice(`could not list generations to prune: ${describeError(error)}`);\n return;\n }\n const superseded = entries\n .filter((entry) => entry.isDirectory() && entry.name !== published)\n .map((entry) => entry.name)\n .sort();\n const removable = superseded.slice(0, Math.max(0, superseded.length - RETAINED_GENERATIONS));\n for (const name of removable) {\n try {\n fs.rmSync(path.join(generationsDirectory, name), { recursive: true, force: true });\n } catch (error) {\n // A generation still held open elsewhere stays; the next sync retries it.\n onNotice(`could not remove superseded generation ${name}: ${describeError(error)}`);\n }\n }\n if (removable.length > 0) onNotice(`pruned ${String(removable.length)} superseded generation(s)`);\n}\n\nfunction describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** Resolves the matrix, stages it into home-scoped worktree storage, and publishes one generation. */\nexport class SyncCommand {\n readonly name = SYNC_COMMAND;\n private readonly settingsMode: SyncSettingsMode;\n private readonly homeDirectory: string | undefined;\n private readonly lockHeld: boolean;\n\n constructor(options: SyncCommandOptions = {}) {\n this.settingsMode = options.settingsMode ?? 'persisted';\n this.homeDirectory = options.homeDirectory;\n this.lockHeld = options.lockHeld ?? false;\n }\n\n matches(args: string[]): boolean {\n return args[0] === this.name;\n }\n\n async execute(\n args: string[],\n environment: NodeJS.ProcessEnv = process.env,\n currentDirectory = process.cwd(),\n output: SyncOutput = process.stdout,\n ): Promise<number> {\n const check = args.includes(CHECK_OPTION);\n const force = args.includes(FORCE_OPTION);\n const rest = args.slice(1).filter((argument) => argument !== CHECK_OPTION && argument !== FORCE_OPTION);\n const homeDirectory = this.homeDirectory ?? environment.HOME ?? os.homedir();\n const inheritedRoot = environment[HARNESS_ROOT_ENV];\n const repoRoot = inheritedRoot\n ? path.resolve(inheritedRoot)\n : resolveDoomConfigurationRoot(currentDirectory, homeDirectory);\n const defaultMajorMode = loadMajorModesConfig(repoRoot, homeDirectory).defaultMajorMode;\n const defaultDomains = loadDomains(repoRoot, homeDirectory).defaultDomains;\n const parsed = parseHarnessArgs(\n rest,\n selectionEnvironment(repoRoot, environment),\n currentDirectory,\n defaultMajorMode,\n defaultDomains,\n );\n const selection = toSelection(parsed.options);\n const agentDirectory = piAgentDirectory(environment, homeDirectory);\n if (this.settingsMode === 'persisted' && !check) {\n if (piExtensionDispatcherIsUpgradeable(agentDirectory)) {\n const previousVersion = piExtensionDispatcherVersion(agentDirectory);\n writePiExtensionAlias(agentDirectory);\n output.write(\n `repair: upgraded Pi user dispatcher from protocol ${String(previousVersion)} to ${String(PI_DISPATCHER_VERSION)}\\n`,\n );\n }\n const drift = piIntegrationDrift(agentDirectory);\n if (drift.length > 0) {\n throw new Error(`DoomPi Pi integration is not ready:\\n${drift.map((entry) => ` ${entry}`).join('\\n')}`);\n }\n }\n if (check) {\n const majorModesConfig = loadMajorModesConfig(repoRoot, homeDirectory);\n const missingPackages = missingLayerPackageSpecifiers(\n majorModesConfig,\n Object.keys(majorModesConfig.layers),\n createLayerResolvers(repoRoot),\n );\n if (missingPackages.length > 0) {\n output.write(\n `doompi sync is out of date:\\n${missingPackages\n .map((specifier) => ` configured package is not installed: ${specifier}`)\n .join('\\n')}\\n`,\n );\n return 1;\n }\n let located: ReturnType<typeof readLocatedSyncState>;\n try {\n located = readLocatedSyncState(repoRoot, homeDirectory);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n output.write(`doompi sync is out of date:\\n ${detail}\\n`);\n return 1;\n }\n const expectedCompositionFingerprint = selectionCompositionFingerprint(repoRoot, parsed.options, homeDirectory);\n const drift = collectDrift(\n repoRoot,\n selection,\n located?.state,\n environment,\n this.settingsMode,\n expectedCompositionFingerprint,\n );\n if (drift.length === 0) {\n output.write('doompi sync is up to date\\n');\n return 0;\n }\n output.write(`doompi sync is out of date:\\n${drift.map((entry) => ` ${entry}`).join('\\n')}\\n`);\n return 1;\n }\n\n // Publishing an identical generation is not a no-op: it moves the\n // registration, so every attached cockpit reloads and the previous\n // generation becomes garbage. Same inputs, same published result.\n const driftOptions = {\n repoRoot,\n homeDirectory,\n requireWebBundle: Boolean(environment.DOOMPI_WEB_PACKAGE_ROOT),\n };\n if (!force && readSyncDrift(driftOptions).fresh) {\n output.write('doompi sync is already up to date\\n');\n return 0;\n }\n\n const progress = new SyncProgress(output);\n const releaseLock = this.lockHeld\n ? undefined\n : await acquireSyncLocationLock(resolveSyncLocation(repoRoot, homeDirectory));\n let result: SyncResult;\n try {\n // A concurrent publisher may have resolved the drift while this command\n // waited for the lock. Avoid moving the registration for no change.\n if (!force && readSyncDrift(driftOptions).fresh) {\n output.write('doompi sync is already up to date\\n');\n return 0;\n }\n result = await this.stage(repoRoot, parsed.options, environment, homeDirectory, progress);\n } finally {\n await releaseLock?.();\n }\n emitFrontendHooks(repoRoot, output);\n output.write(formatSyncResult(result, this.settingsMode === 'embedded' ? 'dpi' : 'pi'));\n return 0;\n }\n\n private async stage(\n repoRoot: string,\n options: Omit<HarnessOptions, 'repoRoot'>,\n environment: NodeJS.ProcessEnv,\n homeDirectory: string,\n progress: SyncProgress,\n ): Promise<SyncResult> {\n const location = resolveSyncLocation(repoRoot, homeDirectory);\n const generation = `${Date.now().toString(36)}-${crypto.randomUUID()}`;\n const directory = syncGenerationDirectory(location, generation);\n await fs.promises.mkdir(location.generationsDirectory, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });\n // The leaf is created without `recursive`, so an existing path is an error\n // rather than something to adopt: the cockpit signs and serves whatever the\n // published generation holds, and sync must only ever publish bytes it\n // wrote itself into a directory it just created.\n await fs.promises.mkdir(directory, { mode: PRIVATE_DIRECTORY_MODE });\n\n try {\n const staged = progress.start(SYNC_LABEL, 'resolving the matrix and staging resources');\n const context = await buildHarnessContext({\n ...options,\n repoRoot: location.root,\n homeDirectory,\n cwd: location.root,\n resourceDirectory: directory,\n });\n await ensureLayerPackages({\n repoRoot: location.root,\n config: context.majorModesConfig,\n layers: Object.keys(context.majorModesConfig.layers),\n environment,\n });\n staged(`${String(context.resources.skillCount)} skills, ${String(context.resources.agentCount)} agents`);\n const selection = toSelection(options);\n const resolvers = createLayerResolvers(location.root);\n const resolved = recordResolvedEntries(context.majorModesConfig, resolvers);\n const compositionFingerprint = selectionCompositionFingerprint(location.root, options, homeDirectory);\n const agentDirectory = piAgentDirectory(environment, homeDirectory);\n const persistedThemePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n const themePath = this.settingsMode === 'persisted' ? persistedThemePath : context.defaultThemePath;\n const state: SyncState = {\n version: SYNC_STATE_VERSION,\n root: location.root,\n identity: location.identity,\n inputsHash: computeInputsHash(location.root, selection, homeDirectory),\n webSourcesHash: computeWebSourcesHash(resolved),\n compositionFingerprint,\n selection,\n env: recordedEnvironment(context.environment),\n fileState: {\n profileEnvironment: loadHarnessState(context.environment).state.profileEnvironment,\n pluginHooks: context.resources.pluginHooks,\n mcpProjection: context.resources.mcpProjection,\n },\n resolved,\n baseline: {\n mcpConfigPath: context.resources.mcpConfigPath,\n personaFile: context.environment[PERSONA_FILE_ENV],\n themePath,\n themeName: DEFAULT_THEME_NAME,\n },\n };\n\n const precompiled = progress.start(RUNTIME_LABEL, 'precompiling the mode bundles');\n const synced = await buildSyncedRuntime(location.root, environment, homeDirectory, { state, directory });\n precompiled(`${String(Object.keys(synced.bundles).length)} mode bundles`);\n const statePath = await writeSyncState(\n location.root,\n synced.state,\n homeDirectory,\n path.join(directory, 'state.json'),\n );\n\n const webProgress = progress.start(WEB_LABEL, 'bundling the web cockpit plugins');\n const web = await syncWebBundle({\n repoRoot: location.root,\n resolvedEntries: synced.state.resolved,\n environment,\n outputDirectory: path.join(directory, 'web-bundle'),\n onNotice: (message) => progress.line(WEB_LABEL, message),\n });\n if (web.status === 'failed') throw new Error(`Cockpit bundle failed: ${web.reason}`);\n webProgress(web.status === 'bundled' ? `cockpit bundled with plugins: ${web.pluginIds.join(', ')}` : web.reason);\n\n const apiProgress = progress.start(API_LABEL, 'generating the package API routes');\n const api = syncApiRoutes({\n resolvedEntries: synced.state.resolved,\n outputDirectory: path.join(directory, 'api'),\n onNotice: (message) => progress.line(API_LABEL, message),\n });\n apiProgress(`routes written to ${api.directory}`);\n\n publishSyncRegistration(\n location.root,\n {\n version: SYNC_REGISTRATION_VERSION,\n root: location.root,\n identity: location.identity,\n generation,\n generationRoot: directory,\n statePath,\n stateSha256: syncStateSha256(statePath),\n webDirectory: web.status === 'bundled' ? web.assetsDir : null,\n apiDirectory: api.directory,\n package: packageRegistrationFor(location.root),\n },\n homeDirectory,\n );\n pruneSupersededGenerations(location.generationsDirectory, generation, (message) =>\n progress.line(SYNC_LABEL, message),\n );\n const projectSettingsPath = this.settingsMode === 'persisted' ? writeProjectPiSettings(location.root) : undefined;\n\n return {\n statePath,\n ...(projectSettingsPath ? { projectSettingsPath } : {}),\n selection,\n mcpServers: synced.state.baseline.mcpConfigPath ? readMcpServerNames(synced.state.baseline.mcpConfigPath) : [],\n skillCount: context.resources.skillCount,\n agentCount: context.resources.agentCount,\n };\n } catch (error) {\n await fs.promises.rm(directory, { recursive: true, force: true });\n throw error;\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"syncCommand.d.cts","names":[],"sources":["../../../src/commands/syncCommand.ts"],"mappings":";;;;KAoHK,aAAa;KAEN;UAEK;EACf,eAAe;;EAEf;;EAEA;;UAGe;EACf;;EAEA;;EAEA;EACA,WAAW;EACX;EACA;EACA;;iBAGc,oBAAoB,aAAa,OAAO,aAAa;;;;;;;;;iBAmBrD,qBAAqB,kBAAkB,aAAa,OAAO,aAAa,OAAO;iBAe/E,YACd,SAAS,KAAK,kEACb;iBASa,gCACd,kBACA,SAAS,KAAK,sEACd;;iBAuCc,aACd,kBACA,WAAW,eACX,OAAO,uBACP,cAAa,OAAO,YACpB,eAAc,kBACd;iBAkEc,iBAAiB,QAAQ,YAAY;;cAyGxC;WACF;mBACQ;mBACA;mBACA;EAEjB,YAAY,UAAS;EAMrB,QAAQ;EAIF,QACJ,gBACA,cAAa,OAAO,YACpB,2BACA,SAAQ,aACP;UAsGW"}
1
+ {"version":3,"file":"syncCommand.d.cts","names":[],"sources":["../../../src/commands/syncCommand.ts"],"mappings":";;;;KAoHK,aAAa;KAEN;UAEK;EACf,eAAe;;EAEf;;EAEA;;UAGe;EACf;;EAEA;;EAEA;EACA,WAAW;EACX;EACA;EACA;;iBAGc,oBAAoB,aAAa,OAAO,aAAa;;;;;;;;;iBAmBrD,qBAAqB,kBAAkB,aAAa,OAAO,aAAa,OAAO;iBAe/E,YACd,SAAS,KAAK,kEACb;iBASa,gCACd,kBACA,SAAS,KAAK,sEACd;;iBAuCc,aACd,kBACA,WAAW,eACX,OAAO,uBACP,cAAa,OAAO,YACpB,eAAc,kBACd;iBAkEc,iBAAiB,QAAQ,YAAY;;cAyGxC;WACF;mBACQ;mBACA;mBACA;EAEjB,YAAY,UAAS;EAMrB,QAAQ;EAIF,QACJ,gBACA,cAAa,OAAO,YACpB,2BACA,SAAQ,aACP;UA2GW"}
@@ -1 +1 @@
1
- {"version":3,"file":"syncCommand.d.mts","names":[],"sources":["../../../src/commands/syncCommand.ts"],"mappings":";;;;KAoHK,aAAa;KAEN;UAEK;EACf,eAAe;;EAEf;;EAEA;;UAGe;EACf;;EAEA;;EAEA;EACA,WAAW;EACX;EACA;EACA;;iBAGc,oBAAoB,aAAa,OAAO,aAAa;;;;;;;;;iBAmBrD,qBAAqB,kBAAkB,aAAa,OAAO,aAAa,OAAO;iBAe/E,YACd,SAAS,KAAK,kEACb;iBASa,gCACd,kBACA,SAAS,KAAK,sEACd;;iBAuCc,aACd,kBACA,WAAW,eACX,OAAO,uBACP,cAAa,OAAO,YACpB,eAAc,kBACd;iBAkEc,iBAAiB,QAAQ,YAAY;;cAyGxC;WACF;mBACQ;mBACA;mBACA;EAEjB,YAAY,UAAS;EAMrB,QAAQ;EAIF,QACJ,gBACA,cAAa,OAAO,YACpB,2BACA,SAAQ,aACP;UAsGW"}
1
+ {"version":3,"file":"syncCommand.d.mts","names":[],"sources":["../../../src/commands/syncCommand.ts"],"mappings":";;;;KAoHK,aAAa;KAEN;UAEK;EACf,eAAe;;EAEf;;EAEA;;UAGe;EACf;;EAEA;;EAEA;EACA,WAAW;EACX;EACA;EACA;;iBAGc,oBAAoB,aAAa,OAAO,aAAa;;;;;;;;;iBAmBrD,qBAAqB,kBAAkB,aAAa,OAAO,aAAa,OAAO;iBAe/E,YACd,SAAS,KAAK,kEACb;iBASa,gCACd,kBACA,SAAS,KAAK,sEACd;;iBAuCc,aACd,kBACA,WAAW,eACX,OAAO,uBACP,cAAa,OAAO,YACpB,eAAc,kBACd;iBAkEc,iBAAiB,QAAQ,YAAY;;cAyGxC;WACF;mBACQ;mBACA;mBACA;EAEjB,YAAY,UAAS;EAMrB,QAAQ;EAIF,QACJ,gBACA,cAAa,OAAO,YACpB,2BACA,SAAQ,aACP;UA2GW"}
@@ -4,7 +4,7 @@ import{resolveDoomConfigurationRoot as e}from"../adapters/repository/repository.
4
4
  `)}`)}if(s){let e=B(m,d),t=F(e,Object.keys(e.layers),c(m));if(t.length>0)return o.write(`doompi sync is out of date:\n${t.map(e=>` configured package is not installed: ${e}`).join(`
5
5
  `)}\n`),1;let n;try{n=f(m,d)}catch(e){let t=e instanceof Error?e.message:String(e);return o.write(`doompi sync is out of date:\n ${t}\n`),1}let r=Y(m,_.options,d),a=Z(m,v,n?.state,i,this.settingsMode,r);return a.length===0?(o.write(`doompi sync is up to date
6
6
  `),0):(o.write(`doompi sync is out of date:\n${a.map(e=>` ${e}`).join(`
7
- `)}\n`),1)}if(!l&&I({repoRoot:m,homeDirectory:d}).fresh)return o.write(`doompi sync is already up to date
8
- `),0;let b=new ce(o),S=this.lockHeld?void 0:await n(r(m,d)),C;try{if(!l&&I({repoRoot:m,homeDirectory:d}).fresh)return o.write(`doompi sync is already up to date
9
- `),0;C=await this.stage(m,_.options,i,d,b)}finally{await S?.()}return be(m,o),o.write(Q(C,this.settingsMode===`embedded`?`dpi`:`pi`)),0}async stage(e,t,n,s,l){let f=r(e,s),h=`${Date.now().toString(36)}-${ue.randomUUID()}`,_=i(f,h);await R.promises.mkdir(f.generationsDirectory,{recursive:!0,mode:448}),await R.promises.mkdir(_,{mode:448});try{let e=l.start(G,`resolving the matrix and staging resources`),r=await ee({...t,repoRoot:f.root,homeDirectory:s,cwd:f.root,resourceDirectory:_});await te({repoRoot:f.root,config:r.majorModesConfig,layers:Object.keys(r.majorModesConfig.layers),environment:n}),e(`${String(r.resources.skillCount)} skills, ${String(r.resources.agentCount)} agents`);let i=J(t),v=c(f.root),y=m(r.majorModesConfig,v),b=Y(f.root,t,s),x=A(n,s),S=L.join(j(x),`${V}.json`),w=this.settingsMode===`persisted`?S:r.defaultThemePath,T={version:13,root:f.root,identity:f.identity,inputsHash:u(f.root,i,s),webSourcesHash:d(y),compositionFingerprint:b,selection:i,env:K(r.environment),fileState:{profileEnvironment:C(r.environment).state.profileEnvironment,pluginHooks:r.resources.pluginHooks,mcpProjection:r.resources.mcpProjection},resolved:y,baseline:{mcpConfigPath:r.resources.mcpConfigPath,personaFile:r.environment.DOOMPI_PERSONA_FILE,themePath:w,themeName:V}},E=l.start(`runtime`,`precompiling the mode bundles`),D=await ae(f.root,n,s,{state:T,directory:_});E(`${String(Object.keys(D.bundles).length)} mode bundles`);let O=await g(f.root,D.state,s,L.join(_,`state.json`)),k=l.start(`web`,`bundling the web cockpit plugins`),M=await se({repoRoot:f.root,resolvedEntries:D.state.resolved,environment:n,outputDirectory:L.join(_,`web-bundle`),onNotice:e=>l.line(`web`,e)});if(M.status===`failed`)throw Error(`Cockpit bundle failed: ${M.reason}`);k(M.status===`bundled`?`cockpit bundled with plugins: ${M.pluginIds.join(`, `)}`:M.reason);let N=l.start(`api`,`generating the package API routes`),P=oe({resolvedEntries:D.state.resolved,outputDirectory:L.join(_,`api`),onNotice:e=>l.line(`api`,e)});N(`routes written to ${P.directory}`),a(f.root,{version:1,root:f.root,identity:f.identity,generation:h,generationRoot:_,statePath:O,stateSha256:o(O),webDirectory:M.status===`bundled`?M.assetsDir:null,apiDirectory:P.directory,package:Se(f.root)},s),Ce(f.generationsDirectory,h,e=>l.line(G,e));let F=this.settingsMode===`persisted`?ie(f.root):void 0;return{statePath:O,...F?{projectSettingsPath:F}:{},selection:i,mcpServers:D.state.baseline.mcpConfigPath?p(D.state.baseline.mcpConfigPath):[],skillCount:r.resources.skillCount,agentCount:r.resources.agentCount}}catch(e){throw await R.promises.rm(_,{recursive:!0,force:!0}),e}}};export{we as SyncCommand,Z as collectDrift,Q as formatSyncResult,K as recordedEnvironment,Y as selectionCompositionFingerprint,q as selectionEnvironment,J as toSelection};
7
+ `)}\n`),1)}let b={repoRoot:m,homeDirectory:d,requireWebBundle:!!i.DOOMPI_WEB_PACKAGE_ROOT};if(!l&&I(b).fresh)return o.write(`doompi sync is already up to date
8
+ `),0;let S=new ce(o),C=this.lockHeld?void 0:await n(r(m,d)),E;try{if(!l&&I(b).fresh)return o.write(`doompi sync is already up to date
9
+ `),0;E=await this.stage(m,_.options,i,d,S)}finally{await C?.()}return be(m,o),o.write(Q(E,this.settingsMode===`embedded`?`dpi`:`pi`)),0}async stage(e,t,n,s,l){let f=r(e,s),h=`${Date.now().toString(36)}-${ue.randomUUID()}`,_=i(f,h);await R.promises.mkdir(f.generationsDirectory,{recursive:!0,mode:448}),await R.promises.mkdir(_,{mode:448});try{let e=l.start(G,`resolving the matrix and staging resources`),r=await ee({...t,repoRoot:f.root,homeDirectory:s,cwd:f.root,resourceDirectory:_});await te({repoRoot:f.root,config:r.majorModesConfig,layers:Object.keys(r.majorModesConfig.layers),environment:n}),e(`${String(r.resources.skillCount)} skills, ${String(r.resources.agentCount)} agents`);let i=J(t),v=c(f.root),y=m(r.majorModesConfig,v),b=Y(f.root,t,s),x=A(n,s),S=L.join(j(x),`${V}.json`),w=this.settingsMode===`persisted`?S:r.defaultThemePath,T={version:13,root:f.root,identity:f.identity,inputsHash:u(f.root,i,s),webSourcesHash:d(y),compositionFingerprint:b,selection:i,env:K(r.environment),fileState:{profileEnvironment:C(r.environment).state.profileEnvironment,pluginHooks:r.resources.pluginHooks,mcpProjection:r.resources.mcpProjection},resolved:y,baseline:{mcpConfigPath:r.resources.mcpConfigPath,personaFile:r.environment.DOOMPI_PERSONA_FILE,themePath:w,themeName:V}},E=l.start(`runtime`,`precompiling the mode bundles`),D=await ae(f.root,n,s,{state:T,directory:_});E(`${String(Object.keys(D.bundles).length)} mode bundles`);let O=await g(f.root,D.state,s,L.join(_,`state.json`)),k=l.start(`web`,`bundling the web cockpit plugins`),M=await se({repoRoot:f.root,resolvedEntries:D.state.resolved,environment:n,outputDirectory:L.join(_,`web-bundle`),onNotice:e=>l.line(`web`,e)});if(M.status===`failed`)throw Error(`Cockpit bundle failed: ${M.reason}`);k(M.status===`bundled`?`cockpit bundled with plugins: ${M.pluginIds.join(`, `)}`:M.reason);let N=l.start(`api`,`generating the package API routes`),P=oe({resolvedEntries:D.state.resolved,outputDirectory:L.join(_,`api`),onNotice:e=>l.line(`api`,e)});N(`routes written to ${P.directory}`),a(f.root,{version:1,root:f.root,identity:f.identity,generation:h,generationRoot:_,statePath:O,stateSha256:o(O),webDirectory:M.status===`bundled`?M.assetsDir:null,apiDirectory:P.directory,package:Se(f.root)},s),Ce(f.generationsDirectory,h,e=>l.line(G,e));let F=this.settingsMode===`persisted`?ie(f.root):void 0;return{statePath:O,...F?{projectSettingsPath:F}:{},selection:i,mcpServers:D.state.baseline.mcpConfigPath?p(D.state.baseline.mcpConfigPath):[],skillCount:r.resources.skillCount,agentCount:r.resources.agentCount}}catch(e){throw await R.promises.rm(_,{recursive:!0,force:!0}),e}}};export{we as SyncCommand,Z as collectDrift,Q as formatSyncResult,K as recordedEnvironment,Y as selectionCompositionFingerprint,q as selectionEnvironment,J as toSelection};
10
10
  //# sourceMappingURL=syncCommand.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"syncCommand.mjs","names":[],"sources":["../../../src/commands/syncCommand.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { filterHookDisabledLayers, resolveLayers } from '@agimon-ai/doompi-config/majorModes';\nimport { buildHarnessContext } from '../adapters/harnessContext.ts';\nimport { ensureLayerPackages, missingLayerPackageSpecifiers } from '../adapters/layerPackageInstaller.ts';\nimport { readBootstrapStatus } from '../adapters/bootstrapLocator.ts';\nimport { DOOM_PACKAGE_NAME } from '../adapters/doomPackage.ts';\nimport { doomPiPackageRoot, piExtensionAliasIsCurrent, writePiExtensionAlias } from '../adapters/piExtensionAlias.ts';\nimport {\n PI_DISPATCHER_VERSION,\n piExtensionDispatcherIsUpgradeable,\n piExtensionDispatcherVersion,\n} from '../adapters/piExtensionDispatcher.ts';\nimport {\n mergePiSettings,\n piAgentDirectory,\n piThemeDirectory,\n readPiSettings,\n serializePiSettings,\n} from '../adapters/piSettings.ts';\nimport {\n DUPLICATE_REGISTRATION_DRIFT,\n projectRegistersDoom,\n writeProjectPiSettings,\n} from '../adapters/projectPiSettings.ts';\nimport { buildSyncedRuntime } from '../adapters/syncedRuntimeBuilder.ts';\nimport { acquireSyncLocationLock, resolveSyncLocation, syncGenerationDirectory } from '../adapters/syncLocation.ts';\nimport {\n publishSyncRegistration,\n SYNC_REGISTRATION_VERSION,\n syncStateSha256,\n type SyncPackageRegistration,\n} from '../adapters/syncRegistration.ts';\nimport { readSyncDrift } from '../adapters/syncDrift.ts';\nimport { syncApiRoutes } from '../adapters/apiRoutesSync.ts';\nimport { syncWebBundle } from '../adapters/webBundleSync.ts';\nimport {\n computeInputsHash,\n computeWebSourcesHash,\n readLocatedSyncState,\n readMcpServerNames,\n recordResolvedEntries,\n SYNC_STATE_VERSION,\n type SyncSelection,\n type SyncState,\n syncStateRootMatches,\n writeSyncState,\n} from '../adapters/syncState.ts';\nimport { HARNESS_STATE_POINTER, loadHarnessState } from '../adapters/config/harnessState';\nimport { loadDomains } from '@agimon-ai/doompi-config/domains';\nimport { loadMajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { DEFAULT_THEME, DEFAULT_THEME_NAME } from '@agimon-ai/doompi-ui/theme';\nimport { loadDoomConfig } from '../services/config/projectTrust';\nimport { createLayerResolvers, PERSONA_ENTRY, resolveExtensionComposition } from '../services/extensionAssembler.ts';\nimport type { HarnessOptions } from '../types/interfaces/harness';\nimport { resolveDoomConfigurationRoot } from '../adapters/repository/repository';\nimport { DOOMPI_DOMAINS_ENV, DOOMPI_MAJOR_MODE_ENV, DOOMPI_PROFILE_ENV } from './cli/matrixOptions.ts';\nimport { parseHarnessArgs } from './cli/options.ts';\nimport { SyncProgress, type SyncProgressOutput } from './syncPresenter.ts';\n\n/**\n * `doom-pi sync`: resolve the matrix once and write it where plain Pi finds it.\n *\n * The doom-emacs split. Everything that needs a real Node process (module\n * resolution, staging skills and agents, generating the MCP config) happens\n * here, and the doom-pi extension then only reads what this produced. The\n * launcher is untouched and keeps resolving the same matrix per run.\n */\n\nconst SYNC_COMMAND = 'sync';\nconst CHECK_OPTION = '--check';\n/** Republishes even when nothing drifted, for a generation suspected of being damaged. */\nconst FORCE_OPTION = '--force';\nconst HARNESS_ROOT_ENV = 'DOOMPI_ROOT';\nconst PERSONA_FILE_ENV = 'DOOMPI_PERSONA_FILE';\nconst HOOK_EMITTER = path.join('tools', 'harness', 'emit-hooks.mjs');\nconst NONE = '(none)';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\n/** Published generations kept behind the current one, so a running hub mid-read survives a prune. */\nconst RETAINED_GENERATIONS = 1;\nconst SYNC_LABEL = 'sync';\nconst RUNTIME_LABEL = 'runtime';\nconst WEB_LABEL = 'web';\nconst API_LABEL = 'api';\n\n/**\n * Harness variables worth recording, by prefix or exact name.\n *\n * An allowlist rather than the whole environment: the state file is a snapshot\n * of resolved configuration, and dumping `process.env` into it would write\n * every credential the sync happened to run with onto disk.\n */\nconst RECORDED_PREFIXES = ['DOOMPI_'];\nconst RECORDED_KEYS = ['CLAUDE_PROJECT_DIR', 'CODEX_REPO_ROOT', 'ORIGINAL_REPO_PATH', 'MCP_UI_VIEWER'];\n/**\n * Launcher-only values a synced session must not inherit.\n *\n * The child extension list is recomposed on every load, and the subagent binary\n * points at `pi.sh`, which a session started as plain `pi` should not shell out\n * to: Doom Team resolves Pi's own CLI when the variable is absent.\n */\nconst EXCLUDED_KEYS = new Set([\n 'DOOMPI_CHILD_EXTENSIONS',\n 'DOOMPI_COMPOSED',\n 'DOOMPI_MUTE',\n 'DOOMPI_TEMP_DIR',\n // A pointer to the syncing process's own state file. Recording it would hand\n // every later session a path to a state that died with this one.\n HARNESS_STATE_POINTER,\n 'PI_SUBAGENT_PI_BINARY',\n]);\n\ntype SyncOutput = SyncProgressOutput;\n\nexport type SyncSettingsMode = 'persisted' | 'embedded';\n\nexport interface SyncCommandOptions {\n settingsMode?: SyncSettingsMode;\n /** Test/embedding override; normal CLI execution uses the process home. */\n homeDirectory?: string;\n /** Internal pipeline seam when the caller owns the worktree lock. */\n lockHeld?: boolean;\n}\n\nexport interface SyncResult {\n statePath: string;\n /** Omitted when DPI supplies the integration as a process-local overlay. */\n settingsPath?: string;\n /** Set only when the repository still carried its own DoomPi registration. */\n projectSettingsPath?: string;\n selection: SyncSelection;\n mcpServers: string[];\n skillCount: number;\n agentCount: number;\n}\n\nexport function recordedEnvironment(environment: NodeJS.ProcessEnv): Record<string, string> {\n const recorded: Record<string, string> = {};\n for (const [key, value] of Object.entries(environment)) {\n if (value === undefined || EXCLUDED_KEYS.has(key)) continue;\n if (RECORDED_KEYS.includes(key) || RECORDED_PREFIXES.some((prefix) => key.startsWith(prefix))) {\n recorded[key] = value;\n }\n }\n return recorded;\n}\n\n/**\n * Layers the repository's declared selection under the usual resolution.\n *\n * `.doom/config.yaml` holds what the repository selects by default, the way\n * init.el does for doom-emacs. Seeding the environment the parser reads keeps\n * the precedence the launcher already documents: an explicit flag wins, then an\n * exported variable, then the declared default.\n */\nexport function selectionEnvironment(repoRoot: string, environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const { selection } = loadDoomConfig(repoRoot);\n if (!selection) return environment;\n return {\n ...environment,\n ...(selection.majorMode && !environment[DOOMPI_MAJOR_MODE_ENV]\n ? { [DOOMPI_MAJOR_MODE_ENV]: selection.majorMode }\n : {}),\n ...(selection.profile && !environment[DOOMPI_PROFILE_ENV] ? { [DOOMPI_PROFILE_ENV]: selection.profile } : {}),\n ...(selection.domains && environment[DOOMPI_DOMAINS_ENV] === undefined\n ? { [DOOMPI_DOMAINS_ENV]: selection.domains.join(',') }\n : {}),\n };\n}\n\nexport function toSelection(\n options: Pick<HarnessOptions, 'majorMode' | 'domains' | 'profile' | 'preset'>,\n): SyncSelection {\n return {\n majorMode: options.majorMode,\n domains: options.domains,\n profile: options.profile,\n preset: options.preset,\n };\n}\n\nexport function selectionCompositionFingerprint(\n repoRoot: string,\n options: Pick<HarnessOptions, 'agents' | 'hooks' | 'majorMode' | 'mcp' | 'preset'>,\n homeDirectory: string = os.homedir(),\n): string {\n const majorModesConfig = loadMajorModesConfig(repoRoot, homeDirectory);\n const resolvers = createLayerResolvers(repoRoot);\n return resolveExtensionComposition({\n agents: options.agents,\n autoStop: false,\n mute: false,\n preset: options.preset,\n personaEntry: resolvers.packageEntry(PERSONA_ENTRY),\n majorMode: options.majorMode,\n layers: filterHookDisabledLayers(\n majorModesConfig,\n resolveLayers(majorModesConfig, options.majorMode),\n options.hooks,\n ),\n majorModesConfig,\n resolvers,\n }).fingerprint;\n}\n\n/** Settings, dispatcher and theme differences an init would fix, independent of sync state. */\nfunction piIntegrationDrift(agentDirectory: string): string[] {\n const drift: string[] = [];\n const themePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n const settings = readPiSettings(agentDirectory);\n const merged = mergePiSettings(settings, agentDirectory, { themePath, themeName: DEFAULT_THEME_NAME });\n if (serializePiSettings(merged) !== serializePiSettings(settings)) {\n drift.push('Pi user settings are out of date; run doompi init');\n }\n if (!piExtensionAliasIsCurrent(agentDirectory)) drift.push('Pi user dispatcher is out of date; run doompi init');\n const expectedTheme = `${JSON.stringify(DEFAULT_THEME, null, 2)}\\n`;\n if (!fs.existsSync(themePath) || fs.readFileSync(themePath, 'utf8') !== expectedTheme) {\n drift.push('Pi user theme is out of date; run doompi init');\n }\n return drift;\n}\n\n/** Differences between what a sync would produce and what is on disk. */\nexport function collectDrift(\n repoRoot: string,\n selection: SyncSelection,\n state: SyncState | undefined,\n environment: NodeJS.ProcessEnv = process.env,\n settingsMode: SyncSettingsMode = 'persisted',\n expectedCompositionFingerprint?: string,\n): string[] {\n if (!state) return ['no sync state: run doompi sync'];\n const drift: string[] = [];\n if (!syncStateRootMatches(repoRoot, state.root)) drift.push('sync state belongs to a different repository');\n const recorded = state.selection;\n if (\n recorded.majorMode !== selection.majorMode ||\n recorded.profile !== selection.profile ||\n recorded.preset !== selection.preset ||\n recorded.domains.join(',') !== selection.domains.join(',')\n ) {\n drift.push('selection changed since the last sync');\n }\n // Hashed against the recorded selection, not the requested one, so a\n // selection change is reported once rather than as two findings.\n if (computeInputsHash(repoRoot, recorded, environment.HOME ?? os.homedir()) !== state.inputsHash) {\n drift.push('.doom configuration changed');\n }\n // Re-resolving is what catches a dependency upgrade moving a package, which\n // the inputs hash deliberately does not read.\n if (\n JSON.stringify(\n recordResolvedEntries(\n loadMajorModesConfig(repoRoot, environment.HOME ?? os.homedir()),\n createLayerResolvers(repoRoot),\n ),\n ) !== JSON.stringify(state.resolved)\n ) {\n drift.push('resolved extension paths changed');\n }\n if (expectedCompositionFingerprint && state.compositionFingerprint !== expectedCompositionFingerprint) {\n drift.push('extension composition changed');\n }\n try {\n if (!readBootstrapStatus(repoRoot, undefined, environment.HOME ?? os.homedir()).fresh) {\n drift.push('precompiled runtime is missing or stale');\n }\n } catch {\n drift.push('precompiled runtime is missing or stale');\n }\n\n if (settingsMode === 'persisted') {\n const agentDirectory = piAgentDirectory(environment);\n const themePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n drift.push(...piIntegrationDrift(agentDirectory));\n if (projectRegistersDoom(repoRoot)) drift.push(DUPLICATE_REGISTRATION_DRIFT);\n if (state.baseline.themePath !== themePath || state.baseline.themeName !== DEFAULT_THEME_NAME) {\n drift.push('synced theme location is out of date');\n }\n }\n return drift;\n}\n\n/** Regenerates the hook files the other frontends read before any harness code runs. */\nfunction emitFrontendHooks(repoRoot: string, output: SyncOutput): void {\n const emitter = path.join(repoRoot, HOOK_EMITTER);\n if (!fs.existsSync(emitter)) return;\n const result = spawnSync(process.execPath, [emitter, '--write'], { cwd: repoRoot, encoding: 'utf8' });\n if (result.status === 0) {\n output.write('hooks: regenerated for Claude Code and Codex\\n');\n return;\n }\n output.write(`hooks: emit-hooks failed (${result.stderr?.trim() || `exit ${String(result.status)}`})\\n`);\n}\n\nexport function formatSyncResult(result: SyncResult, runner = 'pi'): string {\n const { selection } = result;\n return [\n `mode: ${selection.majorMode}`,\n `domains: ${selection.domains.join(', ') || NONE}`,\n `profile: ${selection.profile ?? NONE}`,\n `skills: ${result.skillCount}`,\n `agents: ${result.agentCount}`,\n `mcp: ${result.mcpServers.join(', ') || NONE}`,\n `state: ${result.statePath}`,\n ...(result.settingsPath ? [`settings: ${result.settingsPath}`] : []),\n ...(result.projectSettingsPath\n ? [`project: removed duplicate registration from ${result.projectSettingsPath}`]\n : []),\n '',\n `Run ${runner} from the repository root to use it.`,\n '',\n ].join('\\n');\n}\n\n/**\n * The DoomPi package a repository pins for itself, if it pins one.\n *\n * Extensions are version-coupled to the harness that loads them, so the\n * registration must name the copy the repository resolves rather than whichever\n * copy happened to run sync. A globally installed DoomPi syncing a repository\n * that pins its own would otherwise record itself, and the dispatcher would\n * then load the wrong harness for every session in that repository.\n */\nfunction repositoryPackageRoot(repoRoot: string): string | undefined {\n try {\n return path.dirname(\n createRequire(path.join(repoRoot, 'package.json')).resolve(`${DOOM_PACKAGE_NAME}/package.json`),\n );\n } catch {\n // Not pinned here, which is normal; the executing package stands in.\n return undefined;\n }\n}\n\nfunction packageRegistrationFor(repoRoot: string): SyncPackageRegistration {\n const root = fs.realpathSync(repositoryPackageRoot(repoRoot) ?? doomPiPackageRoot());\n const manifestPath = path.join(root, 'package.json');\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as {\n version?: unknown;\n pi?: { extensions?: unknown };\n };\n const version = manifest.version;\n const extensions = manifest.pi?.extensions;\n const extension = Array.isArray(extensions) ? extensions.find((value) => typeof value === 'string') : undefined;\n if (typeof version !== 'string' || typeof extension !== 'string') {\n throw new Error(`Installed DoomPi package at ${root} has no versioned Pi extension entry`);\n }\n return {\n root,\n version,\n manifestPath,\n entry: fs.realpathSync(path.resolve(root, extension)),\n };\n}\n\n/**\n * Removes generations the published one replaced.\n *\n * Each generation holds a full cockpit bundle and runtime, so keeping every one\n * ever built grows without bound. One superseded generation is retained because\n * a hub that resolved its assets a moment ago may still be reading them; older\n * ones have no reader left. Ordered by directory name, whose leading timestamp\n * makes publication order recoverable without reading each state file.\n */\nfunction pruneSupersededGenerations(\n generationsDirectory: string,\n published: string,\n onNotice: (message: string) => void,\n): void {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(generationsDirectory, { withFileTypes: true });\n } catch (error) {\n // Nothing to prune is indistinguishable from an unreadable directory here,\n // and neither is worth failing a sync that already published.\n onNotice(`could not list generations to prune: ${describeError(error)}`);\n return;\n }\n const superseded = entries\n .filter((entry) => entry.isDirectory() && entry.name !== published)\n .map((entry) => entry.name)\n .sort();\n const removable = superseded.slice(0, Math.max(0, superseded.length - RETAINED_GENERATIONS));\n for (const name of removable) {\n try {\n fs.rmSync(path.join(generationsDirectory, name), { recursive: true, force: true });\n } catch (error) {\n // A generation still held open elsewhere stays; the next sync retries it.\n onNotice(`could not remove superseded generation ${name}: ${describeError(error)}`);\n }\n }\n if (removable.length > 0) onNotice(`pruned ${String(removable.length)} superseded generation(s)`);\n}\n\nfunction describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** Resolves the matrix, stages it into home-scoped worktree storage, and publishes one generation. */\nexport class SyncCommand {\n readonly name = SYNC_COMMAND;\n private readonly settingsMode: SyncSettingsMode;\n private readonly homeDirectory: string | undefined;\n private readonly lockHeld: boolean;\n\n constructor(options: SyncCommandOptions = {}) {\n this.settingsMode = options.settingsMode ?? 'persisted';\n this.homeDirectory = options.homeDirectory;\n this.lockHeld = options.lockHeld ?? false;\n }\n\n matches(args: string[]): boolean {\n return args[0] === this.name;\n }\n\n async execute(\n args: string[],\n environment: NodeJS.ProcessEnv = process.env,\n currentDirectory = process.cwd(),\n output: SyncOutput = process.stdout,\n ): Promise<number> {\n const check = args.includes(CHECK_OPTION);\n const force = args.includes(FORCE_OPTION);\n const rest = args.slice(1).filter((argument) => argument !== CHECK_OPTION && argument !== FORCE_OPTION);\n const homeDirectory = this.homeDirectory ?? environment.HOME ?? os.homedir();\n const inheritedRoot = environment[HARNESS_ROOT_ENV];\n const repoRoot = inheritedRoot\n ? path.resolve(inheritedRoot)\n : resolveDoomConfigurationRoot(currentDirectory, homeDirectory);\n const defaultMajorMode = loadMajorModesConfig(repoRoot, homeDirectory).defaultMajorMode;\n const defaultDomains = loadDomains(repoRoot, homeDirectory).defaultDomains;\n const parsed = parseHarnessArgs(\n rest,\n selectionEnvironment(repoRoot, environment),\n currentDirectory,\n defaultMajorMode,\n defaultDomains,\n );\n const selection = toSelection(parsed.options);\n const agentDirectory = piAgentDirectory(environment, homeDirectory);\n if (this.settingsMode === 'persisted' && !check) {\n if (piExtensionDispatcherIsUpgradeable(agentDirectory)) {\n const previousVersion = piExtensionDispatcherVersion(agentDirectory);\n writePiExtensionAlias(agentDirectory);\n output.write(\n `repair: upgraded Pi user dispatcher from protocol ${String(previousVersion)} to ${String(PI_DISPATCHER_VERSION)}\\n`,\n );\n }\n const drift = piIntegrationDrift(agentDirectory);\n if (drift.length > 0) {\n throw new Error(`DoomPi Pi integration is not ready:\\n${drift.map((entry) => ` ${entry}`).join('\\n')}`);\n }\n }\n if (check) {\n const majorModesConfig = loadMajorModesConfig(repoRoot, homeDirectory);\n const missingPackages = missingLayerPackageSpecifiers(\n majorModesConfig,\n Object.keys(majorModesConfig.layers),\n createLayerResolvers(repoRoot),\n );\n if (missingPackages.length > 0) {\n output.write(\n `doompi sync is out of date:\\n${missingPackages\n .map((specifier) => ` configured package is not installed: ${specifier}`)\n .join('\\n')}\\n`,\n );\n return 1;\n }\n let located: ReturnType<typeof readLocatedSyncState>;\n try {\n located = readLocatedSyncState(repoRoot, homeDirectory);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n output.write(`doompi sync is out of date:\\n ${detail}\\n`);\n return 1;\n }\n const expectedCompositionFingerprint = selectionCompositionFingerprint(repoRoot, parsed.options, homeDirectory);\n const drift = collectDrift(\n repoRoot,\n selection,\n located?.state,\n environment,\n this.settingsMode,\n expectedCompositionFingerprint,\n );\n if (drift.length === 0) {\n output.write('doompi sync is up to date\\n');\n return 0;\n }\n output.write(`doompi sync is out of date:\\n${drift.map((entry) => ` ${entry}`).join('\\n')}\\n`);\n return 1;\n }\n\n // Publishing an identical generation is not a no-op: it moves the\n // registration, so every attached cockpit reloads and the previous\n // generation becomes garbage. Same inputs, same published result.\n if (!force && readSyncDrift({ repoRoot, homeDirectory }).fresh) {\n output.write('doompi sync is already up to date\\n');\n return 0;\n }\n\n const progress = new SyncProgress(output);\n const releaseLock = this.lockHeld\n ? undefined\n : await acquireSyncLocationLock(resolveSyncLocation(repoRoot, homeDirectory));\n let result: SyncResult;\n try {\n // A concurrent publisher may have resolved the drift while this command\n // waited for the lock. Avoid moving the registration for no change.\n if (!force && readSyncDrift({ repoRoot, homeDirectory }).fresh) {\n output.write('doompi sync is already up to date\\n');\n return 0;\n }\n result = await this.stage(repoRoot, parsed.options, environment, homeDirectory, progress);\n } finally {\n await releaseLock?.();\n }\n emitFrontendHooks(repoRoot, output);\n output.write(formatSyncResult(result, this.settingsMode === 'embedded' ? 'dpi' : 'pi'));\n return 0;\n }\n\n private async stage(\n repoRoot: string,\n options: Omit<HarnessOptions, 'repoRoot'>,\n environment: NodeJS.ProcessEnv,\n homeDirectory: string,\n progress: SyncProgress,\n ): Promise<SyncResult> {\n const location = resolveSyncLocation(repoRoot, homeDirectory);\n const generation = `${Date.now().toString(36)}-${crypto.randomUUID()}`;\n const directory = syncGenerationDirectory(location, generation);\n await fs.promises.mkdir(location.generationsDirectory, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });\n // The leaf is created without `recursive`, so an existing path is an error\n // rather than something to adopt: the cockpit signs and serves whatever the\n // published generation holds, and sync must only ever publish bytes it\n // wrote itself into a directory it just created.\n await fs.promises.mkdir(directory, { mode: PRIVATE_DIRECTORY_MODE });\n\n try {\n const staged = progress.start(SYNC_LABEL, 'resolving the matrix and staging resources');\n const context = await buildHarnessContext({\n ...options,\n repoRoot: location.root,\n homeDirectory,\n cwd: location.root,\n resourceDirectory: directory,\n });\n await ensureLayerPackages({\n repoRoot: location.root,\n config: context.majorModesConfig,\n layers: Object.keys(context.majorModesConfig.layers),\n environment,\n });\n staged(`${String(context.resources.skillCount)} skills, ${String(context.resources.agentCount)} agents`);\n const selection = toSelection(options);\n const resolvers = createLayerResolvers(location.root);\n const resolved = recordResolvedEntries(context.majorModesConfig, resolvers);\n const compositionFingerprint = selectionCompositionFingerprint(location.root, options, homeDirectory);\n const agentDirectory = piAgentDirectory(environment, homeDirectory);\n const persistedThemePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n const themePath = this.settingsMode === 'persisted' ? persistedThemePath : context.defaultThemePath;\n const state: SyncState = {\n version: SYNC_STATE_VERSION,\n root: location.root,\n identity: location.identity,\n inputsHash: computeInputsHash(location.root, selection, homeDirectory),\n webSourcesHash: computeWebSourcesHash(resolved),\n compositionFingerprint,\n selection,\n env: recordedEnvironment(context.environment),\n fileState: {\n profileEnvironment: loadHarnessState(context.environment).state.profileEnvironment,\n pluginHooks: context.resources.pluginHooks,\n mcpProjection: context.resources.mcpProjection,\n },\n resolved,\n baseline: {\n mcpConfigPath: context.resources.mcpConfigPath,\n personaFile: context.environment[PERSONA_FILE_ENV],\n themePath,\n themeName: DEFAULT_THEME_NAME,\n },\n };\n\n const precompiled = progress.start(RUNTIME_LABEL, 'precompiling the mode bundles');\n const synced = await buildSyncedRuntime(location.root, environment, homeDirectory, { state, directory });\n precompiled(`${String(Object.keys(synced.bundles).length)} mode bundles`);\n const statePath = await writeSyncState(\n location.root,\n synced.state,\n homeDirectory,\n path.join(directory, 'state.json'),\n );\n\n const webProgress = progress.start(WEB_LABEL, 'bundling the web cockpit plugins');\n const web = await syncWebBundle({\n repoRoot: location.root,\n resolvedEntries: synced.state.resolved,\n environment,\n outputDirectory: path.join(directory, 'web-bundle'),\n onNotice: (message) => progress.line(WEB_LABEL, message),\n });\n if (web.status === 'failed') throw new Error(`Cockpit bundle failed: ${web.reason}`);\n webProgress(web.status === 'bundled' ? `cockpit bundled with plugins: ${web.pluginIds.join(', ')}` : web.reason);\n\n const apiProgress = progress.start(API_LABEL, 'generating the package API routes');\n const api = syncApiRoutes({\n resolvedEntries: synced.state.resolved,\n outputDirectory: path.join(directory, 'api'),\n onNotice: (message) => progress.line(API_LABEL, message),\n });\n apiProgress(`routes written to ${api.directory}`);\n\n publishSyncRegistration(\n location.root,\n {\n version: SYNC_REGISTRATION_VERSION,\n root: location.root,\n identity: location.identity,\n generation,\n generationRoot: directory,\n statePath,\n stateSha256: syncStateSha256(statePath),\n webDirectory: web.status === 'bundled' ? web.assetsDir : null,\n apiDirectory: api.directory,\n package: packageRegistrationFor(location.root),\n },\n homeDirectory,\n );\n pruneSupersededGenerations(location.generationsDirectory, generation, (message) =>\n progress.line(SYNC_LABEL, message),\n );\n const projectSettingsPath = this.settingsMode === 'persisted' ? writeProjectPiSettings(location.root) : undefined;\n\n return {\n statePath,\n ...(projectSettingsPath ? { projectSettingsPath } : {}),\n selection,\n mcpServers: synced.state.baseline.mcpConfigPath ? readMcpServerNames(synced.state.baseline.mcpConfigPath) : [],\n skillCount: context.resources.skillCount,\n agentCount: context.resources.agentCount,\n };\n } catch (error) {\n await fs.promises.rm(directory, { recursive: true, force: true });\n throw error;\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"syncCommand.mjs","names":[],"sources":["../../../src/commands/syncCommand.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { filterHookDisabledLayers, resolveLayers } from '@agimon-ai/doompi-config/majorModes';\nimport { buildHarnessContext } from '../adapters/harnessContext.ts';\nimport { ensureLayerPackages, missingLayerPackageSpecifiers } from '../adapters/layerPackageInstaller.ts';\nimport { readBootstrapStatus } from '../adapters/bootstrapLocator.ts';\nimport { DOOM_PACKAGE_NAME } from '../adapters/doomPackage.ts';\nimport { doomPiPackageRoot, piExtensionAliasIsCurrent, writePiExtensionAlias } from '../adapters/piExtensionAlias.ts';\nimport {\n PI_DISPATCHER_VERSION,\n piExtensionDispatcherIsUpgradeable,\n piExtensionDispatcherVersion,\n} from '../adapters/piExtensionDispatcher.ts';\nimport {\n mergePiSettings,\n piAgentDirectory,\n piThemeDirectory,\n readPiSettings,\n serializePiSettings,\n} from '../adapters/piSettings.ts';\nimport {\n DUPLICATE_REGISTRATION_DRIFT,\n projectRegistersDoom,\n writeProjectPiSettings,\n} from '../adapters/projectPiSettings.ts';\nimport { buildSyncedRuntime } from '../adapters/syncedRuntimeBuilder.ts';\nimport { acquireSyncLocationLock, resolveSyncLocation, syncGenerationDirectory } from '../adapters/syncLocation.ts';\nimport {\n publishSyncRegistration,\n SYNC_REGISTRATION_VERSION,\n syncStateSha256,\n type SyncPackageRegistration,\n} from '../adapters/syncRegistration.ts';\nimport { readSyncDrift } from '../adapters/syncDrift.ts';\nimport { syncApiRoutes } from '../adapters/apiRoutesSync.ts';\nimport { syncWebBundle } from '../adapters/webBundleSync.ts';\nimport {\n computeInputsHash,\n computeWebSourcesHash,\n readLocatedSyncState,\n readMcpServerNames,\n recordResolvedEntries,\n SYNC_STATE_VERSION,\n type SyncSelection,\n type SyncState,\n syncStateRootMatches,\n writeSyncState,\n} from '../adapters/syncState.ts';\nimport { HARNESS_STATE_POINTER, loadHarnessState } from '../adapters/config/harnessState';\nimport { loadDomains } from '@agimon-ai/doompi-config/domains';\nimport { loadMajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { DEFAULT_THEME, DEFAULT_THEME_NAME } from '@agimon-ai/doompi-ui/theme';\nimport { loadDoomConfig } from '../services/config/projectTrust';\nimport { createLayerResolvers, PERSONA_ENTRY, resolveExtensionComposition } from '../services/extensionAssembler.ts';\nimport type { HarnessOptions } from '../types/interfaces/harness';\nimport { resolveDoomConfigurationRoot } from '../adapters/repository/repository';\nimport { DOOMPI_DOMAINS_ENV, DOOMPI_MAJOR_MODE_ENV, DOOMPI_PROFILE_ENV } from './cli/matrixOptions.ts';\nimport { parseHarnessArgs } from './cli/options.ts';\nimport { SyncProgress, type SyncProgressOutput } from './syncPresenter.ts';\n\n/**\n * `doom-pi sync`: resolve the matrix once and write it where plain Pi finds it.\n *\n * The doom-emacs split. Everything that needs a real Node process (module\n * resolution, staging skills and agents, generating the MCP config) happens\n * here, and the doom-pi extension then only reads what this produced. The\n * launcher is untouched and keeps resolving the same matrix per run.\n */\n\nconst SYNC_COMMAND = 'sync';\nconst CHECK_OPTION = '--check';\n/** Republishes even when nothing drifted, for a generation suspected of being damaged. */\nconst FORCE_OPTION = '--force';\nconst HARNESS_ROOT_ENV = 'DOOMPI_ROOT';\nconst PERSONA_FILE_ENV = 'DOOMPI_PERSONA_FILE';\nconst HOOK_EMITTER = path.join('tools', 'harness', 'emit-hooks.mjs');\nconst NONE = '(none)';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\n/** Published generations kept behind the current one, so a running hub mid-read survives a prune. */\nconst RETAINED_GENERATIONS = 1;\nconst SYNC_LABEL = 'sync';\nconst RUNTIME_LABEL = 'runtime';\nconst WEB_LABEL = 'web';\nconst API_LABEL = 'api';\n\n/**\n * Harness variables worth recording, by prefix or exact name.\n *\n * An allowlist rather than the whole environment: the state file is a snapshot\n * of resolved configuration, and dumping `process.env` into it would write\n * every credential the sync happened to run with onto disk.\n */\nconst RECORDED_PREFIXES = ['DOOMPI_'];\nconst RECORDED_KEYS = ['CLAUDE_PROJECT_DIR', 'CODEX_REPO_ROOT', 'ORIGINAL_REPO_PATH', 'MCP_UI_VIEWER'];\n/**\n * Launcher-only values a synced session must not inherit.\n *\n * The child extension list is recomposed on every load, and the subagent binary\n * points at `pi.sh`, which a session started as plain `pi` should not shell out\n * to: Doom Team resolves Pi's own CLI when the variable is absent.\n */\nconst EXCLUDED_KEYS = new Set([\n 'DOOMPI_CHILD_EXTENSIONS',\n 'DOOMPI_COMPOSED',\n 'DOOMPI_MUTE',\n 'DOOMPI_TEMP_DIR',\n // A pointer to the syncing process's own state file. Recording it would hand\n // every later session a path to a state that died with this one.\n HARNESS_STATE_POINTER,\n 'PI_SUBAGENT_PI_BINARY',\n]);\n\ntype SyncOutput = SyncProgressOutput;\n\nexport type SyncSettingsMode = 'persisted' | 'embedded';\n\nexport interface SyncCommandOptions {\n settingsMode?: SyncSettingsMode;\n /** Test/embedding override; normal CLI execution uses the process home. */\n homeDirectory?: string;\n /** Internal pipeline seam when the caller owns the worktree lock. */\n lockHeld?: boolean;\n}\n\nexport interface SyncResult {\n statePath: string;\n /** Omitted when DPI supplies the integration as a process-local overlay. */\n settingsPath?: string;\n /** Set only when the repository still carried its own DoomPi registration. */\n projectSettingsPath?: string;\n selection: SyncSelection;\n mcpServers: string[];\n skillCount: number;\n agentCount: number;\n}\n\nexport function recordedEnvironment(environment: NodeJS.ProcessEnv): Record<string, string> {\n const recorded: Record<string, string> = {};\n for (const [key, value] of Object.entries(environment)) {\n if (value === undefined || EXCLUDED_KEYS.has(key)) continue;\n if (RECORDED_KEYS.includes(key) || RECORDED_PREFIXES.some((prefix) => key.startsWith(prefix))) {\n recorded[key] = value;\n }\n }\n return recorded;\n}\n\n/**\n * Layers the repository's declared selection under the usual resolution.\n *\n * `.doom/config.yaml` holds what the repository selects by default, the way\n * init.el does for doom-emacs. Seeding the environment the parser reads keeps\n * the precedence the launcher already documents: an explicit flag wins, then an\n * exported variable, then the declared default.\n */\nexport function selectionEnvironment(repoRoot: string, environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const { selection } = loadDoomConfig(repoRoot);\n if (!selection) return environment;\n return {\n ...environment,\n ...(selection.majorMode && !environment[DOOMPI_MAJOR_MODE_ENV]\n ? { [DOOMPI_MAJOR_MODE_ENV]: selection.majorMode }\n : {}),\n ...(selection.profile && !environment[DOOMPI_PROFILE_ENV] ? { [DOOMPI_PROFILE_ENV]: selection.profile } : {}),\n ...(selection.domains && environment[DOOMPI_DOMAINS_ENV] === undefined\n ? { [DOOMPI_DOMAINS_ENV]: selection.domains.join(',') }\n : {}),\n };\n}\n\nexport function toSelection(\n options: Pick<HarnessOptions, 'majorMode' | 'domains' | 'profile' | 'preset'>,\n): SyncSelection {\n return {\n majorMode: options.majorMode,\n domains: options.domains,\n profile: options.profile,\n preset: options.preset,\n };\n}\n\nexport function selectionCompositionFingerprint(\n repoRoot: string,\n options: Pick<HarnessOptions, 'agents' | 'hooks' | 'majorMode' | 'mcp' | 'preset'>,\n homeDirectory: string = os.homedir(),\n): string {\n const majorModesConfig = loadMajorModesConfig(repoRoot, homeDirectory);\n const resolvers = createLayerResolvers(repoRoot);\n return resolveExtensionComposition({\n agents: options.agents,\n autoStop: false,\n mute: false,\n preset: options.preset,\n personaEntry: resolvers.packageEntry(PERSONA_ENTRY),\n majorMode: options.majorMode,\n layers: filterHookDisabledLayers(\n majorModesConfig,\n resolveLayers(majorModesConfig, options.majorMode),\n options.hooks,\n ),\n majorModesConfig,\n resolvers,\n }).fingerprint;\n}\n\n/** Settings, dispatcher and theme differences an init would fix, independent of sync state. */\nfunction piIntegrationDrift(agentDirectory: string): string[] {\n const drift: string[] = [];\n const themePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n const settings = readPiSettings(agentDirectory);\n const merged = mergePiSettings(settings, agentDirectory, { themePath, themeName: DEFAULT_THEME_NAME });\n if (serializePiSettings(merged) !== serializePiSettings(settings)) {\n drift.push('Pi user settings are out of date; run doompi init');\n }\n if (!piExtensionAliasIsCurrent(agentDirectory)) drift.push('Pi user dispatcher is out of date; run doompi init');\n const expectedTheme = `${JSON.stringify(DEFAULT_THEME, null, 2)}\\n`;\n if (!fs.existsSync(themePath) || fs.readFileSync(themePath, 'utf8') !== expectedTheme) {\n drift.push('Pi user theme is out of date; run doompi init');\n }\n return drift;\n}\n\n/** Differences between what a sync would produce and what is on disk. */\nexport function collectDrift(\n repoRoot: string,\n selection: SyncSelection,\n state: SyncState | undefined,\n environment: NodeJS.ProcessEnv = process.env,\n settingsMode: SyncSettingsMode = 'persisted',\n expectedCompositionFingerprint?: string,\n): string[] {\n if (!state) return ['no sync state: run doompi sync'];\n const drift: string[] = [];\n if (!syncStateRootMatches(repoRoot, state.root)) drift.push('sync state belongs to a different repository');\n const recorded = state.selection;\n if (\n recorded.majorMode !== selection.majorMode ||\n recorded.profile !== selection.profile ||\n recorded.preset !== selection.preset ||\n recorded.domains.join(',') !== selection.domains.join(',')\n ) {\n drift.push('selection changed since the last sync');\n }\n // Hashed against the recorded selection, not the requested one, so a\n // selection change is reported once rather than as two findings.\n if (computeInputsHash(repoRoot, recorded, environment.HOME ?? os.homedir()) !== state.inputsHash) {\n drift.push('.doom configuration changed');\n }\n // Re-resolving is what catches a dependency upgrade moving a package, which\n // the inputs hash deliberately does not read.\n if (\n JSON.stringify(\n recordResolvedEntries(\n loadMajorModesConfig(repoRoot, environment.HOME ?? os.homedir()),\n createLayerResolvers(repoRoot),\n ),\n ) !== JSON.stringify(state.resolved)\n ) {\n drift.push('resolved extension paths changed');\n }\n if (expectedCompositionFingerprint && state.compositionFingerprint !== expectedCompositionFingerprint) {\n drift.push('extension composition changed');\n }\n try {\n if (!readBootstrapStatus(repoRoot, undefined, environment.HOME ?? os.homedir()).fresh) {\n drift.push('precompiled runtime is missing or stale');\n }\n } catch {\n drift.push('precompiled runtime is missing or stale');\n }\n\n if (settingsMode === 'persisted') {\n const agentDirectory = piAgentDirectory(environment);\n const themePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n drift.push(...piIntegrationDrift(agentDirectory));\n if (projectRegistersDoom(repoRoot)) drift.push(DUPLICATE_REGISTRATION_DRIFT);\n if (state.baseline.themePath !== themePath || state.baseline.themeName !== DEFAULT_THEME_NAME) {\n drift.push('synced theme location is out of date');\n }\n }\n return drift;\n}\n\n/** Regenerates the hook files the other frontends read before any harness code runs. */\nfunction emitFrontendHooks(repoRoot: string, output: SyncOutput): void {\n const emitter = path.join(repoRoot, HOOK_EMITTER);\n if (!fs.existsSync(emitter)) return;\n const result = spawnSync(process.execPath, [emitter, '--write'], { cwd: repoRoot, encoding: 'utf8' });\n if (result.status === 0) {\n output.write('hooks: regenerated for Claude Code and Codex\\n');\n return;\n }\n output.write(`hooks: emit-hooks failed (${result.stderr?.trim() || `exit ${String(result.status)}`})\\n`);\n}\n\nexport function formatSyncResult(result: SyncResult, runner = 'pi'): string {\n const { selection } = result;\n return [\n `mode: ${selection.majorMode}`,\n `domains: ${selection.domains.join(', ') || NONE}`,\n `profile: ${selection.profile ?? NONE}`,\n `skills: ${result.skillCount}`,\n `agents: ${result.agentCount}`,\n `mcp: ${result.mcpServers.join(', ') || NONE}`,\n `state: ${result.statePath}`,\n ...(result.settingsPath ? [`settings: ${result.settingsPath}`] : []),\n ...(result.projectSettingsPath\n ? [`project: removed duplicate registration from ${result.projectSettingsPath}`]\n : []),\n '',\n `Run ${runner} from the repository root to use it.`,\n '',\n ].join('\\n');\n}\n\n/**\n * The DoomPi package a repository pins for itself, if it pins one.\n *\n * Extensions are version-coupled to the harness that loads them, so the\n * registration must name the copy the repository resolves rather than whichever\n * copy happened to run sync. A globally installed DoomPi syncing a repository\n * that pins its own would otherwise record itself, and the dispatcher would\n * then load the wrong harness for every session in that repository.\n */\nfunction repositoryPackageRoot(repoRoot: string): string | undefined {\n try {\n return path.dirname(\n createRequire(path.join(repoRoot, 'package.json')).resolve(`${DOOM_PACKAGE_NAME}/package.json`),\n );\n } catch {\n // Not pinned here, which is normal; the executing package stands in.\n return undefined;\n }\n}\n\nfunction packageRegistrationFor(repoRoot: string): SyncPackageRegistration {\n const root = fs.realpathSync(repositoryPackageRoot(repoRoot) ?? doomPiPackageRoot());\n const manifestPath = path.join(root, 'package.json');\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as {\n version?: unknown;\n pi?: { extensions?: unknown };\n };\n const version = manifest.version;\n const extensions = manifest.pi?.extensions;\n const extension = Array.isArray(extensions) ? extensions.find((value) => typeof value === 'string') : undefined;\n if (typeof version !== 'string' || typeof extension !== 'string') {\n throw new Error(`Installed DoomPi package at ${root} has no versioned Pi extension entry`);\n }\n return {\n root,\n version,\n manifestPath,\n entry: fs.realpathSync(path.resolve(root, extension)),\n };\n}\n\n/**\n * Removes generations the published one replaced.\n *\n * Each generation holds a full cockpit bundle and runtime, so keeping every one\n * ever built grows without bound. One superseded generation is retained because\n * a hub that resolved its assets a moment ago may still be reading them; older\n * ones have no reader left. Ordered by directory name, whose leading timestamp\n * makes publication order recoverable without reading each state file.\n */\nfunction pruneSupersededGenerations(\n generationsDirectory: string,\n published: string,\n onNotice: (message: string) => void,\n): void {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(generationsDirectory, { withFileTypes: true });\n } catch (error) {\n // Nothing to prune is indistinguishable from an unreadable directory here,\n // and neither is worth failing a sync that already published.\n onNotice(`could not list generations to prune: ${describeError(error)}`);\n return;\n }\n const superseded = entries\n .filter((entry) => entry.isDirectory() && entry.name !== published)\n .map((entry) => entry.name)\n .sort();\n const removable = superseded.slice(0, Math.max(0, superseded.length - RETAINED_GENERATIONS));\n for (const name of removable) {\n try {\n fs.rmSync(path.join(generationsDirectory, name), { recursive: true, force: true });\n } catch (error) {\n // A generation still held open elsewhere stays; the next sync retries it.\n onNotice(`could not remove superseded generation ${name}: ${describeError(error)}`);\n }\n }\n if (removable.length > 0) onNotice(`pruned ${String(removable.length)} superseded generation(s)`);\n}\n\nfunction describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** Resolves the matrix, stages it into home-scoped worktree storage, and publishes one generation. */\nexport class SyncCommand {\n readonly name = SYNC_COMMAND;\n private readonly settingsMode: SyncSettingsMode;\n private readonly homeDirectory: string | undefined;\n private readonly lockHeld: boolean;\n\n constructor(options: SyncCommandOptions = {}) {\n this.settingsMode = options.settingsMode ?? 'persisted';\n this.homeDirectory = options.homeDirectory;\n this.lockHeld = options.lockHeld ?? false;\n }\n\n matches(args: string[]): boolean {\n return args[0] === this.name;\n }\n\n async execute(\n args: string[],\n environment: NodeJS.ProcessEnv = process.env,\n currentDirectory = process.cwd(),\n output: SyncOutput = process.stdout,\n ): Promise<number> {\n const check = args.includes(CHECK_OPTION);\n const force = args.includes(FORCE_OPTION);\n const rest = args.slice(1).filter((argument) => argument !== CHECK_OPTION && argument !== FORCE_OPTION);\n const homeDirectory = this.homeDirectory ?? environment.HOME ?? os.homedir();\n const inheritedRoot = environment[HARNESS_ROOT_ENV];\n const repoRoot = inheritedRoot\n ? path.resolve(inheritedRoot)\n : resolveDoomConfigurationRoot(currentDirectory, homeDirectory);\n const defaultMajorMode = loadMajorModesConfig(repoRoot, homeDirectory).defaultMajorMode;\n const defaultDomains = loadDomains(repoRoot, homeDirectory).defaultDomains;\n const parsed = parseHarnessArgs(\n rest,\n selectionEnvironment(repoRoot, environment),\n currentDirectory,\n defaultMajorMode,\n defaultDomains,\n );\n const selection = toSelection(parsed.options);\n const agentDirectory = piAgentDirectory(environment, homeDirectory);\n if (this.settingsMode === 'persisted' && !check) {\n if (piExtensionDispatcherIsUpgradeable(agentDirectory)) {\n const previousVersion = piExtensionDispatcherVersion(agentDirectory);\n writePiExtensionAlias(agentDirectory);\n output.write(\n `repair: upgraded Pi user dispatcher from protocol ${String(previousVersion)} to ${String(PI_DISPATCHER_VERSION)}\\n`,\n );\n }\n const drift = piIntegrationDrift(agentDirectory);\n if (drift.length > 0) {\n throw new Error(`DoomPi Pi integration is not ready:\\n${drift.map((entry) => ` ${entry}`).join('\\n')}`);\n }\n }\n if (check) {\n const majorModesConfig = loadMajorModesConfig(repoRoot, homeDirectory);\n const missingPackages = missingLayerPackageSpecifiers(\n majorModesConfig,\n Object.keys(majorModesConfig.layers),\n createLayerResolvers(repoRoot),\n );\n if (missingPackages.length > 0) {\n output.write(\n `doompi sync is out of date:\\n${missingPackages\n .map((specifier) => ` configured package is not installed: ${specifier}`)\n .join('\\n')}\\n`,\n );\n return 1;\n }\n let located: ReturnType<typeof readLocatedSyncState>;\n try {\n located = readLocatedSyncState(repoRoot, homeDirectory);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n output.write(`doompi sync is out of date:\\n ${detail}\\n`);\n return 1;\n }\n const expectedCompositionFingerprint = selectionCompositionFingerprint(repoRoot, parsed.options, homeDirectory);\n const drift = collectDrift(\n repoRoot,\n selection,\n located?.state,\n environment,\n this.settingsMode,\n expectedCompositionFingerprint,\n );\n if (drift.length === 0) {\n output.write('doompi sync is up to date\\n');\n return 0;\n }\n output.write(`doompi sync is out of date:\\n${drift.map((entry) => ` ${entry}`).join('\\n')}\\n`);\n return 1;\n }\n\n // Publishing an identical generation is not a no-op: it moves the\n // registration, so every attached cockpit reloads and the previous\n // generation becomes garbage. Same inputs, same published result.\n const driftOptions = {\n repoRoot,\n homeDirectory,\n requireWebBundle: Boolean(environment.DOOMPI_WEB_PACKAGE_ROOT),\n };\n if (!force && readSyncDrift(driftOptions).fresh) {\n output.write('doompi sync is already up to date\\n');\n return 0;\n }\n\n const progress = new SyncProgress(output);\n const releaseLock = this.lockHeld\n ? undefined\n : await acquireSyncLocationLock(resolveSyncLocation(repoRoot, homeDirectory));\n let result: SyncResult;\n try {\n // A concurrent publisher may have resolved the drift while this command\n // waited for the lock. Avoid moving the registration for no change.\n if (!force && readSyncDrift(driftOptions).fresh) {\n output.write('doompi sync is already up to date\\n');\n return 0;\n }\n result = await this.stage(repoRoot, parsed.options, environment, homeDirectory, progress);\n } finally {\n await releaseLock?.();\n }\n emitFrontendHooks(repoRoot, output);\n output.write(formatSyncResult(result, this.settingsMode === 'embedded' ? 'dpi' : 'pi'));\n return 0;\n }\n\n private async stage(\n repoRoot: string,\n options: Omit<HarnessOptions, 'repoRoot'>,\n environment: NodeJS.ProcessEnv,\n homeDirectory: string,\n progress: SyncProgress,\n ): Promise<SyncResult> {\n const location = resolveSyncLocation(repoRoot, homeDirectory);\n const generation = `${Date.now().toString(36)}-${crypto.randomUUID()}`;\n const directory = syncGenerationDirectory(location, generation);\n await fs.promises.mkdir(location.generationsDirectory, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });\n // The leaf is created without `recursive`, so an existing path is an error\n // rather than something to adopt: the cockpit signs and serves whatever the\n // published generation holds, and sync must only ever publish bytes it\n // wrote itself into a directory it just created.\n await fs.promises.mkdir(directory, { mode: PRIVATE_DIRECTORY_MODE });\n\n try {\n const staged = progress.start(SYNC_LABEL, 'resolving the matrix and staging resources');\n const context = await buildHarnessContext({\n ...options,\n repoRoot: location.root,\n homeDirectory,\n cwd: location.root,\n resourceDirectory: directory,\n });\n await ensureLayerPackages({\n repoRoot: location.root,\n config: context.majorModesConfig,\n layers: Object.keys(context.majorModesConfig.layers),\n environment,\n });\n staged(`${String(context.resources.skillCount)} skills, ${String(context.resources.agentCount)} agents`);\n const selection = toSelection(options);\n const resolvers = createLayerResolvers(location.root);\n const resolved = recordResolvedEntries(context.majorModesConfig, resolvers);\n const compositionFingerprint = selectionCompositionFingerprint(location.root, options, homeDirectory);\n const agentDirectory = piAgentDirectory(environment, homeDirectory);\n const persistedThemePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n const themePath = this.settingsMode === 'persisted' ? persistedThemePath : context.defaultThemePath;\n const state: SyncState = {\n version: SYNC_STATE_VERSION,\n root: location.root,\n identity: location.identity,\n inputsHash: computeInputsHash(location.root, selection, homeDirectory),\n webSourcesHash: computeWebSourcesHash(resolved),\n compositionFingerprint,\n selection,\n env: recordedEnvironment(context.environment),\n fileState: {\n profileEnvironment: loadHarnessState(context.environment).state.profileEnvironment,\n pluginHooks: context.resources.pluginHooks,\n mcpProjection: context.resources.mcpProjection,\n },\n resolved,\n baseline: {\n mcpConfigPath: context.resources.mcpConfigPath,\n personaFile: context.environment[PERSONA_FILE_ENV],\n themePath,\n themeName: DEFAULT_THEME_NAME,\n },\n };\n\n const precompiled = progress.start(RUNTIME_LABEL, 'precompiling the mode bundles');\n const synced = await buildSyncedRuntime(location.root, environment, homeDirectory, { state, directory });\n precompiled(`${String(Object.keys(synced.bundles).length)} mode bundles`);\n const statePath = await writeSyncState(\n location.root,\n synced.state,\n homeDirectory,\n path.join(directory, 'state.json'),\n );\n\n const webProgress = progress.start(WEB_LABEL, 'bundling the web cockpit plugins');\n const web = await syncWebBundle({\n repoRoot: location.root,\n resolvedEntries: synced.state.resolved,\n environment,\n outputDirectory: path.join(directory, 'web-bundle'),\n onNotice: (message) => progress.line(WEB_LABEL, message),\n });\n if (web.status === 'failed') throw new Error(`Cockpit bundle failed: ${web.reason}`);\n webProgress(web.status === 'bundled' ? `cockpit bundled with plugins: ${web.pluginIds.join(', ')}` : web.reason);\n\n const apiProgress = progress.start(API_LABEL, 'generating the package API routes');\n const api = syncApiRoutes({\n resolvedEntries: synced.state.resolved,\n outputDirectory: path.join(directory, 'api'),\n onNotice: (message) => progress.line(API_LABEL, message),\n });\n apiProgress(`routes written to ${api.directory}`);\n\n publishSyncRegistration(\n location.root,\n {\n version: SYNC_REGISTRATION_VERSION,\n root: location.root,\n identity: location.identity,\n generation,\n generationRoot: directory,\n statePath,\n stateSha256: syncStateSha256(statePath),\n webDirectory: web.status === 'bundled' ? web.assetsDir : null,\n apiDirectory: api.directory,\n package: packageRegistrationFor(location.root),\n },\n homeDirectory,\n );\n pruneSupersededGenerations(location.generationsDirectory, generation, (message) =>\n progress.line(SYNC_LABEL, message),\n );\n const projectSettingsPath = this.settingsMode === 'persisted' ? writeProjectPiSettings(location.root) : undefined;\n\n return {\n statePath,\n ...(projectSettingsPath ? { projectSettingsPath } : {}),\n selection,\n mcpServers: synced.state.baseline.mcpConfigPath ? readMcpServerNames(synced.state.baseline.mcpConfigPath) : [],\n skillCount: context.resources.skillCount,\n agentCount: context.resources.agentCount,\n };\n } catch (error) {\n await fs.promises.rm(directory, { recursive: true, force: true });\n throw error;\n }\n }\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e=require("../../services/modeCatalog.cjs"),t=require("../../adapters/contextDetailStore.cjs"),n=require("../../services/contextCatalog.cjs"),r=require("../../services/minorModeProjection.cjs"),i=require("./minorModeCommand.cjs");let a=require("@agimon-ai/doompi-extension-contracts/cordis-host"),o=require("@agimon-ai/doompi-extension-contracts/mode"),s=require("@agimon-ai/doompi-extension-contracts/help"),c=require("@agimon-ai/doompi-extension-contracts/notification"),l=require("@agimon-ai/doompi-extension-contracts/transition");function u(e,t,n,r){let i=(0,c.readDoomNotificationService)(e);if(i){try{Promise.resolve(i.request({body:n,level:r})).catch(()=>void 0)}catch{}return}t.ui.notify(n,r)}function d(e){return e.includes(`voice`)?`voice`:e.includes(`leader`)?`leader`:e.includes(`ui`)?`ui`:`system`}function f(e){let t=e.current?.getSnapshot();return t?t.modes.filter(e=>e.state.activation===`active`).map(e=>({id:e.descriptor.id,label:e.descriptor.label})):[]}async function p(e){let r={current:void 0};i.registerMinorModeCommand(e,()=>r.current);let o={};e.on(`session_start`,()=>{setTimeout(()=>{r.republish?.(),o.publisher?.publish()},500)}),e.on(`turn_start`,()=>{o.publisher?.publish()});let s=await(0,a.connectDoomCordisHost)(e,`@agimon-ai/doompi/mode-catalog`),c=s.root.plugin(i=>(o.publisher=n.createContextPublisher(e,i,{readMinorModes:()=>f(r),readSessionId:()=>r.sessionId,writeDetail:(e,n,r)=>void t.writeContextDetail(e,n,r),removeDetail:t.removeContextDetail}),m(i,e,r,()=>void o.publisher?.publish()),()=>{o.publisher?.dispose(),o.publisher=void 0}));try{await c}catch(e){throw await c.dispose(),await s.dispose(),e}let l;e.on(`session_shutdown`,()=>l??=(async()=>{try{await c.dispose()}finally{await s.dispose()}})())}function m(t,n,i,c){t.inject([s.DOOM_HELP_SERVICE],e=>{let t=(0,s.requireDoomHelpService)(e).register({source:`@agimon-ai/doompi`,moduleUrl:require("url").pathToFileURL(__filename).href,skills:[{name:`doompi-author-extension`,description:`Create or update a DoomPi extension package inside the DoomPi monorepo or as an external npm package. Use for package layout, Pi discovery entries, shared Cordis lifecycle, package-owned Help, and extension verification.`}]});return()=>t.dispose()}),t.inject([a.DOOM_CORDIS_SESSION_SERVICE,l.DOOM_TRANSITION_SERVICE],t=>{let s=t.get(a.DOOM_CORDIS_SESSION_SERVICE),f=s.context,p=(0,l.requireDoomTransitionCoordinator)(t),m=s.reason===`reload`,h=s.sessionId,g=m?(0,l.consumeMinorModeReloadHandoff)(h):void 0;m||(0,l.discardMinorModeReloadHandoff)(h);let _=f.hasUI&&f.mode===`tui`?`tui`:`headless`,v=e.createMinorModeCatalogHost({sessionKind:_,context:f,restoreSnapshot:g,onRestorationError(e){u(t,f,`Could not restore a minor mode after reload: ${e instanceof Error?e.message:String(e)}`,`warning`)},async routeInvocation(e,t,n){let r,i=await p.execute({sessionId:f.sessionManager.getSessionId(),hostGeneration:p.hostGeneration,operationId:e.operationId,source:d(t),target:{axis:`minor-mode`,action:e,requesterSource:t}},async()=>(r=await n(),`applied`));if(i.outcome!==`applied`||!r)throw Error(`Minor-mode transition was ${i.outcome}: ${i.diagnostics.join(`, `)}`);return r}});t.provide(o.DOOM_MINOR_MODE_CATALOG_SERVICE,v),i.current=v,i.sessionId=h;let y,b,x=()=>{b=void 0;let e=r.projectMinorModes(v.getSnapshot(),_),t=JSON.stringify(e);t!==y&&(y=t,n.appendEntry(o.DOOM_MINOR_MODE_ENTRY_TYPE,e),c())},S=v.subscribe(()=>{b??=setTimeout(x,50)});return i.republish=()=>{y=void 0,x()},()=>{b&&clearTimeout(b),S(),i.republish=void 0,i.current=void 0,i.sessionId=void 0,v.dispose()}})}exports.default=p,exports.modeCatalogExtension=p;
1
+ const e=require("../../services/modeCatalog.cjs"),t=require("../../adapters/contextDetailStore.cjs"),n=require("../../services/contextCatalog.cjs"),r=require("../../services/minorModeProjection.cjs"),i=require("./minorModeCommand.cjs");let a=require("@agimon-ai/doompi-extension-contracts/cordis-host"),o=require("@agimon-ai/doompi-extension-contracts/mode"),s=require("@agimon-ai/doompi-extension-contracts/mcp-status"),c=require("@agimon-ai/doompi-extension-contracts/help"),l=require("@agimon-ai/doompi-extension-contracts/notification"),u=require("@agimon-ai/doompi-extension-contracts/transition");function d(e,t,n,r){let i=(0,l.readDoomNotificationService)(e);if(i){try{Promise.resolve(i.request({body:n,level:r})).catch(()=>void 0)}catch{}return}t.ui.notify(n,r)}function f(e){return e.includes(`voice`)?`voice`:e.includes(`leader`)?`leader`:e.includes(`ui`)?`ui`:`system`}function p(e){let t=e.current?.getSnapshot();return t?t.modes.filter(e=>e.state.activation===`active`).map(e=>({id:e.descriptor.id,label:e.descriptor.label})):[]}async function m(e){let r={current:void 0};i.registerMinorModeCommand(e,()=>r.current);let o={};e.on(`session_start`,()=>{setTimeout(()=>{r.republish?.(),o.publisher?.publish()},500)}),e.on(`turn_start`,()=>{o.publisher?.publish()});let c=await(0,a.connectDoomCordisHost)(e,`@agimon-ai/doompi/mode-catalog`),l=c.root.plugin(i=>(o.publisher=n.createContextPublisher(e,i,{readMinorModes:()=>p(r),readSessionId:()=>r.sessionId,writeDetail:(e,n,r)=>void t.writeContextDetail(e,n,r),removeDetail:t.removeContextDetail}),i.inject([s.DOOM_MCP_STATUS_SERVICE],e=>{let t=()=>void o.publisher?.publish(),n=(0,s.readDoomMcpStatus)(e)?.onChange?.(t);return t(),n}),h(i,e,r,()=>void o.publisher?.publish()),()=>{o.publisher?.dispose(),o.publisher=void 0}));try{await l}catch(e){throw await l.dispose(),await c.dispose(),e}let u;e.on(`session_shutdown`,()=>u??=(async()=>{try{await l.dispose()}finally{await c.dispose()}})())}function h(t,n,i,s){t.inject([c.DOOM_HELP_SERVICE],e=>{let t=(0,c.requireDoomHelpService)(e).register({source:`@agimon-ai/doompi`,moduleUrl:require("url").pathToFileURL(__filename).href,skills:[{name:`doompi-author-extension`,description:`Create or update a DoomPi extension package inside the DoomPi monorepo or as an external npm package. Use for package layout, Pi discovery entries, shared Cordis lifecycle, package-owned Help, and extension verification.`}]});return()=>t.dispose()}),t.inject([a.DOOM_CORDIS_SESSION_SERVICE,u.DOOM_TRANSITION_SERVICE],t=>{let c=t.get(a.DOOM_CORDIS_SESSION_SERVICE),l=c.context,p=(0,u.requireDoomTransitionCoordinator)(t),m=c.reason===`reload`,h=c.sessionId,g=m?(0,u.consumeMinorModeReloadHandoff)(h):void 0;m||(0,u.discardMinorModeReloadHandoff)(h);let _=l.hasUI&&l.mode===`tui`?`tui`:`headless`,v=e.createMinorModeCatalogHost({sessionKind:_,context:l,restoreSnapshot:g,onRestorationError(e){d(t,l,`Could not restore a minor mode after reload: ${e instanceof Error?e.message:String(e)}`,`warning`)},async routeInvocation(e,t,n){let r,i=await p.execute({sessionId:l.sessionManager.getSessionId(),hostGeneration:p.hostGeneration,operationId:e.operationId,source:f(t),target:{axis:`minor-mode`,action:e,requesterSource:t}},async()=>(r=await n(),`applied`));if(i.outcome!==`applied`||!r)throw Error(`Minor-mode transition was ${i.outcome}: ${i.diagnostics.join(`, `)}`);return r}});t.provide(o.DOOM_MINOR_MODE_CATALOG_SERVICE,v),i.current=v,i.sessionId=h;let y,b,x=()=>{b=void 0;let e=r.projectMinorModes(v.getSnapshot(),_),t=JSON.stringify(e);t!==y&&(y=t,n.appendEntry(o.DOOM_MINOR_MODE_ENTRY_TYPE,e),s())},S=v.subscribe(()=>{b??=setTimeout(x,50)});return i.republish=()=>{y=void 0,x()},()=>{b&&clearTimeout(b),S(),i.republish=void 0,i.current=void 0,i.sessionId=void 0,v.dispose()}})}exports.default=m,exports.modeCatalogExtension=m;
2
2
  //# sourceMappingURL=modeCatalog.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"modeCatalog.cjs","names":["readDoomNotificationService","connectDoomCordisHost","createContextPublisher","writeContextDetail","removeContextDetail","DOOM_HELP_SERVICE","requireDoomHelpService","DOOM_CORDIS_SESSION_SERVICE","DOOM_TRANSITION_SERVICE","requireDoomTransitionCoordinator","consumeMinorModeReloadHandoff","createMinorModeCatalogHost","DOOM_MINOR_MODE_CATALOG_SERVICE","projectMinorModes","DOOM_MINOR_MODE_ENTRY_TYPE"],"sources":["../../../../src/extensions/entries/modeCatalog.ts"],"sourcesContent":["import {\n DOOM_MINOR_MODE_CATALOG_SERVICE,\n DOOM_MINOR_MODE_ENTRY_TYPE,\n type MinorModeActionResponse,\n type MinorModeCatalogService,\n} from '@agimon-ai/doompi-extension-contracts/mode';\nimport {\n connectDoomCordisHost,\n DOOM_CORDIS_SESSION_SERVICE,\n type DoomCordisSessionService,\n} from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport {\n type DoomNotificationLevel,\n readDoomNotificationService,\n} from '@agimon-ai/doompi-extension-contracts/notification';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport {\n consumeMinorModeReloadHandoff,\n discardMinorModeReloadHandoff,\n type MinorModeCatalogHost,\n DOOM_TRANSITION_SERVICE,\n requireDoomTransitionCoordinator,\n type TransitionSource,\n} from '@agimon-ai/doompi-extension-contracts/transition';\nimport type { Context } from '@deepseek-ai/cordis';\nimport { removeContextDetail, writeContextDetail } from '../../adapters/contextDetailStore.ts';\nimport { type ContextPublisher, createContextPublisher } from '../../services/contextCatalog.ts';\nimport { projectMinorModes } from '../../services/minorModeProjection.ts';\nimport { createMinorModeCatalogHost } from '../../services/modeCatalog.ts';\nimport { registerMinorModeCommand } from './minorModeCommand.ts';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi/mode-catalog';\nconst HELP_CONTRIBUTION_SOURCE = '@agimon-ai/doompi';\n/** Registrations and state flips arrive in bursts; one entry covers a burst. */\nconst PROJECTION_SETTLE_MS = 50;\n/**\n * Pi's rpc mode subscribes to session events only after bindExtensions()\n * returns, which is after session_start has run, so an entry journaled during\n * startup never reaches a client. A publish deferred past that point does.\n */\nconst BOOT_PUBLISH_DELAY_MS = 500;\n\ninterface CatalogBinding {\n current: MinorModeCatalogService | undefined;\n /** Journals the projection even if unchanged; set while a session is bound. */\n republish?: () => void;\n /**\n * The bound session, which the detail store is keyed by.\n *\n * The server launches the agent with `--session-id`, so this is the same id\n * the cockpit and the session API know the session by, and a browser asking\n * for a row's detail looks under the file this session wrote.\n */\n sessionId?: string;\n}\n\nfunction notify(cordis: Context, context: ExtensionContext, body: string, level: DoomNotificationLevel): void {\n const service = readDoomNotificationService(cordis);\n if (service) {\n try {\n void Promise.resolve(service.request({ body, level })).catch(() => undefined);\n } catch {\n // Notification delivery is best effort and does not fall back after routing.\n }\n return;\n }\n context.ui.notify(body, level);\n}\n\nfunction transitionSource(requesterSource: string): TransitionSource {\n if (requesterSource.includes('voice')) return 'voice';\n if (requesterSource.includes('leader')) return 'leader';\n if (requesterSource.includes('ui')) return 'ui';\n return 'system';\n}\n\n/**\n * The minor modes that are actually on, for the composition panel.\n *\n * Read from the bound catalog at publish time rather than passed in, because a\n * mode switching on is one of the reasons a publish happens at all, and a\n * remembered list would describe the composition before the switch.\n */\nfunction activeMinorModes(binding: CatalogBinding): { id: string; label: string }[] {\n const snapshot = binding.current?.getSnapshot();\n if (!snapshot) return [];\n return snapshot.modes\n .filter((record) => record.state.activation === 'active')\n .map((record) => ({ id: record.descriptor.id, label: record.descriptor.label }));\n}\n\nexport async function modeCatalogExtension(pi: ExtensionAPI): Promise<void> {\n // The command outlives any one session: it is registered once and reads the\n // catalog binding the session-scoped inject below sets and clears.\n const activeCatalog: CatalogBinding = { current: undefined };\n registerMinorModeCommand(pi, () => activeCatalog.current);\n // The composition is only complete once every extension has registered, so\n // the inventory is read on the same deferred tick the catalog republishes on.\n const contextBinding: { publisher?: ContextPublisher } = {};\n pi.on('session_start', () => {\n setTimeout(() => {\n activeCatalog.republish?.();\n void contextBinding.publisher?.publish();\n }, BOOT_PUBLISH_DELAY_MS);\n });\n // MCP status is pull-only and servers connect long after startup, so the\n // boot read above cannot see them. A turn is the moment the composition is\n // actually sent, which makes it both the freshest and the most meaningful\n // time to price. Identical compositions are skipped, so a quiet session\n // journals nothing extra.\n pi.on('turn_start', () => {\n void contextBinding.publisher?.publish();\n });\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin((cordis: Context) => {\n contextBinding.publisher = createContextPublisher(pi, cordis, {\n readMinorModes: () => activeMinorModes(activeCatalog),\n readSessionId: () => activeCatalog.sessionId,\n writeDetail: (sessionId, revision, items) => void writeContextDetail(sessionId, revision, items),\n removeDetail: removeContextDetail,\n });\n modeCatalogPlugin(cordis, pi, activeCatalog, () => void contextBinding.publisher?.publish());\n return () => {\n contextBinding.publisher?.dispose();\n contextBinding.publisher = undefined;\n };\n });\n try {\n await fiber;\n } catch (error) {\n await fiber.dispose();\n await connection.dispose();\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\nfunction modeCatalogPlugin(\n cordis: Context,\n pi: Pick<ExtensionAPI, 'appendEntry'>,\n activeCatalog: CatalogBinding,\n onCatalogChanged: () => void,\n): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: HELP_CONTRIBUTION_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-author-extension',\n description:\n 'Create or update a DoomPi extension package inside the DoomPi monorepo or as an external npm package. Use for package layout, Pi discovery entries, shared Cordis lifecycle, package-owned Help, and extension verification.',\n },\n ],\n });\n return () => contribution.dispose();\n });\n\n cordis.inject([DOOM_CORDIS_SESSION_SERVICE, DOOM_TRANSITION_SERVICE], (sessionContext) => {\n const session = sessionContext.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService;\n const context = session.context;\n const coordinator = requireDoomTransitionCoordinator(sessionContext);\n const reloadSession = session.reason === 'reload';\n const sessionId = session.sessionId;\n const restoreSnapshot = reloadSession ? consumeMinorModeReloadHandoff(sessionId) : undefined;\n if (!reloadSession) discardMinorModeReloadHandoff(sessionId);\n const sessionKind = context.hasUI && context.mode === 'tui' ? 'tui' : 'headless';\n const catalog: MinorModeCatalogHost = createMinorModeCatalogHost({\n sessionKind,\n context,\n restoreSnapshot,\n onRestorationError(error) {\n notify(\n sessionContext,\n context,\n `Could not restore a minor mode after reload: ${error instanceof Error ? error.message : String(error)}`,\n 'warning',\n );\n },\n async routeInvocation(request, requesterSource, invoke) {\n let response: MinorModeActionResponse | undefined;\n const result = await coordinator.execute(\n {\n sessionId: context.sessionManager.getSessionId(),\n hostGeneration: coordinator.hostGeneration,\n operationId: request.operationId,\n source: transitionSource(requesterSource),\n target: { axis: 'minor-mode', action: request, requesterSource },\n },\n async () => {\n response = await invoke();\n return 'applied';\n },\n );\n if (result.outcome !== 'applied' || !response) {\n throw new Error(`Minor-mode transition was ${result.outcome}: ${result.diagnostics.join(', ')}`);\n }\n return response;\n },\n });\n sessionContext.provide(DOOM_MINOR_MODE_CATALOG_SERVICE, catalog);\n activeCatalog.current = catalog;\n activeCatalog.sessionId = sessionId;\n\n // Journal the projection so RPC clients see catalog state live and on\n // replay; identical projections are skipped so detail churn stays cheap.\n let published: string | undefined;\n let settle: NodeJS.Timeout | undefined;\n const publish = (): void => {\n settle = undefined;\n const projection = projectMinorModes(catalog.getSnapshot(), sessionKind);\n const serialized = JSON.stringify(projection);\n if (serialized === published) return;\n published = serialized;\n pi.appendEntry(DOOM_MINOR_MODE_ENTRY_TYPE, projection);\n // A mode changing is the usual reason the toolbox changed too.\n onCatalogChanged();\n };\n const unsubscribe = catalog.subscribe(() => {\n settle ??= setTimeout(publish, PROJECTION_SETTLE_MS);\n });\n activeCatalog.republish = () => {\n published = undefined;\n publish();\n };\n\n return () => {\n if (settle) clearTimeout(settle);\n unsubscribe();\n activeCatalog.republish = undefined;\n activeCatalog.current = undefined;\n activeCatalog.sessionId = undefined;\n catalog.dispose();\n };\n });\n}\n\nexport default modeCatalogExtension;\n"],"mappings":"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"}
1
+ {"version":3,"file":"modeCatalog.cjs","names":["readDoomNotificationService","connectDoomCordisHost","createContextPublisher","writeContextDetail","removeContextDetail","DOOM_MCP_STATUS_SERVICE","readDoomMcpStatus","DOOM_HELP_SERVICE","requireDoomHelpService","DOOM_CORDIS_SESSION_SERVICE","DOOM_TRANSITION_SERVICE","requireDoomTransitionCoordinator","consumeMinorModeReloadHandoff","createMinorModeCatalogHost","DOOM_MINOR_MODE_CATALOG_SERVICE","projectMinorModes","DOOM_MINOR_MODE_ENTRY_TYPE"],"sources":["../../../../src/extensions/entries/modeCatalog.ts"],"sourcesContent":["import { DOOM_MCP_STATUS_SERVICE, readDoomMcpStatus } from '@agimon-ai/doompi-extension-contracts/mcp-status';\nimport {\n DOOM_MINOR_MODE_CATALOG_SERVICE,\n DOOM_MINOR_MODE_ENTRY_TYPE,\n type MinorModeActionResponse,\n type MinorModeCatalogService,\n} from '@agimon-ai/doompi-extension-contracts/mode';\nimport {\n connectDoomCordisHost,\n DOOM_CORDIS_SESSION_SERVICE,\n type DoomCordisSessionService,\n} from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport {\n type DoomNotificationLevel,\n readDoomNotificationService,\n} from '@agimon-ai/doompi-extension-contracts/notification';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport {\n consumeMinorModeReloadHandoff,\n discardMinorModeReloadHandoff,\n type MinorModeCatalogHost,\n DOOM_TRANSITION_SERVICE,\n requireDoomTransitionCoordinator,\n type TransitionSource,\n} from '@agimon-ai/doompi-extension-contracts/transition';\nimport type { Context } from '@deepseek-ai/cordis';\nimport { removeContextDetail, writeContextDetail } from '../../adapters/contextDetailStore.ts';\nimport { type ContextPublisher, createContextPublisher } from '../../services/contextCatalog.ts';\nimport { projectMinorModes } from '../../services/minorModeProjection.ts';\nimport { createMinorModeCatalogHost } from '../../services/modeCatalog.ts';\nimport { registerMinorModeCommand } from './minorModeCommand.ts';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi/mode-catalog';\nconst HELP_CONTRIBUTION_SOURCE = '@agimon-ai/doompi';\n/** Registrations and state flips arrive in bursts; one entry covers a burst. */\nconst PROJECTION_SETTLE_MS = 50;\n/**\n * Pi's rpc mode subscribes to session events only after bindExtensions()\n * returns, which is after session_start has run, so an entry journaled during\n * startup never reaches a client. A publish deferred past that point does.\n */\nconst BOOT_PUBLISH_DELAY_MS = 500;\n\ninterface CatalogBinding {\n current: MinorModeCatalogService | undefined;\n /** Journals the projection even if unchanged; set while a session is bound. */\n republish?: () => void;\n /**\n * The bound session, which the detail store is keyed by.\n *\n * The server launches the agent with `--session-id`, so this is the same id\n * the cockpit and the session API know the session by, and a browser asking\n * for a row's detail looks under the file this session wrote.\n */\n sessionId?: string;\n}\n\nfunction notify(cordis: Context, context: ExtensionContext, body: string, level: DoomNotificationLevel): void {\n const service = readDoomNotificationService(cordis);\n if (service) {\n try {\n void Promise.resolve(service.request({ body, level })).catch(() => undefined);\n } catch {\n // Notification delivery is best effort and does not fall back after routing.\n }\n return;\n }\n context.ui.notify(body, level);\n}\n\nfunction transitionSource(requesterSource: string): TransitionSource {\n if (requesterSource.includes('voice')) return 'voice';\n if (requesterSource.includes('leader')) return 'leader';\n if (requesterSource.includes('ui')) return 'ui';\n return 'system';\n}\n\n/**\n * The minor modes that are actually on, for the composition panel.\n *\n * Read from the bound catalog at publish time rather than passed in, because a\n * mode switching on is one of the reasons a publish happens at all, and a\n * remembered list would describe the composition before the switch.\n */\nfunction activeMinorModes(binding: CatalogBinding): { id: string; label: string }[] {\n const snapshot = binding.current?.getSnapshot();\n if (!snapshot) return [];\n return snapshot.modes\n .filter((record) => record.state.activation === 'active')\n .map((record) => ({ id: record.descriptor.id, label: record.descriptor.label }));\n}\n\nexport async function modeCatalogExtension(pi: ExtensionAPI): Promise<void> {\n // The command outlives any one session: it is registered once and reads the\n // catalog binding the session-scoped inject below sets and clears.\n const activeCatalog: CatalogBinding = { current: undefined };\n registerMinorModeCommand(pi, () => activeCatalog.current);\n // The composition is only complete once every extension has registered, so\n // the inventory is read on the same deferred tick the catalog republishes on.\n const contextBinding: { publisher?: ContextPublisher } = {};\n pi.on('session_start', () => {\n setTimeout(() => {\n activeCatalog.republish?.();\n void contextBinding.publisher?.publish();\n }, BOOT_PUBLISH_DELAY_MS);\n });\n // Turns still refresh active-tool costs, including for older pull-only MCP providers.\n // Identical compositions are skipped rather than journaled again.\n pi.on('turn_start', () => {\n void contextBinding.publisher?.publish();\n });\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin((cordis: Context) => {\n contextBinding.publisher = createContextPublisher(pi, cordis, {\n readMinorModes: () => activeMinorModes(activeCatalog),\n readSessionId: () => activeCatalog.sessionId,\n writeDetail: (sessionId, revision, items) => void writeContextDetail(sessionId, revision, items),\n removeDetail: removeContextDetail,\n });\n cordis.inject([DOOM_MCP_STATUS_SERVICE], (mcpContext) => {\n const publish = () => void contextBinding.publisher?.publish();\n const unsubscribe = readDoomMcpStatus(mcpContext)?.onChange?.(publish);\n // Subscribe first, then read: discovery may already have finished before binding.\n publish();\n return unsubscribe;\n });\n modeCatalogPlugin(cordis, pi, activeCatalog, () => void contextBinding.publisher?.publish());\n return () => {\n contextBinding.publisher?.dispose();\n contextBinding.publisher = undefined;\n };\n });\n try {\n await fiber;\n } catch (error) {\n await fiber.dispose();\n await connection.dispose();\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\nfunction modeCatalogPlugin(\n cordis: Context,\n pi: Pick<ExtensionAPI, 'appendEntry'>,\n activeCatalog: CatalogBinding,\n onCatalogChanged: () => void,\n): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: HELP_CONTRIBUTION_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-author-extension',\n description:\n 'Create or update a DoomPi extension package inside the DoomPi monorepo or as an external npm package. Use for package layout, Pi discovery entries, shared Cordis lifecycle, package-owned Help, and extension verification.',\n },\n ],\n });\n return () => contribution.dispose();\n });\n\n cordis.inject([DOOM_CORDIS_SESSION_SERVICE, DOOM_TRANSITION_SERVICE], (sessionContext) => {\n const session = sessionContext.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService;\n const context = session.context;\n const coordinator = requireDoomTransitionCoordinator(sessionContext);\n const reloadSession = session.reason === 'reload';\n const sessionId = session.sessionId;\n const restoreSnapshot = reloadSession ? consumeMinorModeReloadHandoff(sessionId) : undefined;\n if (!reloadSession) discardMinorModeReloadHandoff(sessionId);\n const sessionKind = context.hasUI && context.mode === 'tui' ? 'tui' : 'headless';\n const catalog: MinorModeCatalogHost = createMinorModeCatalogHost({\n sessionKind,\n context,\n restoreSnapshot,\n onRestorationError(error) {\n notify(\n sessionContext,\n context,\n `Could not restore a minor mode after reload: ${error instanceof Error ? error.message : String(error)}`,\n 'warning',\n );\n },\n async routeInvocation(request, requesterSource, invoke) {\n let response: MinorModeActionResponse | undefined;\n const result = await coordinator.execute(\n {\n sessionId: context.sessionManager.getSessionId(),\n hostGeneration: coordinator.hostGeneration,\n operationId: request.operationId,\n source: transitionSource(requesterSource),\n target: { axis: 'minor-mode', action: request, requesterSource },\n },\n async () => {\n response = await invoke();\n return 'applied';\n },\n );\n if (result.outcome !== 'applied' || !response) {\n throw new Error(`Minor-mode transition was ${result.outcome}: ${result.diagnostics.join(', ')}`);\n }\n return response;\n },\n });\n sessionContext.provide(DOOM_MINOR_MODE_CATALOG_SERVICE, catalog);\n activeCatalog.current = catalog;\n activeCatalog.sessionId = sessionId;\n\n // Journal the projection so RPC clients see catalog state live and on\n // replay; identical projections are skipped so detail churn stays cheap.\n let published: string | undefined;\n let settle: NodeJS.Timeout | undefined;\n const publish = (): void => {\n settle = undefined;\n const projection = projectMinorModes(catalog.getSnapshot(), sessionKind);\n const serialized = JSON.stringify(projection);\n if (serialized === published) return;\n published = serialized;\n pi.appendEntry(DOOM_MINOR_MODE_ENTRY_TYPE, projection);\n // A mode changing is the usual reason the toolbox changed too.\n onCatalogChanged();\n };\n const unsubscribe = catalog.subscribe(() => {\n settle ??= setTimeout(publish, PROJECTION_SETTLE_MS);\n });\n activeCatalog.republish = () => {\n published = undefined;\n publish();\n };\n\n return () => {\n if (settle) clearTimeout(settle);\n unsubscribe();\n activeCatalog.republish = undefined;\n activeCatalog.current = undefined;\n activeCatalog.sessionId = undefined;\n catalog.dispose();\n };\n });\n}\n\nexport default modeCatalogExtension;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"modeCatalog.d.cts","names":[],"sources":["../../../../src/extensions/entries/modeCatalog.ts"],"mappings":";;iBA4FsB,qBAAqB,IAAI,eAAe"}
1
+ {"version":3,"file":"modeCatalog.d.cts","names":[],"sources":["../../../../src/extensions/entries/modeCatalog.ts"],"mappings":";;iBA6FsB,qBAAqB,IAAI,eAAe"}
@@ -1 +1 @@
1
- {"version":3,"file":"modeCatalog.d.mts","names":[],"sources":["../../../../src/extensions/entries/modeCatalog.ts"],"mappings":";;iBA4FsB,qBAAqB,IAAI,eAAe"}
1
+ {"version":3,"file":"modeCatalog.d.mts","names":[],"sources":["../../../../src/extensions/entries/modeCatalog.ts"],"mappings":";;iBA6FsB,qBAAqB,IAAI,eAAe"}
@@ -1,2 +1,2 @@
1
- import{createMinorModeCatalogHost as e}from"../../services/modeCatalog.mjs";import{removeContextDetail as t,writeContextDetail as n}from"../../adapters/contextDetailStore.mjs";import{createContextPublisher as r}from"../../services/contextCatalog.mjs";import{projectMinorModes as i}from"../../services/minorModeProjection.mjs";import{registerMinorModeCommand as a}from"./minorModeCommand.mjs";import{DOOM_CORDIS_SESSION_SERVICE as o,connectDoomCordisHost as s}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_MINOR_MODE_CATALOG_SERVICE as c,DOOM_MINOR_MODE_ENTRY_TYPE as l}from"@agimon-ai/doompi-extension-contracts/mode";import{DOOM_HELP_SERVICE as u,requireDoomHelpService as d}from"@agimon-ai/doompi-extension-contracts/help";import{readDoomNotificationService as f}from"@agimon-ai/doompi-extension-contracts/notification";import{DOOM_TRANSITION_SERVICE as p,consumeMinorModeReloadHandoff as m,discardMinorModeReloadHandoff as h,requireDoomTransitionCoordinator as g}from"@agimon-ai/doompi-extension-contracts/transition";function _(e,t,n,r){let i=f(e);if(i){try{Promise.resolve(i.request({body:n,level:r})).catch(()=>void 0)}catch{}return}t.ui.notify(n,r)}function v(e){return e.includes(`voice`)?`voice`:e.includes(`leader`)?`leader`:e.includes(`ui`)?`ui`:`system`}function y(e){let t=e.current?.getSnapshot();return t?t.modes.filter(e=>e.state.activation===`active`).map(e=>({id:e.descriptor.id,label:e.descriptor.label})):[]}async function b(e){let i={current:void 0};a(e,()=>i.current);let o={};e.on(`session_start`,()=>{setTimeout(()=>{i.republish?.(),o.publisher?.publish()},500)}),e.on(`turn_start`,()=>{o.publisher?.publish()});let c=await s(e,`@agimon-ai/doompi/mode-catalog`),l=c.root.plugin(a=>(o.publisher=r(e,a,{readMinorModes:()=>y(i),readSessionId:()=>i.sessionId,writeDetail:(e,t,r)=>void n(e,t,r),removeDetail:t}),x(a,e,i,()=>void o.publisher?.publish()),()=>{o.publisher?.dispose(),o.publisher=void 0}));try{await l}catch(e){throw await l.dispose(),await c.dispose(),e}let u;e.on(`session_shutdown`,()=>u??=(async()=>{try{await l.dispose()}finally{await c.dispose()}})())}function x(t,n,r,a){t.inject([u],e=>{let t=d(e).register({source:`@agimon-ai/doompi`,moduleUrl:import.meta.url,skills:[{name:`doompi-author-extension`,description:`Create or update a DoomPi extension package inside the DoomPi monorepo or as an external npm package. Use for package layout, Pi discovery entries, shared Cordis lifecycle, package-owned Help, and extension verification.`}]});return()=>t.dispose()}),t.inject([o,p],t=>{let s=t.get(o),u=s.context,d=g(t),f=s.reason===`reload`,p=s.sessionId,y=f?m(p):void 0;f||h(p);let b=u.hasUI&&u.mode===`tui`?`tui`:`headless`,x=e({sessionKind:b,context:u,restoreSnapshot:y,onRestorationError(e){_(t,u,`Could not restore a minor mode after reload: ${e instanceof Error?e.message:String(e)}`,`warning`)},async routeInvocation(e,t,n){let r,i=await d.execute({sessionId:u.sessionManager.getSessionId(),hostGeneration:d.hostGeneration,operationId:e.operationId,source:v(t),target:{axis:`minor-mode`,action:e,requesterSource:t}},async()=>(r=await n(),`applied`));if(i.outcome!==`applied`||!r)throw Error(`Minor-mode transition was ${i.outcome}: ${i.diagnostics.join(`, `)}`);return r}});t.provide(c,x),r.current=x,r.sessionId=p;let S,C,w=()=>{C=void 0;let e=i(x.getSnapshot(),b),t=JSON.stringify(e);t!==S&&(S=t,n.appendEntry(l,e),a())},T=x.subscribe(()=>{C??=setTimeout(w,50)});return r.republish=()=>{S=void 0,w()},()=>{C&&clearTimeout(C),T(),r.republish=void 0,r.current=void 0,r.sessionId=void 0,x.dispose()}})}export{b as default,b as modeCatalogExtension};
1
+ import{createMinorModeCatalogHost as e}from"../../services/modeCatalog.mjs";import{removeContextDetail as t,writeContextDetail as n}from"../../adapters/contextDetailStore.mjs";import{createContextPublisher as r}from"../../services/contextCatalog.mjs";import{projectMinorModes as i}from"../../services/minorModeProjection.mjs";import{registerMinorModeCommand as a}from"./minorModeCommand.mjs";import{DOOM_CORDIS_SESSION_SERVICE as o,connectDoomCordisHost as s}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_MINOR_MODE_CATALOG_SERVICE as c,DOOM_MINOR_MODE_ENTRY_TYPE as l}from"@agimon-ai/doompi-extension-contracts/mode";import{DOOM_MCP_STATUS_SERVICE as u,readDoomMcpStatus as d}from"@agimon-ai/doompi-extension-contracts/mcp-status";import{DOOM_HELP_SERVICE as f,requireDoomHelpService as p}from"@agimon-ai/doompi-extension-contracts/help";import{readDoomNotificationService as m}from"@agimon-ai/doompi-extension-contracts/notification";import{DOOM_TRANSITION_SERVICE as h,consumeMinorModeReloadHandoff as g,discardMinorModeReloadHandoff as _,requireDoomTransitionCoordinator as v}from"@agimon-ai/doompi-extension-contracts/transition";function y(e,t,n,r){let i=m(e);if(i){try{Promise.resolve(i.request({body:n,level:r})).catch(()=>void 0)}catch{}return}t.ui.notify(n,r)}function b(e){return e.includes(`voice`)?`voice`:e.includes(`leader`)?`leader`:e.includes(`ui`)?`ui`:`system`}function x(e){let t=e.current?.getSnapshot();return t?t.modes.filter(e=>e.state.activation===`active`).map(e=>({id:e.descriptor.id,label:e.descriptor.label})):[]}async function S(e){let i={current:void 0};a(e,()=>i.current);let o={};e.on(`session_start`,()=>{setTimeout(()=>{i.republish?.(),o.publisher?.publish()},500)}),e.on(`turn_start`,()=>{o.publisher?.publish()});let c=await s(e,`@agimon-ai/doompi/mode-catalog`),l=c.root.plugin(a=>(o.publisher=r(e,a,{readMinorModes:()=>x(i),readSessionId:()=>i.sessionId,writeDetail:(e,t,r)=>void n(e,t,r),removeDetail:t}),a.inject([u],e=>{let t=()=>void o.publisher?.publish(),n=d(e)?.onChange?.(t);return t(),n}),C(a,e,i,()=>void o.publisher?.publish()),()=>{o.publisher?.dispose(),o.publisher=void 0}));try{await l}catch(e){throw await l.dispose(),await c.dispose(),e}let f;e.on(`session_shutdown`,()=>f??=(async()=>{try{await l.dispose()}finally{await c.dispose()}})())}function C(t,n,r,a){t.inject([f],e=>{let t=p(e).register({source:`@agimon-ai/doompi`,moduleUrl:import.meta.url,skills:[{name:`doompi-author-extension`,description:`Create or update a DoomPi extension package inside the DoomPi monorepo or as an external npm package. Use for package layout, Pi discovery entries, shared Cordis lifecycle, package-owned Help, and extension verification.`}]});return()=>t.dispose()}),t.inject([o,h],t=>{let s=t.get(o),u=s.context,d=v(t),f=s.reason===`reload`,p=s.sessionId,m=f?g(p):void 0;f||_(p);let h=u.hasUI&&u.mode===`tui`?`tui`:`headless`,x=e({sessionKind:h,context:u,restoreSnapshot:m,onRestorationError(e){y(t,u,`Could not restore a minor mode after reload: ${e instanceof Error?e.message:String(e)}`,`warning`)},async routeInvocation(e,t,n){let r,i=await d.execute({sessionId:u.sessionManager.getSessionId(),hostGeneration:d.hostGeneration,operationId:e.operationId,source:b(t),target:{axis:`minor-mode`,action:e,requesterSource:t}},async()=>(r=await n(),`applied`));if(i.outcome!==`applied`||!r)throw Error(`Minor-mode transition was ${i.outcome}: ${i.diagnostics.join(`, `)}`);return r}});t.provide(c,x),r.current=x,r.sessionId=p;let S,C,w=()=>{C=void 0;let e=i(x.getSnapshot(),h),t=JSON.stringify(e);t!==S&&(S=t,n.appendEntry(l,e),a())},T=x.subscribe(()=>{C??=setTimeout(w,50)});return r.republish=()=>{S=void 0,w()},()=>{C&&clearTimeout(C),T(),r.republish=void 0,r.current=void 0,r.sessionId=void 0,x.dispose()}})}export{S as default,S as modeCatalogExtension};
2
2
  //# sourceMappingURL=modeCatalog.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"modeCatalog.mjs","names":[],"sources":["../../../../src/extensions/entries/modeCatalog.ts"],"sourcesContent":["import {\n DOOM_MINOR_MODE_CATALOG_SERVICE,\n DOOM_MINOR_MODE_ENTRY_TYPE,\n type MinorModeActionResponse,\n type MinorModeCatalogService,\n} from '@agimon-ai/doompi-extension-contracts/mode';\nimport {\n connectDoomCordisHost,\n DOOM_CORDIS_SESSION_SERVICE,\n type DoomCordisSessionService,\n} from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport {\n type DoomNotificationLevel,\n readDoomNotificationService,\n} from '@agimon-ai/doompi-extension-contracts/notification';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport {\n consumeMinorModeReloadHandoff,\n discardMinorModeReloadHandoff,\n type MinorModeCatalogHost,\n DOOM_TRANSITION_SERVICE,\n requireDoomTransitionCoordinator,\n type TransitionSource,\n} from '@agimon-ai/doompi-extension-contracts/transition';\nimport type { Context } from '@deepseek-ai/cordis';\nimport { removeContextDetail, writeContextDetail } from '../../adapters/contextDetailStore.ts';\nimport { type ContextPublisher, createContextPublisher } from '../../services/contextCatalog.ts';\nimport { projectMinorModes } from '../../services/minorModeProjection.ts';\nimport { createMinorModeCatalogHost } from '../../services/modeCatalog.ts';\nimport { registerMinorModeCommand } from './minorModeCommand.ts';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi/mode-catalog';\nconst HELP_CONTRIBUTION_SOURCE = '@agimon-ai/doompi';\n/** Registrations and state flips arrive in bursts; one entry covers a burst. */\nconst PROJECTION_SETTLE_MS = 50;\n/**\n * Pi's rpc mode subscribes to session events only after bindExtensions()\n * returns, which is after session_start has run, so an entry journaled during\n * startup never reaches a client. A publish deferred past that point does.\n */\nconst BOOT_PUBLISH_DELAY_MS = 500;\n\ninterface CatalogBinding {\n current: MinorModeCatalogService | undefined;\n /** Journals the projection even if unchanged; set while a session is bound. */\n republish?: () => void;\n /**\n * The bound session, which the detail store is keyed by.\n *\n * The server launches the agent with `--session-id`, so this is the same id\n * the cockpit and the session API know the session by, and a browser asking\n * for a row's detail looks under the file this session wrote.\n */\n sessionId?: string;\n}\n\nfunction notify(cordis: Context, context: ExtensionContext, body: string, level: DoomNotificationLevel): void {\n const service = readDoomNotificationService(cordis);\n if (service) {\n try {\n void Promise.resolve(service.request({ body, level })).catch(() => undefined);\n } catch {\n // Notification delivery is best effort and does not fall back after routing.\n }\n return;\n }\n context.ui.notify(body, level);\n}\n\nfunction transitionSource(requesterSource: string): TransitionSource {\n if (requesterSource.includes('voice')) return 'voice';\n if (requesterSource.includes('leader')) return 'leader';\n if (requesterSource.includes('ui')) return 'ui';\n return 'system';\n}\n\n/**\n * The minor modes that are actually on, for the composition panel.\n *\n * Read from the bound catalog at publish time rather than passed in, because a\n * mode switching on is one of the reasons a publish happens at all, and a\n * remembered list would describe the composition before the switch.\n */\nfunction activeMinorModes(binding: CatalogBinding): { id: string; label: string }[] {\n const snapshot = binding.current?.getSnapshot();\n if (!snapshot) return [];\n return snapshot.modes\n .filter((record) => record.state.activation === 'active')\n .map((record) => ({ id: record.descriptor.id, label: record.descriptor.label }));\n}\n\nexport async function modeCatalogExtension(pi: ExtensionAPI): Promise<void> {\n // The command outlives any one session: it is registered once and reads the\n // catalog binding the session-scoped inject below sets and clears.\n const activeCatalog: CatalogBinding = { current: undefined };\n registerMinorModeCommand(pi, () => activeCatalog.current);\n // The composition is only complete once every extension has registered, so\n // the inventory is read on the same deferred tick the catalog republishes on.\n const contextBinding: { publisher?: ContextPublisher } = {};\n pi.on('session_start', () => {\n setTimeout(() => {\n activeCatalog.republish?.();\n void contextBinding.publisher?.publish();\n }, BOOT_PUBLISH_DELAY_MS);\n });\n // MCP status is pull-only and servers connect long after startup, so the\n // boot read above cannot see them. A turn is the moment the composition is\n // actually sent, which makes it both the freshest and the most meaningful\n // time to price. Identical compositions are skipped, so a quiet session\n // journals nothing extra.\n pi.on('turn_start', () => {\n void contextBinding.publisher?.publish();\n });\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin((cordis: Context) => {\n contextBinding.publisher = createContextPublisher(pi, cordis, {\n readMinorModes: () => activeMinorModes(activeCatalog),\n readSessionId: () => activeCatalog.sessionId,\n writeDetail: (sessionId, revision, items) => void writeContextDetail(sessionId, revision, items),\n removeDetail: removeContextDetail,\n });\n modeCatalogPlugin(cordis, pi, activeCatalog, () => void contextBinding.publisher?.publish());\n return () => {\n contextBinding.publisher?.dispose();\n contextBinding.publisher = undefined;\n };\n });\n try {\n await fiber;\n } catch (error) {\n await fiber.dispose();\n await connection.dispose();\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\nfunction modeCatalogPlugin(\n cordis: Context,\n pi: Pick<ExtensionAPI, 'appendEntry'>,\n activeCatalog: CatalogBinding,\n onCatalogChanged: () => void,\n): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: HELP_CONTRIBUTION_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-author-extension',\n description:\n 'Create or update a DoomPi extension package inside the DoomPi monorepo or as an external npm package. Use for package layout, Pi discovery entries, shared Cordis lifecycle, package-owned Help, and extension verification.',\n },\n ],\n });\n return () => contribution.dispose();\n });\n\n cordis.inject([DOOM_CORDIS_SESSION_SERVICE, DOOM_TRANSITION_SERVICE], (sessionContext) => {\n const session = sessionContext.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService;\n const context = session.context;\n const coordinator = requireDoomTransitionCoordinator(sessionContext);\n const reloadSession = session.reason === 'reload';\n const sessionId = session.sessionId;\n const restoreSnapshot = reloadSession ? consumeMinorModeReloadHandoff(sessionId) : undefined;\n if (!reloadSession) discardMinorModeReloadHandoff(sessionId);\n const sessionKind = context.hasUI && context.mode === 'tui' ? 'tui' : 'headless';\n const catalog: MinorModeCatalogHost = createMinorModeCatalogHost({\n sessionKind,\n context,\n restoreSnapshot,\n onRestorationError(error) {\n notify(\n sessionContext,\n context,\n `Could not restore a minor mode after reload: ${error instanceof Error ? error.message : String(error)}`,\n 'warning',\n );\n },\n async routeInvocation(request, requesterSource, invoke) {\n let response: MinorModeActionResponse | undefined;\n const result = await coordinator.execute(\n {\n sessionId: context.sessionManager.getSessionId(),\n hostGeneration: coordinator.hostGeneration,\n operationId: request.operationId,\n source: transitionSource(requesterSource),\n target: { axis: 'minor-mode', action: request, requesterSource },\n },\n async () => {\n response = await invoke();\n return 'applied';\n },\n );\n if (result.outcome !== 'applied' || !response) {\n throw new Error(`Minor-mode transition was ${result.outcome}: ${result.diagnostics.join(', ')}`);\n }\n return response;\n },\n });\n sessionContext.provide(DOOM_MINOR_MODE_CATALOG_SERVICE, catalog);\n activeCatalog.current = catalog;\n activeCatalog.sessionId = sessionId;\n\n // Journal the projection so RPC clients see catalog state live and on\n // replay; identical projections are skipped so detail churn stays cheap.\n let published: string | undefined;\n let settle: NodeJS.Timeout | undefined;\n const publish = (): void => {\n settle = undefined;\n const projection = projectMinorModes(catalog.getSnapshot(), sessionKind);\n const serialized = JSON.stringify(projection);\n if (serialized === published) return;\n published = serialized;\n pi.appendEntry(DOOM_MINOR_MODE_ENTRY_TYPE, projection);\n // A mode changing is the usual reason the toolbox changed too.\n onCatalogChanged();\n };\n const unsubscribe = catalog.subscribe(() => {\n settle ??= setTimeout(publish, PROJECTION_SETTLE_MS);\n });\n activeCatalog.republish = () => {\n published = undefined;\n publish();\n };\n\n return () => {\n if (settle) clearTimeout(settle);\n unsubscribe();\n activeCatalog.republish = undefined;\n activeCatalog.current = undefined;\n activeCatalog.sessionId = undefined;\n catalog.dispose();\n };\n });\n}\n\nexport default modeCatalogExtension;\n"],"mappings":"mhCAyDA,SAAS,EAAO,EAAiB,EAA2B,EAAc,EAAoC,CAC5G,IAAM,EAAU,EAA4B,CAAM,EAClD,GAAI,EAAS,CACX,GAAI,CACF,QAAa,QAAQ,EAAQ,QAAQ,CAAE,OAAM,OAAM,CAAC,CAAC,CAAC,CAAC,UAAY,IAAA,EAAS,CAC9E,MAAQ,CAER,CACA,MACF,CACA,EAAQ,GAAG,OAAO,EAAM,CAAK,CAC/B,CAEA,SAAS,EAAiB,EAA2C,CAInE,OAHI,EAAgB,SAAS,OAAO,EAAU,QAC1C,EAAgB,SAAS,QAAQ,EAAU,SAC3C,EAAgB,SAAS,IAAI,EAAU,KACpC,QACT,CASA,SAAS,EAAiB,EAA0D,CAClF,IAAM,EAAW,EAAQ,SAAS,YAAY,EAE9C,OADK,EACE,EAAS,MACb,OAAQ,GAAW,EAAO,MAAM,aAAe,QAAQ,CAAC,CACxD,IAAK,IAAY,CAAE,GAAI,EAAO,WAAW,GAAI,MAAO,EAAO,WAAW,KAAM,EAAE,EAH3D,CAAC,CAIzB,CAEA,eAAsB,EAAqB,EAAiC,CAG1E,IAAM,EAAgC,CAAE,QAAS,IAAA,EAAU,EAC3D,EAAyB,MAAU,EAAc,OAAO,EAGxD,IAAM,EAAmD,CAAC,EAC1D,EAAG,GAAG,oBAAuB,CAC3B,eAAiB,CACf,EAAc,YAAY,EAC1B,EAAoB,WAAW,QAAQ,CACzC,EAAG,GAAqB,CAC1B,CAAC,EAMD,EAAG,GAAG,iBAAoB,CACxB,EAAoB,WAAW,QAAQ,CACzC,CAAC,EACD,IAAM,EAAa,MAAM,EAAsB,EAAI,gCAAc,EAC3D,EAAQ,EAAW,KAAK,OAAQ,IACpC,EAAe,UAAY,EAAuB,EAAI,EAAQ,CAC5D,mBAAsB,EAAiB,CAAa,EACpD,kBAAqB,EAAc,UACnC,aAAc,EAAW,EAAU,IAAU,KAAK,EAAmB,EAAW,EAAU,CAAK,EAC/F,aAAc,CAChB,CAAC,EACD,EAAkB,EAAQ,EAAI,MAAqB,KAAK,EAAe,WAAW,QAAQ,CAAC,MAC9E,CACX,EAAe,WAAW,QAAQ,EAClC,EAAe,UAAY,IAAA,EAC7B,EACD,EACD,GAAI,CACF,MAAM,CACR,OAAS,EAAO,CAGd,MAFA,MAAM,EAAM,QAAQ,EACpB,MAAM,EAAW,QAAQ,EACnB,CACR,CACA,IAAI,EACJ,EAAG,GACD,uBAEG,KAAc,SAAY,CACzB,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACF,EAAA,CAAG,CACP,CACF,CAEA,SAAS,EACP,EACA,EACA,EACA,EACM,CACN,EAAO,OAAO,CAAC,CAAiB,EAAI,GAAgB,CAClD,IAAM,EAAe,EAAuB,CAAW,CAAC,CAAC,SAAS,CAChE,OAAQ,oBACR,UAAW,YAAY,IACvB,OAAQ,CACN,CACE,KAAM,0BACN,YACE,8NACJ,CACF,CACF,CAAC,EACD,UAAa,EAAa,QAAQ,CACpC,CAAC,EAED,EAAO,OAAO,CAAC,EAA6B,CAAuB,EAAI,GAAmB,CACxF,IAAM,EAAU,EAAe,IAAI,CAA2B,EACxD,EAAU,EAAQ,QAClB,EAAc,EAAiC,CAAc,EAC7D,EAAgB,EAAQ,SAAW,SACnC,EAAY,EAAQ,UACpB,EAAkB,EAAgB,EAA8B,CAAS,EAAI,IAAA,GAC9E,GAAe,EAA8B,CAAS,EAC3D,IAAM,EAAc,EAAQ,OAAS,EAAQ,OAAS,MAAQ,MAAQ,WAChE,EAAgC,EAA2B,CAC/D,cACA,UACA,kBACA,mBAAmB,EAAO,CACxB,EACE,EACA,EACA,gDAAgD,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,IACrG,SACF,CACF,EACA,MAAM,gBAAgB,EAAS,EAAiB,EAAQ,CACtD,IAAI,EACE,EAAS,MAAM,EAAY,QAC/B,CACE,UAAW,EAAQ,eAAe,aAAa,EAC/C,eAAgB,EAAY,eAC5B,YAAa,EAAQ,YACrB,OAAQ,EAAiB,CAAe,EACxC,OAAQ,CAAE,KAAM,aAAc,OAAQ,EAAS,iBAAgB,CACjE,EACA,UACE,EAAW,MAAM,EAAO,EACjB,UAEX,EACA,GAAI,EAAO,UAAY,WAAa,CAAC,EACnC,MAAU,MAAM,6BAA6B,EAAO,QAAQ,IAAI,EAAO,YAAY,KAAK,IAAI,GAAG,EAEjG,OAAO,CACT,CACF,CAAC,EACD,EAAe,QAAQ,EAAiC,CAAO,EAC/D,EAAc,QAAU,EACxB,EAAc,UAAY,EAI1B,IAAI,EACA,EACE,MAAsB,CAC1B,EAAS,IAAA,GACT,IAAM,EAAa,EAAkB,EAAQ,YAAY,EAAG,CAAW,EACjE,EAAa,KAAK,UAAU,CAAU,EACxC,IAAe,IACnB,EAAY,EACZ,EAAG,YAAY,EAA4B,CAAU,EAErD,EAAiB,EACnB,EACM,EAAc,EAAQ,cAAgB,CAC1C,IAAW,WAAW,EAAS,EAAoB,CACrD,CAAC,EAMD,MALA,GAAc,cAAkB,CAC9B,EAAY,IAAA,GACZ,EAAQ,CACV,MAEa,CACP,GAAQ,aAAa,CAAM,EAC/B,EAAY,EACZ,EAAc,UAAY,IAAA,GAC1B,EAAc,QAAU,IAAA,GACxB,EAAc,UAAY,IAAA,GAC1B,EAAQ,QAAQ,CAClB,CACF,CAAC,CACH"}
1
+ {"version":3,"file":"modeCatalog.mjs","names":[],"sources":["../../../../src/extensions/entries/modeCatalog.ts"],"sourcesContent":["import { DOOM_MCP_STATUS_SERVICE, readDoomMcpStatus } from '@agimon-ai/doompi-extension-contracts/mcp-status';\nimport {\n DOOM_MINOR_MODE_CATALOG_SERVICE,\n DOOM_MINOR_MODE_ENTRY_TYPE,\n type MinorModeActionResponse,\n type MinorModeCatalogService,\n} from '@agimon-ai/doompi-extension-contracts/mode';\nimport {\n connectDoomCordisHost,\n DOOM_CORDIS_SESSION_SERVICE,\n type DoomCordisSessionService,\n} from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport {\n type DoomNotificationLevel,\n readDoomNotificationService,\n} from '@agimon-ai/doompi-extension-contracts/notification';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport {\n consumeMinorModeReloadHandoff,\n discardMinorModeReloadHandoff,\n type MinorModeCatalogHost,\n DOOM_TRANSITION_SERVICE,\n requireDoomTransitionCoordinator,\n type TransitionSource,\n} from '@agimon-ai/doompi-extension-contracts/transition';\nimport type { Context } from '@deepseek-ai/cordis';\nimport { removeContextDetail, writeContextDetail } from '../../adapters/contextDetailStore.ts';\nimport { type ContextPublisher, createContextPublisher } from '../../services/contextCatalog.ts';\nimport { projectMinorModes } from '../../services/minorModeProjection.ts';\nimport { createMinorModeCatalogHost } from '../../services/modeCatalog.ts';\nimport { registerMinorModeCommand } from './minorModeCommand.ts';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi/mode-catalog';\nconst HELP_CONTRIBUTION_SOURCE = '@agimon-ai/doompi';\n/** Registrations and state flips arrive in bursts; one entry covers a burst. */\nconst PROJECTION_SETTLE_MS = 50;\n/**\n * Pi's rpc mode subscribes to session events only after bindExtensions()\n * returns, which is after session_start has run, so an entry journaled during\n * startup never reaches a client. A publish deferred past that point does.\n */\nconst BOOT_PUBLISH_DELAY_MS = 500;\n\ninterface CatalogBinding {\n current: MinorModeCatalogService | undefined;\n /** Journals the projection even if unchanged; set while a session is bound. */\n republish?: () => void;\n /**\n * The bound session, which the detail store is keyed by.\n *\n * The server launches the agent with `--session-id`, so this is the same id\n * the cockpit and the session API know the session by, and a browser asking\n * for a row's detail looks under the file this session wrote.\n */\n sessionId?: string;\n}\n\nfunction notify(cordis: Context, context: ExtensionContext, body: string, level: DoomNotificationLevel): void {\n const service = readDoomNotificationService(cordis);\n if (service) {\n try {\n void Promise.resolve(service.request({ body, level })).catch(() => undefined);\n } catch {\n // Notification delivery is best effort and does not fall back after routing.\n }\n return;\n }\n context.ui.notify(body, level);\n}\n\nfunction transitionSource(requesterSource: string): TransitionSource {\n if (requesterSource.includes('voice')) return 'voice';\n if (requesterSource.includes('leader')) return 'leader';\n if (requesterSource.includes('ui')) return 'ui';\n return 'system';\n}\n\n/**\n * The minor modes that are actually on, for the composition panel.\n *\n * Read from the bound catalog at publish time rather than passed in, because a\n * mode switching on is one of the reasons a publish happens at all, and a\n * remembered list would describe the composition before the switch.\n */\nfunction activeMinorModes(binding: CatalogBinding): { id: string; label: string }[] {\n const snapshot = binding.current?.getSnapshot();\n if (!snapshot) return [];\n return snapshot.modes\n .filter((record) => record.state.activation === 'active')\n .map((record) => ({ id: record.descriptor.id, label: record.descriptor.label }));\n}\n\nexport async function modeCatalogExtension(pi: ExtensionAPI): Promise<void> {\n // The command outlives any one session: it is registered once and reads the\n // catalog binding the session-scoped inject below sets and clears.\n const activeCatalog: CatalogBinding = { current: undefined };\n registerMinorModeCommand(pi, () => activeCatalog.current);\n // The composition is only complete once every extension has registered, so\n // the inventory is read on the same deferred tick the catalog republishes on.\n const contextBinding: { publisher?: ContextPublisher } = {};\n pi.on('session_start', () => {\n setTimeout(() => {\n activeCatalog.republish?.();\n void contextBinding.publisher?.publish();\n }, BOOT_PUBLISH_DELAY_MS);\n });\n // Turns still refresh active-tool costs, including for older pull-only MCP providers.\n // Identical compositions are skipped rather than journaled again.\n pi.on('turn_start', () => {\n void contextBinding.publisher?.publish();\n });\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin((cordis: Context) => {\n contextBinding.publisher = createContextPublisher(pi, cordis, {\n readMinorModes: () => activeMinorModes(activeCatalog),\n readSessionId: () => activeCatalog.sessionId,\n writeDetail: (sessionId, revision, items) => void writeContextDetail(sessionId, revision, items),\n removeDetail: removeContextDetail,\n });\n cordis.inject([DOOM_MCP_STATUS_SERVICE], (mcpContext) => {\n const publish = () => void contextBinding.publisher?.publish();\n const unsubscribe = readDoomMcpStatus(mcpContext)?.onChange?.(publish);\n // Subscribe first, then read: discovery may already have finished before binding.\n publish();\n return unsubscribe;\n });\n modeCatalogPlugin(cordis, pi, activeCatalog, () => void contextBinding.publisher?.publish());\n return () => {\n contextBinding.publisher?.dispose();\n contextBinding.publisher = undefined;\n };\n });\n try {\n await fiber;\n } catch (error) {\n await fiber.dispose();\n await connection.dispose();\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\nfunction modeCatalogPlugin(\n cordis: Context,\n pi: Pick<ExtensionAPI, 'appendEntry'>,\n activeCatalog: CatalogBinding,\n onCatalogChanged: () => void,\n): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: HELP_CONTRIBUTION_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-author-extension',\n description:\n 'Create or update a DoomPi extension package inside the DoomPi monorepo or as an external npm package. Use for package layout, Pi discovery entries, shared Cordis lifecycle, package-owned Help, and extension verification.',\n },\n ],\n });\n return () => contribution.dispose();\n });\n\n cordis.inject([DOOM_CORDIS_SESSION_SERVICE, DOOM_TRANSITION_SERVICE], (sessionContext) => {\n const session = sessionContext.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService;\n const context = session.context;\n const coordinator = requireDoomTransitionCoordinator(sessionContext);\n const reloadSession = session.reason === 'reload';\n const sessionId = session.sessionId;\n const restoreSnapshot = reloadSession ? consumeMinorModeReloadHandoff(sessionId) : undefined;\n if (!reloadSession) discardMinorModeReloadHandoff(sessionId);\n const sessionKind = context.hasUI && context.mode === 'tui' ? 'tui' : 'headless';\n const catalog: MinorModeCatalogHost = createMinorModeCatalogHost({\n sessionKind,\n context,\n restoreSnapshot,\n onRestorationError(error) {\n notify(\n sessionContext,\n context,\n `Could not restore a minor mode after reload: ${error instanceof Error ? error.message : String(error)}`,\n 'warning',\n );\n },\n async routeInvocation(request, requesterSource, invoke) {\n let response: MinorModeActionResponse | undefined;\n const result = await coordinator.execute(\n {\n sessionId: context.sessionManager.getSessionId(),\n hostGeneration: coordinator.hostGeneration,\n operationId: request.operationId,\n source: transitionSource(requesterSource),\n target: { axis: 'minor-mode', action: request, requesterSource },\n },\n async () => {\n response = await invoke();\n return 'applied';\n },\n );\n if (result.outcome !== 'applied' || !response) {\n throw new Error(`Minor-mode transition was ${result.outcome}: ${result.diagnostics.join(', ')}`);\n }\n return response;\n },\n });\n sessionContext.provide(DOOM_MINOR_MODE_CATALOG_SERVICE, catalog);\n activeCatalog.current = catalog;\n activeCatalog.sessionId = sessionId;\n\n // Journal the projection so RPC clients see catalog state live and on\n // replay; identical projections are skipped so detail churn stays cheap.\n let published: string | undefined;\n let settle: NodeJS.Timeout | undefined;\n const publish = (): void => {\n settle = undefined;\n const projection = projectMinorModes(catalog.getSnapshot(), sessionKind);\n const serialized = JSON.stringify(projection);\n if (serialized === published) return;\n published = serialized;\n pi.appendEntry(DOOM_MINOR_MODE_ENTRY_TYPE, projection);\n // A mode changing is the usual reason the toolbox changed too.\n onCatalogChanged();\n };\n const unsubscribe = catalog.subscribe(() => {\n settle ??= setTimeout(publish, PROJECTION_SETTLE_MS);\n });\n activeCatalog.republish = () => {\n published = undefined;\n publish();\n };\n\n return () => {\n if (settle) clearTimeout(settle);\n unsubscribe();\n activeCatalog.republish = undefined;\n activeCatalog.current = undefined;\n activeCatalog.sessionId = undefined;\n catalog.dispose();\n };\n });\n}\n\nexport default modeCatalogExtension;\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi",
3
- "version": "0.0.1-alpha.56",
3
+ "version": "0.0.1-alpha.58",
4
4
  "description": "Opinionated, composable Pi distribution for scoped agent tools, skills, MCP servers, and developer workflows.",
5
5
  "keywords": [
6
6
  "ai",
@@ -320,17 +320,17 @@
320
320
  "rolldown": "1.2.6",
321
321
  "typebox": "1.3.19",
322
322
  "yaml": "2.9.0",
323
- "@agimon-ai/doompi-autostop": "0.0.1-alpha.33",
324
- "@agimon-ai/doompi-cache": "0.0.1-alpha.22",
325
- "@agimon-ai/doompi-config": "0.0.1-alpha.55",
326
- "@agimon-ai/doompi-domain": "0.0.1-alpha.34",
327
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.55",
328
- "@agimon-ai/doompi-major-mode": "0.0.1-alpha.34",
329
- "@agimon-ai/doompi-notification": "0.0.1-alpha.33",
330
- "@agimon-ai/doompi-profile": "0.0.1-alpha.34",
331
- "@agimon-ai/doompi-skill": "0.0.1-alpha.34",
332
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.55",
333
- "@agimon-ai/doompi-ui": "0.0.1-alpha.56"
323
+ "@agimon-ai/doompi-autostop": "0.0.1-alpha.34",
324
+ "@agimon-ai/doompi-cache": "0.0.1-alpha.23",
325
+ "@agimon-ai/doompi-config": "0.0.1-alpha.56",
326
+ "@agimon-ai/doompi-domain": "0.0.1-alpha.35",
327
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.56",
328
+ "@agimon-ai/doompi-major-mode": "0.0.1-alpha.35",
329
+ "@agimon-ai/doompi-notification": "0.0.1-alpha.34",
330
+ "@agimon-ai/doompi-profile": "0.0.1-alpha.35",
331
+ "@agimon-ai/doompi-skill": "0.0.1-alpha.35",
332
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.56",
333
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.57"
334
334
  },
335
335
  "devDependencies": {
336
336
  "@earendil-works/pi-ai": "0.85.0",
@@ -340,7 +340,7 @@
340
340
  "@vitest/coverage-v8": "4.1.11",
341
341
  "typescript": "7.0.2",
342
342
  "vitest": "4.1.11",
343
- "@agimon-ai/doompi-runner": "0.0.1-alpha.56"
343
+ "@agimon-ai/doompi-runner": "0.0.1-alpha.57"
344
344
  },
345
345
  "peerDependencies": {
346
346
  "@earendil-works/pi-tui": "0.85.0"