odai-dsh-agent 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/README.md +3 -3
  2. package/build/bin/odai-dsh-agent.d.mts +3 -0
  3. package/build/bin/odai-dsh-agent.d.mts.map +1 -0
  4. package/build/bin/odai-dsh-agent.mjs +114 -0
  5. package/build/bin/odai-dsh-agent.mjs.map +1 -0
  6. package/build/src/dsh-version.d.mts +16 -0
  7. package/build/src/dsh-version.d.mts.map +1 -0
  8. package/build/src/dsh-version.mjs +20 -0
  9. package/build/src/dsh-version.mjs.map +1 -0
  10. package/build/src/installer.d.mts +46 -0
  11. package/build/src/installer.d.mts.map +1 -0
  12. package/build/src/installer.mjs +397 -0
  13. package/build/src/installer.mjs.map +1 -0
  14. package/package.json +13 -12
  15. package/preset/odai/runtime/compaction-config.d.mts +66 -0
  16. package/preset/odai/runtime/compaction-config.d.mts.map +1 -0
  17. package/preset/odai/runtime/compaction-config.mjs +320 -342
  18. package/preset/odai/runtime/compaction-config.mjs.map +1 -0
  19. package/preset/odai/runtime/compaction-runtime.d.mts +68 -0
  20. package/preset/odai/runtime/compaction-runtime.d.mts.map +1 -0
  21. package/preset/odai/runtime/compaction-runtime.mjs +157 -0
  22. package/preset/odai/runtime/compaction-runtime.mjs.map +1 -0
  23. package/preset/odai/runtime/context-activation.d.mts +31 -0
  24. package/preset/odai/runtime/context-activation.d.mts.map +1 -0
  25. package/preset/odai/runtime/context-activation.mjs +55 -49
  26. package/preset/odai/runtime/context-activation.mjs.map +1 -0
  27. package/preset/odai/runtime/context-capability.d.mts +16 -0
  28. package/preset/odai/runtime/context-capability.d.mts.map +1 -0
  29. package/preset/odai/runtime/context-capability.mjs +79 -74
  30. package/preset/odai/runtime/context-capability.mjs.map +1 -0
  31. package/preset/odai/runtime/governance.d.mts +30 -0
  32. package/preset/odai/runtime/governance.d.mts.map +1 -0
  33. package/preset/odai/runtime/governance.mjs +126 -113
  34. package/preset/odai/runtime/governance.mjs.map +1 -0
  35. package/preset/odai/runtime/human-care.d.mts +13 -0
  36. package/preset/odai/runtime/human-care.d.mts.map +1 -0
  37. package/preset/odai/runtime/human-care.mjs +45 -42
  38. package/preset/odai/runtime/human-care.mjs.map +1 -0
  39. package/preset/odai/runtime/human-safety-continuity-store.d.mts +43 -0
  40. package/preset/odai/runtime/human-safety-continuity-store.d.mts.map +1 -0
  41. package/preset/odai/runtime/human-safety-continuity-store.mjs +193 -215
  42. package/preset/odai/runtime/human-safety-continuity-store.mjs.map +1 -0
  43. package/preset/odai/runtime/human-safety-continuity.d.mts +27 -0
  44. package/preset/odai/runtime/human-safety-continuity.d.mts.map +1 -0
  45. package/preset/odai/runtime/human-safety-continuity.mjs +205 -216
  46. package/preset/odai/runtime/human-safety-continuity.mjs.map +1 -0
  47. package/preset/odai/runtime/human-safety.d.mts +14 -0
  48. package/preset/odai/runtime/human-safety.d.mts.map +1 -0
  49. package/preset/odai/runtime/human-safety.mjs +50 -47
  50. package/preset/odai/runtime/human-safety.mjs.map +1 -0
  51. package/preset/odai/runtime/index.d.mts +8 -0
  52. package/preset/odai/runtime/index.d.mts.map +1 -0
  53. package/preset/odai/runtime/index.mjs +161 -2473
  54. package/preset/odai/runtime/index.mjs.map +1 -0
  55. package/preset/odai/runtime/lifecycle-runtime.d.mts +83 -0
  56. package/preset/odai/runtime/lifecycle-runtime.d.mts.map +1 -0
  57. package/preset/odai/runtime/lifecycle-runtime.mjs +1081 -0
  58. package/preset/odai/runtime/lifecycle-runtime.mjs.map +1 -0
  59. package/preset/odai/runtime/model-route.d.mts +22 -0
  60. package/preset/odai/runtime/model-route.d.mts.map +1 -0
  61. package/preset/odai/runtime/model-route.mjs +68 -60
  62. package/preset/odai/runtime/model-route.mjs.map +1 -0
  63. package/preset/odai/runtime/output-config.d.mts +54 -0
  64. package/preset/odai/runtime/output-config.d.mts.map +1 -0
  65. package/preset/odai/runtime/output-config.mjs +289 -315
  66. package/preset/odai/runtime/output-config.mjs.map +1 -0
  67. package/preset/odai/runtime/output-policy-state.d.mts +3 -0
  68. package/preset/odai/runtime/output-policy-state.d.mts.map +1 -0
  69. package/preset/odai/runtime/output-policy-state.mjs +41 -29
  70. package/preset/odai/runtime/output-policy-state.mjs.map +1 -0
  71. package/preset/odai/runtime/prompt-runtime.d.mts +55 -0
  72. package/preset/odai/runtime/prompt-runtime.d.mts.map +1 -0
  73. package/preset/odai/runtime/prompt-runtime.mjs +303 -0
  74. package/preset/odai/runtime/prompt-runtime.mjs.map +1 -0
  75. package/preset/odai/runtime/research-packet.d.mts +30 -0
  76. package/preset/odai/runtime/research-packet.d.mts.map +1 -0
  77. package/preset/odai/runtime/research-packet.mjs +116 -117
  78. package/preset/odai/runtime/research-packet.mjs.map +1 -0
  79. package/preset/odai/runtime/responsibility-gap.d.mts +26 -0
  80. package/preset/odai/runtime/responsibility-gap.d.mts.map +1 -0
  81. package/preset/odai/runtime/responsibility-gap.mjs +91 -85
  82. package/preset/odai/runtime/responsibility-gap.mjs.map +1 -0
  83. package/preset/odai/runtime/responsibility-scope.d.mts +69 -0
  84. package/preset/odai/runtime/responsibility-scope.d.mts.map +1 -0
  85. package/preset/odai/runtime/responsibility-scope.mjs +199 -171
  86. package/preset/odai/runtime/responsibility-scope.mjs.map +1 -0
  87. package/preset/odai/runtime/role-overlays.d.mts +5 -0
  88. package/preset/odai/runtime/role-overlays.d.mts.map +1 -0
  89. package/preset/odai/runtime/role-overlays.mjs +14 -13
  90. package/preset/odai/runtime/role-overlays.mjs.map +1 -0
  91. package/preset/odai/runtime/route-card.d.mts +42 -0
  92. package/preset/odai/runtime/route-card.d.mts.map +1 -0
  93. package/preset/odai/runtime/route-card.mjs +158 -146
  94. package/preset/odai/runtime/route-card.mjs.map +1 -0
  95. package/preset/odai/runtime/router.d.mts +79 -0
  96. package/preset/odai/runtime/router.d.mts.map +1 -0
  97. package/preset/odai/runtime/router.mjs +491 -559
  98. package/preset/odai/runtime/router.mjs.map +1 -0
  99. package/preset/odai/runtime/routing-config.d.mts +76 -0
  100. package/preset/odai/runtime/routing-config.d.mts.map +1 -0
  101. package/preset/odai/runtime/routing-config.mjs +373 -438
  102. package/preset/odai/runtime/routing-config.mjs.map +1 -0
  103. package/preset/odai/runtime/routing-context.d.mts +47 -0
  104. package/preset/odai/runtime/routing-context.d.mts.map +1 -0
  105. package/preset/odai/runtime/routing-context.mjs +238 -273
  106. package/preset/odai/runtime/routing-context.mjs.map +1 -0
  107. package/preset/odai/runtime/runtime-config.d.mts +24 -0
  108. package/preset/odai/runtime/runtime-config.d.mts.map +1 -0
  109. package/preset/odai/runtime/runtime-config.mjs +181 -0
  110. package/preset/odai/runtime/runtime-config.mjs.map +1 -0
  111. package/preset/odai/runtime/runtime-support.d.mts +91 -0
  112. package/preset/odai/runtime/runtime-support.d.mts.map +1 -0
  113. package/preset/odai/runtime/runtime-support.mjs +371 -0
  114. package/preset/odai/runtime/runtime-support.mjs.map +1 -0
  115. package/preset/odai/runtime/runtime-types.d.mts +235 -0
  116. package/preset/odai/runtime/runtime-types.d.mts.map +1 -0
  117. package/preset/odai/runtime/runtime-types.mjs +4 -0
  118. package/preset/odai/runtime/runtime-types.mjs.map +1 -0
  119. package/preset/odai/runtime/semantic-memory-store.d.mts +110 -0
  120. package/preset/odai/runtime/semantic-memory-store.d.mts.map +1 -0
  121. package/preset/odai/runtime/semantic-memory-store.mjs +271 -385
  122. package/preset/odai/runtime/semantic-memory-store.mjs.map +1 -0
  123. package/preset/odai/runtime/semantic-memory.d.mts +88 -0
  124. package/preset/odai/runtime/semantic-memory.d.mts.map +1 -0
  125. package/preset/odai/runtime/semantic-memory.mjs +790 -750
  126. package/preset/odai/runtime/semantic-memory.mjs.map +1 -0
  127. package/preset/odai/runtime/session-compat.d.mts +31 -0
  128. package/preset/odai/runtime/session-compat.d.mts.map +1 -0
  129. package/preset/odai/runtime/session-compat.mjs +301 -328
  130. package/preset/odai/runtime/session-compat.mjs.map +1 -0
  131. package/preset/odai/runtime/session-evidence.d.mts +25 -0
  132. package/preset/odai/runtime/session-evidence.d.mts.map +1 -0
  133. package/preset/odai/runtime/session-evidence.mjs +309 -287
  134. package/preset/odai/runtime/session-evidence.mjs.map +1 -0
  135. package/preset/odai/runtime/skill-bundle.d.mts +53 -0
  136. package/preset/odai/runtime/skill-bundle.d.mts.map +1 -0
  137. package/preset/odai/runtime/skill-bundle.mjs +286 -276
  138. package/preset/odai/runtime/skill-bundle.mjs.map +1 -0
  139. package/preset/odai/runtime/skill-evolution.d.mts +112 -0
  140. package/preset/odai/runtime/skill-evolution.d.mts.map +1 -0
  141. package/preset/odai/runtime/skill-evolution.mjs +1287 -1261
  142. package/preset/odai/runtime/skill-evolution.mjs.map +1 -0
  143. package/preset/odai/runtime/skill-selection-state.d.mts +5 -0
  144. package/preset/odai/runtime/skill-selection-state.d.mts.map +1 -0
  145. package/preset/odai/runtime/skill-selection-state.mjs +59 -43
  146. package/preset/odai/runtime/skill-selection-state.mjs.map +1 -0
  147. package/preset/odai/runtime/skill-selector.d.mts +28 -0
  148. package/preset/odai/runtime/skill-selector.d.mts.map +1 -0
  149. package/preset/odai/runtime/skill-selector.mjs +100 -109
  150. package/preset/odai/runtime/skill-selector.mjs.map +1 -0
  151. package/preset/odai/runtime/skill-source-config.d.mts +32 -0
  152. package/preset/odai/runtime/skill-source-config.d.mts.map +1 -0
  153. package/preset/odai/runtime/skill-source-config.mjs +200 -210
  154. package/preset/odai/runtime/skill-source-config.mjs.map +1 -0
  155. package/preset/odai/runtime/store-lock.d.mts +2 -0
  156. package/preset/odai/runtime/store-lock.d.mts.map +1 -0
  157. package/preset/odai/runtime/store-lock.mjs +92 -87
  158. package/preset/odai/runtime/store-lock.mjs.map +1 -0
  159. package/preset/odai/runtime/tool-runtime.d.mts +43 -0
  160. package/preset/odai/runtime/tool-runtime.d.mts.map +1 -0
  161. package/preset/odai/runtime/tool-runtime.mjs +221 -0
  162. package/preset/odai/runtime/tool-runtime.mjs.map +1 -0
  163. package/bin/odai-dsh-agent.mjs +0 -108
  164. package/scripts/verify-agent-load.mjs +0 -260
  165. package/src/dsh-version.mjs +0 -24
  166. package/src/installer.mjs +0 -385
@@ -1,260 +0,0 @@
1
- import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
2
- import { existsSync, readFileSync, realpathSync } from "node:fs";
3
- import { createRequire } from "node:module";
4
- import { createServer } from "node:net";
5
- import { tmpdir } from "node:os";
6
- import { dirname, resolve } from "node:path";
7
- import { fileURLToPath, pathToFileURL } from "node:url";
8
- import { execFileSync } from "node:child_process";
9
- import { randomUUID } from "node:crypto";
10
-
11
- import { spawnDsh } from "../src/dsh-version.mjs";
12
- import {
13
- installAgentPreset,
14
- renderAgentCompositionForDsh,
15
- SUPPORTED_DSH_VERSIONS,
16
- } from "../src/installer.mjs";
17
-
18
- const agentRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
19
- const repoRoot = resolve(agentRoot, "../..");
20
- const dsh = process.env.DSH_BIN ?? "dsh";
21
- const dshRoot = process.env.DSH_PACKAGE_ROOT
22
- ? resolve(process.env.DSH_PACKAGE_ROOT)
23
- : findDshPackageRoot(dsh);
24
- const requireFromDsh = createRequire(resolve(dshRoot, "package.json"));
25
- const scopeModule = requireFromDsh.resolve("@deepseek-ai/dsh-scope");
26
- const targetDshVersion = await verifyPinnedComposition();
27
- const scratch = await mkdtemp(resolve(tmpdir(), "odai-agent-scope-"));
28
- const home = resolve(scratch, "home");
29
- const workspace = resolve(scratch, "workspace");
30
- const sourceRoot = resolve(scratch, "source-preset");
31
- const markerPath = resolve(scratch, "scope-results.json");
32
- const probePluginPath = resolve(scratch, "scope-probe-plugin.mjs");
33
- const patchPath = resolve(scratch, "scope-probe.patch.yml");
34
-
35
- const yaml = (value) => JSON.stringify(value);
36
-
37
- function findDshPackageRoot(command) {
38
- const locator = process.platform === "win32" ? "where" : "which";
39
- const located = existsSync(command)
40
- ? [resolve(command)]
41
- : execFileSync(locator, [command], { encoding: "utf8" })
42
- .trim()
43
- .split(/\r?\n/u)
44
- .filter(Boolean);
45
- const candidates = new Set();
46
- for (const path of located) {
47
- const commandDir = dirname(realpathSync(path));
48
- candidates.add(commandDir);
49
- candidates.add(resolve(commandDir, "node_modules/@deepseek-ai/dsh"));
50
- candidates.add(resolve(commandDir, "../@deepseek-ai/dsh"));
51
- }
52
- for (const candidate of candidates) {
53
- let current = candidate;
54
- for (;;) {
55
- try {
56
- const metadata = JSON.parse(readFileSync(resolve(current, "package.json"), "utf8"));
57
- if (metadata.name === "@deepseek-ai/dsh") return current;
58
- } catch (error) {
59
- if (error?.code !== "ENOENT") throw error;
60
- }
61
- const parent = dirname(current);
62
- if (parent === current) break;
63
- current = parent;
64
- }
65
- }
66
- throw new Error(`cannot locate the @deepseek-ai/dsh package behind ${command}`);
67
- }
68
-
69
- async function verifyPinnedComposition() {
70
- const dshMetadata = JSON.parse(await readFile(resolve(dshRoot, "package.json"), "utf8"));
71
- if (!SUPPORTED_DSH_VERSIONS.includes(dshMetadata.version)) {
72
- throw new Error(`agent preset expects one of ${SUPPORTED_DSH_VERSIONS.join(", ")}, found ${dshMetadata.version}`);
73
- }
74
-
75
- const standard = (await readFile(resolve(dshRoot, "config/agent-presets/standard/agent.cordis.yml"), "utf8"))
76
- .replace(/\r\n/gu, "\n");
77
- const odaiSuffix = [
78
- "# Odai contributes scoped prompt, guard, routing, user-owned responsibility",
79
- "# mappings, and evidence listeners. Base controller selection stays host-owned.",
80
- "- id: odai-governance",
81
- " name: ./runtime/index.mjs",
82
- " config:",
83
- " routing:",
84
- " mode: auto",
85
- " provider: spawn",
86
- ].join("\n");
87
- const expected = `${standard
88
- .replace(
89
- "# The preset's own persona, shadowing the deployment default for this agent.\n# `{{model}}` and `{{cwd}}` resolve from the agent's own route and workspace.",
90
- "# The preset's own model-neutral persona shadows the deployment default. Auto\n# routing can upgrade after prompt assembly; `{{cwd}}` remains workspace-local.",
91
- )
92
- .replace(
93
- "You are a coding agent powered by the {{model}} model. Your working directory is {{cwd}}.",
94
- "You are Odai, a coding agent. Your working directory is {{cwd}}.",
95
- )
96
- .trimEnd()}\n\n${odaiSuffix}`;
97
- const actual = renderAgentCompositionForDsh(
98
- await readFile(resolve(agentRoot, "preset/odai/agent.cordis.yml"), "utf8"),
99
- dshMetadata.version,
100
- ).trimEnd();
101
- if (actual !== expected) {
102
- throw new Error(`Odai Agent composition drifted from the DSH ${dshMetadata.version} standard preset`);
103
- }
104
- return dshMetadata.version;
105
- }
106
-
107
- async function freePort() {
108
- return await new Promise((accept, reject) => {
109
- const server = createServer();
110
- server.once("error", reject);
111
- server.listen(0, "127.0.0.1", () => {
112
- const address = server.address();
113
- const port = typeof address === "object" && address ? address.port : undefined;
114
- server.close((error) => error ? reject(error) : accept(port));
115
- });
116
- });
117
- }
118
-
119
- async function rpc(baseUrl, method, payload) {
120
- const rpcId = randomUUID();
121
- const response = await fetch(`${baseUrl}/api/${method}`, {
122
- method: "POST",
123
- headers: { "content-type": "application/json" },
124
- body: JSON.stringify({ type: "client-request", rpcId, method, payload }),
125
- });
126
- const text = await response.text();
127
- if (!response.ok) throw new Error(`${method} HTTP ${response.status}: ${text}`);
128
- const body = JSON.parse(text);
129
- if (body.result?.ok !== true) throw new Error(`${method} failed: ${text}`);
130
- return body.result.value;
131
- }
132
-
133
- async function waitForServer(baseUrl, child, output) {
134
- const deadline = Date.now() + 20_000;
135
- while (Date.now() < deadline) {
136
- if (child.exitCode !== null) throw new Error(`dsh web exited early (${child.exitCode})\n${output()}`);
137
- try {
138
- await rpc(baseUrl, "agentPreset.list", {});
139
- return;
140
- } catch {
141
- await new Promise((accept) => setTimeout(accept, 75));
142
- }
143
- }
144
- throw new Error(`timed out waiting for dsh web\n${output()}`);
145
- }
146
-
147
- async function waitForMarker(child, output) {
148
- const deadline = Date.now() + 15_000;
149
- while (Date.now() < deadline) {
150
- if (existsSync(markerPath)) return JSON.parse(await readFile(markerPath, "utf8"));
151
- if (child.exitCode !== null) throw new Error(`dsh web exited before probe completed (${child.exitCode})\n${output()}`);
152
- await new Promise((accept) => setTimeout(accept, 50));
153
- }
154
- throw new Error(`timed out waiting for scope marker\n${output()}`);
155
- }
156
-
157
- async function terminateChild(child) {
158
- if (process.platform === "win32" && child.pid) {
159
- try {
160
- execFileSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" });
161
- } catch {
162
- if (child.exitCode === null) child.kill("SIGTERM");
163
- }
164
- } else if (child.exitCode === null) {
165
- child.kill("SIGTERM");
166
- }
167
- await new Promise((accept) => {
168
- if (child.exitCode !== null) return accept();
169
- const timeout = setTimeout(() => { child.kill("SIGKILL"); accept(); }, 3_000);
170
- child.once("exit", () => { clearTimeout(timeout); accept(); });
171
- });
172
- }
173
-
174
- await mkdir(workspace, { recursive: true });
175
- await cp(resolve(agentRoot, "preset/odai"), sourceRoot, { recursive: true });
176
- const developmentRuntime = resolve(repoRoot, "dsh/runtime/src");
177
- const developmentSkill = resolve(repoRoot, "skills/odai");
178
- if (existsSync(developmentRuntime) && existsSync(developmentSkill)) {
179
- await Promise.all([
180
- cp(developmentRuntime, resolve(sourceRoot, "runtime"), { recursive: true }),
181
- cp(developmentSkill, resolve(sourceRoot, "skills/odai"), { recursive: true }),
182
- ]);
183
- } else if (!existsSync(resolve(sourceRoot, "runtime/index.mjs"))
184
- || !existsSync(resolve(sourceRoot, "skills/odai/SKILL.md"))) {
185
- throw new Error("Odai Agent verification requires either repository sources or packaged runtime and skill files");
186
- }
187
- await installAgentPreset({ dshHome: home, sourceRoot, dshVersion: targetDshVersion });
188
-
189
- const probePlugin = `import { existsSync, writeFileSync } from "node:fs";\nimport { bindScopeParent } from ${JSON.stringify(pathToFileURL(scopeModule).href)};\n\nexport const name = "odai-agent-scope-probe";\nexport const inject = ["systemPrompt", "tools"];\n\nexport function apply(ctx, config) {\n const results = {};\n let writing = Promise.resolve();\n\n ctx.on("agent/created", ({ agent }) => {\n writing = writing.then(async () => {\n const preset = agent.session?.header?.agentPreset;\n if (preset !== "standard" && preset !== "odai") return;\n\n const assembly = await ctx.systemPrompt.assemble({ agent, scope: agent });\n const canonicalSections = assembly.sections.filter((section) => section.name === "odai:canonical-governance");\n if (preset === "odai") {\n const coldNames = assembly.tools.map((tool) => tool.name).filter((toolName) => toolName.startsWith("odai_")).sort();\n const expectedColdNames = ["odai_context_capability", "odai_responsibility_gap"];\n if (JSON.stringify(coldNames) !== JSON.stringify(expectedColdNames)) {\n throw new Error(\`cold Odai tool schema mismatch: \${JSON.stringify(coldNames)}\`);\n }\n const hiddenResult = await ctx.tools.execute({\n callId: "scope-probe-odai-hidden-routing",\n name: "odai_routing_config",\n arguments: { action: "show" },\n agent,\n signal: new AbortController().signal,\n });\n if (!hiddenResult.isError || !/unknown tool/u.test(hiddenResult.error?.message ?? "")) {\n throw new Error(\`hidden Odai routing tool remained executable: \${JSON.stringify(hiddenResult)}\`);\n }\n agent.session.append("turn/start", { turn: 1 });\n agent.session.append("step/start", { turn: 1, step: 1 });\n const gatewayResult = await ctx.tools.execute({\n callId: "scope-probe-odai-routing-gateway",\n name: "odai_context_capability",\n arguments: { capability: "routing-config" },\n agent,\n signal: new AbortController().signal,\n });\n if (gatewayResult.isError) throw new Error(\`Odai capability gateway failed: \${JSON.stringify(gatewayResult)}\`);\n const activatedAssembly = await ctx.systemPrompt.assemble({ agent, scope: agent });\n const activatedNames = activatedAssembly.tools.map((tool) => tool.name).filter((toolName) => toolName.startsWith("odai_")).sort();\n const expectedActivatedNames = ["odai_context_capability", "odai_responsibility_gap", "odai_routing_config"].sort();\n if (JSON.stringify(activatedNames) !== JSON.stringify(expectedActivatedNames)) {\n throw new Error(\`activated Odai tool schema mismatch: \${JSON.stringify(activatedNames)}\`);\n }\n }\n const writePath = preset === "odai" ? config.odaiWritePath : config.standardWritePath;\n const childSession = new Proxy(agent.session, {\n get(target, property) {\n if (property === "header") {\n return { ...target.header, origin: "subagent", delegationDepth: 1 };\n }\n return Reflect.get(target, property, target);\n },\n });\n const child = { id: agent.id, session: childSession };\n bindScopeParent(child, agent);\n const toolResult = await ctx.tools.execute({\n callId: \`scope-probe-\${preset}\`,\n name: "write",\n arguments: { file_path: writePath, content: \`\${preset} child write reached body\\n\` },\n agent: child,\n signal: new AbortController().signal,\n });\n\n let routingProtected;\n if (preset === "odai") {\n const routingResult = await ctx.tools.execute({\n callId: "scope-probe-odai-routing-config",\n name: "odai_routing_config",\n arguments: { action: "set", responsibility: "executor", provider: "probe-provider", model: "probe-executor", reasoningEffort: "high" },\n agent,\n signal: new AbortController().signal,\n });\n routingProtected = routingResult.isError === true\n && /NO_ADAPTER/u.test(routingResult.error?.message ?? "")\n && !existsSync(config.routingConfigPath);\n }\n\n results[preset] = {\n canonicalSectionCount: canonicalSections.length,\n toolIsError: toolResult.isError === true,\n toolError: toolResult.isError === true ? toolResult.error?.message : undefined,\n writeReachedBody: existsSync(writePath),\n ...(routingProtected === undefined ? {} : { routingProtected }),\n ...(preset === "odai" ? { toolExposureSynchronized: true } : {}),\n };\n if (results.standard && results.odai) {\n writeFileSync(config.markerPath, JSON.stringify(results, null, 2) + "\\n", "utf8");\n }\n }).catch((error) => {\n writeFileSync(config.markerPath, JSON.stringify({ probeError: error?.stack ?? String(error) }, null, 2) + "\\n", "utf8");\n });\n }, { global: true });\n}\n`;
190
- await writeFile(probePluginPath, probePlugin, "utf8");
191
- await writeFile(patchPath, [
192
- "- insert:",
193
- " - id: odai-agent-scope-probe",
194
- ` name: ${yaml(pathToFileURL(probePluginPath).href)}`,
195
- " config:",
196
- ` markerPath: ${yaml(markerPath)}`,
197
- ` standardWritePath: ${yaml(resolve(workspace, "standard-child-write.txt"))}`,
198
- ` odaiWritePath: ${yaml(resolve(workspace, "odai-child-write.txt"))}`,
199
- ` routingConfigPath: ${yaml(resolve(home, "odai/routing.json"))}`,
200
- "",
201
- ].join("\n"), "utf8");
202
-
203
- const port = await freePort();
204
- const baseUrl = `http://127.0.0.1:${port}`;
205
- const child = spawnDsh(dsh, [
206
- "--profile", "web",
207
- "--patch", patchPath,
208
- "--host", "127.0.0.1",
209
- "--port", String(port),
210
- ], {
211
- cwd: workspace,
212
- env: {
213
- ...process.env,
214
- DSH_HOME: home,
215
- DSH_TELEMETRY_MODE: "DISABLED",
216
- },
217
- stdio: ["ignore", "pipe", "pipe"],
218
- });
219
- let output = "";
220
- child.stdout.on("data", (chunk) => { output += chunk.toString(); });
221
- child.stderr.on("data", (chunk) => { output += chunk.toString(); });
222
- const capturedOutput = () => output;
223
-
224
- try {
225
- await waitForServer(baseUrl, child, capturedOutput);
226
- const roster = await rpc(baseUrl, "agentPreset.list", {});
227
- const ids = roster.presets.map((preset) => preset.id);
228
- if (!ids.includes("standard") || !ids.includes("odai")) {
229
- throw new Error(`expected standard and odai presets, got ${JSON.stringify(ids)}`);
230
- }
231
- await rpc(baseUrl, "session.create", { cwd: workspace, agentPreset: "standard" });
232
- await rpc(baseUrl, "session.create", { cwd: workspace, agentPreset: "odai" });
233
- const results = await waitForMarker(child, capturedOutput);
234
- if (results.probeError) throw new Error(results.probeError);
235
- if (results.standard?.canonicalSectionCount !== 0) {
236
- throw new Error(`standard saw odai canonical prompt: ${JSON.stringify(results)}`);
237
- }
238
- if (results.standard?.toolError?.startsWith("ODAI_SUBAGENT_BOUNDARY:")) {
239
- throw new Error(`standard child call reached the odai guard: ${JSON.stringify(results)}`);
240
- }
241
- if (results.odai?.canonicalSectionCount !== 1) {
242
- throw new Error(`odai canonical prompt count was not exactly one: ${JSON.stringify(results)}`);
243
- }
244
- if (results.odai?.writeReachedBody !== false || results.odai?.toolIsError !== true
245
- || !results.odai?.toolError?.startsWith("ODAI_SUBAGENT_BOUNDARY:")) {
246
- throw new Error(`odai child write was not denied by odai guard: ${JSON.stringify(results)}`);
247
- }
248
- if (results.odai?.toolExposureSynchronized !== true) {
249
- throw new Error(`odai tool schemas and execution visibility were not synchronized: ${JSON.stringify(results)}`);
250
- }
251
- if (results.odai?.routingProtected !== true) {
252
- throw new Error(`odai routing tool did not reject an unavailable route without mutation: ${JSON.stringify(results)}`);
253
- }
254
- process.stdout.write(`${JSON.stringify({ scratch, roster: ids, results }, null, 2)}\n`);
255
- } finally {
256
- await terminateChild(child);
257
- if (process.env.KEEP_ODAI_SCOPE_PROBE !== "1") {
258
- await rm(scratch, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
259
- }
260
- }
@@ -1,24 +0,0 @@
1
- import { execFileSync, spawn } from "node:child_process";
2
-
3
- export function readDshVersion({
4
- dsh = process.env.DSH_BIN ?? "dsh",
5
- platform = process.platform,
6
- execute = execFileSync,
7
- } = {}) {
8
- return execute(normalizeDshCommand(dsh, platform), ["-V"], dshProcessOptions({ encoding: "utf8" }, platform)).trim();
9
- }
10
-
11
- export function spawnDsh(command, args, options = {}, {
12
- platform = process.platform,
13
- execute = spawn,
14
- } = {}) {
15
- return execute(normalizeDshCommand(command, platform), args, dshProcessOptions(options, platform));
16
- }
17
-
18
- function normalizeDshCommand(command, platform) {
19
- return platform === "win32" && command === "dsh" ? "dsh.cmd" : command;
20
- }
21
-
22
- function dshProcessOptions(options, platform) {
23
- return platform === "win32" ? { ...options, shell: true } : options;
24
- }
package/src/installer.mjs DELETED
@@ -1,385 +0,0 @@
1
- import { createHash, randomUUID } from "node:crypto";
2
- import {
3
- chmod,
4
- cp,
5
- lstat,
6
- mkdir,
7
- readFile,
8
- readdir,
9
- rename,
10
- rm,
11
- writeFile,
12
- } from "node:fs/promises";
13
- import { homedir } from "node:os";
14
- import { dirname, relative, resolve, sep } from "node:path";
15
- import { fileURLToPath, pathToFileURL } from "node:url";
16
-
17
- import { parse as parseYaml } from "yaml";
18
-
19
- export const DEFAULT_PRESET_ID = "odai";
20
- export const MANIFEST_FILE = ".odai-agent.json";
21
-
22
- const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
23
- const defaultSourceRoot = resolve(packageRoot, "preset/odai");
24
- const packageMetadata = JSON.parse(await readFile(resolve(packageRoot, "package.json"), "utf8"));
25
- const DSH_VERSION_PATTERN = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u;
26
- export const SUPPORTED_DSH_VERSIONS = Object.freeze(
27
- packageMetadata.peerDependencies["@deepseek-ai/dsh"]
28
- .split(/\s*\|\|\s*/u)
29
- .filter(Boolean),
30
- );
31
- if (SUPPORTED_DSH_VERSIONS.length === 0 || SUPPORTED_DSH_VERSIONS.some((version) => !DSH_VERSION_PATTERN.test(version))) {
32
- throw new Error("odai-dsh-agent peer dependency must list exact supported DSH versions");
33
- }
34
- const SOURCE_DSH_VERSION = "0.1.0-rc.8";
35
- const OPTIONAL_PROVIDER_COMMENTS = Object.freeze({
36
- "0.1.0-rc.7": " # Production dsh does not install these optional providers. An opting-in\n # Profile mounts each provider once on the host plane; copy this preset,\n # then remove `disabled` from the matching tool row.",
37
- "0.1.0-rc.8": " # Production dsh does not install these optional providers. Install the\n # matching Bundle in this Profile and restart the Host, then copy this\n # preset and remove `disabled` from the matching tool row. Host availability\n # alone grants no tool.",
38
- });
39
- const compositionVersions = Object.keys(OPTIONAL_PROVIDER_COMMENTS);
40
- if (JSON.stringify(SUPPORTED_DSH_VERSIONS) !== JSON.stringify(compositionVersions)) {
41
- throw new Error(`Odai Agent peer versions ${SUPPORTED_DSH_VERSIONS.join(", ")} must exactly match composition contracts ${compositionVersions.join(", ")}`);
42
- }
43
- if (compositionVersions.at(-1) !== SOURCE_DSH_VERSION) {
44
- throw new Error(`Odai Agent source composition targets ${SOURCE_DSH_VERSION}, not contract baseline ${compositionVersions.at(-1)}`);
45
- }
46
- export const SUPPORTED_DSH_VERSION = SOURCE_DSH_VERSION;
47
- const requiredFiles = Object.freeze([
48
- "agent.cordis.yml",
49
- "preset.yml",
50
- "runtime/index.mjs",
51
- "runtime/session-compat.mjs",
52
- "runtime/session-evidence.mjs",
53
- "runtime/skill-bundle.mjs",
54
- "runtime/skill-evolution.mjs",
55
- "runtime/skill-selection-state.mjs",
56
- "runtime/skill-selector.mjs",
57
- "runtime/skill-source-config.mjs",
58
- "skills/odai/SKILL.md",
59
- "skills/odai/manifest.json",
60
- ]);
61
-
62
- function replaceCompositionContract(composition, before, after, label) {
63
- const first = composition.indexOf(before);
64
- if (first < 0 || composition.indexOf(before, first + before.length) >= 0) {
65
- throw new Error(`Odai Agent source composition does not contain exactly one ${label} contract`);
66
- }
67
- return `${composition.slice(0, first)}${after}${composition.slice(first + before.length)}`;
68
- }
69
-
70
- export function renderAgentCompositionForDsh(composition, dshVersion = SUPPORTED_DSH_VERSION) {
71
- if (typeof composition !== "string" || composition.trim() === "") {
72
- throw new TypeError("agent composition must be a non-empty string");
73
- }
74
- if (!SUPPORTED_DSH_VERSIONS.includes(dshVersion)) {
75
- throw new Error(`unsupported DSH version ${dshVersion || "<empty>"}; expected one of ${SUPPORTED_DSH_VERSIONS.join(", ")}`);
76
- }
77
- let rendered = composition.replace(/\r\n/gu, "\n");
78
- if (dshVersion === SOURCE_DSH_VERSION) return rendered;
79
- const targetComment = OPTIONAL_PROVIDER_COMMENTS[dshVersion];
80
- if (!targetComment) throw new Error(`missing Agent composition contract for DSH ${dshVersion}`);
81
- rendered = replaceCompositionContract(
82
- rendered,
83
- OPTIONAL_PROVIDER_COMMENTS[SOURCE_DSH_VERSION],
84
- targetComment,
85
- "optional-provider comment",
86
- );
87
- return rendered;
88
- }
89
-
90
- export function resolveDshHome(configured, env = process.env) {
91
- const value = configured ?? env.DSH_HOME ?? resolve(homedir(), ".dsh");
92
- if (typeof value !== "string" || value.trim() === "") {
93
- throw new TypeError("DSH home must be a non-empty path");
94
- }
95
- const trimmed = value.trim();
96
- if (trimmed === "~") return homedir();
97
- if (trimmed.startsWith(`~${sep}`)) return resolve(homedir(), trimmed.slice(2));
98
- return resolve(trimmed);
99
- }
100
-
101
- export async function inspectAgentInstallation(options = {}) {
102
- const presetId = assertPresetId(options.presetId ?? DEFAULT_PRESET_ID);
103
- const dshHome = resolveDshHome(options.dshHome);
104
- const target = resolve(dshHome, ".agent-presets", presetId);
105
- const state = await inspectTarget(target, presetId);
106
- return Object.freeze({ dshHome, presetId, target, ...state });
107
- }
108
-
109
- export async function installAgentPreset(options = {}) {
110
- const presetId = assertPresetId(options.presetId ?? DEFAULT_PRESET_ID);
111
- const dshHome = resolveDshHome(options.dshHome);
112
- const sourceRoot = resolve(options.sourceRoot ?? defaultSourceRoot);
113
- const dshVersion = options.dshVersion ?? SUPPORTED_DSH_VERSION;
114
- if (!SUPPORTED_DSH_VERSIONS.includes(dshVersion)) {
115
- throw new Error(`unsupported DSH version ${dshVersion || "<empty>"}; expected one of ${SUPPORTED_DSH_VERSIONS.join(", ")}`);
116
- }
117
- const targetRoot = resolve(dshHome, ".agent-presets");
118
- const target = resolve(targetRoot, presetId);
119
- const current = await inspectTarget(target, presetId);
120
- const previousCompositionSize = current.status === "installed"
121
- ? Buffer.byteLength(await readFile(resolve(target, "agent.cordis.yml")))
122
- : undefined;
123
-
124
- if (current.status === "drifted") {
125
- throw new Error(`refusing to replace modified preset at ${target}: ${current.issues.join("; ")}`);
126
- }
127
-
128
- await assertSource(sourceRoot);
129
- const sessionCompatibility = await repairSessionCompatibility(
130
- dshHome,
131
- [sourceRoot],
132
- options.confirmDshStopped === true,
133
- options.processScanner,
134
- );
135
- await mkdir(targetRoot, { recursive: true, mode: 0o700 });
136
- const staging = resolve(targetRoot, `.${presetId}.tmp-${process.pid}-${randomUUID()}`);
137
- const backup = resolve(targetRoot, `.${presetId}.backup-${process.pid}-${randomUUID()}`);
138
- let backupCreated = false;
139
-
140
- try {
141
- await cp(sourceRoot, staging, { recursive: true, errorOnExist: true });
142
- await tightenTree(staging);
143
- const compositionPath = resolve(staging, "agent.cordis.yml");
144
- const composition = renderAgentCompositionForDsh(await readFile(compositionPath, "utf8"), dshVersion);
145
- let stampedComposition = `${composition.trimEnd()}\n# odai-dsh-agent generation ${packageMetadata.version}:${randomUUID()}\n`;
146
- // Supported DSH releases key standing generations by mtime and size, so size must change even on same-tick updates.
147
- if (previousCompositionSize === Buffer.byteLength(stampedComposition)) {
148
- stampedComposition += "# odai-dsh-agent generation size bump\n";
149
- }
150
- await writeFile(compositionPath, stampedComposition, "utf8");
151
- const files = await hashTree(staging);
152
- const manifest = {
153
- schemaVersion: 1,
154
- package: packageMetadata.name,
155
- version: packageMetadata.version,
156
- dshVersion,
157
- presetId,
158
- files,
159
- };
160
- const manifestPath = resolve(staging, MANIFEST_FILE);
161
- await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
162
- await chmod(manifestPath, 0o600);
163
-
164
- if (current.status === "installed") {
165
- await rename(target, backup);
166
- backupCreated = true;
167
- }
168
- await rename(staging, target);
169
- if (backupCreated) {
170
- await rm(backup, { recursive: true, force: true });
171
- backupCreated = false;
172
- }
173
-
174
- return Object.freeze({
175
- operation: current.status === "installed" ? "updated" : "installed",
176
- dshHome,
177
- presetId,
178
- target,
179
- version: packageMetadata.version,
180
- dshVersion,
181
- trust: "user",
182
- security: "DSH user presets have the same privileges as shell access; install only reviewed preset code.",
183
- sessionCompatibility,
184
- });
185
- } catch (error) {
186
- await rm(staging, { recursive: true, force: true });
187
- if (backupCreated) {
188
- const targetState = await pathState(target);
189
- if (targetState === "missing") await rename(backup, target);
190
- }
191
- throw error;
192
- } finally {
193
- await rm(backup, { recursive: true, force: true });
194
- }
195
- }
196
-
197
- export async function uninstallAgentPreset(options = {}) {
198
- const inspected = await inspectAgentInstallation(options);
199
- if (inspected.status === "absent") {
200
- return Object.freeze({ operation: "absent", target: inspected.target, presetId: inspected.presetId });
201
- }
202
- if (inspected.status === "drifted") {
203
- throw new Error(`refusing to remove modified preset at ${inspected.target}: ${inspected.issues.join("; ")}`);
204
- }
205
- await assertPresetIsNotDefault(inspected.dshHome, inspected.presetId);
206
- const sessionCompatibility = await repairSessionCompatibility(
207
- inspected.dshHome,
208
- [defaultSourceRoot, inspected.target],
209
- options.confirmDshStopped === true,
210
- options.processScanner,
211
- );
212
- if (sessionCompatibility.failures.length > 0) {
213
- throw new Error(`refusing to uninstall before legacy Odai session evidence is made ignorable: ${sessionCompatibility.failures.map((failure) => `${failure.path}: ${failure.error}`).join("; ")}`);
214
- }
215
- await rm(inspected.target, { recursive: true, force: true });
216
- return Object.freeze({
217
- operation: "uninstalled",
218
- target: inspected.target,
219
- presetId: inspected.presetId,
220
- sessionCompatibility,
221
- });
222
- }
223
-
224
- async function assertPresetIsNotDefault(dshHome, presetId) {
225
- const settingsPath = resolve(dshHome, "settings.yaml");
226
- let text;
227
- try {
228
- text = await readFile(settingsPath, "utf8");
229
- } catch (error) {
230
- if (error?.code === "ENOENT") return;
231
- throw error;
232
- }
233
- let settings;
234
- try {
235
- settings = parseYaml(text) ?? {};
236
- } catch (error) {
237
- throw new Error(`cannot verify the default agent preset in ${settingsPath}: ${error.message}`);
238
- }
239
- if (settings?.["agent-presets"]?.default === presetId) {
240
- throw new Error(`refusing to uninstall the default preset ${presetId}; select another agent-presets.default first`);
241
- }
242
- }
243
-
244
- async function inspectTarget(target, presetId) {
245
- const state = await pathState(target);
246
- if (state === "missing") return { status: "absent", issues: [] };
247
- if (state !== "directory") return { status: "drifted", issues: ["target is not a regular directory"] };
248
-
249
- let manifest;
250
- try {
251
- manifest = JSON.parse(await readFile(resolve(target, MANIFEST_FILE), "utf8"));
252
- } catch (error) {
253
- return { status: "drifted", issues: [`managed manifest is unavailable: ${error.message}`] };
254
- }
255
-
256
- const issues = validateManifest(manifest, presetId);
257
- let actualFiles = {};
258
- try {
259
- actualFiles = await hashTree(target, { exclude: new Set([MANIFEST_FILE]) });
260
- } catch (error) {
261
- issues.push(error.message);
262
- }
263
-
264
- if (issues.length === 0) {
265
- const expectedFiles = manifest.files;
266
- for (const [path, hash] of Object.entries(expectedFiles)) {
267
- if (!(path in actualFiles)) issues.push(`missing managed file ${path}`);
268
- else if (actualFiles[path] !== hash) issues.push(`modified managed file ${path}`);
269
- }
270
- for (const path of Object.keys(actualFiles)) {
271
- if (!(path in expectedFiles)) issues.push(`unmanaged file ${path}`);
272
- }
273
- }
274
-
275
- return {
276
- status: issues.length === 0 ? "installed" : "drifted",
277
- issues,
278
- version: typeof manifest?.version === "string" ? manifest.version : undefined,
279
- dshVersion: typeof manifest?.dshVersion === "string" ? manifest.dshVersion : undefined,
280
- };
281
- }
282
-
283
- function validateManifest(manifest, presetId) {
284
- const issues = [];
285
- if (manifest?.schemaVersion !== 1) issues.push("unsupported managed manifest schema");
286
- if (manifest?.package !== packageMetadata.name) issues.push("managed manifest package mismatch");
287
- if (manifest?.presetId !== presetId) issues.push("managed manifest preset id mismatch");
288
- if (!manifest?.files || typeof manifest.files !== "object" || Array.isArray(manifest.files)) {
289
- issues.push("managed manifest file map is invalid");
290
- }
291
- return issues;
292
- }
293
-
294
- async function repairSessionCompatibility(dshHome, sourceRoots, confirmDshStopped, processScanner) {
295
- for (const sourceRoot of sourceRoots) {
296
- const modulePath = resolve(sourceRoot, "runtime/session-compat.mjs");
297
- if (await pathState(modulePath) !== "file") continue;
298
- const compatibility = await import(pathToFileURL(modulePath).href);
299
- if (typeof compatibility.inspectLegacySessionLogs !== "function"
300
- || typeof compatibility.repairLegacySessionLogs !== "function") {
301
- throw new Error(`agent package session compatibility module is invalid: ${modulePath}`);
302
- }
303
- const inspected = compatibility.inspectLegacySessionLogs({ dshHome });
304
- if (inspected.failures.length > 0) {
305
- throw new Error(`cannot inspect legacy Odai session evidence safely: ${inspected.failures.map((failure) => `${failure.path}: ${failure.error}`).join("; ")}`);
306
- }
307
- if (inspected.matchedEvents === 0) return inspected;
308
- if (!confirmDshStopped) {
309
- throw new Error(`found ${inspected.matchedEvents} legacy Odai session event(s); stop every DSH process and rerun with --yes before changing the Agent preset`);
310
- }
311
- const repaired = compatibility.repairLegacySessionLogs({
312
- dshHome,
313
- confirmDshStopped: true,
314
- processScanner,
315
- });
316
- if (repaired.failures.length > 0) {
317
- throw new Error(`cannot repair legacy Odai session evidence safely: ${repaired.failures.map((failure) => `${failure.path}: ${failure.error}`).join("; ")}`);
318
- }
319
- return repaired;
320
- }
321
- throw new Error("agent package is incomplete: missing runtime/session-compat.mjs");
322
- }
323
-
324
- async function assertSource(sourceRoot) {
325
- for (const path of requiredFiles) {
326
- const state = await pathState(resolve(sourceRoot, path));
327
- if (state !== "file") throw new Error(`agent package is incomplete: missing ${path}`);
328
- }
329
- }
330
-
331
- async function tightenTree(root) {
332
- await chmod(root, 0o700);
333
- const entries = await readdir(root, { withFileTypes: true });
334
- for (const entry of entries) {
335
- const path = resolve(root, entry.name);
336
- if (entry.isSymbolicLink()) throw new Error(`symbolic link is not allowed in managed preset: ${entry.name}`);
337
- if (entry.isDirectory()) await tightenTree(path);
338
- else if (entry.isFile()) await chmod(path, 0o600);
339
- else throw new Error(`unsupported filesystem entry in managed preset: ${entry.name}`);
340
- }
341
- }
342
-
343
- async function hashTree(root, options = {}) {
344
- const found = {};
345
- await walk(root, "");
346
- return Object.fromEntries(Object.entries(found).sort(([left], [right]) => left.localeCompare(right)));
347
-
348
- async function walk(base, prefix) {
349
- const entries = await readdir(base, { withFileTypes: true });
350
- entries.sort((left, right) => left.name.localeCompare(right.name));
351
- for (const entry of entries) {
352
- const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
353
- if (options.exclude?.has(relativePath)) continue;
354
- const absolutePath = resolve(base, entry.name);
355
- if (entry.isSymbolicLink()) throw new Error(`symbolic link is not allowed in managed preset: ${relativePath}`);
356
- if (entry.isDirectory()) await walk(absolutePath, relativePath);
357
- else if (entry.isFile()) found[relativePath] = await hashFile(absolutePath);
358
- else throw new Error(`unsupported filesystem entry in managed preset: ${relativePath}`);
359
- }
360
- }
361
- }
362
-
363
- async function hashFile(path) {
364
- return createHash("sha256").update(await readFile(path)).digest("hex");
365
- }
366
-
367
- async function pathState(path) {
368
- try {
369
- const stats = await lstat(path);
370
- if (stats.isSymbolicLink()) return "symlink";
371
- if (stats.isDirectory()) return "directory";
372
- if (stats.isFile()) return "file";
373
- return "other";
374
- } catch (error) {
375
- if (error?.code === "ENOENT") return "missing";
376
- throw error;
377
- }
378
- }
379
-
380
- function assertPresetId(value) {
381
- if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(value)) {
382
- throw new TypeError("preset id must contain only letters, digits, dots, underscores, or hyphens");
383
- }
384
- return value;
385
- }