@khorsheed/dsh-local-agent-dsh 0.2.0-rc.1 → 0.2.0
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/CHANGELOG.md +16 -0
- package/README.en.md +23 -136
- package/README.i18n.yaml +2 -2
- package/README.md +23 -136
- package/lib/client.js +155 -83
- package/lib/{dsh-cli-provider-UhWvivte.js → dsh-cli-provider-B7s3zH0m.js} +108 -27
- package/lib/dsh-cli-provider-DYRuRpVz.js +1920 -0
- package/lib/dsh-cli-provider.js +1 -1
- package/lib/index.js +40 -27
- package/lib/provision.js +327 -152
- package/lib/types/client/SettingsCard.d.ts +41 -13
- package/lib/types/client/SettingsCard.js +22 -6
- package/lib/types/client/icons.d.ts +1 -1
- package/lib/types/client/index.d.ts +5 -27
- package/lib/types/client/index.js +87 -44
- package/lib/types/client/locales.js +2 -2
- package/lib/types/dsh-cli-provider.js +82 -47
- package/lib/types/index.d.ts +9 -1
- package/lib/types/index.js +35 -21
- package/lib/types/live-driver.js +72 -54
- package/lib/types/model-catalog.d.ts +1 -0
- package/lib/types/model-catalog.js +9 -4
- package/lib/types/model-launch.d.ts +14 -0
- package/lib/types/model-launch.js +58 -0
- package/lib/types/provision.d.ts +166 -119
- package/lib/types/provision.js +376 -166
- package/lib/types/session-mirror.d.ts +55 -0
- package/lib/types/session-mirror.js +155 -1
- package/locale/en.json +2 -2
- package/locale/zh.json +2 -2
- package/package.json +27 -25
- package/lib/invariant.js +0 -33
- package/lib/types/invariant.d.ts +0 -16
- package/lib/types/invariant.js +0 -37
package/lib/provision.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { dirname, isAbsolute, join } from "node:path";
|
|
3
|
-
import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { homedir } from "node:os";
|
|
2
|
+
import { dirname, isAbsolute, join, relative } from "node:path";
|
|
3
|
+
import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
5
4
|
import yaml from "js-yaml";
|
|
6
5
|
//#region lib/types/provision.js
|
|
7
6
|
/**
|
|
@@ -11,29 +10,38 @@ import yaml from "js-yaml";
|
|
|
11
10
|
* `<scoped home>/profiles/<name>` — a manifest listing the official
|
|
12
11
|
* `@deepseek-ai/dsh-base` layer, the family headless composition as the
|
|
13
12
|
* profile's own patch layer (copied verbatim from the headless bundle's
|
|
14
|
-
* `cordis.patch.yml`), and
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* `cordis.patch.yml`), and `@khorsheed/dsh-local-agent-dsh-headless` in
|
|
14
|
+
* the sub-profile's node_modules (the loader resolves the patch's insert
|
|
15
|
+
* rows through it) — a copy of the bundle for the host profile, a symlink
|
|
16
|
+
* for the `-unit` twin and for the pinned single-profile form (see
|
|
17
|
+
* `HeadlessBundleMount`).
|
|
17
18
|
* Everything else the boot needs (dsh-base and its whole dependency graph,
|
|
18
19
|
* cordis, the healed `profiles/node_modules` fallback) is resolved from the
|
|
19
20
|
* dsh installation anchor automatically, so provisioning costs no pnpm
|
|
20
21
|
* install.
|
|
21
22
|
*
|
|
22
|
-
* A sub-profile may additionally
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
23
|
+
* A sub-profile may additionally compose an AGENT PRESET (host 0.2.0's
|
|
24
|
+
* declarative form): the bundle package that declares the preset — an
|
|
25
|
+
* `@deepseek-ai/dsh-agent-preset` row in its own bundle patch — joins the
|
|
26
|
+
* manifest's bundle list, and one appended patch layer inserts
|
|
27
|
+
* `@deepseek-ai/dsh-agent-preset-registry` with that preset as its
|
|
28
|
+
* `default`. Without it the sub-dsh's model-facing rows come from the host
|
|
29
|
+
* plane and the agent reads them off the global layer — which is why "the
|
|
30
|
+
* same harness under two presets" was not a thing a condition could ask for.
|
|
31
|
+
* With it, the scope directory IS the capability face: two scopes naming two
|
|
32
|
+
* presets are two subjects, and the difference is files a person can read.
|
|
29
33
|
*
|
|
30
|
-
* Where the preset
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
34
|
+
* Where the preset PACKAGE lives is the scope's business too. The scope keeps
|
|
35
|
+
* its own copy at `<scoped home>/preset-packages/<id>` and the sub-profile
|
|
36
|
+
* resolves it through a RELATIVE symlink in its own `node_modules`, so the
|
|
37
|
+
* package is found identically on the host and inside an evaluation unit
|
|
38
|
+
* (which bind-mounts the scope, and nothing else). The copy comes from the
|
|
39
|
+
* evaluation instance's own installation of the same package, which is also
|
|
40
|
+
* what the instance's capability catalog measures. `provisionDshScope` below
|
|
41
|
+
* is the entry that makes the copy, records what the scope composes in a
|
|
42
|
+
* file of its own, and composes it. (0.1.x wrote a roster row for
|
|
43
|
+
* `@deepseek-ai/dsh-agent-presets` and a `<scoped home>/.agent-presets/<id>`
|
|
44
|
+
* directory instead; 0.2.0 ships neither the roster nor the directory scan.)
|
|
37
45
|
*
|
|
38
46
|
* It may also carry a PERMISSION BOUNDARY: one appended patch layer pinning
|
|
39
47
|
* `sandbox-policy`'s mode and `user-approval`'s policy to one of dsh's own
|
|
@@ -42,7 +50,7 @@ import yaml from "js-yaml";
|
|
|
42
50
|
* sub-dsh on a developer's machine and the wrong one inside an evaluation
|
|
43
51
|
* unit, where the container IS the boundary and nobody is there to answer an
|
|
44
52
|
* approval prompt. The layer is a FILE in the scope directory for the same
|
|
45
|
-
* reason the
|
|
53
|
+
* reason the preset layer is: a scoped home bind-mounted into a unit carries its
|
|
46
54
|
* own boundary with it, and the value never has to ride the spawn env (where
|
|
47
55
|
* every name enters the unit's composite fingerprint) or be baked into an
|
|
48
56
|
* image.
|
|
@@ -74,40 +82,44 @@ const APPROVAL_FOR = {
|
|
|
74
82
|
"workspace-write": "ask",
|
|
75
83
|
"danger-full-access": "never"
|
|
76
84
|
};
|
|
77
|
-
/** The
|
|
78
|
-
const
|
|
85
|
+
/** The registry row's id inside the sub-profile patch. */
|
|
86
|
+
const PRESET_REGISTRY_ROW_ID = "agent-preset-registry";
|
|
79
87
|
/**
|
|
80
|
-
* The
|
|
88
|
+
* The registry package the registry row names.
|
|
81
89
|
*
|
|
82
90
|
* Deliberately NOT symlinked into the sub-profile the way the headless bundle
|
|
83
91
|
* is. The headless bundle is a `@khorsheed` package the dsh installation does
|
|
84
|
-
* not carry, so it must be linked in from the plugin installation; the
|
|
85
|
-
* is an OFFICIAL package, already in the installation anchor's
|
|
86
|
-
* `@deepseek-ai/dsh-base`. Linking a second copy would give it
|
|
87
|
-
* `@deepseek-ai/cordis` — and cordis does service lookup and type
|
|
88
|
-
* instance identity, so the symptom would be silently missing
|
|
89
|
-
* than an error
|
|
90
|
-
*
|
|
91
|
-
* roster gets the loader's own "cannot resolve" message, which names the
|
|
92
|
+
* not carry, so it must be linked in from the plugin installation; the
|
|
93
|
+
* registry is an OFFICIAL package, already in the installation anchor's
|
|
94
|
+
* closure beside `@deepseek-ai/dsh-base`. Linking a second copy would give it
|
|
95
|
+
* a second `@deepseek-ai/cordis` — and cordis does service lookup and type
|
|
96
|
+
* checks by instance identity, so the symptom would be silently missing
|
|
97
|
+
* services rather than an error. A deployment whose anchor genuinely lacks the
|
|
98
|
+
* registry gets the loader's own "cannot resolve" message, which names the
|
|
92
99
|
* module better than provisioning could.
|
|
93
100
|
*/
|
|
94
|
-
const
|
|
95
|
-
/**
|
|
101
|
+
const PRESET_REGISTRY_MODULE = "@deepseek-ai/dsh-agent-preset-registry";
|
|
102
|
+
/** The declarative preset row a preset bundle package carries in its patch. */
|
|
103
|
+
const PRESET_ROW_MODULE = "@deepseek-ai/dsh-agent-preset";
|
|
104
|
+
/** A preset id doubles as a directory name inside the scope. */
|
|
96
105
|
const SUB_PROFILE_PRESET_ID_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
97
|
-
/**
|
|
98
|
-
const
|
|
106
|
+
/** An npm package name the sub-profile manifest may list as a bundle. */
|
|
107
|
+
const PACKAGE_NAME_RE = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/;
|
|
108
|
+
/** Header line that opens the generated preset layer (the parse anchor). */
|
|
109
|
+
const PRESET_BLOCK_HEADER = "# --- agent preset (written by local-agent-dsh provisioning) ---";
|
|
99
110
|
/** Header line that opens the generated permission layer (the parse anchor). */
|
|
100
111
|
const PERMISSION_BLOCK_HEADER = "# --- permission boundary (written by local-agent-dsh provisioning) ---";
|
|
101
112
|
/** The sub-profile's bundle layer list: the official base layer. The headless
|
|
102
113
|
* composition rides the profile's own patch layer (see the module doc). */
|
|
103
114
|
const SUB_PROFILE_BUNDLES = ["@deepseek-ai/dsh-base"];
|
|
104
115
|
/** The sub-profile manifest the boot reads. */
|
|
105
|
-
function subProfileManifest(profileName) {
|
|
116
|
+
function subProfileManifest(profileName, presetPackage) {
|
|
117
|
+
const bundles = presetPackage === void 0 ? [...SUB_PROFILE_BUNDLES] : [...SUB_PROFILE_BUNDLES, presetPackage];
|
|
106
118
|
return JSON.stringify({
|
|
107
119
|
name: `dsh-profile-${profileName}`,
|
|
108
120
|
private: true,
|
|
109
121
|
dependencies: {},
|
|
110
|
-
dsh: { profile: { bundles
|
|
122
|
+
dsh: { profile: { bundles } }
|
|
111
123
|
}, void 0, 2) + "\n";
|
|
112
124
|
}
|
|
113
125
|
/**
|
|
@@ -122,55 +134,51 @@ function resolveHeadlessBundleDir(config = {}) {
|
|
|
122
134
|
const manifestPath = createRequire(import.meta.url).resolve("@khorsheed/dsh-local-agent-dsh-headless/package.json");
|
|
123
135
|
return dirname(manifestPath);
|
|
124
136
|
}
|
|
125
|
-
/** YAML-quote a scalar the
|
|
137
|
+
/** YAML-quote a scalar the generated layers carry (ids are operator input). */
|
|
126
138
|
function yamlString(value) {
|
|
127
139
|
return JSON.stringify(value);
|
|
128
140
|
}
|
|
129
141
|
/**
|
|
130
|
-
* The
|
|
142
|
+
* The preset layer appended to the sub-profile's patch, or `''` when the
|
|
131
143
|
* sub-profile composes no preset.
|
|
132
144
|
*
|
|
145
|
+
* One insert of the 0.2.0 preset registry with the preset as its `default`:
|
|
146
|
+
* every sub-dsh session that names no preset joins this one (the headless
|
|
147
|
+
* agent loader mounts it through the registry's `mount`). The preset's own
|
|
148
|
+
* declaration rides the bundle layer — the package that declares it is in
|
|
149
|
+
* the manifest's bundle list — so this layer never repeats the composition.
|
|
150
|
+
*
|
|
133
151
|
* It is a SEPARATE patch operation appended after the headless bundle's own
|
|
134
152
|
* list, which is what keeps the two independent: an upgraded headless bundle
|
|
135
|
-
* rewrites its half and the
|
|
153
|
+
* rewrites its half and the preset survives, and dropping the preset drops
|
|
136
154
|
* exactly these lines.
|
|
137
|
-
* @param preset - the
|
|
155
|
+
* @param preset - the preset to compose, or undefined for none.
|
|
138
156
|
* @returns the YAML text to append (already newline-terminated), or `''`.
|
|
139
157
|
*/
|
|
140
|
-
function
|
|
158
|
+
function presetRegistryLayer(preset) {
|
|
141
159
|
if (preset === void 0) return "";
|
|
142
160
|
if (!SUB_PROFILE_PRESET_ID_RE.test(preset.id)) throw new Error(`provision-dsh: preset id ${JSON.stringify(preset.id)} must match ${String(SUB_PROFILE_PRESET_ID_RE)} (it is a directory name)`);
|
|
143
|
-
|
|
161
|
+
return `${[
|
|
144
162
|
"",
|
|
145
163
|
PRESET_BLOCK_HEADER,
|
|
146
164
|
"# The sub-dsh composes every session from this preset; the headless agent",
|
|
147
|
-
"# loader
|
|
148
|
-
"#
|
|
165
|
+
"# loader mounts it through the registry. Remove this layer and the agent",
|
|
166
|
+
"# falls back to the global layer.",
|
|
149
167
|
"- insert:",
|
|
150
|
-
` - id: ${
|
|
151
|
-
` name: '${
|
|
168
|
+
` - id: ${PRESET_REGISTRY_ROW_ID}`,
|
|
169
|
+
` name: '${PRESET_REGISTRY_MODULE}'`,
|
|
152
170
|
" config:",
|
|
153
171
|
` default: ${yamlString(preset.id)}`
|
|
154
|
-
]
|
|
155
|
-
if (preset.includeShippedRoot !== void 0) lines.push(` includeShippedRoot: ${String(preset.includeShippedRoot)}`);
|
|
156
|
-
if (preset.includeUserRoot !== void 0) lines.push(` includeUserRoot: ${String(preset.includeUserRoot)}`);
|
|
157
|
-
if (preset.roots !== void 0 && preset.roots.length > 0) {
|
|
158
|
-
lines.push(" roots:");
|
|
159
|
-
for (const root of preset.roots) {
|
|
160
|
-
lines.push(` - path: ${yamlString(root.path)}`);
|
|
161
|
-
if (root.trust !== void 0) lines.push(` trust: ${root.trust}`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return `${lines.join("\n")}\n`;
|
|
172
|
+
].join("\n")}\n`;
|
|
165
173
|
}
|
|
166
174
|
/**
|
|
167
175
|
* The preset id a provisioned sub-profile's patch composes, read back from
|
|
168
|
-
* the file. The
|
|
176
|
+
* the file. The preset layer is generated, so it is also parseable: a lock
|
|
169
177
|
* check or a status surface can answer "which preset does this scope run?"
|
|
170
178
|
* without booting the sub-dsh.
|
|
171
179
|
* @param homeDir - the harness scoped home.
|
|
172
180
|
* @param profileName - the sub-profile name (default {@link DEFAULT_SUB_PROFILE_NAME}).
|
|
173
|
-
* @returns the preset id, or undefined when the sub-profile composes no
|
|
181
|
+
* @returns the preset id, or undefined when the sub-profile composes no preset.
|
|
174
182
|
*/
|
|
175
183
|
function readSubProfilePreset(homeDir, profileName = DEFAULT_SUB_PROFILE_NAME) {
|
|
176
184
|
const patchPath = join(homeDir, "profiles", profileName, PROFILE_PATCH_FILENAME);
|
|
@@ -267,57 +275,114 @@ function ensureSymlink(link, target) {
|
|
|
267
275
|
rmSync(link, { force: true });
|
|
268
276
|
symlinkSync(target, link);
|
|
269
277
|
}
|
|
278
|
+
/** Suffix of the evaluation-unit twin of a sub-profile (see `unitHeadlessBundleDir`). */
|
|
279
|
+
const UNIT_PROFILE_SUFFIX = "-unit";
|
|
280
|
+
/**
|
|
281
|
+
* The profile name an evaluation UNIT launches for this config: the
|
|
282
|
+
* `-unit` twin when one is configured, else the one sub-profile.
|
|
283
|
+
* @param config - provisioning config.
|
|
284
|
+
* @returns the profile name a container round passes to `--profile`.
|
|
285
|
+
*/
|
|
286
|
+
function unitSubProfileName(config = {}) {
|
|
287
|
+
const profileName = config.profileName ?? "headless-local-agent-dsh";
|
|
288
|
+
return config.unitHeadlessBundleDir === void 0 ? profileName : `${profileName}${UNIT_PROFILE_SUFFIX}`;
|
|
289
|
+
}
|
|
290
|
+
/** Write one file only when its content differs. */
|
|
291
|
+
function writeIfChanged(path, content) {
|
|
292
|
+
if (!existsSync(path) || readFileSync(path, "utf8") !== content) writeFileSync(path, content);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Make `target` a byte-for-byte copy of the headless bundle `source`. A
|
|
296
|
+
* symlink left by an earlier scheme is replaced; an identical copy is left
|
|
297
|
+
* untouched, so re-provisioning before every round costs one comparison.
|
|
298
|
+
*/
|
|
299
|
+
function ensureBundleCopy(target, source) {
|
|
300
|
+
let stat;
|
|
301
|
+
try {
|
|
302
|
+
stat = lstatSync(target);
|
|
303
|
+
} catch (error) {
|
|
304
|
+
if (error.code !== "ENOENT") throw error;
|
|
305
|
+
}
|
|
306
|
+
if (stat?.isSymbolicLink() === true) rmSync(target, { force: true });
|
|
307
|
+
else if (stat !== void 0 && presetTreesEqual(source, target)) return;
|
|
308
|
+
if (presetTreeFiles(source) === void 0) throw new Error(`provision-dsh: headless bundle ${source} is missing or not a plain tree of files`);
|
|
309
|
+
mirrorPresetTree(source, target);
|
|
310
|
+
}
|
|
311
|
+
/** Provision one sub-profile directory against one headless bundle directory. */
|
|
312
|
+
function provisionOneSubProfile(homeDir, profileName, bundleDir, patchSourceDir, config, mount) {
|
|
313
|
+
const profileDir = join(homeDir, "profiles", profileName);
|
|
314
|
+
mkdirSync(profileDir, { recursive: true });
|
|
315
|
+
const presetPackage = config.preset?.package;
|
|
316
|
+
if (presetPackage !== void 0 && !PACKAGE_NAME_RE.test(presetPackage)) throw new Error(`provision-dsh: preset package name ${JSON.stringify(presetPackage)} is not an npm package name`);
|
|
317
|
+
writeIfChanged(join(profileDir, "package.json"), subProfileManifest(profileName, presetPackage));
|
|
318
|
+
writeIfChanged(join(profileDir, PROFILE_PATCH_FILENAME), readFileSync(join(patchSourceDir, HEADLESS_PATCH_FILENAME), "utf8") + presetRegistryLayer(config.preset) + permissionBoundaryLayer(config.permissions) + (config.inheritModelConfiguration ? "\n# Selected local model; values and credentials stay in the launch environment.\n- id: llm-pi-ai\n config:\n providers: !!js JSON.parse(process.env.DSH_LOCAL_AGENT_MODEL_CONFIG || \"{}\").piAi?.providers ?? {}\n- id: llm-deepseek\n config: !!js JSON.parse(process.env.DSH_LOCAL_AGENT_MODEL_CONFIG || \"{}\").deepseek ?? {}\n" : ""));
|
|
319
|
+
const bundleLink = join(profileDir, "node_modules", "@khorsheed", "dsh-local-agent-dsh-headless");
|
|
320
|
+
mkdirSync(dirname(bundleLink), { recursive: true });
|
|
321
|
+
if (mount === "copy") ensureBundleCopy(bundleLink, bundleDir);
|
|
322
|
+
else ensureSymlink(bundleLink, bundleDir);
|
|
323
|
+
if (presetPackage !== void 0 && config.preset !== void 0) {
|
|
324
|
+
const presetLink = join(profileDir, "node_modules", ...presetPackage.split("/"));
|
|
325
|
+
mkdirSync(dirname(presetLink), { recursive: true });
|
|
326
|
+
ensureSymlink(presetLink, relative(dirname(presetLink), scopePresetPackageDir(homeDir, config.preset.id)));
|
|
327
|
+
}
|
|
328
|
+
return profileDir;
|
|
329
|
+
}
|
|
270
330
|
/**
|
|
271
331
|
* Provision (idempotently) the sub-dsh headless profile in the harness
|
|
272
332
|
* scoped home: manifest, the headless patch as the profile's own patch
|
|
273
|
-
* layer, and the bundle symlink
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
333
|
+
* layer, and the headless bundle (a copy; a symlink in the `-unit` twin or under a lone `headlessBundleDir` pin) — plus, when a preset package is composed,
|
|
334
|
+
* the relative link resolving it from the scope's own copy, and, when
|
|
335
|
+
* `unitHeadlessBundleDir` is configured, the `-unit` twin a container round
|
|
336
|
+
* launches. A file whose content already matches is left untouched, so
|
|
337
|
+
* re-running after a restart is a no-op; a drifted file (an upgraded headless
|
|
338
|
+
* bundle, or a profile provisioned by an earlier scheme) is rewritten, which
|
|
339
|
+
* heals it before the sub-dsh boots.
|
|
278
340
|
* @param homeDir - the `dsh` harness's scoped home (its `$DSH_HOME` root).
|
|
279
341
|
* @param config - profile naming and bundle-dir overrides.
|
|
280
|
-
* @returns the absolute sub-profile directory.
|
|
342
|
+
* @returns the absolute (host) sub-profile directory.
|
|
281
343
|
*/
|
|
282
344
|
function provisionDshSubProfile(homeDir, config = {}) {
|
|
283
345
|
const profileName = config.profileName ?? "headless-local-agent-dsh";
|
|
284
|
-
const
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const patchContent = readFileSync(join(bundleDir, HEADLESS_PATCH_FILENAME), "utf8") + presetRosterLayer(config.preset) + permissionBoundaryLayer(config.permissions);
|
|
292
|
-
if (!existsSync(patchPath) || readFileSync(patchPath, "utf8") !== patchContent) writeFileSync(patchPath, patchContent);
|
|
293
|
-
const bundleLink = join(profileDir, "node_modules", "@khorsheed", "dsh-local-agent-dsh-headless");
|
|
294
|
-
mkdirSync(dirname(bundleLink), { recursive: true });
|
|
295
|
-
ensureSymlink(bundleLink, bundleDir);
|
|
346
|
+
const hostBundleDir = resolveHeadlessBundleDir(config);
|
|
347
|
+
const unitDir = config.unitHeadlessBundleDir;
|
|
348
|
+
const profileDir = provisionOneSubProfile(homeDir, profileName, hostBundleDir, hostBundleDir, config, config.headlessBundleDir !== void 0 && unitDir === void 0 ? "link" : "copy");
|
|
349
|
+
if (unitDir !== void 0) {
|
|
350
|
+
const patchSource = existsSync(join(unitDir, HEADLESS_PATCH_FILENAME)) ? unitDir : hostBundleDir;
|
|
351
|
+
provisionOneSubProfile(homeDir, unitSubProfileName(config), unitDir, patchSource, config, "link");
|
|
352
|
+
}
|
|
296
353
|
return profileDir;
|
|
297
354
|
}
|
|
298
355
|
/**
|
|
299
|
-
*
|
|
300
|
-
*
|
|
356
|
+
* Where a scope keeps its own copy of each preset PACKAGE it composes:
|
|
357
|
+
* `<scoped home>/preset-packages/<id>`. (0.1.x used `.agent-presets/<id>`,
|
|
358
|
+
* the directory roster's derived user root; 0.2.0 has no directory scan.)
|
|
301
359
|
*/
|
|
302
|
-
const
|
|
303
|
-
/**
|
|
304
|
-
|
|
360
|
+
const SCOPE_PRESET_PACKAGES_DIR = "preset-packages";
|
|
361
|
+
/**
|
|
362
|
+
* The scope's copy of one preset package.
|
|
363
|
+
* @param homeDir - the harness scoped home.
|
|
364
|
+
* @param id - the preset id.
|
|
365
|
+
* @returns the directory, whether or not it exists.
|
|
366
|
+
*/
|
|
367
|
+
function scopePresetPackageDir(homeDir, id) {
|
|
368
|
+
return join(homeDir, SCOPE_PRESET_PACKAGES_DIR, id);
|
|
369
|
+
}
|
|
305
370
|
/**
|
|
306
371
|
* The scope's own declaration of what its sub-profile composes.
|
|
307
372
|
*
|
|
308
373
|
* A FILE in the scope directory, for the third time and the same reason as
|
|
309
|
-
* the
|
|
374
|
+
* the preset layer and the permission boundary: a scoped home bind-mounted
|
|
310
375
|
* into an evaluation unit carries its own composition with it, and nothing
|
|
311
376
|
* has to ride the spawn env or be baked into an image.
|
|
312
377
|
*
|
|
313
378
|
* It exists because the plugin's own `config` is instance-global — one
|
|
314
379
|
* deployment, one answer — while a preset is a property of the SUBJECT. Two
|
|
315
|
-
* scopes
|
|
316
|
-
* to build it was to hand-append the
|
|
380
|
+
* scopes composing two presets was therefore unexpressible, and the only way
|
|
381
|
+
* to build it was to hand-append the preset layer to each scope's patch. That
|
|
317
382
|
* hand edit does not survive: this module regenerates the patch WHOLE, and
|
|
318
383
|
* the registry re-provisions a scope the first time anything names it in a
|
|
319
|
-
* fresh host process — so the
|
|
320
|
-
* and the next read-back reports a scope that
|
|
384
|
+
* fresh host process — so the layer silently disappears on the next restart,
|
|
385
|
+
* and the next read-back reports a scope that composes nothing.
|
|
321
386
|
*/
|
|
322
387
|
const SCOPE_SUB_PROFILE_FILENAME = "sub-profile.json";
|
|
323
388
|
/**
|
|
@@ -381,32 +446,30 @@ const EXPRESSION_SCHEMA = yaml.DEFAULT_SCHEMA.extend([
|
|
|
381
446
|
/** A quoted absolute path inside an expression's source text. */
|
|
382
447
|
const QUOTED_ABSOLUTE_RE = /(['"])(\/[^'"\n]*)\1/g;
|
|
383
448
|
/**
|
|
384
|
-
* Every absolute filesystem path a preset
|
|
449
|
+
* Every absolute filesystem path a preset bundle patch names.
|
|
385
450
|
*
|
|
386
451
|
* An absolute path is the one thing in a composition that cannot be true in
|
|
387
452
|
* two places at once, and a preset used as an evaluation factor is copied:
|
|
388
|
-
* it is read on the host from the
|
|
453
|
+
* it is read on the host from the instance's installation, again from the
|
|
389
454
|
* scope's own copy, and a third time inside a unit where the scope is bound
|
|
390
455
|
* at a different mount point. `skill-filesystem` resolves each
|
|
391
456
|
* `customSkillDirs` entry with `resolve()`, so an absolute entry is taken as
|
|
392
457
|
* written and a bare relative one resolves against the process's cwd —
|
|
393
458
|
* neither travels.
|
|
394
459
|
*
|
|
395
|
-
* What does travel is the
|
|
396
|
-
* `
|
|
460
|
+
* What does travel is resolving the package's own directory through the
|
|
461
|
+
* loader's `baseUrl` (the declaring profile's root), the form the official
|
|
462
|
+
* web-app presets use:
|
|
397
463
|
*
|
|
398
464
|
* ```yaml
|
|
399
465
|
* customSkillDirs:
|
|
400
|
-
* - !!js "process.getBuiltinModule('node:
|
|
466
|
+
* - !!js "process.getBuiltinModule('node:path').join(process.getBuiltinModule('node:path').dirname(process.getBuiltinModule('node:module').createRequire(baseUrl).resolve('<pkg>/package.json')), 'skills')"
|
|
401
467
|
* ```
|
|
402
468
|
*
|
|
403
|
-
* `baseUrl` is the composition's own directory (the include rewrites it), so
|
|
404
|
-
* the root resolves wherever the preset is installed.
|
|
405
|
-
*
|
|
406
469
|
* Unparsable YAML reports NO findings: the loader is the authority on whether
|
|
407
470
|
* a composition loads, and refusing a preset because this reader could not
|
|
408
471
|
* parse it would be this module overruling it.
|
|
409
|
-
* @param text - the
|
|
472
|
+
* @param text - the bundle patch content.
|
|
410
473
|
* @returns the offending literals, in document order, deduplicated.
|
|
411
474
|
*/
|
|
412
475
|
function compositionAbsolutePaths(text) {
|
|
@@ -443,7 +506,7 @@ function compositionAbsolutePaths(text) {
|
|
|
443
506
|
walk(document);
|
|
444
507
|
return found;
|
|
445
508
|
}
|
|
446
|
-
/** Every regular file under `dir`, as posix-ish relative paths, sorted. */
|
|
509
|
+
/** Every regular file under `dir`, as posix-ish relative paths, sorted (a top-level `node_modules` is not part of a preset). */
|
|
447
510
|
function presetTreeFiles(dir) {
|
|
448
511
|
const files = [];
|
|
449
512
|
const walk = (absDir, rel) => {
|
|
@@ -454,6 +517,7 @@ function presetTreeFiles(dir) {
|
|
|
454
517
|
return false;
|
|
455
518
|
}
|
|
456
519
|
for (const entry of entries) {
|
|
520
|
+
if (rel === "" && entry.name === "node_modules") continue;
|
|
457
521
|
const relChild = rel === "" ? entry.name : `${rel}/${entry.name}`;
|
|
458
522
|
if (entry.isSymbolicLink()) return false;
|
|
459
523
|
if (entry.isDirectory()) {
|
|
@@ -469,12 +533,13 @@ function presetTreeFiles(dir) {
|
|
|
469
533
|
return files.sort();
|
|
470
534
|
}
|
|
471
535
|
/**
|
|
472
|
-
* Whether two preset directories are the same directory, byte for
|
|
536
|
+
* Whether two preset package directories are the same directory, byte for
|
|
537
|
+
* byte.
|
|
473
538
|
*
|
|
474
539
|
* This is the whole claim the evaluation rests on: a capability face carries
|
|
475
540
|
* no filesystem path, so the fingerprint taken against the deployment's copy
|
|
476
541
|
* describes the scope's copy exactly when the two hold the same bytes.
|
|
477
|
-
* @param a - one preset directory.
|
|
542
|
+
* @param a - one preset package directory.
|
|
478
543
|
* @param b - the other.
|
|
479
544
|
* @returns true when both hold the same files with the same contents.
|
|
480
545
|
*/
|
|
@@ -491,10 +556,10 @@ function presetTreesEqual(a, b) {
|
|
|
491
556
|
}
|
|
492
557
|
});
|
|
493
558
|
}
|
|
494
|
-
/** Copy one preset directory onto another, replacing whatever was there. */
|
|
559
|
+
/** Copy one preset package directory onto another, replacing whatever was there. */
|
|
495
560
|
function mirrorPresetTree(source, target) {
|
|
496
561
|
const files = presetTreeFiles(source);
|
|
497
|
-
if (files === void 0) throw new Error(`provision-dsh: preset
|
|
562
|
+
if (files === void 0) throw new Error(`provision-dsh: preset package ${source} is not a plain tree of files (a symlink or a device node cannot be copied into a scope — an evaluation unit resolves neither)`);
|
|
498
563
|
rmSync(target, {
|
|
499
564
|
recursive: true,
|
|
500
565
|
force: true
|
|
@@ -505,82 +570,189 @@ function mirrorPresetTree(source, target) {
|
|
|
505
570
|
copyFileSync(join(source, name), destination);
|
|
506
571
|
}
|
|
507
572
|
}
|
|
573
|
+
/** A JSON object read off disk, or undefined. */
|
|
574
|
+
function readJsonObject(path) {
|
|
575
|
+
try {
|
|
576
|
+
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
577
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
578
|
+
} catch {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
/** The bundle patch files a package manifest declares (`dsh.bundle.patch`, a string or a list). */
|
|
583
|
+
function bundlePatchFiles(manifest) {
|
|
584
|
+
const dsh = manifest["dsh"];
|
|
585
|
+
if (typeof dsh !== "object" || dsh === null) return [];
|
|
586
|
+
const bundle = dsh["bundle"];
|
|
587
|
+
if (typeof bundle !== "object" || bundle === null) return [];
|
|
588
|
+
const patch = bundle["patch"];
|
|
589
|
+
if (typeof patch === "string") return [patch];
|
|
590
|
+
return Array.isArray(patch) ? patch.filter((item) => typeof item === "string") : [];
|
|
591
|
+
}
|
|
592
|
+
/** The preset ids a bundle patch document declares through `@deepseek-ai/dsh-agent-preset` rows. */
|
|
593
|
+
function declaredPresetIds(text) {
|
|
594
|
+
let document;
|
|
595
|
+
try {
|
|
596
|
+
document = yaml.load(text, { schema: EXPRESSION_SCHEMA });
|
|
597
|
+
} catch {
|
|
598
|
+
return [];
|
|
599
|
+
}
|
|
600
|
+
if (!Array.isArray(document)) return [];
|
|
601
|
+
const ids = [];
|
|
602
|
+
const visit = (row) => {
|
|
603
|
+
if (typeof row !== "object" || row === null || Array.isArray(row)) return;
|
|
604
|
+
const record = row;
|
|
605
|
+
if (record["name"] !== "@deepseek-ai/dsh-agent-preset") return;
|
|
606
|
+
const config = record["config"];
|
|
607
|
+
if (typeof config !== "object" || config === null) return;
|
|
608
|
+
const id = config["id"];
|
|
609
|
+
if (typeof id === "string") ids.push(id);
|
|
610
|
+
};
|
|
611
|
+
for (const operation of document) {
|
|
612
|
+
if (typeof operation !== "object" || operation === null) continue;
|
|
613
|
+
const insert = operation["insert"];
|
|
614
|
+
if (Array.isArray(insert)) insert.forEach(visit);
|
|
615
|
+
else visit(operation);
|
|
616
|
+
}
|
|
617
|
+
return ids;
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Find the bundle package, among a profile's direct dependencies, whose
|
|
621
|
+
* bundle patch declares preset `id`.
|
|
622
|
+
*
|
|
623
|
+
* This is how the 0.2.0 host itself learns the preset: a profile's direct
|
|
624
|
+
* `dsh.bundle`-declaring dependency mounts its patch, and the patch's
|
|
625
|
+
* `@deepseek-ai/dsh-agent-preset` row registers the id. Reading the same files
|
|
626
|
+
* is what lets a scope copy exactly the package the evaluation instance's
|
|
627
|
+
* capability catalog measures.
|
|
628
|
+
* @param profileDir - the instance profile directory (holding `package.json`).
|
|
629
|
+
* @param id - the preset id.
|
|
630
|
+
* @returns the declaring package, or undefined when none (or more than one) declares it.
|
|
631
|
+
*/
|
|
632
|
+
function findPresetPackage(profileDir, id) {
|
|
633
|
+
const manifest = readJsonObject(join(profileDir, "package.json"));
|
|
634
|
+
if (manifest === void 0) return void 0;
|
|
635
|
+
const names = /* @__PURE__ */ new Set();
|
|
636
|
+
for (const field of [
|
|
637
|
+
"dependencies",
|
|
638
|
+
"optionalDependencies",
|
|
639
|
+
"devDependencies"
|
|
640
|
+
]) {
|
|
641
|
+
const deps = manifest[field];
|
|
642
|
+
if (typeof deps === "object" && deps !== null) for (const name of Object.keys(deps)) names.add(name);
|
|
643
|
+
}
|
|
644
|
+
const found = [];
|
|
645
|
+
for (const name of [...names].sort()) {
|
|
646
|
+
if (!PACKAGE_NAME_RE.test(name)) continue;
|
|
647
|
+
let dir;
|
|
648
|
+
try {
|
|
649
|
+
dir = realpathSync(join(profileDir, "node_modules", ...name.split("/")));
|
|
650
|
+
} catch {
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
const packageManifest = readJsonObject(join(dir, "package.json"));
|
|
654
|
+
if (packageManifest === void 0) continue;
|
|
655
|
+
if (bundlePatchFiles(packageManifest).some((file) => {
|
|
656
|
+
try {
|
|
657
|
+
return declaredPresetIds(readFileSync(join(dir, file), "utf8")).includes(id);
|
|
658
|
+
} catch {
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
})) found.push({
|
|
662
|
+
name,
|
|
663
|
+
dir
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
return found.length === 1 ? found[0] : void 0;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Why a preset package directory cannot be copied into a scope, or undefined
|
|
670
|
+
* when it can: it must name itself, declare its bundle patch, and the patch
|
|
671
|
+
* must declare the preset without naming an absolute path.
|
|
672
|
+
*/
|
|
673
|
+
function presetPackageProblem(dir, id) {
|
|
674
|
+
const manifest = readJsonObject(join(dir, "package.json"));
|
|
675
|
+
const name = manifest?.["name"];
|
|
676
|
+
if (manifest === void 0 || typeof name !== "string" || !PACKAGE_NAME_RE.test(name)) return `${dir} has no package.json naming the package`;
|
|
677
|
+
const files = bundlePatchFiles(manifest);
|
|
678
|
+
if (files.length === 0) return `${name} declares no dsh.bundle.patch`;
|
|
679
|
+
let declares = false;
|
|
680
|
+
for (const file of files) {
|
|
681
|
+
let text;
|
|
682
|
+
try {
|
|
683
|
+
text = readFileSync(join(dir, file), "utf8");
|
|
684
|
+
} catch {
|
|
685
|
+
return `${name} declares bundle patch ${file}, which is missing`;
|
|
686
|
+
}
|
|
687
|
+
const offenders = compositionAbsolutePaths(text);
|
|
688
|
+
if (offenders.length > 0) return `${name}'s ${file} names absolute path(s) ${offenders.map((path) => JSON.stringify(path)).join(", ")} — a preset that is copied into a scope and bind-mounted into a unit is read from three different directories, so an absolute path is wrong in at least two of them. Resolve the package's own directory through baseUrl instead: !!js "…dirname(createRequire(baseUrl).resolve('${name}/package.json'))…"`;
|
|
689
|
+
if (declaredPresetIds(text).includes(id)) declares = true;
|
|
690
|
+
}
|
|
691
|
+
return declares ? void 0 : `${name} declares no ${PRESET_ROW_MODULE} row with id ${JSON.stringify(id)}`;
|
|
692
|
+
}
|
|
508
693
|
/**
|
|
509
|
-
* Give one scope its own copy of a preset, from the
|
|
694
|
+
* Give one scope its own copy of a preset package, from the evaluation
|
|
695
|
+
* instance's own installation of it.
|
|
510
696
|
*
|
|
511
|
-
* The copy is what makes a preset reachable on BOTH paths. The
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
*
|
|
515
|
-
* one directory, because the unit binds the scope. A roster pointed at the
|
|
516
|
-
* deployment's root instead resolves on the host and names a path the unit
|
|
517
|
-
* does not have.
|
|
697
|
+
* The copy is what makes a preset reachable on BOTH paths. The sub-profile
|
|
698
|
+
* resolves the package through a relative link to
|
|
699
|
+
* `<scope>/preset-packages/<id>`, which is one directory on the host and
|
|
700
|
+
* inside an evaluation unit, because the unit binds the scope.
|
|
518
701
|
*
|
|
519
702
|
* `refresh` is the difference between provisioning a condition and healing a
|
|
520
|
-
* scope. A deliberate provision re-syncs from the
|
|
703
|
+
* scope. A deliberate provision re-syncs from the instance's copy, because
|
|
521
704
|
* that is the moment a new lock is minted and an edited preset should be
|
|
522
705
|
* picked up. Every other provisioning leaves an existing copy alone: nothing
|
|
523
|
-
* may move the subject under a run
|
|
706
|
+
* may move the subject under a run — and a healing call whose instance no
|
|
707
|
+
* longer carries the package keeps the copy, reporting `matchesSource: false`.
|
|
524
708
|
* @param homeDir - the harness scoped home.
|
|
525
|
-
* @param
|
|
526
|
-
* @param id - the preset id
|
|
709
|
+
* @param instanceProfileDir - the instance profile directory, or undefined when unknown.
|
|
710
|
+
* @param id - the preset id.
|
|
527
711
|
* @param options - `refresh` re-syncs an existing copy.
|
|
528
712
|
* @returns where the copy is and whether it matches the source.
|
|
529
|
-
* @throws when
|
|
713
|
+
* @throws when no copy can be made: no installed package declares the id, or it is not a usable preset package.
|
|
530
714
|
*/
|
|
531
|
-
function snapshotScopePreset(homeDir,
|
|
715
|
+
function snapshotScopePreset(homeDir, instanceProfileDir, id, options = {}) {
|
|
532
716
|
if (!SUB_PROFILE_PRESET_ID_RE.test(id)) throw new Error(`provision-dsh: preset id ${JSON.stringify(id)} must match ${String(SUB_PROFILE_PRESET_ID_RE)} (it is a directory name)`);
|
|
533
|
-
const
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
const
|
|
539
|
-
|
|
717
|
+
const dir = scopePresetPackageDir(homeDir, id);
|
|
718
|
+
const copyName = () => {
|
|
719
|
+
const name = readJsonObject(join(dir, "package.json"))?.["name"];
|
|
720
|
+
return typeof name === "string" && PACKAGE_NAME_RE.test(name) ? name : void 0;
|
|
721
|
+
};
|
|
722
|
+
const source = instanceProfileDir === void 0 ? void 0 : findPresetPackage(instanceProfileDir, id);
|
|
723
|
+
const existing = existsSync(dir) ? copyName() : void 0;
|
|
724
|
+
if (existing !== void 0 && options.refresh !== true) return {
|
|
540
725
|
dir,
|
|
541
|
-
|
|
726
|
+
packageName: existing,
|
|
727
|
+
matchesSource: source !== void 0 && presetTreesEqual(source.dir, dir),
|
|
542
728
|
copied: false
|
|
543
729
|
};
|
|
544
|
-
if (
|
|
730
|
+
if (source === void 0) throw new Error(`provision-dsh: no package installed in ${instanceProfileDir ?? "this instance's profile"} declares preset ${JSON.stringify(id)} (an ${PRESET_ROW_MODULE} row in its dsh.bundle.patch) — add the preset package to the instance profile before a scope can compose it`);
|
|
731
|
+
const problem = presetPackageProblem(source.dir, id);
|
|
732
|
+
if (problem !== void 0) throw new Error(`provision-dsh: preset ${JSON.stringify(id)}: ${problem}`);
|
|
733
|
+
if (existing !== void 0 && presetTreesEqual(source.dir, dir)) return {
|
|
545
734
|
dir,
|
|
735
|
+
packageName: existing,
|
|
546
736
|
matchesSource: true,
|
|
547
737
|
copied: false
|
|
548
738
|
};
|
|
549
|
-
mirrorPresetTree(source, dir);
|
|
739
|
+
mirrorPresetTree(source.dir, dir);
|
|
550
740
|
return {
|
|
551
741
|
dir,
|
|
742
|
+
packageName: source.name,
|
|
552
743
|
matchesSource: true,
|
|
553
744
|
copied: true
|
|
554
745
|
};
|
|
555
746
|
}
|
|
556
747
|
/**
|
|
557
|
-
*
|
|
558
|
-
*
|
|
559
|
-
* The roster's own derivation, repeated here because the provisioning paths
|
|
560
|
-
* that heal a scope (a host round, a live runtime) have no settings service
|
|
561
|
-
* in reach: a non-blank `$DSH_HOME`, else the `~/.dsh` installation default
|
|
562
|
-
* (the precedence of `resolveDshHome` in `@deepseek-ai/dsh-home-paths` —
|
|
563
|
-
* the default was once hardcoded to `.dsh-official`, pointing a desktop
|
|
564
|
-
* instance at another instance's home; 2026-09-30).
|
|
565
|
-
*/
|
|
566
|
-
function defaultPresetRoot() {
|
|
567
|
-
const fromEnv = process.env["DSH_HOME"];
|
|
568
|
-
const home = fromEnv !== void 0 && fromEnv.trim().length > 0 ? fromEnv : join(homedir(), ".dsh");
|
|
569
|
-
return join(home, USER_PRESET_DIR);
|
|
570
|
-
}
|
|
571
|
-
/**
|
|
572
|
-
* Provision one scope: its own preset copy, its declaration, and the
|
|
573
|
-
* sub-profile that rosters it.
|
|
748
|
+
* Provision one scope: its own preset package copy, its declaration, and the
|
|
749
|
+
* sub-profile that composes it.
|
|
574
750
|
*
|
|
575
751
|
* The preset resolves in one order, and the order is the decision: the
|
|
576
752
|
* CALLER's explicit request, then the SCOPE's own declaration, then the
|
|
577
753
|
* deployment's plugin config. The first is how a condition says which subject
|
|
578
754
|
* this scope is; the second is how that survives a restart; the third is the
|
|
579
755
|
* deployment-wide answer every scope had before either existed.
|
|
580
|
-
*
|
|
581
|
-
* A scope-owned preset rosters with NO `roots`. The roster's derived user
|
|
582
|
-
* root is exactly the scope's copy on both paths, so leaving the roots alone
|
|
583
|
-
* is what gives the host and the unit one resolution rule instead of two.
|
|
584
756
|
* @param homeDir - the harness scoped home.
|
|
585
757
|
* @param config - the deployment's sub-profile config.
|
|
586
758
|
* @param options - per-scope inputs.
|
|
@@ -589,10 +761,13 @@ function defaultPresetRoot() {
|
|
|
589
761
|
function provisionDshScope(homeDir, config = {}, options = {}) {
|
|
590
762
|
const requested = options.preset;
|
|
591
763
|
const declared = requested ?? readScopeSubProfile(homeDir)?.preset;
|
|
592
|
-
if (requested !== void 0) writeScopeSubProfile(homeDir, { preset: requested });
|
|
593
764
|
let snapshot;
|
|
594
|
-
if (declared !== void 0) snapshot = snapshotScopePreset(homeDir, options.
|
|
595
|
-
|
|
765
|
+
if (declared !== void 0) snapshot = snapshotScopePreset(homeDir, options.instanceProfileDir, declared, { refresh: requested !== void 0 });
|
|
766
|
+
if (requested !== void 0) writeScopeSubProfile(homeDir, { preset: requested });
|
|
767
|
+
const preset = declared === void 0 || snapshot === void 0 ? config.preset : {
|
|
768
|
+
id: declared,
|
|
769
|
+
package: snapshot.packageName
|
|
770
|
+
};
|
|
596
771
|
return {
|
|
597
772
|
profileDir: provisionDshSubProfile(homeDir, {
|
|
598
773
|
...config,
|
|
@@ -603,4 +778,4 @@ function provisionDshScope(homeDir, config = {}, options = {}) {
|
|
|
603
778
|
};
|
|
604
779
|
}
|
|
605
780
|
//#endregion
|
|
606
|
-
export { DEFAULT_SUB_PROFILE_NAME,
|
|
781
|
+
export { DEFAULT_SUB_PROFILE_NAME, PRESET_REGISTRY_MODULE, PRESET_ROW_MODULE, SCOPE_PRESET_PACKAGES_DIR, SCOPE_SUB_PROFILE_FILENAME, SUB_PROFILE_PRESET_ID_RE, UNIT_PROFILE_SUFFIX, compositionAbsolutePaths, findPresetPackage, permissionBoundaryLayer, presetRegistryLayer, presetTreesEqual, provisionDshScope, provisionDshSubProfile, readScopeSubProfile, readSubProfilePermissions, readSubProfilePreset, resolveHeadlessBundleDir, scopePresetPackageDir, snapshotScopePreset, unitSubProfileName, writeScopeSubProfile };
|