@agimon-ai/doompi-domain 0.0.1-alpha.2 → 0.0.1-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -15
- package/dist/adapters/applyDomains.cjs +1 -1
- package/dist/adapters/applyDomains.cjs.map +1 -1
- package/dist/adapters/applyDomains.d.cts +2 -2
- package/dist/adapters/applyDomains.d.cts.map +1 -1
- package/dist/adapters/applyDomains.d.mts +2 -2
- package/dist/adapters/applyDomains.d.mts.map +1 -1
- package/dist/adapters/applyDomains.mjs +1 -1
- package/dist/adapters/applyDomains.mjs.map +1 -1
- package/dist/adapters/domainCatalog.cjs +1 -1
- package/dist/adapters/domainCatalog.cjs.map +1 -1
- package/dist/adapters/domainCatalog.d.cts +4 -3
- package/dist/adapters/domainCatalog.d.cts.map +1 -1
- package/dist/adapters/domainCatalog.d.mts +4 -3
- package/dist/adapters/domainCatalog.d.mts.map +1 -1
- package/dist/adapters/domainCatalog.mjs +1 -1
- package/dist/adapters/domainCatalog.mjs.map +1 -1
- package/dist/adapters/domainSwitchHandoff.cjs +1 -1
- package/dist/adapters/domainSwitchHandoff.cjs.map +1 -1
- package/dist/adapters/domainSwitchHandoff.mjs +1 -1
- package/dist/adapters/domainSwitchHandoff.mjs.map +1 -1
- package/dist/adapters/mcpFilter.cjs +2 -2
- package/dist/adapters/mcpFilter.mjs +2 -2
- package/dist/adapters/mcpSessionEnvironment.cjs +1 -1
- package/dist/adapters/mcpSessionEnvironment.cjs.map +1 -1
- package/dist/adapters/mcpSessionEnvironment.d.cts +2 -2
- package/dist/adapters/mcpSessionEnvironment.d.cts.map +1 -1
- package/dist/adapters/mcpSessionEnvironment.d.mts +2 -2
- package/dist/adapters/mcpSessionEnvironment.d.mts.map +1 -1
- package/dist/adapters/mcpSessionEnvironment.mjs +1 -1
- package/dist/adapters/mcpSessionEnvironment.mjs.map +1 -1
- package/dist/adapters/pi/extension.cjs +1 -1
- package/dist/adapters/pi/extension.cjs.map +1 -1
- package/dist/adapters/pi/extension.d.cts +2 -6
- package/dist/adapters/pi/extension.d.cts.map +1 -1
- package/dist/adapters/pi/extension.d.mts +2 -6
- package/dist/adapters/pi/extension.d.mts.map +1 -1
- package/dist/adapters/pi/extension.mjs +1 -1
- package/dist/adapters/pi/extension.mjs.map +1 -1
- package/dist/adapters/pi/voiceTool.cjs +1 -1
- package/dist/adapters/pi/voiceTool.cjs.map +1 -1
- package/dist/adapters/pi/voiceTool.d.cts +5 -2
- package/dist/adapters/pi/voiceTool.d.cts.map +1 -1
- package/dist/adapters/pi/voiceTool.d.mts +5 -2
- package/dist/adapters/pi/voiceTool.d.mts.map +1 -1
- package/dist/adapters/pi/voiceTool.mjs +1 -1
- package/dist/adapters/pi/voiceTool.mjs.map +1 -1
- package/dist/adapters/pluginMaterializer.cjs +1 -1
- package/dist/adapters/pluginMaterializer.cjs.map +1 -1
- package/dist/adapters/pluginMaterializer.d.cts.map +1 -1
- package/dist/adapters/pluginMaterializer.d.mts.map +1 -1
- package/dist/adapters/pluginMaterializer.mjs +1 -1
- package/dist/adapters/pluginMaterializer.mjs.map +1 -1
- package/dist/adapters/resourceCollector.cjs +2 -2
- package/dist/adapters/resourceCollector.cjs.map +1 -1
- package/dist/adapters/resourceCollector.d.cts +4 -2
- package/dist/adapters/resourceCollector.d.cts.map +1 -1
- package/dist/adapters/resourceCollector.d.mts +4 -2
- package/dist/adapters/resourceCollector.d.mts.map +1 -1
- package/dist/adapters/resourceCollector.mjs +2 -2
- package/dist/adapters/resourceCollector.mjs.map +1 -1
- package/dist/adapters/skillCacheLocation.cjs +1 -1
- package/dist/adapters/skillCacheLocation.mjs +1 -1
- package/dist/commands/domainsCommand.cjs +1 -1
- package/dist/commands/domainsCommand.cjs.map +1 -1
- package/dist/commands/domainsCommand.d.cts +4 -0
- package/dist/commands/domainsCommand.d.cts.map +1 -1
- package/dist/commands/domainsCommand.d.mts +4 -0
- package/dist/commands/domainsCommand.d.mts.map +1 -1
- package/dist/commands/domainsCommand.mjs +1 -1
- package/dist/commands/domainsCommand.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/mcp.cjs +1 -1
- package/dist/mcp.d.cts +2 -1
- package/dist/mcp.d.mts +2 -1
- package/dist/mcp.mjs +1 -1
- package/dist/resources.cjs +1 -1
- package/dist/resources.d.cts +3 -3
- package/dist/resources.d.mts +3 -3
- package/dist/resources.mjs +1 -1
- package/dist/services/domainText.cjs +1 -1
- package/dist/services/domainText.cjs.map +1 -1
- package/dist/services/domainText.d.cts +5 -1
- package/dist/services/domainText.d.cts.map +1 -1
- package/dist/services/domainText.d.mts +5 -1
- package/dist/services/domainText.d.mts.map +1 -1
- package/dist/services/domainText.mjs +1 -1
- package/dist/services/domainText.mjs.map +1 -1
- package/dist/types/handoff.d.cts +1 -1
- package/dist/types/handoff.d.mts +1 -1
- package/dist/types/resources.d.cts +26 -7
- package/dist/types/resources.d.cts.map +1 -1
- package/dist/types/resources.d.mts +26 -7
- package/dist/types/resources.d.mts.map +1 -1
- package/dist/types/telemetry.d.cts +2 -2
- package/dist/types/telemetry.d.cts.map +1 -1
- package/dist/types/telemetry.d.mts +2 -2
- package/dist/types/telemetry.d.mts.map +1 -1
- package/llms.txt +7 -2
- package/package.json +32 -17
- package/src/prompts/doompi-author-domain/SKILL.md +18 -0
- package/src/prompts/doompi-author-domain/references/domains-contract.md +165 -0
- package/web/index.ts +14 -0
- package/web/tsconfig.json +22 -0
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Domain selection for [DoomPi](https://www.npmjs.com/package/@agimon-ai/doompi) sessions.
|
|
4
4
|
|
|
5
|
-
**Domains** are one of the four axes of a DoomPi selection, alongside the major mode, the profile
|
|
6
|
-
and the minor modes. Unlike a major mode, domains compose: a session selects a set of them, and
|
|
7
|
-
union of what they contribute is what the model sees.
|
|
5
|
+
**Domains** are one of the four axes of a DoomPi selection, alongside the major mode, the profile,
|
|
6
|
+
and the minor modes. Unlike a major mode, domains compose: a session selects a set of them, and
|
|
7
|
+
the union of what they contribute is what the model sees.
|
|
8
8
|
|
|
9
9
|
```yaml
|
|
10
10
|
# .doom/domains.yaml
|
|
@@ -14,12 +14,12 @@ plugins:
|
|
|
14
14
|
infra: plugins/infra
|
|
15
15
|
domains:
|
|
16
16
|
development:
|
|
17
|
-
description: Application code, tests and review tooling.
|
|
17
|
+
description: Application code, tests, and review tooling.
|
|
18
18
|
plugins: [frontend]
|
|
19
19
|
mcp:
|
|
20
20
|
servers: [code-intel]
|
|
21
21
|
platform:
|
|
22
|
-
description: Deployment, infrastructure and incident tooling.
|
|
22
|
+
description: Deployment, infrastructure, and incident tooling.
|
|
23
23
|
plugins: [infra]
|
|
24
24
|
aliases:
|
|
25
25
|
everything: [development, platform]
|
|
@@ -27,7 +27,7 @@ aliases:
|
|
|
27
27
|
|
|
28
28
|
## What it registers
|
|
29
29
|
|
|
30
|
-
| Surface |
|
|
30
|
+
| Surface | Behavior |
|
|
31
31
|
| --------------------------- | ------------------------------------------------------------- |
|
|
32
32
|
| `/domains` | prints the selection, or switches to `name[,name...]` |
|
|
33
33
|
| `/domains` with no argument | opens a multi-select picker in the TUI |
|
|
@@ -36,21 +36,32 @@ aliases:
|
|
|
36
36
|
| autocomplete | comma-triggered completion of domain names inside `/domains` |
|
|
37
37
|
| `resources_discover` | re-answers with the current selection's skills after a reload |
|
|
38
38
|
|
|
39
|
+
When the `@agimon-ai/doompi-help` minor mode is active, this package contributes the
|
|
40
|
+
`doompi-author-domain` skill. It provides package-owned guidance for plugin catalogs, domain
|
|
41
|
+
filters, aliases, defaults, shared skills, and MCP scopes in `domains.yaml`. The contribution is
|
|
42
|
+
registered through the session's `doom/help` Cordis service and is withdrawn with this package's
|
|
43
|
+
plugin fiber.
|
|
44
|
+
|
|
39
45
|
## What a switch stages
|
|
40
46
|
|
|
41
47
|
A domain switch is not just a config write, which is why it does not live in
|
|
42
48
|
`@agimon-ai/doompi-config/selectionSwitch` with the major-mode and profile switches. Applying a
|
|
43
49
|
selection:
|
|
44
50
|
|
|
45
|
-
- materializes any remote (
|
|
51
|
+
- materializes any remote (Git or npm) plugins the selected domains name into a content-addressed
|
|
46
52
|
cache guarded by an install marker;
|
|
47
53
|
- discovers the exact `SKILL.md` files behind the selection, through a manifest cache keyed by
|
|
48
54
|
worktree so a second session does not rescan the tree;
|
|
49
55
|
- adapts each plugin's Claude-authored agent definitions into the form Pi loads, and stages them
|
|
50
56
|
privately (`0600`) beside a dispatcher agent;
|
|
51
|
-
- merges the repository and plugin `.mcp.json` layers,
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
- merges the repository and legacy plugin `.mcp.json` layers, and validates root `mcp.json` for
|
|
58
|
+
schema-gated Agent Plugins v1 (including plugin-relative commands, data directories, and transport
|
|
59
|
+
normalization);
|
|
60
|
+
- applies the selected domains' MCP allowlists and rewrites the proxy's upstream list into a
|
|
61
|
+
private per-run config; and
|
|
62
|
+
- atomically stores an immutable MCP projection in harness state, then reloads Pi. The replacement
|
|
63
|
+
Config factory publishes that snapshot through the session Cordis registry for DoomPi MCP to
|
|
64
|
+
consume; Domain never reaches into the live MCP runtime.
|
|
54
65
|
|
|
55
66
|
An allowlist only takes effect once **every** selected domain declares one. Mixing a scoped domain
|
|
56
67
|
with a domain that has not opted in leaves the session unfiltered, so an unmigrated domain cannot
|
|
@@ -58,15 +69,16 @@ silently lose its tools.
|
|
|
58
69
|
|
|
59
70
|
## Switching from voice
|
|
60
71
|
|
|
61
|
-
The voice tool never applies a switch itself. It plans the transition, parks the validated
|
|
62
|
-
in a TTL-bounded store and sends `/domains --voice-switch-token=…` back as a follow-up,
|
|
63
|
-
happens inside a command handler where it can be the terminal action. The selection
|
|
64
|
-
store rather than in the follow-up text, and the token is bound to the session that
|
|
72
|
+
The voice tool never applies a switch itself. It plans the transition, parks the validated
|
|
73
|
+
selection in a TTL-bounded store, and sends `/domains --voice-switch-token=…` back as a follow-up,
|
|
74
|
+
so the reload happens inside a command handler where it can be the terminal action. The selection
|
|
75
|
+
travels in the store rather than in the follow-up text, and the token is bound to the session that
|
|
76
|
+
minted it.
|
|
65
77
|
|
|
66
78
|
## Installation
|
|
67
79
|
|
|
68
80
|
DoomPi depends on this package and activates it as fixed host core, so a DoomPi install already has
|
|
69
|
-
it. It is not selectable from `.doom/
|
|
81
|
+
it. It is not selectable from `.doom/modes.yaml`; it reads `.doom/domains.yaml`.
|
|
70
82
|
|
|
71
83
|
## License
|
|
72
84
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./mcpFilter.cjs"),n=require("./
|
|
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,agentDirectories:m.agentDirectories,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","
|
|
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 agentDirectories: resources.agentDirectories,\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,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
|
|
8
|
-
*
|
|
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":";;;;;;;;;;;;;;;;;;;;
|
|
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
|
|
8
|
-
*
|
|
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":";;;;;;;;;;;;;;;;;;;;
|
|
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{
|
|
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,agentDirectories:g.agentDirectories,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 {
|
|
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 agentDirectories: resources.agentDirectories,\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,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
|
|
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
|
|
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: (
|
|
14
|
-
describe: (
|
|
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":"
|
|
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: (
|
|
14
|
-
describe: (
|
|
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":"
|
|
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
|
|
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
|
|
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()
|
|
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()
|
|
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":"wDASA,MAAM,EAA+B,OAAO,IAAI,4CAAkC,EAGrE,EAA+B,IAC/B,EAAoC,GACpC,EAA8C,IAC9C,EAA6C,IAmB1D,SAAS,GAA4B,CACnC,IAAM,EAAO,WACb,MAAQ,GAAK,KAAkC,CAAE,QAAS,IAAI,GAAM,CACtE,CAEA,SAAS,EAAQ,EAAsB,CACrC,OAAW,MAAM,iCAAiC,EAAM,EAAE,CAC5D,CAEA,SAAS,EAAoB,EAAwB,CACnD,IAAK,IAAM,KAAa,EAAO,CAC7B,IAAM,EAAO,EAAU,YAAY,CAAC,EACpC,GAAI,IAAS,IAAA,KAAc,GAAQ,IAAQ,IAAS,KAAO,MAAO,EACpE,CACA,MAAO,EACT,CAEA,SAAS,EAAW,EAAe,EAAe,EAAiB,EAA0B,CAC3F,IAAM,EAAO,EAAM,KAAK,EACxB,GAAI,CAAC,GAAQ,EAAK,OAAS,GAAW,EAAoB,CAAI,GAAM,GAAW,CAAC,EAAQ,KAAK,CAAI,EAC/F,MAAM,EAAQ,CAAK,EAErB,OAAO,CACT,CAEA,SAAS,EAAU,EAAuB,CACxC,OAAO,EAAW,EAAO,aAAA,GAAyD,CACpF,CAEA,SAAS,EAAe,EAAuB,CAC7C,OAAO,EAAW,EAAO,kBAAA,GAA8D,CACzF,CAEA,SAAS,EAAY,EAAuB,CAC1C,OAAO,EAAW,EAAO,eAAA,IAA4D,CAAgB,CACvG,CAEA,SAAS,EAAY,EAAuB,CAC1C,OAAO,EAAW,EAAO,uBAAA,IAAqE,CAAgB,CAChH,CAEA,SAAS,EAAM,EAAuB,CACpC,OAAO,EAAW,EAAO,QAAA,IAAsD,CAAgB,CACjG,CAEA,SAAS,EAAQ,EAAqC,CACpD,GAAI,EAAO,OAAA,GACT,MAAU,MAAM,0CAA4E,EAE9F,IAAM,EAAmB,CAAC,EACpB,EAAO,IAAI,IACjB,IAAK,IAAM,KAAS,EAAQ,CAC1B,IAAM,EAAO,EAAW,EAAO,cAAA,IAA2D,CAAgB,EACtG,EAAK,IAAI,CAAI,IACjB,EAAK,IAAI,CAAI,EACb,EAAO,KAAK,CAAI,EAClB,CACA,OAAO,CACT,CAEA,SAAS,EAAM,EAAM,KAAK,IAAI,EAAS,CACrC,IAAK,GAAM,CAAC,EAAK,KAAW,EAAS,CAAC,CAAC,QACjC,EAAO,WAAa,GAAK,EAAS,CAAC,CAAC,QAAQ,OAAO,CAAG,CAE9D,CAEA,SAAS,EAAgB,EAA6B,EAAgD,CAEpG,OADI,EAAO,YAAc,EAAU,EAAS,SAAS,EAC9C,EAAS,iBAAmB,IAAA,IAAa,EAAO,iBAAmB,EAAe,EAAS,cAAc,EADjD,EAEjE,CAEA,SAAS,EAAK,EAAkD,CAC9D,MAAO,CAAE,GAAG,EAAQ,QAAS,CAAC,GAAG,EAAO,OAAO,CAAE,CACnD,CAEA,SAAS,EAAM,EAAyB,EAA0D,CAChG,EAAM,EACN,IAAM,EAAsB,EAAU,EAAQ,SAAS,EACjD,EAA2B,EAAe,EAAQ,cAAc,EAChE,EAAwB,EAAY,EAAQ,WAAW,EACvD,EAAoB,EAAQ,EAAQ,OAAO,EAC3C,EAAwB,EAAY,EAAQ,kBAAkB,EAC9D,EAAY,KAAK,IAAI,EACrB,EAAoC,CACxC,MAAO,sBAAgC,OAAO,WAAW,IACzD,UAAW,EACX,eAAgB,EAChB,YAAa,EACb,QAAS,EACT,mBAAoB,EACpB,YACA,UAAW,EAAY,EACvB,iBACF,EAEA,OADA,EAAS,CAAC,CAAC,QAAQ,IAAI,EAAO,MAAO,CAAM,EACpC,EAAK,CAAM,CACpB,CAEA,SAAS,EAAQ,EAAuC,EAAuD,CAC7G,EAAM,EACN,IAAM,EAAkB,EAAM,CAAY,EACpC,EAAS,EAAS,CAAC,CAAC,QAAQ,IAAI,CAAe,EACjD,MAAC,GAAU,CAAC,EAAgB,EAAQ,CAAQ,KAChD,EAAS,CAAC,CAAC,QAAQ,OAAO,CAAe,EACrC,IAAO,WAAa,KAAK,IAAI,IACjC,OAAO,EAAK,CAAM,CACpB,CAEA,SAAS,EAAQ,EAAuC,EAA+B,CACrF,EAAM,EACN,IAAM,EAAkB,EAAM,CAAY,EACpC,EAAS,EAAS,CAAC,CAAC,QAAQ,IAAI,CAAe,EAErD,MADI,CAAC,GAAU,CAAC,EAAgB,EAAQ,CAAQ,EAAU,GACnD,EAAS,CAAC,CAAC,QAAQ,OAAO,CAAe,CAClD,CAEA,SAAS,EAAkB,EAAyB,EAAoC,CACtF,EAAM,EACN,IAAM,EAAsB,EAAU,CAAkB,EACpD,EAAU,EACd,IAAK,GAAM,CAAC,EAAK,KAAW,EAAS,CAAC,CAAC,QACjC,EAAO,kBAAoB,GAAmB,EAAO,YAAc,IACvE,EAAS,CAAC,CAAC,QAAQ,OAAO,CAAG,EAC7B,GAAW,GAEb,OAAO,CACT,CAEA,SAAS,EAAa,EAAiC,CACrD,IAAI,EAAU,EACd,IAAK,GAAM,CAAC,EAAK,KAAW,EAAS,CAAC,CAAC,QACjC,EAAO,kBAAoB,IAC/B,EAAS,CAAC,CAAC,QAAQ,OAAO,CAAG,EAC7B,GAAW,GAEb,OAAO,CACT,CAEA,SAAgB,GAA2D,CACzE,IAAM,EAAkB,OAAO,WAAW,EACtC,EAAW,GACf,MAAO,CACL,kBACA,MAAM,EAAS,CACb,GAAI,EAAU,MAAU,MAAM,0CAA0C,EACxE,OAAO,EAAM,EAAiB,CAAO,CACvC,EACA,QAAQ,EAAc,EAAU,CAC1B,MACJ,OAAO,EAAQ,EAAU,CAAY,CACvC,EACA,QAAQ,EAAc,EAAU,CAE9B,MADA,CAAI,GACG,EAAQ,EAAU,CAAY,CACvC,EACA,aAAa,EAAoB,CAE/B,OADI,EAAiB,EACd,EAAkB,EAAiB,CAAkB,CAC9D,EACA,SAAU,CAGR,OAFI,EAAiB,GACrB,EAAW,GACJ,EAAa,CAAe,EACrC,CACF,CACF"}
|
|
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":"wDASA,MAAM,EAA+B,OAAO,IAAI,4CAAkC,EAGrE,EAA+B,IAC/B,EAAoC,GACpC,EAA8C,IAC9C,EAA6C,IAmB1D,SAAS,GAA4B,CACnC,IAAM,EAAO,WACb,MAAQ,GAAK,KAAkC,CAAE,QAAS,IAAI,GAAM,CACtE,CAEA,SAAS,EAAQ,EAAsB,CACrC,OAAW,MAAM,iCAAiC,EAAM,EAAE,CAC5D,CAEA,SAAS,EAAoB,EAAwB,CACnD,IAAK,IAAM,KAAa,EAAO,CAC7B,IAAM,EAAO,EAAU,YAAY,CAAC,EACpC,GAAI,IAAS,IAAA,KAAc,GAAQ,IAAQ,IAAS,KAAO,MAAO,EACpE,CACA,MAAO,EACT,CAEA,SAAS,EAAW,EAAgB,EAAe,EAAiB,EAA0B,CAC5F,IAAM,EAAO,OAAO,GAAU,SAAW,EAAM,KAAK,EAAI,GACxD,GAAI,CAAC,GAAQ,EAAK,OAAS,GAAW,EAAoB,CAAI,GAAM,GAAW,CAAC,EAAQ,KAAK,CAAI,EAC/F,MAAM,EAAQ,CAAK,EAErB,OAAO,CACT,CAEA,SAAS,EAAU,EAAuB,CACxC,OAAO,EAAW,EAAO,aAAA,GAAyD,CACpF,CAEA,SAAS,EAAe,EAAuB,CAC7C,OAAO,EAAW,EAAO,kBAAA,GAA8D,CACzF,CAEA,SAAS,EAAY,EAAuB,CAC1C,OAAO,EAAW,EAAO,eAAA,IAA4D,CAAgB,CACvG,CAEA,SAAS,EAAY,EAAuB,CAC1C,OAAO,EAAW,EAAO,uBAAA,IAAqE,CAAgB,CAChH,CAEA,SAAS,EAAM,EAAuB,CACpC,OAAO,EAAW,EAAO,QAAA,IAAsD,CAAgB,CACjG,CAEA,SAAS,EAAQ,EAAqC,CACpD,GAAI,EAAO,OAAA,GACT,MAAU,MAAM,0CAA4E,EAE9F,IAAM,EAAmB,CAAC,EACpB,EAAO,IAAI,IACjB,IAAK,IAAM,KAAS,EAAQ,CAC1B,IAAM,EAAO,EAAW,EAAO,cAAA,IAA2D,CAAgB,EACtG,EAAK,IAAI,CAAI,IACjB,EAAK,IAAI,CAAI,EACb,EAAO,KAAK,CAAI,EAClB,CACA,OAAO,CACT,CAEA,SAAS,EAAM,EAAM,KAAK,IAAI,EAAS,CACrC,IAAK,GAAM,CAAC,EAAK,KAAW,EAAS,CAAC,CAAC,QACjC,EAAO,WAAa,GAAK,EAAS,CAAC,CAAC,QAAQ,OAAO,CAAG,CAE9D,CAEA,SAAS,EAAgB,EAA6B,EAAgD,CAEpG,OADI,EAAO,YAAc,EAAU,EAAS,SAAS,GAC9C,EAAO,iBAAmB,EAAe,EAAS,cAAc,CACzE,CAEA,SAAS,EAAK,EAAkD,CAC9D,MAAO,CAAE,GAAG,EAAQ,QAAS,CAAC,GAAG,EAAO,OAAO,CAAE,CACnD,CAEA,SAAS,EAAM,EAAyB,EAA0D,CAChG,EAAM,EACN,IAAM,EAAsB,EAAU,EAAQ,SAAS,EACjD,EAA2B,EAAe,EAAQ,cAAc,EAChE,EAAwB,EAAY,EAAQ,WAAW,EACvD,EAAoB,EAAQ,EAAQ,OAAO,EAC3C,EAAwB,EAAY,EAAQ,kBAAkB,EAC9D,EAAY,KAAK,IAAI,EACrB,EAAoC,CACxC,MAAO,sBAAgC,OAAO,WAAW,IACzD,UAAW,EACX,eAAgB,EAChB,YAAa,EACb,QAAS,EACT,mBAAoB,EACpB,YACA,UAAW,EAAY,EACvB,iBACF,EAEA,OADA,EAAS,CAAC,CAAC,QAAQ,IAAI,EAAO,MAAO,CAAM,EACpC,EAAK,CAAM,CACpB,CAEA,SAAS,EAAQ,EAAuC,EAAuD,CAC7G,EAAM,EACN,IAAM,EAAkB,EAAM,CAAY,EACpC,EAAS,EAAS,CAAC,CAAC,QAAQ,IAAI,CAAe,EACjD,MAAC,GAAU,CAAC,EAAgB,EAAQ,CAAQ,KAChD,EAAS,CAAC,CAAC,QAAQ,OAAO,CAAe,EACrC,IAAO,WAAa,KAAK,IAAI,IACjC,OAAO,EAAK,CAAM,CACpB,CAEA,SAAS,EAAQ,EAAuC,EAA+B,CACrF,EAAM,EACN,IAAM,EAAkB,EAAM,CAAY,EACpC,EAAS,EAAS,CAAC,CAAC,QAAQ,IAAI,CAAe,EAErD,MADI,CAAC,GAAU,CAAC,EAAgB,EAAQ,CAAQ,EAAU,GACnD,EAAS,CAAC,CAAC,QAAQ,OAAO,CAAe,CAClD,CAEA,SAAS,EAAkB,EAAyB,EAAoC,CACtF,EAAM,EACN,IAAM,EAAsB,EAAU,CAAkB,EACpD,EAAU,EACd,IAAK,GAAM,CAAC,EAAK,KAAW,EAAS,CAAC,CAAC,QACjC,EAAO,kBAAoB,GAAmB,EAAO,YAAc,IACvE,EAAS,CAAC,CAAC,QAAQ,OAAO,CAAG,EAC7B,GAAW,GAEb,OAAO,CACT,CAEA,SAAS,EAAa,EAAiC,CACrD,IAAI,EAAU,EACd,IAAK,GAAM,CAAC,EAAK,KAAW,EAAS,CAAC,CAAC,QACjC,EAAO,kBAAoB,IAC/B,EAAS,CAAC,CAAC,QAAQ,OAAO,CAAG,EAC7B,GAAW,GAEb,OAAO,CACT,CAEA,SAAgB,GAA2D,CACzE,IAAM,EAAkB,OAAO,WAAW,EACtC,EAAW,GACf,MAAO,CACL,kBACA,MAAM,EAAS,CACb,GAAI,EAAU,MAAU,MAAM,0CAA0C,EACxE,OAAO,EAAM,EAAiB,CAAO,CACvC,EACA,QAAQ,EAAc,EAAU,CAC1B,MACJ,OAAO,EAAQ,EAAU,CAAY,CACvC,EACA,QAAQ,EAAc,EAAU,CAE9B,MADA,CAAI,GACG,EAAQ,EAAU,CAAY,CACvC,EACA,aAAa,EAAoB,CAE/B,OADI,EAAiB,EACd,EAAkB,EAAiB,CAAkB,CAC9D,EACA,SAAU,CAGR,OAFI,EAAiB,GACrB,EAAW,GACJ,EAAa,CAAe,EACrC,CACF,CACF"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:
|
|
2
|
-
`)}${(0,i.stringify)({...n,mcpServers:a})}`}async function d(e,
|
|
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
|
|
2
|
-
`)}${a({...n,mcpServers:o})}`}async function f(
|
|
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
|
-
let e=require("@agimon-ai/doompi-mcp");function t(t){let n={repoRoot:t.repoRoot,stagingDirectory:t.stagingDirectory,...t.generatedConfigPath?{generatedConfigPath:t.generatedConfigPath}:{},...t.pluginConfigPaths?.length?{pluginConfigPaths:[...t.pluginConfigPaths]}:{},...t.allowlist?{allowlist:structuredClone(t.allowlist)}:{}};return(0,e.
|
|
1
|
+
let e=require("@agimon-ai/doompi-extension-contracts/mcp-session");function t(t){let n={repoRoot:t.repoRoot,stagingDirectory:t.stagingDirectory,...t.generatedConfigPath?{generatedConfigPath:t.generatedConfigPath}:{},...t.pluginConfigPaths?.length?{pluginConfigPaths:[...t.pluginConfigPaths]}:{},...t.allowlist?{allowlist:structuredClone(t.allowlist)}:{}};return(0,e.doomMcpSessionEnvironment)(n)}exports.mcpSessionEnvironment=t;
|
|
2
2
|
//# sourceMappingURL=mcpSessionEnvironment.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcpSessionEnvironment.cjs","names":["
|
|
1
|
+
{"version":3,"file":"mcpSessionEnvironment.cjs","names":["doomMcpSessionEnvironment"],"sources":["../../src/adapters/mcpSessionEnvironment.ts"],"sourcesContent":["import {\n type DoomMcpSessionAllowlist,\n type DoomMcpSessionConfig,\n doomMcpSessionEnvironment,\n} from '@agimon-ai/doompi-extension-contracts/mcp-session';\n\nexport interface McpSessionEnvironmentInput {\n readonly repoRoot: string;\n readonly stagingDirectory: string;\n readonly generatedConfigPath?: string;\n readonly pluginConfigPaths?: readonly string[];\n readonly allowlist?: DoomMcpSessionAllowlist;\n}\n\n/** Projects the complete domain-selected MCP picture into the Pi session environment. */\nexport function mcpSessionEnvironment(input: McpSessionEnvironmentInput): Record<string, string> {\n const configuration: DoomMcpSessionConfig = {\n repoRoot: input.repoRoot,\n stagingDirectory: input.stagingDirectory,\n ...(input.generatedConfigPath ? { generatedConfigPath: input.generatedConfigPath } : {}),\n ...(input.pluginConfigPaths?.length ? { pluginConfigPaths: [...input.pluginConfigPaths] } : {}),\n ...(input.allowlist ? { allowlist: structuredClone(input.allowlist) } : {}),\n };\n return doomMcpSessionEnvironment(configuration);\n}\n"],"mappings":"mEAeA,SAAgB,EAAsB,EAA2D,CAC/F,IAAM,EAAsC,CAC1C,SAAU,EAAM,SAChB,iBAAkB,EAAM,iBACxB,GAAI,EAAM,oBAAsB,CAAE,oBAAqB,EAAM,mBAAoB,EAAI,CAAC,EACtF,GAAI,EAAM,mBAAmB,OAAS,CAAE,kBAAmB,CAAC,GAAG,EAAM,iBAAiB,CAAE,EAAI,CAAC,EAC7F,GAAI,EAAM,UAAY,CAAE,UAAW,gBAAgB,EAAM,SAAS,CAAE,EAAI,CAAC,CAC3E,EACA,OAAA,EAAOA,EAAAA,0BAAAA,CAA0B,CAAa,CAChD"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DoomMcpSessionAllowlist } from "@agimon-ai/doompi-extension-contracts/mcp-session";
|
|
2
2
|
//#region src/adapters/mcpSessionEnvironment.d.ts
|
|
3
3
|
interface McpSessionEnvironmentInput {
|
|
4
4
|
readonly repoRoot: string;
|
|
5
5
|
readonly stagingDirectory: string;
|
|
6
6
|
readonly generatedConfigPath?: string;
|
|
7
7
|
readonly pluginConfigPaths?: readonly string[];
|
|
8
|
-
readonly allowlist?:
|
|
8
|
+
readonly allowlist?: DoomMcpSessionAllowlist;
|
|
9
9
|
}
|
|
10
10
|
/** Projects the complete domain-selected MCP picture into the Pi session environment. */
|
|
11
11
|
declare function mcpSessionEnvironment(input: McpSessionEnvironmentInput): Record<string, string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcpSessionEnvironment.d.cts","names":[],"sources":["../../src/adapters/mcpSessionEnvironment.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"mcpSessionEnvironment.d.cts","names":[],"sources":["../../src/adapters/mcpSessionEnvironment.ts"],"mappings":";;UAMiB;WACN;WACA;WACA;WACA;WACA,YAAY;;;iBAIP,sBAAsB,OAAO,6BAA6B"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DoomMcpSessionAllowlist } from "@agimon-ai/doompi-extension-contracts/mcp-session";
|
|
2
2
|
//#region src/adapters/mcpSessionEnvironment.d.ts
|
|
3
3
|
interface McpSessionEnvironmentInput {
|
|
4
4
|
readonly repoRoot: string;
|
|
5
5
|
readonly stagingDirectory: string;
|
|
6
6
|
readonly generatedConfigPath?: string;
|
|
7
7
|
readonly pluginConfigPaths?: readonly string[];
|
|
8
|
-
readonly allowlist?:
|
|
8
|
+
readonly allowlist?: DoomMcpSessionAllowlist;
|
|
9
9
|
}
|
|
10
10
|
/** Projects the complete domain-selected MCP picture into the Pi session environment. */
|
|
11
11
|
declare function mcpSessionEnvironment(input: McpSessionEnvironmentInput): Record<string, string>;
|