@agimon-ai/doompi-domain 0.0.1-alpha.2 → 0.0.1-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +8 -3
  2. package/dist/adapters/applyDomains.cjs +1 -1
  3. package/dist/adapters/applyDomains.cjs.map +1 -1
  4. package/dist/adapters/applyDomains.d.cts +2 -2
  5. package/dist/adapters/applyDomains.d.cts.map +1 -1
  6. package/dist/adapters/applyDomains.d.mts +2 -2
  7. package/dist/adapters/applyDomains.d.mts.map +1 -1
  8. package/dist/adapters/applyDomains.mjs +1 -1
  9. package/dist/adapters/applyDomains.mjs.map +1 -1
  10. package/dist/adapters/domainCatalog.cjs +1 -1
  11. package/dist/adapters/domainCatalog.cjs.map +1 -1
  12. package/dist/adapters/domainCatalog.d.cts +4 -3
  13. package/dist/adapters/domainCatalog.d.cts.map +1 -1
  14. package/dist/adapters/domainCatalog.d.mts +4 -3
  15. package/dist/adapters/domainCatalog.d.mts.map +1 -1
  16. package/dist/adapters/domainCatalog.mjs +1 -1
  17. package/dist/adapters/domainCatalog.mjs.map +1 -1
  18. package/dist/adapters/domainSwitchHandoff.cjs +1 -1
  19. package/dist/adapters/domainSwitchHandoff.cjs.map +1 -1
  20. package/dist/adapters/domainSwitchHandoff.mjs +1 -1
  21. package/dist/adapters/domainSwitchHandoff.mjs.map +1 -1
  22. package/dist/adapters/mcpFilter.cjs +2 -2
  23. package/dist/adapters/mcpFilter.mjs +2 -2
  24. package/dist/adapters/pi/extension.cjs +1 -1
  25. package/dist/adapters/pi/extension.cjs.map +1 -1
  26. package/dist/adapters/pi/extension.d.cts +1 -6
  27. package/dist/adapters/pi/extension.d.cts.map +1 -1
  28. package/dist/adapters/pi/extension.d.mts +1 -6
  29. package/dist/adapters/pi/extension.d.mts.map +1 -1
  30. package/dist/adapters/pi/extension.mjs +1 -1
  31. package/dist/adapters/pi/extension.mjs.map +1 -1
  32. package/dist/adapters/pi/voiceTool.cjs +1 -1
  33. package/dist/adapters/pi/voiceTool.cjs.map +1 -1
  34. package/dist/adapters/pi/voiceTool.d.cts +5 -2
  35. package/dist/adapters/pi/voiceTool.d.cts.map +1 -1
  36. package/dist/adapters/pi/voiceTool.d.mts +5 -2
  37. package/dist/adapters/pi/voiceTool.d.mts.map +1 -1
  38. package/dist/adapters/pi/voiceTool.mjs +1 -1
  39. package/dist/adapters/pi/voiceTool.mjs.map +1 -1
  40. package/dist/adapters/pluginMaterializer.cjs +1 -1
  41. package/dist/adapters/pluginMaterializer.cjs.map +1 -1
  42. package/dist/adapters/pluginMaterializer.d.cts.map +1 -1
  43. package/dist/adapters/pluginMaterializer.d.mts.map +1 -1
  44. package/dist/adapters/pluginMaterializer.mjs +1 -1
  45. package/dist/adapters/pluginMaterializer.mjs.map +1 -1
  46. package/dist/adapters/resourceCollector.cjs +2 -2
  47. package/dist/adapters/resourceCollector.cjs.map +1 -1
  48. package/dist/adapters/resourceCollector.d.cts +4 -2
  49. package/dist/adapters/resourceCollector.d.cts.map +1 -1
  50. package/dist/adapters/resourceCollector.d.mts +4 -2
  51. package/dist/adapters/resourceCollector.d.mts.map +1 -1
  52. package/dist/adapters/resourceCollector.mjs +2 -2
  53. package/dist/adapters/resourceCollector.mjs.map +1 -1
  54. package/dist/adapters/skillCacheLocation.cjs +1 -1
  55. package/dist/adapters/skillCacheLocation.mjs +1 -1
  56. package/dist/commands/domainsCommand.cjs +1 -1
  57. package/dist/commands/domainsCommand.cjs.map +1 -1
  58. package/dist/commands/domainsCommand.d.cts +4 -0
  59. package/dist/commands/domainsCommand.d.cts.map +1 -1
  60. package/dist/commands/domainsCommand.d.mts +4 -0
  61. package/dist/commands/domainsCommand.d.mts.map +1 -1
  62. package/dist/commands/domainsCommand.mjs +1 -1
  63. package/dist/commands/domainsCommand.mjs.map +1 -1
  64. package/dist/resources.cjs +1 -1
  65. package/dist/resources.d.cts +3 -3
  66. package/dist/resources.d.mts +3 -3
  67. package/dist/resources.mjs +1 -1
  68. package/dist/types/handoff.d.cts +1 -1
  69. package/dist/types/handoff.d.mts +1 -1
  70. package/dist/types/resources.d.cts +26 -7
  71. package/dist/types/resources.d.cts.map +1 -1
  72. package/dist/types/resources.d.mts +26 -7
  73. package/dist/types/resources.d.mts.map +1 -1
  74. package/package.json +7 -7
package/README.md CHANGED
@@ -48,9 +48,14 @@ selection:
48
48
  worktree so a second session does not rescan the tree;
49
49
  - adapts each plugin's Claude-authored agent definitions into the form Pi loads, and stages them
50
50
  privately (`0600`) beside a dispatcher agent;
51
- - merges the repository and plugin `.mcp.json` layers, applies the union of the selected domains'
52
- MCP allowlists, and rewrites the proxy's upstream list into a per-run config;
53
- - projects the result into the session environment and the harness state, then reloads Pi.
51
+ - merges the repository and legacy plugin `.mcp.json` layers, and validates root `mcp.json` for
52
+ schema-gated Agent Plugins v1 (including plugin-relative commands, data directories and transport
53
+ normalization);
54
+ - applies the selected domains' MCP allowlists and rewrites the proxy's upstream list into a
55
+ private per-run config; and
56
+ - atomically stores an immutable MCP projection in harness state, then reloads Pi. The replacement
57
+ Config factory publishes that snapshot through the session Cordis registry for DoomPi MCP to
58
+ consume; Domain never reaches into the live MCP runtime.
54
59
 
55
60
  An allowlist only takes effect once **every** selected domain declares one. Mixing a scoped domain
56
61
  with a domain that has not opted in leaves the session unfiltered, so an unmigrated domain cannot
@@ -1,2 +1,2 @@
1
- const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./mcpFilter.cjs"),n=require("./mcpSessionEnvironment.cjs"),r=require("./pluginMaterializer.cjs"),i=require("./resourceCollector.cjs");let a=require("node:path");a=e.__toESM(a,1);let o=require("@agimon-ai/doompi-config/domains"),s=require("@agimon-ai/doompi-config/harnessStore"),c=require("@agimon-ai/doompi-mcp");async function l(e,l){let{root:u,temporaryDirectory:d}=(0,s.requireHarnessPaths)(l),f=await r.materializePluginEntries((0,o.resolvePluginEntries)(u,e,[...l.pluginDirectories])),p=t.resolveMcpAllowlist(u,e),m=await i.collectResources(u,f,{agents:l.agents,mcp:l.mcp,temporaryDirectory:d,sharedSkills:(0,o.resolveSharedSkills)(u,e),mcpAllowlist:p});l.mcp?Object.assign(process.env,n.mcpSessionEnvironment({repoRoot:u,stagingDirectory:m.temporaryDirectory,generatedConfigPath:m.mcpConfigPath,pluginConfigPaths:m.pluginMcpConfigPaths,allowlist:p})):delete process.env[c.SESSION_ENV_VAR];let h=(0,s.updateHarnessState)({domains:e,skillDirectories:m.skillDirectories,pluginHooks:m.pluginHooks,mcpConfigPath:m.mcpConfigPath});return process.env.PI_SUBAGENT_EXTRA_AGENT_DIRS=m.agentDirectories.join(a.default.delimiter),process.env.PI_SUBAGENT_EXTRA_SKILL_DIRS=m.skillDirectories.join(a.default.delimiter),h}exports.applyDomains=l;
1
+ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./mcpFilter.cjs"),n=require("./pluginMaterializer.cjs"),r=require("./resourceCollector.cjs");let i=require("node:fs");i=e.__toESM(i,1);let a=require("node:path");a=e.__toESM(a,1);let o=require("@agimon-ai/doompi-config/domains"),s=require("@agimon-ai/doompi-config/harnessStore");async function c(e,c){let{root:l,temporaryDirectory:u}=(0,s.requireHarnessPaths)(c),d=await n.materializePluginEntries((0,o.resolvePluginEntries)(l,e,[...c.pluginDirectories])),f=t.resolveMcpAllowlist(l,e),p=await i.default.promises.mkdtemp(a.default.join(u,`domains-`)),m;try{m=await r.collectResources(l,d,{agents:c.agents,mcp:c.mcp,temporaryDirectory:p,sharedSkills:(0,o.resolveSharedSkills)(l,e),mcpAllowlist:f})}catch(e){throw await i.default.promises.rm(p,{recursive:!0,force:!0}),e}let h;try{h=(0,s.updateHarnessState)({domains:e,skillDirectories:m.skillDirectories,pluginHooks:m.pluginHooks,mcpConfigPath:m.mcpConfigPath,mcpProjection:m.mcpProjection})}catch(e){throw await i.default.promises.rm(p,{recursive:!0,force:!0}),e}return process.env.PI_SUBAGENT_EXTRA_AGENT_DIRS=m.agentDirectories.join(a.default.delimiter),process.env.PI_SUBAGENT_EXTRA_SKILL_DIRS=m.skillDirectories.join(a.default.delimiter),h}exports.applyDomains=c;
2
2
  //# sourceMappingURL=applyDomains.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"applyDomains.cjs","names":["requireHarnessPaths","materializePluginEntries","resolvePluginEntries","resolveMcpAllowlist","collectResources","resolveSharedSkills","mcpSessionEnvironment","SESSION_ENV_VAR","updateHarnessState","path"],"sources":["../../src/adapters/applyDomains.ts"],"sourcesContent":["import path from 'node:path';\nimport { resolvePluginEntries, resolveSharedSkills } from '@agimon-ai/doompi-config/domains';\nimport { requireHarnessPaths, updateHarnessState } from '@agimon-ai/doompi-config/harnessStore';\nimport type { DoomHarnessContext, HarnessState } from '@agimon-ai/doompi-config/types';\nimport { SESSION_ENV_VAR } from '@agimon-ai/doompi-mcp';\nimport { resolveMcpAllowlist } from './mcpFilter.ts';\nimport { mcpSessionEnvironment } from './mcpSessionEnvironment.ts';\nimport { materializePluginEntries } from './pluginMaterializer.ts';\nimport { collectResources } from './resourceCollector.ts';\n\n/**\n * Live domain switching: skills, plugin hooks, MCP config and subagent directories.\n *\n * Writes through the harness state module and relies on Pi's reload\n * re-executing extension modules, which drops their module-level caches and\n * makes them re-read the environment.\n *\n * What reload cannot do is change the SET of loaded extensions: Pi freezes the\n * CLI --extension list at construction and re-resolves that same array on every\n * reload. Callers report those parts as needing a relaunch instead of pretending\n * they applied.\n *\n * The major-mode and profile switches live in\n * @agimon-ai/doompi-config/selectionSwitch: they are config writes with no\n * resource staging, and the synced startup path applies them before any command\n * exists to run. Domains stays here because it stages plugins, skills and MCP\n * config.\n */\nexport async function applyDomains(domainNames: string[], state: DoomHarnessContext): Promise<HarnessState> {\n const { root, temporaryDirectory } = requireHarnessPaths(state);\n\n const pluginEntries = await materializePluginEntries(\n resolvePluginEntries(root, domainNames, [...state.pluginDirectories]),\n );\n const mcpAllowlist = resolveMcpAllowlist(root, domainNames);\n const resources = await collectResources(root, pluginEntries, {\n agents: state.agents,\n mcp: state.mcp,\n temporaryDirectory,\n sharedSkills: resolveSharedSkills(root, domainNames),\n mcpAllowlist,\n });\n\n if (state.mcp) {\n Object.assign(\n process.env,\n mcpSessionEnvironment({\n repoRoot: root,\n stagingDirectory: resources.temporaryDirectory,\n generatedConfigPath: resources.mcpConfigPath,\n pluginConfigPaths: resources.pluginMcpConfigPaths,\n allowlist: mcpAllowlist,\n }),\n );\n } else {\n delete process.env[SESSION_ENV_VAR];\n }\n const updated = updateHarnessState({\n domains: domainNames,\n skillDirectories: resources.skillDirectories,\n pluginHooks: resources.pluginHooks,\n mcpConfigPath: resources.mcpConfigPath,\n });\n process.env.PI_SUBAGENT_EXTRA_AGENT_DIRS = resources.agentDirectories.join(path.delimiter);\n process.env.PI_SUBAGENT_EXTRA_SKILL_DIRS = resources.skillDirectories.join(path.delimiter);\n return updated;\n}\n"],"mappings":"0XA4BA,eAAsB,EAAa,EAAuB,EAAkD,CAC1G,GAAM,CAAE,OAAM,uBAAA,EAAuBA,EAAAA,oBAAAA,CAAoB,CAAK,EAExD,EAAgB,MAAMC,EAAAA,0BAAAA,EAC1BC,EAAAA,qBAAAA,CAAqB,EAAM,EAAa,CAAC,GAAG,EAAM,iBAAiB,CAAC,CACtE,EACM,EAAeC,EAAAA,oBAAoB,EAAM,CAAW,EACpD,EAAY,MAAMC,EAAAA,iBAAiB,EAAM,EAAe,CAC5D,OAAQ,EAAM,OACd,IAAK,EAAM,IACX,qBACA,cAAA,EAAcC,EAAAA,oBAAAA,CAAoB,EAAM,CAAW,EACnD,cACF,CAAC,EAEG,EAAM,IACR,OAAO,OACL,QAAQ,IACRC,EAAAA,sBAAsB,CACpB,SAAU,EACV,iBAAkB,EAAU,mBAC5B,oBAAqB,EAAU,cAC/B,kBAAmB,EAAU,qBAC7B,UAAW,CACb,CAAC,CACH,EAEA,OAAO,QAAQ,IAAIC,EAAAA,iBAErB,IAAM,GAAA,EAAUC,EAAAA,mBAAAA,CAAmB,CACjC,QAAS,EACT,iBAAkB,EAAU,iBAC5B,YAAa,EAAU,YACvB,cAAe,EAAU,aAC3B,CAAC,EAGD,MAFA,SAAQ,IAAI,6BAA+B,EAAU,iBAAiB,KAAKC,EAAAA,QAAK,SAAS,EACzF,QAAQ,IAAI,6BAA+B,EAAU,iBAAiB,KAAKA,EAAAA,QAAK,SAAS,EAClF,CACT"}
1
+ {"version":3,"file":"applyDomains.cjs","names":["requireHarnessPaths","materializePluginEntries","resolvePluginEntries","resolveMcpAllowlist","fs","path","collectResources","resolveSharedSkills","updateHarnessState"],"sources":["../../src/adapters/applyDomains.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { resolvePluginEntries, resolveSharedSkills } from '@agimon-ai/doompi-config/domains';\nimport { requireHarnessPaths, updateHarnessState } from '@agimon-ai/doompi-config/harnessStore';\nimport type { DoomHarnessContext, HarnessState } from '@agimon-ai/doompi-config/types';\nimport { resolveMcpAllowlist } from './mcpFilter.ts';\nimport { materializePluginEntries } from './pluginMaterializer.ts';\nimport { collectResources } from './resourceCollector.ts';\n\n/**\n * Live domain switching: skills, plugin hooks, MCP config and subagent directories.\n *\n * Writes through the harness state module and relies on Pi's reload\n * re-executing extension modules. The replacement Config factory publishes the\n * persisted MCP projection on the new session's Cordis root.\n *\n * What reload cannot do is change the SET of loaded extensions: Pi freezes the\n * CLI --extension list at construction and re-resolves that same array on every\n * reload. Callers report those parts as needing a relaunch instead of pretending\n * they applied.\n *\n * The major-mode and profile switches live in\n * @agimon-ai/doompi-config/selectionSwitch: they are config writes with no\n * resource staging, and the synced startup path applies them before any command\n * exists to run. Domains stays here because it stages plugins, skills and MCP\n * config.\n */\nexport async function applyDomains(domainNames: string[], state: DoomHarnessContext): Promise<HarnessState> {\n const { root, temporaryDirectory } = requireHarnessPaths(state);\n\n const pluginEntries = await materializePluginEntries(\n resolvePluginEntries(root, domainNames, [...state.pluginDirectories]),\n );\n const mcpAllowlist = resolveMcpAllowlist(root, domainNames);\n // Keep the active generation untouched while the candidate is parsed and\n // normalized. Pi's reload later removes the whole process-owned run root, so\n // retired generations need no eager destructive cleanup here.\n const candidateDirectory = await fs.promises.mkdtemp(path.join(temporaryDirectory, 'domains-'));\n let resources: Awaited<ReturnType<typeof collectResources>>;\n try {\n resources = await collectResources(root, pluginEntries, {\n agents: state.agents,\n mcp: state.mcp,\n temporaryDirectory: candidateDirectory,\n sharedSkills: resolveSharedSkills(root, domainNames),\n mcpAllowlist,\n });\n } catch (error) {\n await fs.promises.rm(candidateDirectory, { recursive: true, force: true });\n throw error;\n }\n\n let updated: HarnessState;\n try {\n updated = updateHarnessState({\n domains: domainNames,\n skillDirectories: resources.skillDirectories,\n pluginHooks: resources.pluginHooks,\n mcpConfigPath: resources.mcpConfigPath,\n mcpProjection: resources.mcpProjection,\n });\n } catch (error) {\n await fs.promises.rm(candidateDirectory, { recursive: true, force: true });\n throw error;\n }\n process.env.PI_SUBAGENT_EXTRA_AGENT_DIRS = resources.agentDirectories.join(path.delimiter);\n process.env.PI_SUBAGENT_EXTRA_SKILL_DIRS = resources.skillDirectories.join(path.delimiter);\n return updated;\n}\n"],"mappings":"wVA2BA,eAAsB,EAAa,EAAuB,EAAkD,CAC1G,GAAM,CAAE,OAAM,uBAAA,EAAuBA,EAAAA,oBAAAA,CAAoB,CAAK,EAExD,EAAgB,MAAMC,EAAAA,0BAAAA,EAC1BC,EAAAA,qBAAAA,CAAqB,EAAM,EAAa,CAAC,GAAG,EAAM,iBAAiB,CAAC,CACtE,EACM,EAAeC,EAAAA,oBAAoB,EAAM,CAAW,EAIpD,EAAqB,MAAMC,EAAAA,QAAG,SAAS,QAAQC,EAAAA,QAAK,KAAK,EAAoB,UAAU,CAAC,EAC1F,EACJ,GAAI,CACF,EAAY,MAAMC,EAAAA,iBAAiB,EAAM,EAAe,CACtD,OAAQ,EAAM,OACd,IAAK,EAAM,IACX,mBAAoB,EACpB,cAAA,EAAcC,EAAAA,oBAAAA,CAAoB,EAAM,CAAW,EACnD,cACF,CAAC,CACH,OAAS,EAAO,CAEd,MADA,MAAMH,EAAAA,QAAG,SAAS,GAAG,EAAoB,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,EACnE,CACR,CAEA,IAAI,EACJ,GAAI,CACF,GAAA,EAAUI,EAAAA,mBAAAA,CAAmB,CAC3B,QAAS,EACT,iBAAkB,EAAU,iBAC5B,YAAa,EAAU,YACvB,cAAe,EAAU,cACzB,cAAe,EAAU,aAC3B,CAAC,CACH,OAAS,EAAO,CAEd,MADA,MAAMJ,EAAAA,QAAG,SAAS,GAAG,EAAoB,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,EACnE,CACR,CAGA,MAFA,SAAQ,IAAI,6BAA+B,EAAU,iBAAiB,KAAKC,EAAAA,QAAK,SAAS,EACzF,QAAQ,IAAI,6BAA+B,EAAU,iBAAiB,KAAKA,EAAAA,QAAK,SAAS,EAClF,CACT"}
@@ -4,8 +4,8 @@ import { DoomHarnessContext, HarnessState } from "@agimon-ai/doompi-config/types
4
4
  * Live domain switching: skills, plugin hooks, MCP config and subagent directories.
5
5
  *
6
6
  * Writes through the harness state module and relies on Pi's reload
7
- * re-executing extension modules, which drops their module-level caches and
8
- * makes them re-read the environment.
7
+ * re-executing extension modules. The replacement Config factory publishes the
8
+ * persisted MCP projection on the new session's Cordis root.
9
9
  *
10
10
  * What reload cannot do is change the SET of loaded extensions: Pi freezes the
11
11
  * CLI --extension list at construction and re-resolves that same array on every
@@ -1 +1 @@
1
- {"version":3,"file":"applyDomains.d.cts","names":[],"sources":["../../src/adapters/applyDomains.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;iBA4BsB,aAAa,uBAAuB,OAAO,qBAAqB,QAAQ"}
1
+ {"version":3,"file":"applyDomains.d.cts","names":[],"sources":["../../src/adapters/applyDomains.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;iBA2BsB,aAAa,uBAAuB,OAAO,qBAAqB,QAAQ"}
@@ -4,8 +4,8 @@ import { DoomHarnessContext, HarnessState } from "@agimon-ai/doompi-config/types
4
4
  * Live domain switching: skills, plugin hooks, MCP config and subagent directories.
5
5
  *
6
6
  * Writes through the harness state module and relies on Pi's reload
7
- * re-executing extension modules, which drops their module-level caches and
8
- * makes them re-read the environment.
7
+ * re-executing extension modules. The replacement Config factory publishes the
8
+ * persisted MCP projection on the new session's Cordis root.
9
9
  *
10
10
  * What reload cannot do is change the SET of loaded extensions: Pi freezes the
11
11
  * CLI --extension list at construction and re-resolves that same array on every
@@ -1 +1 @@
1
- {"version":3,"file":"applyDomains.d.mts","names":[],"sources":["../../src/adapters/applyDomains.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;iBA4BsB,aAAa,uBAAuB,OAAO,qBAAqB,QAAQ"}
1
+ {"version":3,"file":"applyDomains.d.mts","names":[],"sources":["../../src/adapters/applyDomains.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;iBA2BsB,aAAa,uBAAuB,OAAO,qBAAqB,QAAQ"}
@@ -1,2 +1,2 @@
1
- import{resolveMcpAllowlist as e}from"./mcpFilter.mjs";import{mcpSessionEnvironment as t}from"./mcpSessionEnvironment.mjs";import{materializePluginEntries as n}from"./pluginMaterializer.mjs";import{collectResources as r}from"./resourceCollector.mjs";import i from"node:path";import{resolvePluginEntries as a,resolveSharedSkills as o}from"@agimon-ai/doompi-config/domains";import{requireHarnessPaths as s,updateHarnessState as c}from"@agimon-ai/doompi-config/harnessStore";import{SESSION_ENV_VAR as l}from"@agimon-ai/doompi-mcp";async function u(u,d){let{root:f,temporaryDirectory:p}=s(d),m=await n(a(f,u,[...d.pluginDirectories])),h=e(f,u),g=await r(f,m,{agents:d.agents,mcp:d.mcp,temporaryDirectory:p,sharedSkills:o(f,u),mcpAllowlist:h});d.mcp?Object.assign(process.env,t({repoRoot:f,stagingDirectory:g.temporaryDirectory,generatedConfigPath:g.mcpConfigPath,pluginConfigPaths:g.pluginMcpConfigPaths,allowlist:h})):delete process.env[l];let _=c({domains:u,skillDirectories:g.skillDirectories,pluginHooks:g.pluginHooks,mcpConfigPath:g.mcpConfigPath});return process.env.PI_SUBAGENT_EXTRA_AGENT_DIRS=g.agentDirectories.join(i.delimiter),process.env.PI_SUBAGENT_EXTRA_SKILL_DIRS=g.skillDirectories.join(i.delimiter),_}export{u as applyDomains};
1
+ import{resolveMcpAllowlist as e}from"./mcpFilter.mjs";import{materializePluginEntries as t}from"./pluginMaterializer.mjs";import{collectResources as n}from"./resourceCollector.mjs";import r from"node:fs";import i from"node:path";import{resolvePluginEntries as a,resolveSharedSkills as o}from"@agimon-ai/doompi-config/domains";import{requireHarnessPaths as s,updateHarnessState as c}from"@agimon-ai/doompi-config/harnessStore";async function l(l,u){let{root:d,temporaryDirectory:f}=s(u),p=await t(a(d,l,[...u.pluginDirectories])),m=e(d,l),h=await r.promises.mkdtemp(i.join(f,`domains-`)),g;try{g=await n(d,p,{agents:u.agents,mcp:u.mcp,temporaryDirectory:h,sharedSkills:o(d,l),mcpAllowlist:m})}catch(e){throw await r.promises.rm(h,{recursive:!0,force:!0}),e}let _;try{_=c({domains:l,skillDirectories:g.skillDirectories,pluginHooks:g.pluginHooks,mcpConfigPath:g.mcpConfigPath,mcpProjection:g.mcpProjection})}catch(e){throw await r.promises.rm(h,{recursive:!0,force:!0}),e}return process.env.PI_SUBAGENT_EXTRA_AGENT_DIRS=g.agentDirectories.join(i.delimiter),process.env.PI_SUBAGENT_EXTRA_SKILL_DIRS=g.skillDirectories.join(i.delimiter),_}export{l as applyDomains};
2
2
  //# sourceMappingURL=applyDomains.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"applyDomains.mjs","names":[],"sources":["../../src/adapters/applyDomains.ts"],"sourcesContent":["import path from 'node:path';\nimport { resolvePluginEntries, resolveSharedSkills } from '@agimon-ai/doompi-config/domains';\nimport { requireHarnessPaths, updateHarnessState } from '@agimon-ai/doompi-config/harnessStore';\nimport type { DoomHarnessContext, HarnessState } from '@agimon-ai/doompi-config/types';\nimport { SESSION_ENV_VAR } from '@agimon-ai/doompi-mcp';\nimport { resolveMcpAllowlist } from './mcpFilter.ts';\nimport { mcpSessionEnvironment } from './mcpSessionEnvironment.ts';\nimport { materializePluginEntries } from './pluginMaterializer.ts';\nimport { collectResources } from './resourceCollector.ts';\n\n/**\n * Live domain switching: skills, plugin hooks, MCP config and subagent directories.\n *\n * Writes through the harness state module and relies on Pi's reload\n * re-executing extension modules, which drops their module-level caches and\n * makes them re-read the environment.\n *\n * What reload cannot do is change the SET of loaded extensions: Pi freezes the\n * CLI --extension list at construction and re-resolves that same array on every\n * reload. Callers report those parts as needing a relaunch instead of pretending\n * they applied.\n *\n * The major-mode and profile switches live in\n * @agimon-ai/doompi-config/selectionSwitch: they are config writes with no\n * resource staging, and the synced startup path applies them before any command\n * exists to run. Domains stays here because it stages plugins, skills and MCP\n * config.\n */\nexport async function applyDomains(domainNames: string[], state: DoomHarnessContext): Promise<HarnessState> {\n const { root, temporaryDirectory } = requireHarnessPaths(state);\n\n const pluginEntries = await materializePluginEntries(\n resolvePluginEntries(root, domainNames, [...state.pluginDirectories]),\n );\n const mcpAllowlist = resolveMcpAllowlist(root, domainNames);\n const resources = await collectResources(root, pluginEntries, {\n agents: state.agents,\n mcp: state.mcp,\n temporaryDirectory,\n sharedSkills: resolveSharedSkills(root, domainNames),\n mcpAllowlist,\n });\n\n if (state.mcp) {\n Object.assign(\n process.env,\n mcpSessionEnvironment({\n repoRoot: root,\n stagingDirectory: resources.temporaryDirectory,\n generatedConfigPath: resources.mcpConfigPath,\n pluginConfigPaths: resources.pluginMcpConfigPaths,\n allowlist: mcpAllowlist,\n }),\n );\n } else {\n delete process.env[SESSION_ENV_VAR];\n }\n const updated = updateHarnessState({\n domains: domainNames,\n skillDirectories: resources.skillDirectories,\n pluginHooks: resources.pluginHooks,\n mcpConfigPath: resources.mcpConfigPath,\n });\n process.env.PI_SUBAGENT_EXTRA_AGENT_DIRS = resources.agentDirectories.join(path.delimiter);\n process.env.PI_SUBAGENT_EXTRA_SKILL_DIRS = resources.skillDirectories.join(path.delimiter);\n return updated;\n}\n"],"mappings":"+gBA4BA,eAAsB,EAAa,EAAuB,EAAkD,CAC1G,GAAM,CAAE,OAAM,sBAAuB,EAAoB,CAAK,EAExD,EAAgB,MAAM,EAC1B,EAAqB,EAAM,EAAa,CAAC,GAAG,EAAM,iBAAiB,CAAC,CACtE,EACM,EAAe,EAAoB,EAAM,CAAW,EACpD,EAAY,MAAM,EAAiB,EAAM,EAAe,CAC5D,OAAQ,EAAM,OACd,IAAK,EAAM,IACX,qBACA,aAAc,EAAoB,EAAM,CAAW,EACnD,cACF,CAAC,EAEG,EAAM,IACR,OAAO,OACL,QAAQ,IACR,EAAsB,CACpB,SAAU,EACV,iBAAkB,EAAU,mBAC5B,oBAAqB,EAAU,cAC/B,kBAAmB,EAAU,qBAC7B,UAAW,CACb,CAAC,CACH,EAEA,OAAO,QAAQ,IAAI,GAErB,IAAM,EAAU,EAAmB,CACjC,QAAS,EACT,iBAAkB,EAAU,iBAC5B,YAAa,EAAU,YACvB,cAAe,EAAU,aAC3B,CAAC,EAGD,MAFA,SAAQ,IAAI,6BAA+B,EAAU,iBAAiB,KAAK,EAAK,SAAS,EACzF,QAAQ,IAAI,6BAA+B,EAAU,iBAAiB,KAAK,EAAK,SAAS,EAClF,CACT"}
1
+ {"version":3,"file":"applyDomains.mjs","names":[],"sources":["../../src/adapters/applyDomains.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { resolvePluginEntries, resolveSharedSkills } from '@agimon-ai/doompi-config/domains';\nimport { requireHarnessPaths, updateHarnessState } from '@agimon-ai/doompi-config/harnessStore';\nimport type { DoomHarnessContext, HarnessState } from '@agimon-ai/doompi-config/types';\nimport { resolveMcpAllowlist } from './mcpFilter.ts';\nimport { materializePluginEntries } from './pluginMaterializer.ts';\nimport { collectResources } from './resourceCollector.ts';\n\n/**\n * Live domain switching: skills, plugin hooks, MCP config and subagent directories.\n *\n * Writes through the harness state module and relies on Pi's reload\n * re-executing extension modules. The replacement Config factory publishes the\n * persisted MCP projection on the new session's Cordis root.\n *\n * What reload cannot do is change the SET of loaded extensions: Pi freezes the\n * CLI --extension list at construction and re-resolves that same array on every\n * reload. Callers report those parts as needing a relaunch instead of pretending\n * they applied.\n *\n * The major-mode and profile switches live in\n * @agimon-ai/doompi-config/selectionSwitch: they are config writes with no\n * resource staging, and the synced startup path applies them before any command\n * exists to run. Domains stays here because it stages plugins, skills and MCP\n * config.\n */\nexport async function applyDomains(domainNames: string[], state: DoomHarnessContext): Promise<HarnessState> {\n const { root, temporaryDirectory } = requireHarnessPaths(state);\n\n const pluginEntries = await materializePluginEntries(\n resolvePluginEntries(root, domainNames, [...state.pluginDirectories]),\n );\n const mcpAllowlist = resolveMcpAllowlist(root, domainNames);\n // Keep the active generation untouched while the candidate is parsed and\n // normalized. Pi's reload later removes the whole process-owned run root, so\n // retired generations need no eager destructive cleanup here.\n const candidateDirectory = await fs.promises.mkdtemp(path.join(temporaryDirectory, 'domains-'));\n let resources: Awaited<ReturnType<typeof collectResources>>;\n try {\n resources = await collectResources(root, pluginEntries, {\n agents: state.agents,\n mcp: state.mcp,\n temporaryDirectory: candidateDirectory,\n sharedSkills: resolveSharedSkills(root, domainNames),\n mcpAllowlist,\n });\n } catch (error) {\n await fs.promises.rm(candidateDirectory, { recursive: true, force: true });\n throw error;\n }\n\n let updated: HarnessState;\n try {\n updated = updateHarnessState({\n domains: domainNames,\n skillDirectories: resources.skillDirectories,\n pluginHooks: resources.pluginHooks,\n mcpConfigPath: resources.mcpConfigPath,\n mcpProjection: resources.mcpProjection,\n });\n } catch (error) {\n await fs.promises.rm(candidateDirectory, { recursive: true, force: true });\n throw error;\n }\n process.env.PI_SUBAGENT_EXTRA_AGENT_DIRS = resources.agentDirectories.join(path.delimiter);\n process.env.PI_SUBAGENT_EXTRA_SKILL_DIRS = resources.skillDirectories.join(path.delimiter);\n return updated;\n}\n"],"mappings":"0aA2BA,eAAsB,EAAa,EAAuB,EAAkD,CAC1G,GAAM,CAAE,OAAM,sBAAuB,EAAoB,CAAK,EAExD,EAAgB,MAAM,EAC1B,EAAqB,EAAM,EAAa,CAAC,GAAG,EAAM,iBAAiB,CAAC,CACtE,EACM,EAAe,EAAoB,EAAM,CAAW,EAIpD,EAAqB,MAAM,EAAG,SAAS,QAAQ,EAAK,KAAK,EAAoB,UAAU,CAAC,EAC1F,EACJ,GAAI,CACF,EAAY,MAAM,EAAiB,EAAM,EAAe,CACtD,OAAQ,EAAM,OACd,IAAK,EAAM,IACX,mBAAoB,EACpB,aAAc,EAAoB,EAAM,CAAW,EACnD,cACF,CAAC,CACH,OAAS,EAAO,CAEd,MADA,MAAM,EAAG,SAAS,GAAG,EAAoB,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,EACnE,CACR,CAEA,IAAI,EACJ,GAAI,CACF,EAAU,EAAmB,CAC3B,QAAS,EACT,iBAAkB,EAAU,iBAC5B,YAAa,EAAU,YACvB,cAAe,EAAU,cACzB,cAAe,EAAU,aAC3B,CAAC,CACH,OAAS,EAAO,CAEd,MADA,MAAM,EAAG,SAAS,GAAG,EAAoB,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,EACnE,CACR,CAGA,MAFA,SAAQ,IAAI,6BAA+B,EAAU,iBAAiB,KAAK,EAAK,SAAS,EACzF,QAAQ,IAAI,6BAA+B,EAAU,iBAAiB,KAAK,EAAK,SAAS,EAClF,CACT"}
@@ -1,2 +1,2 @@
1
- const e=require("../services/domainText.cjs");let t=require("@agimon-ai/doompi-config/harnessStore"),n=require("@agimon-ai/doompi-config/piContext");function r(){let r,i=()=>r??=import(`@agimon-ai/doompi-config/domains`);return{list:async e=>{let r=(0,n.requireDoomConfigContext)(e).harness,a=(0,t.requireHarnessRoot)(r),{defaultDomainsForMajorMode:o,listDomainNames:s,loadDomains:c}=await i(),l=[...new Set(s(a))].sort(),u=c(a),d=(0,n.requireDoomConfigContext)(e).pendingSelection?void 0:(0,n.readDoomConfigSelection)(e),f=d?[...d.domains]:[...r.domains],p=o(r.majorMode,process.env,u.defaultDomains);return{active:f,effective:d?[...d.domains]:r.domains.length>0?[...r.domains]:[...p],available:l}},validate:async(r,a)=>{let o=e.normalizeDomainNames(a),s=(0,n.requireDoomConfigContext)(r).harness,c=(0,t.requireHarnessRoot)(s),{listDomainNames:l,resolvePluginEntries:u}=await i(),d=new Set(l(c)),f=o.filter(e=>!d.has(e));if(f.length>0)throw Error(`Unknown domain: ${f.join(`, `)}`);return u(c,o,[...s.pluginDirectories]),o},describe:async e=>{let t=(0,n.requireDoomConfigContext)(e).harness.root;if(!t)return{};let{loadDomains:r}=await i();return Object.fromEntries(Object.entries(r(t).domains).map(([e,t])=>[e,t.description]))},completions:async(e,t)=>{let{domainCompletionItems:n,domainCompletionPrefix:r,listDomainNames:a}=await i(),o=r(t);if(o===void 0)return;let s=n(a(e),o);return s.length===0?void 0:{prefix:o,items:s}}}}exports.createDomainCatalog=r;
1
+ const e=require("../services/domainText.cjs");let t=require("@agimon-ai/doompi-config/harnessStore"),n=require("@agimon-ai/doompi-config/piContext");function r(r){let i,a=()=>i??=import(`@agimon-ai/doompi-config/domains`);return{list:async e=>{let i=(0,n.requireDoomConfigContext)(r()),o=i.harness,s=(0,t.requireHarnessRoot)(o),{defaultDomainsForMajorMode:c,listDomainNames:l,loadDomains:u}=await a(),d=[...new Set(l(s))].sort(),f=u(s),p=i.pendingSelection?void 0:(0,n.readDoomConfigSelection)(e),m=p?[...p.domains]:[...o.domains],h=c(o.majorMode,process.env,f.defaultDomains);return{active:m,effective:p?[...p.domains]:o.domains.length>0?[...o.domains]:[...h],available:d}},validate:async(i,o)=>{let s=e.normalizeDomainNames(o),c=(0,n.requireDoomConfigContext)(r()).harness,l=(0,t.requireHarnessRoot)(c),{listDomainNames:u,resolvePluginEntries:d}=await a(),f=new Set(u(l)),p=s.filter(e=>!f.has(e));if(p.length>0)throw Error(`Unknown domain: ${p.join(`, `)}`);return d(l,s,[...c.pluginDirectories]),s},describe:async e=>{let t=(0,n.requireDoomConfigContext)(r()).harness.root;if(!t)return{};let{loadDomains:i}=await a();return Object.fromEntries(Object.entries(i(t).domains).map(([e,t])=>[e,t.description]))},completions:async(e,t)=>{let{domainCompletionItems:n,domainCompletionPrefix:r,listDomainNames:i}=await a(),o=r(t);if(o===void 0)return;let s=n(i(e),o);return s.length===0?void 0:{prefix:o,items:s}}}}exports.createDomainCatalog=r;
2
2
  //# sourceMappingURL=domainCatalog.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"domainCatalog.cjs","names":["requireDoomConfigContext","requireHarnessRoot","readDoomConfigSelection","normalizeDomainNames"],"sources":["../../src/adapters/domainCatalog.ts"],"sourcesContent":["import { requireHarnessRoot } from '@agimon-ai/doompi-config/harnessStore';\nimport { readDoomConfigSelection, requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport type { ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { normalizeDomainNames } from '../services/domainText.ts';\nimport type { DomainCompletion, DomainListing } from '../types/domains.ts';\n\n/**\n * The manifest reader every surface of this package shares.\n *\n * Every session registers `/domains`, but most never run it, so the plugin\n * catalogue and the domain manifest stay behind a dynamic import: only a session\n * that actually lists or switches domains pays to read them.\n */\nexport function createDomainCatalog() {\n let config: Promise<typeof import('@agimon-ai/doompi-config/domains')> | undefined;\n const load = () => (config ??= import('@agimon-ai/doompi-config/domains'));\n\n const list = async (ctx: ExtensionContext): Promise<DomainListing> => {\n const state = requireDoomConfigContext(ctx).harness;\n const repoRoot = requireHarnessRoot(state);\n const { defaultDomainsForMajorMode, listDomainNames, loadDomains } = await load();\n const available = [...new Set(listDomainNames(repoRoot))].sort();\n const manifest = loadDomains(repoRoot);\n // A journalled selection outranks the environment, but only once the switch\n // that produced it has settled: a pending one has not been applied yet.\n const selection = requireDoomConfigContext(ctx).pendingSelection ? undefined : readDoomConfigSelection(ctx);\n const active = selection ? [...selection.domains] : [...state.domains];\n const fallback = defaultDomainsForMajorMode(state.majorMode, process.env, manifest.defaultDomains);\n const effective = selection\n ? [...selection.domains]\n : state.domains.length > 0\n ? [...state.domains]\n : [...fallback];\n return { active, effective, available };\n };\n\n const validate = async (ctx: ExtensionContext, values: readonly string[]): Promise<string[]> => {\n const selected = normalizeDomainNames(values);\n const state = requireDoomConfigContext(ctx).harness;\n const repoRoot = requireHarnessRoot(state);\n const { listDomainNames, resolvePluginEntries } = await load();\n const available = new Set(listDomainNames(repoRoot));\n const unknown = selected.filter((name) => !available.has(name));\n if (unknown.length > 0) throw new Error(`Unknown domain: ${unknown.join(', ')}`);\n resolvePluginEntries(repoRoot, selected, [...state.pluginDirectories]);\n return selected;\n };\n\n const describe = async (ctx: ExtensionContext): Promise<Record<string, string | undefined>> => {\n const root = requireDoomConfigContext(ctx).harness.root;\n if (!root) return {};\n const { loadDomains } = await load();\n return Object.fromEntries(\n Object.entries(loadDomains(root).domains).map(([name, domain]) => [name, domain.description]),\n );\n };\n\n const completions = async (root: string, textBeforeCursor: string): Promise<DomainCompletion | undefined> => {\n const { domainCompletionItems, domainCompletionPrefix, listDomainNames } = await load();\n const prefix = domainCompletionPrefix(textBeforeCursor);\n if (prefix === undefined) return undefined;\n // Re-read on every keystroke: a domain added while the session is running\n // has to be completable without a reload.\n const items = domainCompletionItems(listDomainNames(root), prefix);\n return items.length === 0 ? undefined : { prefix, items };\n };\n\n return { list, validate, describe, completions };\n}\n\nexport type DomainCatalog = ReturnType<typeof createDomainCatalog>;\n"],"mappings":"qJAaA,SAAgB,GAAsB,CACpC,IAAI,EACE,MAAc,IAAW,OAAO,oCAoDtC,MAAO,CAAE,UAlDW,IAAkD,CACpE,IAAM,GAAA,EAAQA,EAAAA,yBAAAA,CAAyB,CAAG,CAAC,CAAC,QACtC,GAAA,EAAWC,EAAAA,mBAAAA,CAAmB,CAAK,EACnC,CAAE,6BAA4B,kBAAiB,eAAgB,MAAM,EAAK,EAC1E,EAAY,CAAC,GAAG,IAAI,IAAI,EAAgB,CAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,EACzD,EAAW,EAAY,CAAQ,EAG/B,GAAA,EAAYD,EAAAA,yBAAAA,CAAyB,CAAG,CAAC,CAAC,iBAAmB,IAAA,IAAA,EAAYE,EAAAA,wBAAAA,CAAwB,CAAG,EACpG,EAAS,EAAY,CAAC,GAAG,EAAU,OAAO,EAAI,CAAC,GAAG,EAAM,OAAO,EAC/D,EAAW,EAA2B,EAAM,UAAW,QAAQ,IAAK,EAAS,cAAc,EAMjG,MAAO,CAAE,SAAQ,UALC,EACd,CAAC,GAAG,EAAU,OAAO,EACrB,EAAM,QAAQ,OAAS,EACrB,CAAC,GAAG,EAAM,OAAO,EACjB,CAAC,GAAG,CAAQ,EACU,WAAU,CACxC,EAiCe,eA/BS,EAAuB,IAAiD,CAC9F,IAAM,EAAWC,EAAAA,qBAAqB,CAAM,EACtC,GAAA,EAAQH,EAAAA,yBAAAA,CAAyB,CAAG,CAAC,CAAC,QACtC,GAAA,EAAWC,EAAAA,mBAAAA,CAAmB,CAAK,EACnC,CAAE,kBAAiB,wBAAyB,MAAM,EAAK,EACvD,EAAY,IAAI,IAAI,EAAgB,CAAQ,CAAC,EAC7C,EAAU,EAAS,OAAQ,GAAS,CAAC,EAAU,IAAI,CAAI,CAAC,EAC9D,GAAI,EAAQ,OAAS,EAAG,MAAU,MAAM,mBAAmB,EAAQ,KAAK,IAAI,GAAG,EAE/E,OADA,EAAqB,EAAU,EAAU,CAAC,GAAG,EAAM,iBAAiB,CAAC,EAC9D,CACT,EAqByB,cAnBD,IAAuE,CAC7F,IAAM,GAAA,EAAOD,EAAAA,yBAAAA,CAAyB,CAAG,CAAC,CAAC,QAAQ,KACnD,GAAI,CAAC,EAAM,MAAO,CAAC,EACnB,GAAM,CAAE,eAAgB,MAAM,EAAK,EACnC,OAAO,OAAO,YACZ,OAAO,QAAQ,EAAY,CAAI,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,EAAM,KAAY,CAAC,EAAM,EAAO,WAAW,CAAC,CAC9F,CACF,EAYmC,kBAVR,EAAc,IAAoE,CAC3G,GAAM,CAAE,wBAAuB,yBAAwB,mBAAoB,MAAM,EAAK,EAChF,EAAS,EAAuB,CAAgB,EACtD,GAAI,IAAW,IAAA,GAAW,OAG1B,IAAM,EAAQ,EAAsB,EAAgB,CAAI,EAAG,CAAM,EACjE,OAAO,EAAM,SAAW,EAAI,IAAA,GAAY,CAAE,SAAQ,OAAM,CAC1D,CAE+C,CACjD"}
1
+ {"version":3,"file":"domainCatalog.cjs","names":["requireDoomConfigContext","requireHarnessRoot","readDoomConfigSelection","normalizeDomainNames"],"sources":["../../src/adapters/domainCatalog.ts"],"sourcesContent":["import { requireHarnessRoot } from '@agimon-ai/doompi-config/harnessStore';\nimport { readDoomConfigSelection, requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { normalizeDomainNames } from '../services/domainText.ts';\nimport type { DomainCompletion, DomainListing } from '../types/domains.ts';\n\n/**\n * The manifest reader every surface of this package shares.\n *\n * Every session registers `/domains`, but most never run it, so the plugin\n * catalogue and the domain manifest stay behind a dynamic import: only a session\n * that actually lists or switches domains pays to read them.\n */\nexport function createDomainCatalog(cordisContext: () => Context) {\n let config: Promise<typeof import('@agimon-ai/doompi-config/domains')> | undefined;\n const load = () => (config ??= import('@agimon-ai/doompi-config/domains'));\n\n const list = async (ctx: ExtensionContext): Promise<DomainListing> => {\n const configContext = requireDoomConfigContext(cordisContext());\n const state = configContext.harness;\n const repoRoot = requireHarnessRoot(state);\n const { defaultDomainsForMajorMode, listDomainNames, loadDomains } = await load();\n const available = [...new Set(listDomainNames(repoRoot))].sort();\n const manifest = loadDomains(repoRoot);\n // A journalled selection outranks the environment, but only once the switch\n // that produced it has settled: a pending one has not been applied yet.\n const selection = configContext.pendingSelection ? undefined : readDoomConfigSelection(ctx);\n const active = selection ? [...selection.domains] : [...state.domains];\n const fallback = defaultDomainsForMajorMode(state.majorMode, process.env, manifest.defaultDomains);\n const effective = selection\n ? [...selection.domains]\n : state.domains.length > 0\n ? [...state.domains]\n : [...fallback];\n return { active, effective, available };\n };\n\n const validate = async (_ctx: ExtensionContext, values: readonly string[]): Promise<string[]> => {\n const selected = normalizeDomainNames(values);\n const state = requireDoomConfigContext(cordisContext()).harness;\n const repoRoot = requireHarnessRoot(state);\n const { listDomainNames, resolvePluginEntries } = await load();\n const available = new Set(listDomainNames(repoRoot));\n const unknown = selected.filter((name) => !available.has(name));\n if (unknown.length > 0) throw new Error(`Unknown domain: ${unknown.join(', ')}`);\n resolvePluginEntries(repoRoot, selected, [...state.pluginDirectories]);\n return selected;\n };\n\n const describe = async (_ctx: ExtensionContext): Promise<Record<string, string | undefined>> => {\n const root = requireDoomConfigContext(cordisContext()).harness.root;\n if (!root) return {};\n const { loadDomains } = await load();\n return Object.fromEntries(\n Object.entries(loadDomains(root).domains).map(([name, domain]) => [name, domain.description]),\n );\n };\n\n const completions = async (root: string, textBeforeCursor: string): Promise<DomainCompletion | undefined> => {\n const { domainCompletionItems, domainCompletionPrefix, listDomainNames } = await load();\n const prefix = domainCompletionPrefix(textBeforeCursor);\n if (prefix === undefined) return undefined;\n // Re-read on every keystroke: a domain added while the session is running\n // has to be completable without a reload.\n const items = domainCompletionItems(listDomainNames(root), prefix);\n return items.length === 0 ? undefined : { prefix, items };\n };\n\n return { list, validate, describe, completions };\n}\n\nexport type DomainCatalog = ReturnType<typeof createDomainCatalog>;\n"],"mappings":"qJAcA,SAAgB,EAAoB,EAA8B,CAChE,IAAI,EACE,MAAc,IAAW,OAAO,oCAqDtC,MAAO,CAAE,UAnDW,IAAkD,CACpE,IAAM,GAAA,EAAgBA,EAAAA,yBAAAA,CAAyB,EAAc,CAAC,EACxD,EAAQ,EAAc,QACtB,GAAA,EAAWC,EAAAA,mBAAAA,CAAmB,CAAK,EACnC,CAAE,6BAA4B,kBAAiB,eAAgB,MAAM,EAAK,EAC1E,EAAY,CAAC,GAAG,IAAI,IAAI,EAAgB,CAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,EACzD,EAAW,EAAY,CAAQ,EAG/B,EAAY,EAAc,iBAAmB,IAAA,IAAA,EAAYC,EAAAA,wBAAAA,CAAwB,CAAG,EACpF,EAAS,EAAY,CAAC,GAAG,EAAU,OAAO,EAAI,CAAC,GAAG,EAAM,OAAO,EAC/D,EAAW,EAA2B,EAAM,UAAW,QAAQ,IAAK,EAAS,cAAc,EAMjG,MAAO,CAAE,SAAQ,UALC,EACd,CAAC,GAAG,EAAU,OAAO,EACrB,EAAM,QAAQ,OAAS,EACrB,CAAC,GAAG,EAAM,OAAO,EACjB,CAAC,GAAG,CAAQ,EACU,WAAU,CACxC,EAiCe,eA/BS,EAAwB,IAAiD,CAC/F,IAAM,EAAWC,EAAAA,qBAAqB,CAAM,EACtC,GAAA,EAAQH,EAAAA,yBAAAA,CAAyB,EAAc,CAAC,CAAC,CAAC,QAClD,GAAA,EAAWC,EAAAA,mBAAAA,CAAmB,CAAK,EACnC,CAAE,kBAAiB,wBAAyB,MAAM,EAAK,EACvD,EAAY,IAAI,IAAI,EAAgB,CAAQ,CAAC,EAC7C,EAAU,EAAS,OAAQ,GAAS,CAAC,EAAU,IAAI,CAAI,CAAC,EAC9D,GAAI,EAAQ,OAAS,EAAG,MAAU,MAAM,mBAAmB,EAAQ,KAAK,IAAI,GAAG,EAE/E,OADA,EAAqB,EAAU,EAAU,CAAC,GAAG,EAAM,iBAAiB,CAAC,EAC9D,CACT,EAqByB,cAnBD,IAAwE,CAC9F,IAAM,GAAA,EAAOD,EAAAA,yBAAAA,CAAyB,EAAc,CAAC,CAAC,CAAC,QAAQ,KAC/D,GAAI,CAAC,EAAM,MAAO,CAAC,EACnB,GAAM,CAAE,eAAgB,MAAM,EAAK,EACnC,OAAO,OAAO,YACZ,OAAO,QAAQ,EAAY,CAAI,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,EAAM,KAAY,CAAC,EAAM,EAAO,WAAW,CAAC,CAC9F,CACF,EAYmC,kBAVR,EAAc,IAAoE,CAC3G,GAAM,CAAE,wBAAuB,yBAAwB,mBAAoB,MAAM,EAAK,EAChF,EAAS,EAAuB,CAAgB,EACtD,GAAI,IAAW,IAAA,GAAW,OAG1B,IAAM,EAAQ,EAAsB,EAAgB,CAAI,EAAG,CAAM,EACjE,OAAO,EAAM,SAAW,EAAI,IAAA,GAAY,CAAE,SAAQ,OAAM,CAC1D,CAE+C,CACjD"}
@@ -1,5 +1,6 @@
1
1
  import { DomainCompletion, DomainListing } from "../types/domains.cjs";
2
2
  import { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
+ import { Context } from "@deepseek-ai/cordis";
3
4
  //#region src/adapters/domainCatalog.d.ts
4
5
  /**
5
6
  * The manifest reader every surface of this package shares.
@@ -8,10 +9,10 @@ import { ExtensionContext } from "@earendil-works/pi-coding-agent";
8
9
  * catalogue and the domain manifest stay behind a dynamic import: only a session
9
10
  * that actually lists or switches domains pays to read them.
10
11
  */
11
- declare function createDomainCatalog(): {
12
+ declare function createDomainCatalog(cordisContext: () => Context): {
12
13
  list: (ctx: ExtensionContext) => Promise<DomainListing>;
13
- validate: (ctx: ExtensionContext, values: readonly string[]) => Promise<string[]>;
14
- describe: (ctx: ExtensionContext) => Promise<Record<string, string | undefined>>;
14
+ validate: (_ctx: ExtensionContext, values: readonly string[]) => Promise<string[]>;
15
+ describe: (_ctx: ExtensionContext) => Promise<Record<string, string | undefined>>;
15
16
  completions: (root: string, textBeforeCursor: string) => Promise<DomainCompletion | undefined>;
16
17
  };
17
18
  type DomainCatalog = ReturnType<typeof createDomainCatalog>;
@@ -1 +1 @@
1
- {"version":3,"file":"domainCatalog.d.cts","names":[],"sources":["../../src/adapters/domainCatalog.ts"],"mappings":";;;;;;;;;;iBAagB;EAIW,OAAA,KAAA,qBAAmB,QAAQ;EAmBvB,WAAA,KAAA,kBAAgB,8BAA8B;EAY9C,WAAA,KAAA,qBAAmB,QAAQ;EASvB,cAAA,cAAM,6BAA6B,QAAQ;;KAalE,gBAAgB,kBAAkB"}
1
+ {"version":3,"file":"domainCatalog.d.cts","names":[],"sources":["../../src/adapters/domainCatalog.ts"],"mappings":";;;;;;;;;;;iBAcgB,oBAAoB,qBAAqB;EAI9B,OAAA,KAAA,qBAAmB,QAAQ;EAoBtB,WAAA,MAAA,kBAAgB,8BAA8B;EAY9C,WAAA,MAAA,qBAAmB,QAAQ;EASxB,cAAA,cAAM,6BAA6B,QAAQ;;KAalE,gBAAgB,kBAAkB"}
@@ -1,5 +1,6 @@
1
1
  import { DomainCompletion, DomainListing } from "../types/domains.mjs";
2
2
  import { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
+ import { Context } from "@deepseek-ai/cordis";
3
4
  //#region src/adapters/domainCatalog.d.ts
4
5
  /**
5
6
  * The manifest reader every surface of this package shares.
@@ -8,10 +9,10 @@ import { ExtensionContext } from "@earendil-works/pi-coding-agent";
8
9
  * catalogue and the domain manifest stay behind a dynamic import: only a session
9
10
  * that actually lists or switches domains pays to read them.
10
11
  */
11
- declare function createDomainCatalog(): {
12
+ declare function createDomainCatalog(cordisContext: () => Context): {
12
13
  list: (ctx: ExtensionContext) => Promise<DomainListing>;
13
- validate: (ctx: ExtensionContext, values: readonly string[]) => Promise<string[]>;
14
- describe: (ctx: ExtensionContext) => Promise<Record<string, string | undefined>>;
14
+ validate: (_ctx: ExtensionContext, values: readonly string[]) => Promise<string[]>;
15
+ describe: (_ctx: ExtensionContext) => Promise<Record<string, string | undefined>>;
15
16
  completions: (root: string, textBeforeCursor: string) => Promise<DomainCompletion | undefined>;
16
17
  };
17
18
  type DomainCatalog = ReturnType<typeof createDomainCatalog>;
@@ -1 +1 @@
1
- {"version":3,"file":"domainCatalog.d.mts","names":[],"sources":["../../src/adapters/domainCatalog.ts"],"mappings":";;;;;;;;;;iBAagB;EAIW,OAAA,KAAA,qBAAmB,QAAQ;EAmBvB,WAAA,KAAA,kBAAgB,8BAA8B;EAY9C,WAAA,KAAA,qBAAmB,QAAQ;EASvB,cAAA,cAAM,6BAA6B,QAAQ;;KAalE,gBAAgB,kBAAkB"}
1
+ {"version":3,"file":"domainCatalog.d.mts","names":[],"sources":["../../src/adapters/domainCatalog.ts"],"mappings":";;;;;;;;;;;iBAcgB,oBAAoB,qBAAqB;EAI9B,OAAA,KAAA,qBAAmB,QAAQ;EAoBtB,WAAA,MAAA,kBAAgB,8BAA8B;EAY9C,WAAA,MAAA,qBAAmB,QAAQ;EASxB,cAAA,cAAM,6BAA6B,QAAQ;;KAalE,gBAAgB,kBAAkB"}
@@ -1,2 +1,2 @@
1
- import{normalizeDomainNames as e}from"../services/domainText.mjs";import{requireHarnessRoot as t}from"@agimon-ai/doompi-config/harnessStore";import{readDoomConfigSelection as n,requireDoomConfigContext as r}from"@agimon-ai/doompi-config/piContext";function i(){let i,a=()=>i??=import(`@agimon-ai/doompi-config/domains`);return{list:async e=>{let i=r(e).harness,o=t(i),{defaultDomainsForMajorMode:s,listDomainNames:c,loadDomains:l}=await a(),u=[...new Set(c(o))].sort(),d=l(o),f=r(e).pendingSelection?void 0:n(e),p=f?[...f.domains]:[...i.domains],m=s(i.majorMode,process.env,d.defaultDomains);return{active:p,effective:f?[...f.domains]:i.domains.length>0?[...i.domains]:[...m],available:u}},validate:async(n,i)=>{let o=e(i),s=r(n).harness,c=t(s),{listDomainNames:l,resolvePluginEntries:u}=await a(),d=new Set(l(c)),f=o.filter(e=>!d.has(e));if(f.length>0)throw Error(`Unknown domain: ${f.join(`, `)}`);return u(c,o,[...s.pluginDirectories]),o},describe:async e=>{let t=r(e).harness.root;if(!t)return{};let{loadDomains:n}=await a();return Object.fromEntries(Object.entries(n(t).domains).map(([e,t])=>[e,t.description]))},completions:async(e,t)=>{let{domainCompletionItems:n,domainCompletionPrefix:r,listDomainNames:i}=await a(),o=r(t);if(o===void 0)return;let s=n(i(e),o);return s.length===0?void 0:{prefix:o,items:s}}}}export{i as createDomainCatalog};
1
+ import{normalizeDomainNames as e}from"../services/domainText.mjs";import{requireHarnessRoot as t}from"@agimon-ai/doompi-config/harnessStore";import{readDoomConfigSelection as n,requireDoomConfigContext as r}from"@agimon-ai/doompi-config/piContext";function i(i){let a,o=()=>a??=import(`@agimon-ai/doompi-config/domains`);return{list:async e=>{let a=r(i()),s=a.harness,c=t(s),{defaultDomainsForMajorMode:l,listDomainNames:u,loadDomains:d}=await o(),f=[...new Set(u(c))].sort(),p=d(c),m=a.pendingSelection?void 0:n(e),h=m?[...m.domains]:[...s.domains],g=l(s.majorMode,process.env,p.defaultDomains);return{active:h,effective:m?[...m.domains]:s.domains.length>0?[...s.domains]:[...g],available:f}},validate:async(n,a)=>{let s=e(a),c=r(i()).harness,l=t(c),{listDomainNames:u,resolvePluginEntries:d}=await o(),f=new Set(u(l)),p=s.filter(e=>!f.has(e));if(p.length>0)throw Error(`Unknown domain: ${p.join(`, `)}`);return d(l,s,[...c.pluginDirectories]),s},describe:async e=>{let t=r(i()).harness.root;if(!t)return{};let{loadDomains:n}=await o();return Object.fromEntries(Object.entries(n(t).domains).map(([e,t])=>[e,t.description]))},completions:async(e,t)=>{let{domainCompletionItems:n,domainCompletionPrefix:r,listDomainNames:i}=await o(),a=r(t);if(a===void 0)return;let s=n(i(e),a);return s.length===0?void 0:{prefix:a,items:s}}}}export{i as createDomainCatalog};
2
2
  //# sourceMappingURL=domainCatalog.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"domainCatalog.mjs","names":[],"sources":["../../src/adapters/domainCatalog.ts"],"sourcesContent":["import { requireHarnessRoot } from '@agimon-ai/doompi-config/harnessStore';\nimport { readDoomConfigSelection, requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport type { ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { normalizeDomainNames } from '../services/domainText.ts';\nimport type { DomainCompletion, DomainListing } from '../types/domains.ts';\n\n/**\n * The manifest reader every surface of this package shares.\n *\n * Every session registers `/domains`, but most never run it, so the plugin\n * catalogue and the domain manifest stay behind a dynamic import: only a session\n * that actually lists or switches domains pays to read them.\n */\nexport function createDomainCatalog() {\n let config: Promise<typeof import('@agimon-ai/doompi-config/domains')> | undefined;\n const load = () => (config ??= import('@agimon-ai/doompi-config/domains'));\n\n const list = async (ctx: ExtensionContext): Promise<DomainListing> => {\n const state = requireDoomConfigContext(ctx).harness;\n const repoRoot = requireHarnessRoot(state);\n const { defaultDomainsForMajorMode, listDomainNames, loadDomains } = await load();\n const available = [...new Set(listDomainNames(repoRoot))].sort();\n const manifest = loadDomains(repoRoot);\n // A journalled selection outranks the environment, but only once the switch\n // that produced it has settled: a pending one has not been applied yet.\n const selection = requireDoomConfigContext(ctx).pendingSelection ? undefined : readDoomConfigSelection(ctx);\n const active = selection ? [...selection.domains] : [...state.domains];\n const fallback = defaultDomainsForMajorMode(state.majorMode, process.env, manifest.defaultDomains);\n const effective = selection\n ? [...selection.domains]\n : state.domains.length > 0\n ? [...state.domains]\n : [...fallback];\n return { active, effective, available };\n };\n\n const validate = async (ctx: ExtensionContext, values: readonly string[]): Promise<string[]> => {\n const selected = normalizeDomainNames(values);\n const state = requireDoomConfigContext(ctx).harness;\n const repoRoot = requireHarnessRoot(state);\n const { listDomainNames, resolvePluginEntries } = await load();\n const available = new Set(listDomainNames(repoRoot));\n const unknown = selected.filter((name) => !available.has(name));\n if (unknown.length > 0) throw new Error(`Unknown domain: ${unknown.join(', ')}`);\n resolvePluginEntries(repoRoot, selected, [...state.pluginDirectories]);\n return selected;\n };\n\n const describe = async (ctx: ExtensionContext): Promise<Record<string, string | undefined>> => {\n const root = requireDoomConfigContext(ctx).harness.root;\n if (!root) return {};\n const { loadDomains } = await load();\n return Object.fromEntries(\n Object.entries(loadDomains(root).domains).map(([name, domain]) => [name, domain.description]),\n );\n };\n\n const completions = async (root: string, textBeforeCursor: string): Promise<DomainCompletion | undefined> => {\n const { domainCompletionItems, domainCompletionPrefix, listDomainNames } = await load();\n const prefix = domainCompletionPrefix(textBeforeCursor);\n if (prefix === undefined) return undefined;\n // Re-read on every keystroke: a domain added while the session is running\n // has to be completable without a reload.\n const items = domainCompletionItems(listDomainNames(root), prefix);\n return items.length === 0 ? undefined : { prefix, items };\n };\n\n return { list, validate, describe, completions };\n}\n\nexport type DomainCatalog = ReturnType<typeof createDomainCatalog>;\n"],"mappings":"wPAaA,SAAgB,GAAsB,CACpC,IAAI,EACE,MAAc,IAAW,OAAO,oCAoDtC,MAAO,CAAE,UAlDW,IAAkD,CACpE,IAAM,EAAQ,EAAyB,CAAG,CAAC,CAAC,QACtC,EAAW,EAAmB,CAAK,EACnC,CAAE,6BAA4B,kBAAiB,eAAgB,MAAM,EAAK,EAC1E,EAAY,CAAC,GAAG,IAAI,IAAI,EAAgB,CAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,EACzD,EAAW,EAAY,CAAQ,EAG/B,EAAY,EAAyB,CAAG,CAAC,CAAC,iBAAmB,IAAA,GAAY,EAAwB,CAAG,EACpG,EAAS,EAAY,CAAC,GAAG,EAAU,OAAO,EAAI,CAAC,GAAG,EAAM,OAAO,EAC/D,EAAW,EAA2B,EAAM,UAAW,QAAQ,IAAK,EAAS,cAAc,EAMjG,MAAO,CAAE,SAAQ,UALC,EACd,CAAC,GAAG,EAAU,OAAO,EACrB,EAAM,QAAQ,OAAS,EACrB,CAAC,GAAG,EAAM,OAAO,EACjB,CAAC,GAAG,CAAQ,EACU,WAAU,CACxC,EAiCe,eA/BS,EAAuB,IAAiD,CAC9F,IAAM,EAAW,EAAqB,CAAM,EACtC,EAAQ,EAAyB,CAAG,CAAC,CAAC,QACtC,EAAW,EAAmB,CAAK,EACnC,CAAE,kBAAiB,wBAAyB,MAAM,EAAK,EACvD,EAAY,IAAI,IAAI,EAAgB,CAAQ,CAAC,EAC7C,EAAU,EAAS,OAAQ,GAAS,CAAC,EAAU,IAAI,CAAI,CAAC,EAC9D,GAAI,EAAQ,OAAS,EAAG,MAAU,MAAM,mBAAmB,EAAQ,KAAK,IAAI,GAAG,EAE/E,OADA,EAAqB,EAAU,EAAU,CAAC,GAAG,EAAM,iBAAiB,CAAC,EAC9D,CACT,EAqByB,cAnBD,IAAuE,CAC7F,IAAM,EAAO,EAAyB,CAAG,CAAC,CAAC,QAAQ,KACnD,GAAI,CAAC,EAAM,MAAO,CAAC,EACnB,GAAM,CAAE,eAAgB,MAAM,EAAK,EACnC,OAAO,OAAO,YACZ,OAAO,QAAQ,EAAY,CAAI,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,EAAM,KAAY,CAAC,EAAM,EAAO,WAAW,CAAC,CAC9F,CACF,EAYmC,kBAVR,EAAc,IAAoE,CAC3G,GAAM,CAAE,wBAAuB,yBAAwB,mBAAoB,MAAM,EAAK,EAChF,EAAS,EAAuB,CAAgB,EACtD,GAAI,IAAW,IAAA,GAAW,OAG1B,IAAM,EAAQ,EAAsB,EAAgB,CAAI,EAAG,CAAM,EACjE,OAAO,EAAM,SAAW,EAAI,IAAA,GAAY,CAAE,SAAQ,OAAM,CAC1D,CAE+C,CACjD"}
1
+ {"version":3,"file":"domainCatalog.mjs","names":[],"sources":["../../src/adapters/domainCatalog.ts"],"sourcesContent":["import { requireHarnessRoot } from '@agimon-ai/doompi-config/harnessStore';\nimport { readDoomConfigSelection, requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { normalizeDomainNames } from '../services/domainText.ts';\nimport type { DomainCompletion, DomainListing } from '../types/domains.ts';\n\n/**\n * The manifest reader every surface of this package shares.\n *\n * Every session registers `/domains`, but most never run it, so the plugin\n * catalogue and the domain manifest stay behind a dynamic import: only a session\n * that actually lists or switches domains pays to read them.\n */\nexport function createDomainCatalog(cordisContext: () => Context) {\n let config: Promise<typeof import('@agimon-ai/doompi-config/domains')> | undefined;\n const load = () => (config ??= import('@agimon-ai/doompi-config/domains'));\n\n const list = async (ctx: ExtensionContext): Promise<DomainListing> => {\n const configContext = requireDoomConfigContext(cordisContext());\n const state = configContext.harness;\n const repoRoot = requireHarnessRoot(state);\n const { defaultDomainsForMajorMode, listDomainNames, loadDomains } = await load();\n const available = [...new Set(listDomainNames(repoRoot))].sort();\n const manifest = loadDomains(repoRoot);\n // A journalled selection outranks the environment, but only once the switch\n // that produced it has settled: a pending one has not been applied yet.\n const selection = configContext.pendingSelection ? undefined : readDoomConfigSelection(ctx);\n const active = selection ? [...selection.domains] : [...state.domains];\n const fallback = defaultDomainsForMajorMode(state.majorMode, process.env, manifest.defaultDomains);\n const effective = selection\n ? [...selection.domains]\n : state.domains.length > 0\n ? [...state.domains]\n : [...fallback];\n return { active, effective, available };\n };\n\n const validate = async (_ctx: ExtensionContext, values: readonly string[]): Promise<string[]> => {\n const selected = normalizeDomainNames(values);\n const state = requireDoomConfigContext(cordisContext()).harness;\n const repoRoot = requireHarnessRoot(state);\n const { listDomainNames, resolvePluginEntries } = await load();\n const available = new Set(listDomainNames(repoRoot));\n const unknown = selected.filter((name) => !available.has(name));\n if (unknown.length > 0) throw new Error(`Unknown domain: ${unknown.join(', ')}`);\n resolvePluginEntries(repoRoot, selected, [...state.pluginDirectories]);\n return selected;\n };\n\n const describe = async (_ctx: ExtensionContext): Promise<Record<string, string | undefined>> => {\n const root = requireDoomConfigContext(cordisContext()).harness.root;\n if (!root) return {};\n const { loadDomains } = await load();\n return Object.fromEntries(\n Object.entries(loadDomains(root).domains).map(([name, domain]) => [name, domain.description]),\n );\n };\n\n const completions = async (root: string, textBeforeCursor: string): Promise<DomainCompletion | undefined> => {\n const { domainCompletionItems, domainCompletionPrefix, listDomainNames } = await load();\n const prefix = domainCompletionPrefix(textBeforeCursor);\n if (prefix === undefined) return undefined;\n // Re-read on every keystroke: a domain added while the session is running\n // has to be completable without a reload.\n const items = domainCompletionItems(listDomainNames(root), prefix);\n return items.length === 0 ? undefined : { prefix, items };\n };\n\n return { list, validate, describe, completions };\n}\n\nexport type DomainCatalog = ReturnType<typeof createDomainCatalog>;\n"],"mappings":"wPAcA,SAAgB,EAAoB,EAA8B,CAChE,IAAI,EACE,MAAc,IAAW,OAAO,oCAqDtC,MAAO,CAAE,UAnDW,IAAkD,CACpE,IAAM,EAAgB,EAAyB,EAAc,CAAC,EACxD,EAAQ,EAAc,QACtB,EAAW,EAAmB,CAAK,EACnC,CAAE,6BAA4B,kBAAiB,eAAgB,MAAM,EAAK,EAC1E,EAAY,CAAC,GAAG,IAAI,IAAI,EAAgB,CAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,EACzD,EAAW,EAAY,CAAQ,EAG/B,EAAY,EAAc,iBAAmB,IAAA,GAAY,EAAwB,CAAG,EACpF,EAAS,EAAY,CAAC,GAAG,EAAU,OAAO,EAAI,CAAC,GAAG,EAAM,OAAO,EAC/D,EAAW,EAA2B,EAAM,UAAW,QAAQ,IAAK,EAAS,cAAc,EAMjG,MAAO,CAAE,SAAQ,UALC,EACd,CAAC,GAAG,EAAU,OAAO,EACrB,EAAM,QAAQ,OAAS,EACrB,CAAC,GAAG,EAAM,OAAO,EACjB,CAAC,GAAG,CAAQ,EACU,WAAU,CACxC,EAiCe,eA/BS,EAAwB,IAAiD,CAC/F,IAAM,EAAW,EAAqB,CAAM,EACtC,EAAQ,EAAyB,EAAc,CAAC,CAAC,CAAC,QAClD,EAAW,EAAmB,CAAK,EACnC,CAAE,kBAAiB,wBAAyB,MAAM,EAAK,EACvD,EAAY,IAAI,IAAI,EAAgB,CAAQ,CAAC,EAC7C,EAAU,EAAS,OAAQ,GAAS,CAAC,EAAU,IAAI,CAAI,CAAC,EAC9D,GAAI,EAAQ,OAAS,EAAG,MAAU,MAAM,mBAAmB,EAAQ,KAAK,IAAI,GAAG,EAE/E,OADA,EAAqB,EAAU,EAAU,CAAC,GAAG,EAAM,iBAAiB,CAAC,EAC9D,CACT,EAqByB,cAnBD,IAAwE,CAC9F,IAAM,EAAO,EAAyB,EAAc,CAAC,CAAC,CAAC,QAAQ,KAC/D,GAAI,CAAC,EAAM,MAAO,CAAC,EACnB,GAAM,CAAE,eAAgB,MAAM,EAAK,EACnC,OAAO,OAAO,YACZ,OAAO,QAAQ,EAAY,CAAI,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,EAAM,KAAY,CAAC,EAAM,EAAO,WAAW,CAAC,CAC9F,CACF,EAYmC,kBAVR,EAAc,IAAoE,CAC3G,GAAM,CAAE,wBAAuB,yBAAwB,mBAAoB,MAAM,EAAK,EAChF,EAAS,EAAuB,CAAgB,EACtD,GAAI,IAAW,IAAA,GAAW,OAG1B,IAAM,EAAQ,EAAsB,EAAgB,CAAI,EAAG,CAAM,EACjE,OAAO,EAAM,SAAW,EAAI,IAAA,GAAY,CAAE,SAAQ,OAAM,CAC1D,CAE+C,CACjD"}
@@ -1,2 +1,2 @@
1
- const e=require("../types/domains.cjs"),t=Symbol.for(`@agimon-ai/doompi-domain.switch-handoff.v1`),n=3e4;function r(){let e=globalThis;return e[t]??={records:new Map}}function i(e){return Error(`Invalid domain switch handoff ${e}.`)}function a(e){for(let t of e){let e=t.codePointAt(0);if(e!==void 0&&(e<=31||e===127))return!0}return!1}function o(e,t,n,r){let o=e.trim();if(!o||o.length>n||a(o)||r&&!r.test(o))throw i(t);return o}function s(e){return o(e,`session id`,256)}function c(e){return o(e,`host generation`,256)}function l(t){return o(t,`operation id`,128,e.SAFE_DOMAIN_NAME)}function u(t){return o(t,`reload handoff token`,256,e.SAFE_DOMAIN_NAME)}function d(t){return o(t,`token`,256,e.SAFE_DOMAIN_NAME)}function f(t){if(t.length>32)throw Error(`A maximum of 32 domains may be selected.`);let n=[],r=new Set;for(let i of t){let t=o(i,`domain name`,128,e.SAFE_DOMAIN_NAME);r.has(t)||(r.add(t),n.push(t))}return n}function p(e=Date.now()){for(let[t,n]of r().records)n.expiresAt<=e&&r().records.delete(t)}function m(e,t){return e.sessionId===s(t.sessionId)?t.hostGeneration===void 0||e.hostGeneration===c(t.hostGeneration):!1}function h(e){return{...e,domains:[...e.domains]}}function g(e,t){p();let i=s(t.sessionId),a=c(t.hostGeneration),o=l(t.operationId),d=f(t.domains),m=u(t.reloadHandoffToken),g=Date.now(),_={token:`doom-domain-switch:${crypto.randomUUID()}`,sessionId:i,hostGeneration:a,operationId:o,domains:d,reloadHandoffToken:m,createdAt:g,expiresAt:g+n,ownerGeneration:e};return r().records.set(_.token,_),h(_)}function _(e,t){p();let n=d(t),i=r().records.get(n);if(!(!i||!m(i,e))&&(r().records.delete(n),!(i.expiresAt<=Date.now())))return h(i)}function v(e,t){p();let n=d(t),i=r().records.get(n);return!i||!m(i,e)?!1:r().records.delete(n)}function y(e,t){p();let n=s(t),i=0;for(let[t,a]of r().records)a.ownerGeneration===e&&a.sessionId===n&&(r().records.delete(t),i+=1);return i}function b(e){let t=0;for(let[n,i]of r().records)i.ownerGeneration===e&&(r().records.delete(n),t+=1);return t}function x(){let e=crypto.randomUUID(),t=!1;return{ownerGeneration:e,issue(n){if(t)throw Error(`Domain switch handoff store is disposed.`);return g(e,n)},consume(e,n){if(!t)return _(n,e)},discard(e,n){return!t&&v(n,e)},clearSession(n){return t?0:y(e,n)},dispose(){return t?0:(t=!0,b(e))}}}exports.DOMAIN_SWITCH_HANDOFF_MAX_DOMAINS=32,exports.DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH=256,exports.DOMAIN_SWITCH_HANDOFF_MAX_OPERATION_LENGTH=128,exports.DOMAIN_SWITCH_HANDOFF_TTL_MS=n,exports.createDomainSwitchHandoffStore=x;
1
+ const e=require("../types/domains.cjs"),t=Symbol.for(`@agimon-ai/doompi-domain.switch-handoff.v1`),n=3e4;function r(){let e=globalThis;return e[t]??={records:new Map}}function i(e){return Error(`Invalid domain switch handoff ${e}.`)}function a(e){for(let t of e){let e=t.codePointAt(0);if(e!==void 0&&(e<=31||e===127))return!0}return!1}function o(e,t,n,r){let o=typeof e==`string`?e.trim():``;if(!o||o.length>n||a(o)||r&&!r.test(o))throw i(t);return o}function s(e){return o(e,`session id`,256)}function c(e){return o(e,`host generation`,256)}function l(t){return o(t,`operation id`,128,e.SAFE_DOMAIN_NAME)}function u(t){return o(t,`reload handoff token`,256,e.SAFE_DOMAIN_NAME)}function d(t){return o(t,`token`,256,e.SAFE_DOMAIN_NAME)}function f(t){if(t.length>32)throw Error(`A maximum of 32 domains may be selected.`);let n=[],r=new Set;for(let i of t){let t=o(i,`domain name`,128,e.SAFE_DOMAIN_NAME);r.has(t)||(r.add(t),n.push(t))}return n}function p(e=Date.now()){for(let[t,n]of r().records)n.expiresAt<=e&&r().records.delete(t)}function m(e,t){return e.sessionId===s(t.sessionId)&&e.hostGeneration===c(t.hostGeneration)}function h(e){return{...e,domains:[...e.domains]}}function g(e,t){p();let i=s(t.sessionId),a=c(t.hostGeneration),o=l(t.operationId),d=f(t.domains),m=u(t.reloadHandoffToken),g=Date.now(),_={token:`doom-domain-switch:${crypto.randomUUID()}`,sessionId:i,hostGeneration:a,operationId:o,domains:d,reloadHandoffToken:m,createdAt:g,expiresAt:g+n,ownerGeneration:e};return r().records.set(_.token,_),h(_)}function _(e,t){p();let n=d(t),i=r().records.get(n);if(!(!i||!m(i,e))&&(r().records.delete(n),!(i.expiresAt<=Date.now())))return h(i)}function v(e,t){p();let n=d(t),i=r().records.get(n);return!i||!m(i,e)?!1:r().records.delete(n)}function y(e,t){p();let n=s(t),i=0;for(let[t,a]of r().records)a.ownerGeneration===e&&a.sessionId===n&&(r().records.delete(t),i+=1);return i}function b(e){let t=0;for(let[n,i]of r().records)i.ownerGeneration===e&&(r().records.delete(n),t+=1);return t}function x(){let e=crypto.randomUUID(),t=!1;return{ownerGeneration:e,issue(n){if(t)throw Error(`Domain switch handoff store is disposed.`);return g(e,n)},consume(e,n){if(!t)return _(n,e)},discard(e,n){return!t&&v(n,e)},clearSession(n){return t?0:y(e,n)},dispose(){return t?0:(t=!0,b(e))}}}exports.DOMAIN_SWITCH_HANDOFF_MAX_DOMAINS=32,exports.DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH=256,exports.DOMAIN_SWITCH_HANDOFF_MAX_OPERATION_LENGTH=128,exports.DOMAIN_SWITCH_HANDOFF_TTL_MS=n,exports.createDomainSwitchHandoffStore=x;
2
2
  //# sourceMappingURL=domainSwitchHandoff.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"domainSwitchHandoff.cjs","names":["SAFE_DOMAIN_NAME"],"sources":["../../src/adapters/domainSwitchHandoff.ts"],"sourcesContent":["import type {\n DomainSwitchHandoff,\n DomainSwitchHandoffIdentity,\n DomainSwitchHandoffRequest,\n DomainSwitchHandoffStore,\n} from '../types/handoff.ts';\nimport { SAFE_DOMAIN_NAME } from '../types/domains.ts';\n\nconst DOMAIN_SWITCH_HANDOFF_REGISTRY_KEY = '@agimon-ai/doompi-domain.switch-handoff.v1';\nconst DOMAIN_SWITCH_HANDOFF_SYMBOL = Symbol.for(DOMAIN_SWITCH_HANDOFF_REGISTRY_KEY);\nconst DOMAIN_SWITCH_TOKEN_PREFIX = 'doom-domain-switch:';\n\nexport const DOMAIN_SWITCH_HANDOFF_TTL_MS = 30_000;\nexport const DOMAIN_SWITCH_HANDOFF_MAX_DOMAINS = 32;\nexport const DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH = 256;\nexport const DOMAIN_SWITCH_HANDOFF_MAX_OPERATION_LENGTH = 128;\n\ninterface StoredDomainSwitchHandoff extends DomainSwitchHandoff {\n readonly ownerGeneration: string;\n}\n\ninterface HandoffRegistry {\n readonly records: Map<string, StoredDomainSwitchHandoff>;\n}\n\ntype HandoffGlobal = typeof globalThis & { [DOMAIN_SWITCH_HANDOFF_SYMBOL]?: HandoffRegistry };\n\n/**\n * One registry per process, keyed by a well-known symbol.\n *\n * Pi can reload this module in place, and a reload that lost the parked handoffs\n * would strand the follow-up command the voice tool already sent. Ownership is\n * tracked per store instead, so a disposed store takes only its own records.\n */\nfunction registry(): HandoffRegistry {\n const root = globalThis as HandoffGlobal;\n return (root[DOMAIN_SWITCH_HANDOFF_SYMBOL] ??= { records: new Map() });\n}\n\nfunction invalid(label: string): Error {\n return new Error(`Invalid domain switch handoff ${label}.`);\n}\n\nfunction hasControlCharacter(value: string): boolean {\n for (const character of value) {\n const code = character.codePointAt(0);\n if (code !== undefined && (code <= 0x1f || code === 0x7f)) return true;\n }\n return false;\n}\n\nfunction normalized(value: string, label: string, maximum: number, pattern?: RegExp): string {\n const text = value.trim();\n if (!text || text.length > maximum || hasControlCharacter(text) || (pattern && !pattern.test(text))) {\n throw invalid(label);\n }\n return text;\n}\n\nfunction sessionId(value: string): string {\n return normalized(value, 'session id', DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH);\n}\n\nfunction hostGeneration(value: string): string {\n return normalized(value, 'host generation', DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH);\n}\n\nfunction operationId(value: string): string {\n return normalized(value, 'operation id', DOMAIN_SWITCH_HANDOFF_MAX_OPERATION_LENGTH, SAFE_DOMAIN_NAME);\n}\n\nfunction reloadToken(value: string): string {\n return normalized(value, 'reload handoff token', DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH, SAFE_DOMAIN_NAME);\n}\n\nfunction token(value: string): string {\n return normalized(value, 'token', DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH, SAFE_DOMAIN_NAME);\n}\n\nfunction domains(values: readonly string[]): string[] {\n if (values.length > DOMAIN_SWITCH_HANDOFF_MAX_DOMAINS) {\n throw new Error(`A maximum of ${DOMAIN_SWITCH_HANDOFF_MAX_DOMAINS} domains may be selected.`);\n }\n const result: string[] = [];\n const seen = new Set<string>();\n for (const value of values) {\n const name = normalized(value, 'domain name', DOMAIN_SWITCH_HANDOFF_MAX_OPERATION_LENGTH, SAFE_DOMAIN_NAME);\n if (seen.has(name)) continue;\n seen.add(name);\n result.push(name);\n }\n return result;\n}\n\nfunction prune(now = Date.now()): void {\n for (const [key, record] of registry().records) {\n if (record.expiresAt <= now) registry().records.delete(key);\n }\n}\n\nfunction identityMatches(record: DomainSwitchHandoff, identity: DomainSwitchHandoffIdentity): boolean {\n if (record.sessionId !== sessionId(identity.sessionId)) return false;\n return identity.hostGeneration === undefined || record.hostGeneration === hostGeneration(identity.hostGeneration);\n}\n\nfunction copy(record: DomainSwitchHandoff): DomainSwitchHandoff {\n return { ...record, domains: [...record.domains] };\n}\n\nfunction issue(ownerGeneration: string, request: DomainSwitchHandoffRequest): DomainSwitchHandoff {\n prune();\n const normalizedSessionId = sessionId(request.sessionId);\n const normalizedHostGeneration = hostGeneration(request.hostGeneration);\n const normalizedOperationId = operationId(request.operationId);\n const normalizedDomains = domains(request.domains);\n const normalizedReloadToken = reloadToken(request.reloadHandoffToken);\n const createdAt = Date.now();\n const record: StoredDomainSwitchHandoff = {\n token: `${DOMAIN_SWITCH_TOKEN_PREFIX}${crypto.randomUUID()}`,\n sessionId: normalizedSessionId,\n hostGeneration: normalizedHostGeneration,\n operationId: normalizedOperationId,\n domains: normalizedDomains,\n reloadHandoffToken: normalizedReloadToken,\n createdAt,\n expiresAt: createdAt + DOMAIN_SWITCH_HANDOFF_TTL_MS,\n ownerGeneration,\n };\n registry().records.set(record.token, record);\n return copy(record);\n}\n\nfunction consume(identity: DomainSwitchHandoffIdentity, handoffToken: string): DomainSwitchHandoff | undefined {\n prune();\n const normalizedToken = token(handoffToken);\n const record = registry().records.get(normalizedToken);\n if (!record || !identityMatches(record, identity)) return undefined;\n registry().records.delete(normalizedToken);\n if (record.expiresAt <= Date.now()) return undefined;\n return copy(record);\n}\n\nfunction discard(identity: DomainSwitchHandoffIdentity, handoffToken: string): boolean {\n prune();\n const normalizedToken = token(handoffToken);\n const record = registry().records.get(normalizedToken);\n if (!record || !identityMatches(record, identity)) return false;\n return registry().records.delete(normalizedToken);\n}\n\nfunction clearOwnedSession(ownerGeneration: string, requestedSessionId: string): number {\n prune();\n const normalizedSessionId = sessionId(requestedSessionId);\n let removed = 0;\n for (const [key, record] of registry().records) {\n if (record.ownerGeneration !== ownerGeneration || record.sessionId !== normalizedSessionId) continue;\n registry().records.delete(key);\n removed += 1;\n }\n return removed;\n}\n\nfunction disposeOwner(ownerGeneration: string): number {\n let removed = 0;\n for (const [key, record] of registry().records) {\n if (record.ownerGeneration !== ownerGeneration) continue;\n registry().records.delete(key);\n removed += 1;\n }\n return removed;\n}\n\nexport function createDomainSwitchHandoffStore(): DomainSwitchHandoffStore {\n const ownerGeneration = crypto.randomUUID();\n let disposed = false;\n return {\n ownerGeneration,\n issue(request) {\n if (disposed) throw new Error('Domain switch handoff store is disposed.');\n return issue(ownerGeneration, request);\n },\n consume(handoffToken, identity) {\n if (disposed) return undefined;\n return consume(identity, handoffToken);\n },\n discard(handoffToken, identity) {\n if (disposed) return false;\n return discard(identity, handoffToken);\n },\n clearSession(requestedSessionId) {\n if (disposed) return 0;\n return clearOwnedSession(ownerGeneration, requestedSessionId);\n },\n dispose() {\n if (disposed) return 0;\n disposed = true;\n return disposeOwner(ownerGeneration);\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"domainSwitchHandoff.cjs","names":["SAFE_DOMAIN_NAME"],"sources":["../../src/adapters/domainSwitchHandoff.ts"],"sourcesContent":["import type {\n DomainSwitchHandoff,\n DomainSwitchHandoffIdentity,\n DomainSwitchHandoffRequest,\n DomainSwitchHandoffStore,\n} from '../types/handoff.ts';\nimport { SAFE_DOMAIN_NAME } from '../types/domains.ts';\n\nconst DOMAIN_SWITCH_HANDOFF_REGISTRY_KEY = '@agimon-ai/doompi-domain.switch-handoff.v1';\nconst DOMAIN_SWITCH_HANDOFF_SYMBOL = Symbol.for(DOMAIN_SWITCH_HANDOFF_REGISTRY_KEY);\nconst DOMAIN_SWITCH_TOKEN_PREFIX = 'doom-domain-switch:';\n\nexport const DOMAIN_SWITCH_HANDOFF_TTL_MS = 30_000;\nexport const DOMAIN_SWITCH_HANDOFF_MAX_DOMAINS = 32;\nexport const DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH = 256;\nexport const DOMAIN_SWITCH_HANDOFF_MAX_OPERATION_LENGTH = 128;\n\ninterface StoredDomainSwitchHandoff extends DomainSwitchHandoff {\n readonly ownerGeneration: string;\n}\n\ninterface HandoffRegistry {\n readonly records: Map<string, StoredDomainSwitchHandoff>;\n}\n\ntype HandoffGlobal = typeof globalThis & { [DOMAIN_SWITCH_HANDOFF_SYMBOL]?: HandoffRegistry };\n\n/**\n * One registry per process, keyed by a well-known symbol.\n *\n * Pi can reload this module in place, and a reload that lost the parked handoffs\n * would strand the follow-up command the voice tool already sent. Ownership is\n * tracked per store instead, so a disposed store takes only its own records.\n */\nfunction registry(): HandoffRegistry {\n const root = globalThis as HandoffGlobal;\n return (root[DOMAIN_SWITCH_HANDOFF_SYMBOL] ??= { records: new Map() });\n}\n\nfunction invalid(label: string): Error {\n return new Error(`Invalid domain switch handoff ${label}.`);\n}\n\nfunction hasControlCharacter(value: string): boolean {\n for (const character of value) {\n const code = character.codePointAt(0);\n if (code !== undefined && (code <= 0x1f || code === 0x7f)) return true;\n }\n return false;\n}\n\nfunction normalized(value: unknown, label: string, maximum: number, pattern?: RegExp): string {\n const text = typeof value === 'string' ? value.trim() : '';\n if (!text || text.length > maximum || hasControlCharacter(text) || (pattern && !pattern.test(text))) {\n throw invalid(label);\n }\n return text;\n}\n\nfunction sessionId(value: string): string {\n return normalized(value, 'session id', DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH);\n}\n\nfunction hostGeneration(value: string): string {\n return normalized(value, 'host generation', DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH);\n}\n\nfunction operationId(value: string): string {\n return normalized(value, 'operation id', DOMAIN_SWITCH_HANDOFF_MAX_OPERATION_LENGTH, SAFE_DOMAIN_NAME);\n}\n\nfunction reloadToken(value: string): string {\n return normalized(value, 'reload handoff token', DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH, SAFE_DOMAIN_NAME);\n}\n\nfunction token(value: string): string {\n return normalized(value, 'token', DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH, SAFE_DOMAIN_NAME);\n}\n\nfunction domains(values: readonly string[]): string[] {\n if (values.length > DOMAIN_SWITCH_HANDOFF_MAX_DOMAINS) {\n throw new Error(`A maximum of ${DOMAIN_SWITCH_HANDOFF_MAX_DOMAINS} domains may be selected.`);\n }\n const result: string[] = [];\n const seen = new Set<string>();\n for (const value of values) {\n const name = normalized(value, 'domain name', DOMAIN_SWITCH_HANDOFF_MAX_OPERATION_LENGTH, SAFE_DOMAIN_NAME);\n if (seen.has(name)) continue;\n seen.add(name);\n result.push(name);\n }\n return result;\n}\n\nfunction prune(now = Date.now()): void {\n for (const [key, record] of registry().records) {\n if (record.expiresAt <= now) registry().records.delete(key);\n }\n}\n\nfunction identityMatches(record: DomainSwitchHandoff, identity: DomainSwitchHandoffIdentity): boolean {\n if (record.sessionId !== sessionId(identity.sessionId)) return false;\n return record.hostGeneration === hostGeneration(identity.hostGeneration);\n}\n\nfunction copy(record: DomainSwitchHandoff): DomainSwitchHandoff {\n return { ...record, domains: [...record.domains] };\n}\n\nfunction issue(ownerGeneration: string, request: DomainSwitchHandoffRequest): DomainSwitchHandoff {\n prune();\n const normalizedSessionId = sessionId(request.sessionId);\n const normalizedHostGeneration = hostGeneration(request.hostGeneration);\n const normalizedOperationId = operationId(request.operationId);\n const normalizedDomains = domains(request.domains);\n const normalizedReloadToken = reloadToken(request.reloadHandoffToken);\n const createdAt = Date.now();\n const record: StoredDomainSwitchHandoff = {\n token: `${DOMAIN_SWITCH_TOKEN_PREFIX}${crypto.randomUUID()}`,\n sessionId: normalizedSessionId,\n hostGeneration: normalizedHostGeneration,\n operationId: normalizedOperationId,\n domains: normalizedDomains,\n reloadHandoffToken: normalizedReloadToken,\n createdAt,\n expiresAt: createdAt + DOMAIN_SWITCH_HANDOFF_TTL_MS,\n ownerGeneration,\n };\n registry().records.set(record.token, record);\n return copy(record);\n}\n\nfunction consume(identity: DomainSwitchHandoffIdentity, handoffToken: string): DomainSwitchHandoff | undefined {\n prune();\n const normalizedToken = token(handoffToken);\n const record = registry().records.get(normalizedToken);\n if (!record || !identityMatches(record, identity)) return undefined;\n registry().records.delete(normalizedToken);\n if (record.expiresAt <= Date.now()) return undefined;\n return copy(record);\n}\n\nfunction discard(identity: DomainSwitchHandoffIdentity, handoffToken: string): boolean {\n prune();\n const normalizedToken = token(handoffToken);\n const record = registry().records.get(normalizedToken);\n if (!record || !identityMatches(record, identity)) return false;\n return registry().records.delete(normalizedToken);\n}\n\nfunction clearOwnedSession(ownerGeneration: string, requestedSessionId: string): number {\n prune();\n const normalizedSessionId = sessionId(requestedSessionId);\n let removed = 0;\n for (const [key, record] of registry().records) {\n if (record.ownerGeneration !== ownerGeneration || record.sessionId !== normalizedSessionId) continue;\n registry().records.delete(key);\n removed += 1;\n }\n return removed;\n}\n\nfunction disposeOwner(ownerGeneration: string): number {\n let removed = 0;\n for (const [key, record] of registry().records) {\n if (record.ownerGeneration !== ownerGeneration) continue;\n registry().records.delete(key);\n removed += 1;\n }\n return removed;\n}\n\nexport function createDomainSwitchHandoffStore(): DomainSwitchHandoffStore {\n const ownerGeneration = crypto.randomUUID();\n let disposed = false;\n return {\n ownerGeneration,\n issue(request) {\n if (disposed) throw new Error('Domain switch handoff store is disposed.');\n return issue(ownerGeneration, request);\n },\n consume(handoffToken, identity) {\n if (disposed) return undefined;\n return consume(identity, handoffToken);\n },\n discard(handoffToken, identity) {\n if (disposed) return false;\n return discard(identity, handoffToken);\n },\n clearSession(requestedSessionId) {\n if (disposed) return 0;\n return clearOwnedSession(ownerGeneration, requestedSessionId);\n },\n dispose() {\n if (disposed) return 0;\n disposed = true;\n return disposeOwner(ownerGeneration);\n },\n };\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{SAFE_DOMAIN_NAME as e}from"../types/domains.mjs";const t=Symbol.for(`@agimon-ai/doompi-domain.switch-handoff.v1`),n=3e4,r=32,i=256,a=128;function o(){let e=globalThis;return e[t]??={records:new Map}}function s(e){return Error(`Invalid domain switch handoff ${e}.`)}function c(e){for(let t of e){let e=t.codePointAt(0);if(e!==void 0&&(e<=31||e===127))return!0}return!1}function l(e,t,n,r){let i=e.trim();if(!i||i.length>n||c(i)||r&&!r.test(i))throw s(t);return i}function u(e){return l(e,`session id`,256)}function d(e){return l(e,`host generation`,256)}function f(t){return l(t,`operation id`,128,e)}function p(t){return l(t,`reload handoff token`,256,e)}function m(t){return l(t,`token`,256,e)}function h(t){if(t.length>32)throw Error(`A maximum of 32 domains may be selected.`);let n=[],r=new Set;for(let i of t){let t=l(i,`domain name`,128,e);r.has(t)||(r.add(t),n.push(t))}return n}function g(e=Date.now()){for(let[t,n]of o().records)n.expiresAt<=e&&o().records.delete(t)}function _(e,t){return e.sessionId===u(t.sessionId)?t.hostGeneration===void 0||e.hostGeneration===d(t.hostGeneration):!1}function v(e){return{...e,domains:[...e.domains]}}function y(e,t){g();let r=u(t.sessionId),i=d(t.hostGeneration),a=f(t.operationId),s=h(t.domains),c=p(t.reloadHandoffToken),l=Date.now(),m={token:`doom-domain-switch:${crypto.randomUUID()}`,sessionId:r,hostGeneration:i,operationId:a,domains:s,reloadHandoffToken:c,createdAt:l,expiresAt:l+n,ownerGeneration:e};return o().records.set(m.token,m),v(m)}function b(e,t){g();let n=m(t),r=o().records.get(n);if(!(!r||!_(r,e))&&(o().records.delete(n),!(r.expiresAt<=Date.now())))return v(r)}function x(e,t){g();let n=m(t),r=o().records.get(n);return!r||!_(r,e)?!1:o().records.delete(n)}function S(e,t){g();let n=u(t),r=0;for(let[t,i]of o().records)i.ownerGeneration===e&&i.sessionId===n&&(o().records.delete(t),r+=1);return r}function C(e){let t=0;for(let[n,r]of o().records)r.ownerGeneration===e&&(o().records.delete(n),t+=1);return t}function w(){let e=crypto.randomUUID(),t=!1;return{ownerGeneration:e,issue(n){if(t)throw Error(`Domain switch handoff store is disposed.`);return y(e,n)},consume(e,n){if(!t)return b(n,e)},discard(e,n){return!t&&x(n,e)},clearSession(n){return t?0:S(e,n)},dispose(){return t?0:(t=!0,C(e))}}}export{r as DOMAIN_SWITCH_HANDOFF_MAX_DOMAINS,i as DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH,a as DOMAIN_SWITCH_HANDOFF_MAX_OPERATION_LENGTH,n as DOMAIN_SWITCH_HANDOFF_TTL_MS,w as createDomainSwitchHandoffStore};
1
+ import{SAFE_DOMAIN_NAME as e}from"../types/domains.mjs";const t=Symbol.for(`@agimon-ai/doompi-domain.switch-handoff.v1`),n=3e4,r=32,i=256,a=128;function o(){let e=globalThis;return e[t]??={records:new Map}}function s(e){return Error(`Invalid domain switch handoff ${e}.`)}function c(e){for(let t of e){let e=t.codePointAt(0);if(e!==void 0&&(e<=31||e===127))return!0}return!1}function l(e,t,n,r){let i=typeof e==`string`?e.trim():``;if(!i||i.length>n||c(i)||r&&!r.test(i))throw s(t);return i}function u(e){return l(e,`session id`,256)}function d(e){return l(e,`host generation`,256)}function f(t){return l(t,`operation id`,128,e)}function p(t){return l(t,`reload handoff token`,256,e)}function m(t){return l(t,`token`,256,e)}function h(t){if(t.length>32)throw Error(`A maximum of 32 domains may be selected.`);let n=[],r=new Set;for(let i of t){let t=l(i,`domain name`,128,e);r.has(t)||(r.add(t),n.push(t))}return n}function g(e=Date.now()){for(let[t,n]of o().records)n.expiresAt<=e&&o().records.delete(t)}function _(e,t){return e.sessionId===u(t.sessionId)&&e.hostGeneration===d(t.hostGeneration)}function v(e){return{...e,domains:[...e.domains]}}function y(e,t){g();let r=u(t.sessionId),i=d(t.hostGeneration),a=f(t.operationId),s=h(t.domains),c=p(t.reloadHandoffToken),l=Date.now(),m={token:`doom-domain-switch:${crypto.randomUUID()}`,sessionId:r,hostGeneration:i,operationId:a,domains:s,reloadHandoffToken:c,createdAt:l,expiresAt:l+n,ownerGeneration:e};return o().records.set(m.token,m),v(m)}function b(e,t){g();let n=m(t),r=o().records.get(n);if(!(!r||!_(r,e))&&(o().records.delete(n),!(r.expiresAt<=Date.now())))return v(r)}function x(e,t){g();let n=m(t),r=o().records.get(n);return!r||!_(r,e)?!1:o().records.delete(n)}function S(e,t){g();let n=u(t),r=0;for(let[t,i]of o().records)i.ownerGeneration===e&&i.sessionId===n&&(o().records.delete(t),r+=1);return r}function C(e){let t=0;for(let[n,r]of o().records)r.ownerGeneration===e&&(o().records.delete(n),t+=1);return t}function w(){let e=crypto.randomUUID(),t=!1;return{ownerGeneration:e,issue(n){if(t)throw Error(`Domain switch handoff store is disposed.`);return y(e,n)},consume(e,n){if(!t)return b(n,e)},discard(e,n){return!t&&x(n,e)},clearSession(n){return t?0:S(e,n)},dispose(){return t?0:(t=!0,C(e))}}}export{r as DOMAIN_SWITCH_HANDOFF_MAX_DOMAINS,i as DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH,a as DOMAIN_SWITCH_HANDOFF_MAX_OPERATION_LENGTH,n as DOMAIN_SWITCH_HANDOFF_TTL_MS,w as createDomainSwitchHandoffStore};
2
2
  //# sourceMappingURL=domainSwitchHandoff.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"domainSwitchHandoff.mjs","names":[],"sources":["../../src/adapters/domainSwitchHandoff.ts"],"sourcesContent":["import type {\n DomainSwitchHandoff,\n DomainSwitchHandoffIdentity,\n DomainSwitchHandoffRequest,\n DomainSwitchHandoffStore,\n} from '../types/handoff.ts';\nimport { SAFE_DOMAIN_NAME } from '../types/domains.ts';\n\nconst DOMAIN_SWITCH_HANDOFF_REGISTRY_KEY = '@agimon-ai/doompi-domain.switch-handoff.v1';\nconst DOMAIN_SWITCH_HANDOFF_SYMBOL = Symbol.for(DOMAIN_SWITCH_HANDOFF_REGISTRY_KEY);\nconst DOMAIN_SWITCH_TOKEN_PREFIX = 'doom-domain-switch:';\n\nexport const DOMAIN_SWITCH_HANDOFF_TTL_MS = 30_000;\nexport const DOMAIN_SWITCH_HANDOFF_MAX_DOMAINS = 32;\nexport const DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH = 256;\nexport const DOMAIN_SWITCH_HANDOFF_MAX_OPERATION_LENGTH = 128;\n\ninterface StoredDomainSwitchHandoff extends DomainSwitchHandoff {\n readonly ownerGeneration: string;\n}\n\ninterface HandoffRegistry {\n readonly records: Map<string, StoredDomainSwitchHandoff>;\n}\n\ntype HandoffGlobal = typeof globalThis & { [DOMAIN_SWITCH_HANDOFF_SYMBOL]?: HandoffRegistry };\n\n/**\n * One registry per process, keyed by a well-known symbol.\n *\n * Pi can reload this module in place, and a reload that lost the parked handoffs\n * would strand the follow-up command the voice tool already sent. Ownership is\n * tracked per store instead, so a disposed store takes only its own records.\n */\nfunction registry(): HandoffRegistry {\n const root = globalThis as HandoffGlobal;\n return (root[DOMAIN_SWITCH_HANDOFF_SYMBOL] ??= { records: new Map() });\n}\n\nfunction invalid(label: string): Error {\n return new Error(`Invalid domain switch handoff ${label}.`);\n}\n\nfunction hasControlCharacter(value: string): boolean {\n for (const character of value) {\n const code = character.codePointAt(0);\n if (code !== undefined && (code <= 0x1f || code === 0x7f)) return true;\n }\n return false;\n}\n\nfunction normalized(value: string, label: string, maximum: number, pattern?: RegExp): string {\n const text = value.trim();\n if (!text || text.length > maximum || hasControlCharacter(text) || (pattern && !pattern.test(text))) {\n throw invalid(label);\n }\n return text;\n}\n\nfunction sessionId(value: string): string {\n return normalized(value, 'session id', DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH);\n}\n\nfunction hostGeneration(value: string): string {\n return normalized(value, 'host generation', DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH);\n}\n\nfunction operationId(value: string): string {\n return normalized(value, 'operation id', DOMAIN_SWITCH_HANDOFF_MAX_OPERATION_LENGTH, SAFE_DOMAIN_NAME);\n}\n\nfunction reloadToken(value: string): string {\n return normalized(value, 'reload handoff token', DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH, SAFE_DOMAIN_NAME);\n}\n\nfunction token(value: string): string {\n return normalized(value, 'token', DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH, SAFE_DOMAIN_NAME);\n}\n\nfunction domains(values: readonly string[]): string[] {\n if (values.length > DOMAIN_SWITCH_HANDOFF_MAX_DOMAINS) {\n throw new Error(`A maximum of ${DOMAIN_SWITCH_HANDOFF_MAX_DOMAINS} domains may be selected.`);\n }\n const result: string[] = [];\n const seen = new Set<string>();\n for (const value of values) {\n const name = normalized(value, 'domain name', DOMAIN_SWITCH_HANDOFF_MAX_OPERATION_LENGTH, SAFE_DOMAIN_NAME);\n if (seen.has(name)) continue;\n seen.add(name);\n result.push(name);\n }\n return result;\n}\n\nfunction prune(now = Date.now()): void {\n for (const [key, record] of registry().records) {\n if (record.expiresAt <= now) registry().records.delete(key);\n }\n}\n\nfunction identityMatches(record: DomainSwitchHandoff, identity: DomainSwitchHandoffIdentity): boolean {\n if (record.sessionId !== sessionId(identity.sessionId)) return false;\n return identity.hostGeneration === undefined || record.hostGeneration === hostGeneration(identity.hostGeneration);\n}\n\nfunction copy(record: DomainSwitchHandoff): DomainSwitchHandoff {\n return { ...record, domains: [...record.domains] };\n}\n\nfunction issue(ownerGeneration: string, request: DomainSwitchHandoffRequest): DomainSwitchHandoff {\n prune();\n const normalizedSessionId = sessionId(request.sessionId);\n const normalizedHostGeneration = hostGeneration(request.hostGeneration);\n const normalizedOperationId = operationId(request.operationId);\n const normalizedDomains = domains(request.domains);\n const normalizedReloadToken = reloadToken(request.reloadHandoffToken);\n const createdAt = Date.now();\n const record: StoredDomainSwitchHandoff = {\n token: `${DOMAIN_SWITCH_TOKEN_PREFIX}${crypto.randomUUID()}`,\n sessionId: normalizedSessionId,\n hostGeneration: normalizedHostGeneration,\n operationId: normalizedOperationId,\n domains: normalizedDomains,\n reloadHandoffToken: normalizedReloadToken,\n createdAt,\n expiresAt: createdAt + DOMAIN_SWITCH_HANDOFF_TTL_MS,\n ownerGeneration,\n };\n registry().records.set(record.token, record);\n return copy(record);\n}\n\nfunction consume(identity: DomainSwitchHandoffIdentity, handoffToken: string): DomainSwitchHandoff | undefined {\n prune();\n const normalizedToken = token(handoffToken);\n const record = registry().records.get(normalizedToken);\n if (!record || !identityMatches(record, identity)) return undefined;\n registry().records.delete(normalizedToken);\n if (record.expiresAt <= Date.now()) return undefined;\n return copy(record);\n}\n\nfunction discard(identity: DomainSwitchHandoffIdentity, handoffToken: string): boolean {\n prune();\n const normalizedToken = token(handoffToken);\n const record = registry().records.get(normalizedToken);\n if (!record || !identityMatches(record, identity)) return false;\n return registry().records.delete(normalizedToken);\n}\n\nfunction clearOwnedSession(ownerGeneration: string, requestedSessionId: string): number {\n prune();\n const normalizedSessionId = sessionId(requestedSessionId);\n let removed = 0;\n for (const [key, record] of registry().records) {\n if (record.ownerGeneration !== ownerGeneration || record.sessionId !== normalizedSessionId) continue;\n registry().records.delete(key);\n removed += 1;\n }\n return removed;\n}\n\nfunction disposeOwner(ownerGeneration: string): number {\n let removed = 0;\n for (const [key, record] of registry().records) {\n if (record.ownerGeneration !== ownerGeneration) continue;\n registry().records.delete(key);\n removed += 1;\n }\n return removed;\n}\n\nexport function createDomainSwitchHandoffStore(): DomainSwitchHandoffStore {\n const ownerGeneration = crypto.randomUUID();\n let disposed = false;\n return {\n ownerGeneration,\n issue(request) {\n if (disposed) throw new Error('Domain switch handoff store is disposed.');\n return issue(ownerGeneration, request);\n },\n consume(handoffToken, identity) {\n if (disposed) return undefined;\n return consume(identity, handoffToken);\n },\n discard(handoffToken, identity) {\n if (disposed) return false;\n return discard(identity, handoffToken);\n },\n clearSession(requestedSessionId) {\n if (disposed) return 0;\n return clearOwnedSession(ownerGeneration, requestedSessionId);\n },\n dispose() {\n if (disposed) return 0;\n disposed = true;\n return disposeOwner(ownerGeneration);\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"domainSwitchHandoff.mjs","names":[],"sources":["../../src/adapters/domainSwitchHandoff.ts"],"sourcesContent":["import type {\n DomainSwitchHandoff,\n DomainSwitchHandoffIdentity,\n DomainSwitchHandoffRequest,\n DomainSwitchHandoffStore,\n} from '../types/handoff.ts';\nimport { SAFE_DOMAIN_NAME } from '../types/domains.ts';\n\nconst DOMAIN_SWITCH_HANDOFF_REGISTRY_KEY = '@agimon-ai/doompi-domain.switch-handoff.v1';\nconst DOMAIN_SWITCH_HANDOFF_SYMBOL = Symbol.for(DOMAIN_SWITCH_HANDOFF_REGISTRY_KEY);\nconst DOMAIN_SWITCH_TOKEN_PREFIX = 'doom-domain-switch:';\n\nexport const DOMAIN_SWITCH_HANDOFF_TTL_MS = 30_000;\nexport const DOMAIN_SWITCH_HANDOFF_MAX_DOMAINS = 32;\nexport const DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH = 256;\nexport const DOMAIN_SWITCH_HANDOFF_MAX_OPERATION_LENGTH = 128;\n\ninterface StoredDomainSwitchHandoff extends DomainSwitchHandoff {\n readonly ownerGeneration: string;\n}\n\ninterface HandoffRegistry {\n readonly records: Map<string, StoredDomainSwitchHandoff>;\n}\n\ntype HandoffGlobal = typeof globalThis & { [DOMAIN_SWITCH_HANDOFF_SYMBOL]?: HandoffRegistry };\n\n/**\n * One registry per process, keyed by a well-known symbol.\n *\n * Pi can reload this module in place, and a reload that lost the parked handoffs\n * would strand the follow-up command the voice tool already sent. Ownership is\n * tracked per store instead, so a disposed store takes only its own records.\n */\nfunction registry(): HandoffRegistry {\n const root = globalThis as HandoffGlobal;\n return (root[DOMAIN_SWITCH_HANDOFF_SYMBOL] ??= { records: new Map() });\n}\n\nfunction invalid(label: string): Error {\n return new Error(`Invalid domain switch handoff ${label}.`);\n}\n\nfunction hasControlCharacter(value: string): boolean {\n for (const character of value) {\n const code = character.codePointAt(0);\n if (code !== undefined && (code <= 0x1f || code === 0x7f)) return true;\n }\n return false;\n}\n\nfunction normalized(value: unknown, label: string, maximum: number, pattern?: RegExp): string {\n const text = typeof value === 'string' ? value.trim() : '';\n if (!text || text.length > maximum || hasControlCharacter(text) || (pattern && !pattern.test(text))) {\n throw invalid(label);\n }\n return text;\n}\n\nfunction sessionId(value: string): string {\n return normalized(value, 'session id', DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH);\n}\n\nfunction hostGeneration(value: string): string {\n return normalized(value, 'host generation', DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH);\n}\n\nfunction operationId(value: string): string {\n return normalized(value, 'operation id', DOMAIN_SWITCH_HANDOFF_MAX_OPERATION_LENGTH, SAFE_DOMAIN_NAME);\n}\n\nfunction reloadToken(value: string): string {\n return normalized(value, 'reload handoff token', DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH, SAFE_DOMAIN_NAME);\n}\n\nfunction token(value: string): string {\n return normalized(value, 'token', DOMAIN_SWITCH_HANDOFF_MAX_IDENTIFIER_LENGTH, SAFE_DOMAIN_NAME);\n}\n\nfunction domains(values: readonly string[]): string[] {\n if (values.length > DOMAIN_SWITCH_HANDOFF_MAX_DOMAINS) {\n throw new Error(`A maximum of ${DOMAIN_SWITCH_HANDOFF_MAX_DOMAINS} domains may be selected.`);\n }\n const result: string[] = [];\n const seen = new Set<string>();\n for (const value of values) {\n const name = normalized(value, 'domain name', DOMAIN_SWITCH_HANDOFF_MAX_OPERATION_LENGTH, SAFE_DOMAIN_NAME);\n if (seen.has(name)) continue;\n seen.add(name);\n result.push(name);\n }\n return result;\n}\n\nfunction prune(now = Date.now()): void {\n for (const [key, record] of registry().records) {\n if (record.expiresAt <= now) registry().records.delete(key);\n }\n}\n\nfunction identityMatches(record: DomainSwitchHandoff, identity: DomainSwitchHandoffIdentity): boolean {\n if (record.sessionId !== sessionId(identity.sessionId)) return false;\n return record.hostGeneration === hostGeneration(identity.hostGeneration);\n}\n\nfunction copy(record: DomainSwitchHandoff): DomainSwitchHandoff {\n return { ...record, domains: [...record.domains] };\n}\n\nfunction issue(ownerGeneration: string, request: DomainSwitchHandoffRequest): DomainSwitchHandoff {\n prune();\n const normalizedSessionId = sessionId(request.sessionId);\n const normalizedHostGeneration = hostGeneration(request.hostGeneration);\n const normalizedOperationId = operationId(request.operationId);\n const normalizedDomains = domains(request.domains);\n const normalizedReloadToken = reloadToken(request.reloadHandoffToken);\n const createdAt = Date.now();\n const record: StoredDomainSwitchHandoff = {\n token: `${DOMAIN_SWITCH_TOKEN_PREFIX}${crypto.randomUUID()}`,\n sessionId: normalizedSessionId,\n hostGeneration: normalizedHostGeneration,\n operationId: normalizedOperationId,\n domains: normalizedDomains,\n reloadHandoffToken: normalizedReloadToken,\n createdAt,\n expiresAt: createdAt + DOMAIN_SWITCH_HANDOFF_TTL_MS,\n ownerGeneration,\n };\n registry().records.set(record.token, record);\n return copy(record);\n}\n\nfunction consume(identity: DomainSwitchHandoffIdentity, handoffToken: string): DomainSwitchHandoff | undefined {\n prune();\n const normalizedToken = token(handoffToken);\n const record = registry().records.get(normalizedToken);\n if (!record || !identityMatches(record, identity)) return undefined;\n registry().records.delete(normalizedToken);\n if (record.expiresAt <= Date.now()) return undefined;\n return copy(record);\n}\n\nfunction discard(identity: DomainSwitchHandoffIdentity, handoffToken: string): boolean {\n prune();\n const normalizedToken = token(handoffToken);\n const record = registry().records.get(normalizedToken);\n if (!record || !identityMatches(record, identity)) return false;\n return registry().records.delete(normalizedToken);\n}\n\nfunction clearOwnedSession(ownerGeneration: string, requestedSessionId: string): number {\n prune();\n const normalizedSessionId = sessionId(requestedSessionId);\n let removed = 0;\n for (const [key, record] of registry().records) {\n if (record.ownerGeneration !== ownerGeneration || record.sessionId !== normalizedSessionId) continue;\n registry().records.delete(key);\n removed += 1;\n }\n return removed;\n}\n\nfunction disposeOwner(ownerGeneration: string): number {\n let removed = 0;\n for (const [key, record] of registry().records) {\n if (record.ownerGeneration !== ownerGeneration) continue;\n registry().records.delete(key);\n removed += 1;\n }\n return removed;\n}\n\nexport function createDomainSwitchHandoffStore(): DomainSwitchHandoffStore {\n const ownerGeneration = crypto.randomUUID();\n let disposed = false;\n return {\n ownerGeneration,\n issue(request) {\n if (disposed) throw new Error('Domain switch handoff store is disposed.');\n return issue(ownerGeneration, request);\n },\n consume(handoffToken, identity) {\n if (disposed) return undefined;\n return consume(identity, handoffToken);\n },\n discard(handoffToken, identity) {\n if (disposed) return false;\n return discard(identity, handoffToken);\n },\n clearSession(requestedSessionId) {\n if (disposed) return 0;\n return clearOwnedSession(ownerGeneration, requestedSessionId);\n },\n dispose() {\n if (disposed) return 0;\n disposed = true;\n return disposeOwner(ownerGeneration);\n },\n };\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:path");t=e.__toESM(t,1);let n=require("@agimon-ai/doompi-config/domains"),r=require("node:fs");r=e.__toESM(r,1);let i=require("yaml");const a=`agiflow-proxy`,o=`mcp-config.yaml`,s=`--config`;function c(e,t){let r=(0,n.loadDomains)(e),i=(0,n.expandDomainNames)(r,t);if(i.length===0)return;let a=new Set,o=new Set;for(let e of i){let t=r.domains[e]?.mcp;if(!t)return;for(let e of t.servers??[])a.add(e);for(let e of t.proxy??[])o.add(e)}return{servers:[...a],proxy:[...o]}}function l(e,t){if(!t||t.length===0)return e;let n=e.mcpServers??{},r={};for(let[e,i]of Object.entries(n))t.includes(e)&&(r[e]=i);return{...e,mcpServers:r}}function u(e,t){if(!t||t.length===0)return e;let n=(0,i.parse)(e)??{},r=n.mcpServers??{},a={},o=[];for(let[e,n]of Object.entries(r))t.includes(e)?a[e]=n:o.push(e);return`${[`# Generated by doompi for this run. Do not edit.`,`# Upstreams kept: ${Object.keys(a).join(`, `)||`(none)`}`,`# Upstreams removed by the domain allowlist: ${o.join(`, `)||`(none)`}`,``].join(`
2
- `)}${(0,i.stringify)({...n,mcpServers:a})}`}async function d(e,t){try{return await r.default.promises.copyFile(e,t),!0}catch(e){if(e.code===`ENOENT`)return!1;throw e}}async function f(e,n,i){await r.default.promises.mkdir(i,{recursive:!0});let c=JSON.parse(await r.default.promises.readFile(e,`utf8`)),l=c.mcpServers??{},u=l[a],f=t.default.join(n,o);if(u){let e=[...u.args??[]],n=e.indexOf(s);if(n!==-1){let r=t.default.join(i,o);await d(f,r)&&(e[n+1]=r,l[a]={...u,args:e})}}let p=t.default.join(i,`mcp.json`);return await r.default.promises.writeFile(p,`${JSON.stringify({...c,mcpServers:l},null,2)}\n`,{mode:384}),p}async function p(e,n,i,c){if(!n)return e;let d=l(e,n.servers),f=d.mcpServers??{},p=f[a];if(!p||!n.proxy||n.proxy.length===0)return d;let m=[...p.args??[]],h=m.indexOf(s);if(h===-1||!m[h+1])return d;let g=t.default.resolve(i,m[h+1]),_;try{_=await r.default.promises.readFile(g,`utf8`)}catch(e){if(e.code===`ENOENT`)return d;throw e}let v=t.default.join(c,o);return await r.default.promises.writeFile(v,u(_,n.proxy),{mode:384}),m[h+1]=v,{...d,mcpServers:{...f,[a]:{...p,args:m}}}}exports.PROXY_SERVER_NAME=a,exports.applyMcpAllowlist=p,exports.filterMcpServers=l,exports.filterProxyConfig=u,exports.persistMcpConfig=f,exports.resolveMcpAllowlist=c;
1
+ const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);let r=require("@agimon-ai/doompi-config/domains"),i=require("yaml");const a=`agiflow-proxy`,o=`mcp-config.yaml`,s=`--config`;function c(e,t){let n=(0,r.loadDomains)(e),i=(0,r.expandDomainNames)(n,t);if(i.length===0)return;let a=new Set,o=new Set;for(let e of i){let t=n.domains[e]?.mcp;if(!t)return;for(let e of t.servers??[])a.add(e);for(let e of t.proxy??[])o.add(e)}return{servers:[...a],proxy:[...o]}}function l(e,t){if(!t||t.length===0)return e;let n=e.mcpServers??{},r={};for(let[e,i]of Object.entries(n))t.includes(e)&&(r[e]=i);return{...e,mcpServers:r}}function u(e,t){if(!t||t.length===0)return e;let n=(0,i.parse)(e)??{},r=n.mcpServers??{},a={},o=[];for(let[e,n]of Object.entries(r))t.includes(e)?a[e]=n:o.push(e);return`${[`# Generated by doompi for this run. Do not edit.`,`# Upstreams kept: ${Object.keys(a).join(`, `)||`(none)`}`,`# Upstreams removed by the domain allowlist: ${o.join(`, `)||`(none)`}`,``].join(`
2
+ `)}${(0,i.stringify)({...n,mcpServers:a})}`}async function d(e,n){try{return await t.default.promises.copyFile(e,n),!0}catch(e){if(e.code===`ENOENT`)return!1;throw e}}async function f(e,r,i){await t.default.promises.mkdir(i,{recursive:!0});let c=JSON.parse(await t.default.promises.readFile(e,`utf8`)),l=c.mcpServers??{},u=l[a],f=n.default.join(r,o);if(u){let e=[...u.args??[]],t=e.indexOf(s);if(t!==-1){let r=n.default.join(i,o);await d(f,r)&&(e[t+1]=r,l[a]={...u,args:e})}}let p=n.default.join(i,`mcp.json`);return await t.default.promises.writeFile(p,`${JSON.stringify({...c,mcpServers:l},null,2)}\n`,{mode:384}),p}async function p(e,r,i,c){if(!r)return e;let d=l(e,r.servers),f=d.mcpServers??{},p=f[a];if(!p||!r.proxy||r.proxy.length===0)return d;let m=[...p.args??[]],h=m.indexOf(s);if(h===-1||!m[h+1])return d;let g=n.default.resolve(i,m[h+1]),_;try{_=await t.default.promises.readFile(g,`utf8`)}catch(e){if(e.code===`ENOENT`)return d;throw e}let v=n.default.join(c,o);return await t.default.promises.writeFile(v,u(_,r.proxy),{mode:384}),m[h+1]=v,{...d,mcpServers:{...f,[a]:{...p,args:m}}}}exports.PROXY_SERVER_NAME=a,exports.applyMcpAllowlist=p,exports.filterMcpServers=l,exports.filterProxyConfig=u,exports.persistMcpConfig=f,exports.resolveMcpAllowlist=c;
3
3
  //# sourceMappingURL=mcpFilter.cjs.map
@@ -1,3 +1,3 @@
1
- import e from"node:path";import{expandDomainNames as t,loadDomains as n}from"@agimon-ai/doompi-config/domains";import r from"node:fs";import{parse as i,stringify as a}from"yaml";const o=`agiflow-proxy`,s=`mcp-config.yaml`,c=`--config`;function l(e,r){let i=n(e),a=t(i,r);if(a.length===0)return;let o=new Set,s=new Set;for(let e of a){let t=i.domains[e]?.mcp;if(!t)return;for(let e of t.servers??[])o.add(e);for(let e of t.proxy??[])s.add(e)}return{servers:[...o],proxy:[...s]}}function u(e,t){if(!t||t.length===0)return e;let n=e.mcpServers??{},r={};for(let[e,i]of Object.entries(n))t.includes(e)&&(r[e]=i);return{...e,mcpServers:r}}function d(e,t){if(!t||t.length===0)return e;let n=i(e)??{},r=n.mcpServers??{},o={},s=[];for(let[e,n]of Object.entries(r))t.includes(e)?o[e]=n:s.push(e);return`${[`# Generated by doompi for this run. Do not edit.`,`# Upstreams kept: ${Object.keys(o).join(`, `)||`(none)`}`,`# Upstreams removed by the domain allowlist: ${s.join(`, `)||`(none)`}`,``].join(`
2
- `)}${a({...n,mcpServers:o})}`}async function f(e,t){try{return await r.promises.copyFile(e,t),!0}catch(e){if(e.code===`ENOENT`)return!1;throw e}}async function p(t,n,i){await r.promises.mkdir(i,{recursive:!0});let a=JSON.parse(await r.promises.readFile(t,`utf8`)),l=a.mcpServers??{},u=l[o],d=e.join(n,s);if(u){let t=[...u.args??[]],n=t.indexOf(c);if(n!==-1){let r=e.join(i,s);await f(d,r)&&(t[n+1]=r,l[o]={...u,args:t})}}let p=e.join(i,`mcp.json`);return await r.promises.writeFile(p,`${JSON.stringify({...a,mcpServers:l},null,2)}\n`,{mode:384}),p}async function m(t,n,i,a){if(!n)return t;let l=u(t,n.servers),f=l.mcpServers??{},p=f[o];if(!p||!n.proxy||n.proxy.length===0)return l;let m=[...p.args??[]],h=m.indexOf(c);if(h===-1||!m[h+1])return l;let g=e.resolve(i,m[h+1]),_;try{_=await r.promises.readFile(g,`utf8`)}catch(e){if(e.code===`ENOENT`)return l;throw e}let v=e.join(a,s);return await r.promises.writeFile(v,d(_,n.proxy),{mode:384}),m[h+1]=v,{...l,mcpServers:{...f,[o]:{...p,args:m}}}}export{o as PROXY_SERVER_NAME,m as applyMcpAllowlist,u as filterMcpServers,d as filterProxyConfig,p as persistMcpConfig,l as resolveMcpAllowlist};
1
+ import e from"node:fs";import t from"node:path";import{expandDomainNames as n,loadDomains as r}from"@agimon-ai/doompi-config/domains";import{parse as i,stringify as a}from"yaml";const o=`agiflow-proxy`,s=`mcp-config.yaml`,c=`--config`;function l(e,t){let i=r(e),a=n(i,t);if(a.length===0)return;let o=new Set,s=new Set;for(let e of a){let t=i.domains[e]?.mcp;if(!t)return;for(let e of t.servers??[])o.add(e);for(let e of t.proxy??[])s.add(e)}return{servers:[...o],proxy:[...s]}}function u(e,t){if(!t||t.length===0)return e;let n=e.mcpServers??{},r={};for(let[e,i]of Object.entries(n))t.includes(e)&&(r[e]=i);return{...e,mcpServers:r}}function d(e,t){if(!t||t.length===0)return e;let n=i(e)??{},r=n.mcpServers??{},o={},s=[];for(let[e,n]of Object.entries(r))t.includes(e)?o[e]=n:s.push(e);return`${[`# Generated by doompi for this run. Do not edit.`,`# Upstreams kept: ${Object.keys(o).join(`, `)||`(none)`}`,`# Upstreams removed by the domain allowlist: ${s.join(`, `)||`(none)`}`,``].join(`
2
+ `)}${a({...n,mcpServers:o})}`}async function f(t,n){try{return await e.promises.copyFile(t,n),!0}catch(e){if(e.code===`ENOENT`)return!1;throw e}}async function p(n,r,i){await e.promises.mkdir(i,{recursive:!0});let a=JSON.parse(await e.promises.readFile(n,`utf8`)),l=a.mcpServers??{},u=l[o],d=t.join(r,s);if(u){let e=[...u.args??[]],n=e.indexOf(c);if(n!==-1){let r=t.join(i,s);await f(d,r)&&(e[n+1]=r,l[o]={...u,args:e})}}let p=t.join(i,`mcp.json`);return await e.promises.writeFile(p,`${JSON.stringify({...a,mcpServers:l},null,2)}\n`,{mode:384}),p}async function m(n,r,i,a){if(!r)return n;let l=u(n,r.servers),f=l.mcpServers??{},p=f[o];if(!p||!r.proxy||r.proxy.length===0)return l;let m=[...p.args??[]],h=m.indexOf(c);if(h===-1||!m[h+1])return l;let g=t.resolve(i,m[h+1]),_;try{_=await e.promises.readFile(g,`utf8`)}catch(e){if(e.code===`ENOENT`)return l;throw e}let v=t.join(a,s);return await e.promises.writeFile(v,d(_,r.proxy),{mode:384}),m[h+1]=v,{...l,mcpServers:{...f,[o]:{...p,args:m}}}}export{o as PROXY_SERVER_NAME,m as applyMcpAllowlist,u as filterMcpServers,d as filterProxyConfig,p as persistMcpConfig,l as resolveMcpAllowlist};
3
3
  //# sourceMappingURL=mcpFilter.mjs.map
@@ -1,2 +1,2 @@
1
- const e=require("../../types/domains.cjs"),t=require("../domainCatalog.cjs"),n=require("../domainSwitchHandoff.cjs"),r=require("../../commands/domainsCommand.cjs"),i=require("../telemetry/logSinkTelemetry.cjs"),a=require("./voiceTool.cjs");let o=require("@agimon-ai/doompi-config/piContext"),s=require("@deepseek-ai/cordis");function c(){let e,t,n;return{apply:()=>e??=Promise.resolve().then(()=>require("../applyDomains.cjs")),journal:()=>t??=import(`@agimon-ai/doompi-config/piContext`),picker:()=>n??=import(`@agimon-ai/doompi-ui/components/matrixPicker`)}}async function l(l,u=i.createDomainTelemetry()){let d=new s.Context,f=c(),p=t.createDomainCatalog(),m=n.createDomainSwitchHandoffStore(),h,g=()=>(h??=d.fiber.dispose(),h);try{d.effect(function*(){a.registerDomainVoiceCapabilities(l,p,m),r.registerDomainsCommand(l,u,{catalog:p,handoffs:m,applyDomains:async(e,t)=>(await f.apply()).applyDomains(e,t),loadConfigJournal:f.journal,loadPicker:f.picker}),l.on(`resources_discover`,(e,t)=>({skillPaths:[...(0,o.requireDoomConfigContext)(t).harness.skillDirectories]})),l.on(`session_shutdown`,(e,t)=>{m.clearSession(t.sessionManager.getSessionId())}),l.on(`session_start`,(e,t)=>{let n=(0,o.requireDoomConfigContext)(t).harness;t.ui.addAutocompleteProvider(e=>({triggerCharacters:[`,`],async getSuggestions(t,r,i,a){let o=n.root,s=t[r]??``;return(o?await p.completions(o,s.slice(0,i)):void 0)??e.getSuggestions(t,r,i,a)},applyCompletion(t,n,r,i,a){return e.applyCompletion(t,n,r,i,a)},shouldTriggerFileCompletion(t,n,r){return e.shouldTriggerFileCompletion?.(t,n,r)??!0}}))}),yield()=>{m.dispose()}},e.DOMAIN_SOURCE)}catch(e){throw await g(),e}l.on(`session_shutdown`,g)}exports.default=l,exports.domainsExtension=l;
1
+ const e=require("../../types/domains.cjs"),t=require("../domainCatalog.cjs"),n=require("../domainSwitchHandoff.cjs"),r=require("../../commands/domainsCommand.cjs"),i=require("../telemetry/logSinkTelemetry.cjs"),a=require("./voiceTool.cjs");let o=require("@agimon-ai/doompi-config/piContext"),s=require("@agimon-ai/doompi-extension-contracts/voice-tools"),c=require("@agimon-ai/doompi-extension-contracts/config"),l=require("@agimon-ai/doompi-extension-contracts/cordis-host"),u=require("@agimon-ai/doompi-extension-contracts/transition"),d=require("@agimon-ai/doompi-extension-contracts/voice-reload-handoff");function f(){let e,t,n;return{apply:()=>e??=Promise.resolve().then(()=>require("../applyDomains.cjs")),journal:()=>t??=import(`@agimon-ai/doompi-config/piContext`),picker:()=>n??=import(`@agimon-ai/doompi-ui/components/matrixPicker`)}}function p(i,{pi:l,telemetry:p}){let m=f(),h,g=i.inject([c.DOOM_CONFIG_SERVICE,u.DOOM_TRANSITION_SERVICE],e=>(h=e,()=>{h===e&&(h=void 0)})),_=()=>{if(!h)throw Error(`Doom domain runtime is waiting for the session config service.`);return h},v=t.createDomainCatalog(_),y=n.createDomainSwitchHandoffStore(),b=(0,d.createVoiceReloadHandoffStore)({now:()=>Date.now(),createToken:()=>crypto.randomUUID()});i.inject([c.DOOM_CONFIG_SERVICE,u.DOOM_TRANSITION_SERVICE,s.DOOM_VOICE_TOOLS_SERVICE],e=>a.registerDomainVoiceCapabilities((0,s.requireDoomVoiceToolsService)(e),l,v,y,b,()=>e)),i.effect(function*(){r.registerDomainsCommand(l,p,{cordisContext:_,catalog:v,handoffs:y,reloadHandoffs:b,applyDomains:async(e,t)=>(await m.apply()).applyDomains(e,t),loadConfigJournal:m.journal,loadPicker:m.picker}),l.on(`resources_discover`,(e,t)=>({skillPaths:[...(0,o.requireDoomConfigContext)(_()).harness.skillDirectories]})),l.on(`session_shutdown`,(e,t)=>{y.clearSession(t.sessionManager.getSessionId())}),l.on(`session_start`,async(e,t)=>{await g.await();let n=(0,o.requireDoomConfigContext)(_()).harness;t.ui.addAutocompleteProvider(e=>({triggerCharacters:[`,`],async getSuggestions(t,r,i,a){let o=n.root,s=t[r]??``;return(o?await v.completions(o,s.slice(0,i)):void 0)??e.getSuggestions(t,r,i,a)},applyCompletion(t,n,r,i,a){return e.applyCompletion(t,n,r,i,a)},shouldTriggerFileCompletion(t,n,r){return e.shouldTriggerFileCompletion?.(t,n,r)??!0}}))}),yield()=>{y.dispose()}},e.DOMAIN_SOURCE)}async function m(t,n=i.createDomainTelemetry()){let r=await(0,l.connectDoomCordisHost)(t,e.DOMAIN_SOURCE),a=r.root.plugin(p,{pi:t,telemetry:n});try{await a}catch(e){try{await a.dispose()}finally{await r.dispose()}throw e}let o;t.on(`session_shutdown`,()=>o??=(async()=>{try{await a.dispose()}finally{await r.dispose()}})())}exports.default=m,exports.domainsExtension=m;
2
2
  //# sourceMappingURL=extension.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.cjs","names":["createDomainTelemetry","Context","createDomainCatalog","createDomainSwitchHandoffStore","requireDoomConfigContext","DOMAIN_SOURCE"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { registerDomainsCommand } from '../../commands/domainsCommand.ts';\nimport { DOMAIN_SOURCE } from '../../types/domains.ts';\nimport type { DomainTelemetry } from '../../types/telemetry.ts';\nimport { createDomainCatalog } from '../domainCatalog.ts';\nimport { createDomainSwitchHandoffStore } from '../domainSwitchHandoff.ts';\nimport { createDomainTelemetry } from '../telemetry/logSinkTelemetry.ts';\nimport { registerDomainVoiceCapabilities } from './voiceTool.ts';\n\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\n\n/**\n * Everything a switch needs beyond the session, loaded on first use.\n *\n * Every session registers /domains, but most never run it, so the plugin\n * materializer, the resource collector and the picker stay off the startup path.\n */\nfunction lazyModules() {\n let apply: Promise<typeof import('../applyDomains.ts')> | undefined;\n let journal: Promise<typeof import('@agimon-ai/doompi-config/piContext')> | undefined;\n let picker: Promise<typeof import('@agimon-ai/doompi-ui/components/matrixPicker')> | undefined;\n return {\n apply: () => (apply ??= import('../applyDomains.ts')),\n journal: () => (journal ??= import('@agimon-ai/doompi-config/piContext')),\n picker: () => (picker ??= import('@agimon-ai/doompi-ui/components/matrixPicker')),\n };\n}\n\n/**\n * The package's single standard Pi factory, and the owner of the cordis Context.\n *\n * Pi can reload an extension in-process, so every registration is yielded to the\n * fiber and one dispose on session shutdown unwinds them in reverse.\n */\nexport async function domainsExtension(\n pi: ExtensionAPI,\n telemetry: DomainTelemetry = createDomainTelemetry(),\n): Promise<void> {\n const cordis = new Context();\n const load = lazyModules();\n const catalog = createDomainCatalog();\n const handoffs = createDomainSwitchHandoffStore();\n let disposal: Promise<void> | undefined;\n const dispose = (): Promise<void> => {\n disposal ??= cordis.fiber.dispose();\n return disposal;\n };\n\n try {\n cordis.effect(function* () {\n registerDomainVoiceCapabilities(pi, catalog, handoffs);\n registerDomainsCommand(pi, telemetry, {\n catalog,\n handoffs,\n applyDomains: async (domains, state) => (await load.apply()).applyDomains(domains, state),\n loadConfigJournal: load.journal,\n loadPicker: load.picker,\n });\n\n // Pi reloads base resources, then asks extensions to extend them again.\n // Read the rebound context so a domain switch discovers only the new\n // selection.\n pi.on('resources_discover', (_event, ctx) => ({\n skillPaths: [...requireDoomConfigContext(ctx).harness.skillDirectories],\n }));\n\n pi.on('session_shutdown', (_event, ctx) => {\n handoffs.clearSession(ctx.sessionManager.getSessionId());\n });\n\n pi.on('session_start', (_event, ctx) => {\n const state = requireDoomConfigContext(ctx).harness;\n ctx.ui.addAutocompleteProvider((current) => ({\n triggerCharacters: [','],\n async getSuggestions(lines, cursorLine, cursorCol, options) {\n const repoRoot = state.root;\n const line = lines[cursorLine] ?? '';\n const completion = repoRoot ? await catalog.completions(repoRoot, line.slice(0, cursorCol)) : undefined;\n return completion ?? current.getSuggestions(lines, cursorLine, cursorCol, options);\n },\n applyCompletion(lines, cursorLine, cursorCol, item, prefix) {\n return current.applyCompletion(lines, cursorLine, cursorCol, item, prefix);\n },\n shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {\n return current.shouldTriggerFileCompletion?.(lines, cursorLine, cursorCol) ?? true;\n },\n }));\n });\n\n // Pi owns command and hook deregistration across a reload, so the fiber\n // only has to drop the handoffs this package parked for the session.\n yield () => {\n handoffs.dispose();\n };\n }, DOMAIN_SOURCE);\n } catch (error) {\n await dispose();\n throw error;\n }\n\n pi.on(SESSION_SHUTDOWN_EVENT, dispose);\n}\n\nexport default domainsExtension;\n"],"mappings":"qUAmBA,SAAS,GAAc,CACrB,IAAI,EACA,EACA,EACJ,MAAO,CACL,UAAc,IAAA,QAAA,QAAA,CAAA,CAAA,SAAA,QAAU,qBAAA,CAAA,EACxB,YAAgB,IAAY,OAAO,sCACnC,WAAe,IAAW,OAAO,+CACnC,CACF,CAQA,eAAsB,EACpB,EACA,EAA6BA,EAAAA,sBAAsB,EACpC,CACf,IAAM,EAAS,IAAIC,EAAAA,QACb,EAAO,EAAY,EACnB,EAAUC,EAAAA,oBAAoB,EAC9B,EAAWC,EAAAA,+BAA+B,EAC5C,EACE,OACJ,IAAa,EAAO,MAAM,QAAQ,EAC3B,GAGT,GAAI,CACF,EAAO,OAAO,WAAa,CACzB,EAAA,gCAAgC,EAAI,EAAS,CAAQ,EACrD,EAAA,uBAAuB,EAAI,EAAW,CACpC,UACA,WACA,aAAc,MAAO,EAAS,KAAW,MAAM,EAAK,MAAM,EAAA,CAAG,aAAa,EAAS,CAAK,EACxF,kBAAmB,EAAK,QACxB,WAAY,EAAK,MACnB,CAAC,EAKD,EAAG,GAAG,sBAAuB,EAAQ,KAAS,CAC5C,WAAY,CAAC,IAAA,EAAGC,EAAAA,yBAAAA,CAAyB,CAAG,CAAC,CAAC,QAAQ,gBAAgB,CACxE,EAAE,EAEF,EAAG,GAAG,oBAAqB,EAAQ,IAAQ,CACzC,EAAS,aAAa,EAAI,eAAe,aAAa,CAAC,CACzD,CAAC,EAED,EAAG,GAAG,iBAAkB,EAAQ,IAAQ,CACtC,IAAM,GAAA,EAAQA,EAAAA,yBAAAA,CAAyB,CAAG,CAAC,CAAC,QAC5C,EAAI,GAAG,wBAAyB,IAAa,CAC3C,kBAAmB,CAAC,GAAG,EACvB,MAAM,eAAe,EAAO,EAAY,EAAW,EAAS,CAC1D,IAAM,EAAW,EAAM,KACjB,EAAO,EAAM,IAAe,GAElC,OADmB,EAAW,MAAM,EAAQ,YAAY,EAAU,EAAK,MAAM,EAAG,CAAS,CAAC,EAAI,IAAA,KACzE,EAAQ,eAAe,EAAO,EAAY,EAAW,CAAO,CACnF,EACA,gBAAgB,EAAO,EAAY,EAAW,EAAM,EAAQ,CAC1D,OAAO,EAAQ,gBAAgB,EAAO,EAAY,EAAW,EAAM,CAAM,CAC3E,EACA,4BAA4B,EAAO,EAAY,EAAW,CACxD,OAAO,EAAQ,8BAA8B,EAAO,EAAY,CAAS,GAAK,EAChF,CACF,EAAE,CACJ,CAAC,EAID,SAAY,CACV,EAAS,QAAQ,CACnB,CACF,EAAGC,EAAAA,aAAa,CAClB,OAAS,EAAO,CAEd,MADA,MAAM,EAAQ,EACR,CACR,CAEA,EAAG,GAAG,mBAAwB,CAAO,CACvC"}
1
+ {"version":3,"file":"extension.cjs","names":["DOOM_CONFIG_SERVICE","DOOM_TRANSITION_SERVICE","createDomainCatalog","createDomainSwitchHandoffStore","createVoiceReloadHandoffStore","DOOM_VOICE_TOOLS_SERVICE","registerDomainVoiceCapabilities","requireDoomVoiceToolsService","requireDoomConfigContext","DOMAIN_SOURCE","createDomainTelemetry","connectDoomCordisHost"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport { DOOM_CONFIG_SERVICE } from '@agimon-ai/doompi-extension-contracts/config';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_TRANSITION_SERVICE } from '@agimon-ai/doompi-extension-contracts/transition';\nimport {\n DOOM_VOICE_TOOLS_SERVICE,\n requireDoomVoiceToolsService,\n} from '@agimon-ai/doompi-extension-contracts/voice-tools';\nimport { createVoiceReloadHandoffStore } from '@agimon-ai/doompi-extension-contracts/voice-reload-handoff';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { registerDomainsCommand } from '../../commands/domainsCommand.ts';\nimport { DOMAIN_SOURCE } from '../../types/domains.ts';\nimport type { DomainTelemetry } from '../../types/telemetry.ts';\nimport { createDomainCatalog } from '../domainCatalog.ts';\nimport { createDomainSwitchHandoffStore } from '../domainSwitchHandoff.ts';\nimport { createDomainTelemetry } from '../telemetry/logSinkTelemetry.ts';\nimport { registerDomainVoiceCapabilities } from './voiceTool.ts';\n\n/**\n * Everything a switch needs beyond the session, loaded on first use.\n *\n * Every session registers /domains, but most never run it, so the plugin\n * materializer, the resource collector and the picker stay off the startup path.\n */\nfunction lazyModules() {\n let apply: Promise<typeof import('../applyDomains.ts')> | undefined;\n let journal: Promise<typeof import('@agimon-ai/doompi-config/piContext')> | undefined;\n let picker: Promise<typeof import('@agimon-ai/doompi-ui/components/matrixPicker')> | undefined;\n return {\n apply: () => (apply ??= import('../applyDomains.ts')),\n journal: () => (journal ??= import('@agimon-ai/doompi-config/piContext')),\n picker: () => (picker ??= import('@agimon-ai/doompi-ui/components/matrixPicker')),\n };\n}\n\ninterface DomainPluginConfig {\n readonly pi: ExtensionAPI;\n readonly telemetry: DomainTelemetry;\n}\n\nfunction domainPlugin(cordis: Context, { pi, telemetry }: DomainPluginConfig): void {\n const load = lazyModules();\n let activeContext: Context | undefined;\n const runtimeInjection = cordis.inject([DOOM_CONFIG_SERVICE, DOOM_TRANSITION_SERVICE], (context) => {\n activeContext = context;\n return () => {\n if (activeContext === context) activeContext = undefined;\n };\n });\n const requireRuntimeContext = (): Context => {\n if (!activeContext) throw new Error('Doom domain runtime is waiting for the session config service.');\n return activeContext;\n };\n const catalog = createDomainCatalog(requireRuntimeContext);\n const handoffs = createDomainSwitchHandoffStore();\n const reloadHandoffs = createVoiceReloadHandoffStore({\n now: () => Date.now(),\n createToken: () => crypto.randomUUID(),\n });\n cordis.inject([DOOM_CONFIG_SERVICE, DOOM_TRANSITION_SERVICE, DOOM_VOICE_TOOLS_SERVICE], (voiceContext) =>\n registerDomainVoiceCapabilities(\n requireDoomVoiceToolsService(voiceContext),\n pi,\n catalog,\n handoffs,\n reloadHandoffs,\n () => voiceContext,\n ),\n );\n cordis.effect(function* () {\n registerDomainsCommand(pi, telemetry, {\n cordisContext: requireRuntimeContext,\n catalog,\n handoffs,\n reloadHandoffs,\n applyDomains: async (domains, state) => (await load.apply()).applyDomains(domains, state),\n loadConfigJournal: load.journal,\n loadPicker: load.picker,\n });\n\n // Pi reloads base resources, then asks extensions to extend them again.\n // Read the rebound context so a domain switch discovers only the new\n // selection.\n pi.on('resources_discover', (_event, _ctx) => ({\n skillPaths: [...requireDoomConfigContext(requireRuntimeContext()).harness.skillDirectories],\n }));\n\n pi.on('session_shutdown', (_event, ctx) => {\n handoffs.clearSession(ctx.sessionManager.getSessionId());\n });\n\n pi.on('session_start', async (_event, ctx) => {\n await runtimeInjection.await();\n const state = requireDoomConfigContext(requireRuntimeContext()).harness;\n ctx.ui.addAutocompleteProvider((current) => ({\n triggerCharacters: [','],\n async getSuggestions(lines, cursorLine, cursorCol, options) {\n const repoRoot = state.root;\n const line = lines[cursorLine] ?? '';\n const completion = repoRoot ? await catalog.completions(repoRoot, line.slice(0, cursorCol)) : undefined;\n return completion ?? current.getSuggestions(lines, cursorLine, cursorCol, options);\n },\n applyCompletion(lines, cursorLine, cursorCol, item, prefix) {\n return current.applyCompletion(lines, cursorLine, cursorCol, item, prefix);\n },\n shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {\n return current.shouldTriggerFileCompletion?.(lines, cursorLine, cursorCol) ?? true;\n },\n }));\n });\n\n yield () => {\n handoffs.dispose();\n };\n }, DOMAIN_SOURCE);\n}\n\n/** The package's single standard Pi factory. */\nexport async function domainsExtension(\n pi: ExtensionAPI,\n telemetry: DomainTelemetry = createDomainTelemetry(),\n): Promise<void> {\n const connection = await connectDoomCordisHost(pi, DOMAIN_SOURCE);\n const fiber = connection.root.plugin(domainPlugin, { pi, telemetry });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\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\nexport default domainsExtension;\n"],"mappings":"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"}
@@ -1,12 +1,7 @@
1
1
  import { DomainTelemetry } from "../../types/telemetry.cjs";
2
2
  import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  //#region src/adapters/pi/extension.d.ts
4
- /**
5
- * The package's single standard Pi factory, and the owner of the cordis Context.
6
- *
7
- * Pi can reload an extension in-process, so every registration is yielded to the
8
- * fiber and one dispose on session shutdown unwinds them in reverse.
9
- */
4
+ /** The package's single standard Pi factory. */
10
5
  declare function domainsExtension(pi: ExtensionAPI, telemetry?: DomainTelemetry): Promise<void>;
11
6
  //#endregion
12
7
  export { domainsExtension as default, domainsExtension };
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;;;;;;iBAoCsB,iBACpB,IAAI,cACJ,YAAW,kBACV"}
1
+ {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBAuHsB,iBACpB,IAAI,cACJ,YAAW,kBACV"}
@@ -1,12 +1,7 @@
1
1
  import { DomainTelemetry } from "../../types/telemetry.mjs";
2
2
  import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  //#region src/adapters/pi/extension.d.ts
4
- /**
5
- * The package's single standard Pi factory, and the owner of the cordis Context.
6
- *
7
- * Pi can reload an extension in-process, so every registration is yielded to the
8
- * fiber and one dispose on session shutdown unwinds them in reverse.
9
- */
4
+ /** The package's single standard Pi factory. */
10
5
  declare function domainsExtension(pi: ExtensionAPI, telemetry?: DomainTelemetry): Promise<void>;
11
6
  //#endregion
12
7
  export { domainsExtension as default, domainsExtension };
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;;;;;;iBAoCsB,iBACpB,IAAI,cACJ,YAAW,kBACV"}
1
+ {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBAuHsB,iBACpB,IAAI,cACJ,YAAW,kBACV"}