odai-dsh-agent 0.2.5 → 0.2.7

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 +398 -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 +190 -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 +1140 -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 +93 -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 +15 -14
  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 +80 -0
  96. package/preset/odai/runtime/router.d.mts.map +1 -0
  97. package/preset/odai/runtime/router.mjs +509 -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 +64 -0
  104. package/preset/odai/runtime/routing-context.d.mts.map +1 -0
  105. package/preset/odai/runtime/routing-context.mjs +398 -277
  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 +237 -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,318 +1,328 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { existsSync, readFileSync, realpathSync } from "node:fs";
3
3
  import { dirname, isAbsolute, relative, resolve } from "node:path";
4
-
4
+ import { isUnknownRecord } from "./runtime-types.mjs";
5
5
  export const ODAI_RUNTIME_CONTRACT = 3;
6
6
  export const SKILL_MANIFEST_FILE = "manifest.json";
7
7
  export const SKILL_SOURCE_MODES = Object.freeze(["bundled", "auto", "user"]);
8
-
9
8
  const MANIFEST_FIELDS = new Set([
10
- "schemaVersion",
11
- "name",
12
- "skillVersion",
13
- "runtimeContract",
14
- "requiredFiles",
9
+ "schemaVersion",
10
+ "name",
11
+ "skillVersion",
12
+ "runtimeContract",
13
+ "requiredFiles",
15
14
  ]);
16
15
  const REQUIRED_RUNTIME_FILES = Object.freeze([
17
- "SKILL.md",
18
- "assets/routing-roles/controller.md",
19
- "assets/routing-roles/researcher.md",
20
- "assets/routing-roles/planner.md",
21
- "assets/routing-roles/executor.md",
22
- "assets/routing-roles/reviewer.md",
23
- "assets/routing-roles/frontend.md",
16
+ "SKILL.md",
17
+ "assets/routing-roles/controller.md",
18
+ "assets/routing-roles/researcher.md",
19
+ "assets/routing-roles/planner.md",
20
+ "assets/routing-roles/executor.md",
21
+ "assets/routing-roles/reviewer.md",
22
+ "assets/routing-roles/frontend.md",
24
23
  ]);
25
24
  const PROJECT_SOURCES = new Set(["project-dsh", "project-agents", "custom"]);
26
25
  const USER_SOURCES = new Set(["user-dsh", "user-agents"]);
27
26
  const VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/u;
28
-
29
27
  function assertPlainObject(value, field) {
30
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
31
- throw new TypeError(`${field} must be an object`);
32
- }
33
- return value;
28
+ if (!isUnknownRecord(value))
29
+ throw new TypeError(`${field} must be an object`);
30
+ return value;
31
+ }
32
+ function isSkillSourceMode(value) {
33
+ return typeof value === "string" && SKILL_SOURCE_MODES.includes(value);
34
34
  }
35
-
36
35
  export function parseSkillVersion(value, field = "skillVersion") {
37
- if (typeof value !== "string") throw new TypeError(`${field} must be a string`);
38
- const match = value.match(VERSION_PATTERN);
39
- if (!match) throw new TypeError(`${field} must use SemVer 2.0.0 syntax`);
40
- const prerelease = match[4] === undefined ? [] : match[4].split(".");
41
- for (const identifier of prerelease) {
42
- if (/^\d+$/u.test(identifier) && identifier.length > 1 && identifier.startsWith("0")) {
43
- throw new TypeError(`${field} has a numeric prerelease identifier with a leading zero`);
36
+ if (typeof value !== "string")
37
+ throw new TypeError(`${field} must be a string`);
38
+ const match = value.match(VERSION_PATTERN);
39
+ if (!match)
40
+ throw new TypeError(`${field} must use SemVer 2.0.0 syntax`);
41
+ const prerelease = match[4] === undefined ? [] : match[4].split(".");
42
+ for (const identifier of prerelease) {
43
+ if (/^\d+$/u.test(identifier) && identifier.length > 1 && identifier.startsWith("0")) {
44
+ throw new TypeError(`${field} has a numeric prerelease identifier with a leading zero`);
45
+ }
44
46
  }
45
- }
46
- return Object.freeze({
47
- core: Object.freeze(match.slice(1, 4)),
48
- prerelease: Object.freeze(prerelease),
49
- });
47
+ return Object.freeze({
48
+ core: Object.freeze([match[1], match[2], match[3]]),
49
+ prerelease: Object.freeze(prerelease),
50
+ });
50
51
  }
51
-
52
52
  function compareNumericIdentifier(left, right) {
53
- if (left.length < right.length) return -1;
54
- if (left.length > right.length) return 1;
55
- if (left < right) return -1;
56
- if (left > right) return 1;
57
- return 0;
53
+ if (left.length < right.length)
54
+ return -1;
55
+ if (left.length > right.length)
56
+ return 1;
57
+ if (left < right)
58
+ return -1;
59
+ if (left > right)
60
+ return 1;
61
+ return 0;
58
62
  }
59
-
60
63
  function comparePrerelease(left, right) {
61
- if (left.length === 0 && right.length === 0) return 0;
62
- if (left.length === 0) return 1;
63
- if (right.length === 0) return -1;
64
- const length = Math.max(left.length, right.length);
65
- for (let index = 0; index < length; index += 1) {
66
- const leftValue = left[index];
67
- const rightValue = right[index];
68
- if (leftValue === undefined) return -1;
69
- if (rightValue === undefined) return 1;
70
- const leftNumeric = /^\d+$/u.test(leftValue);
71
- const rightNumeric = /^\d+$/u.test(rightValue);
72
- if (leftNumeric && rightNumeric) {
73
- const difference = compareNumericIdentifier(leftValue, rightValue);
74
- if (difference !== 0) return difference;
75
- continue;
64
+ if (left.length === 0 && right.length === 0)
65
+ return 0;
66
+ if (left.length === 0)
67
+ return 1;
68
+ if (right.length === 0)
69
+ return -1;
70
+ const length = Math.max(left.length, right.length);
71
+ for (let index = 0; index < length; index += 1) {
72
+ const leftValue = left[index];
73
+ const rightValue = right[index];
74
+ if (leftValue === undefined)
75
+ return -1;
76
+ if (rightValue === undefined)
77
+ return 1;
78
+ const leftNumeric = /^\d+$/u.test(leftValue);
79
+ const rightNumeric = /^\d+$/u.test(rightValue);
80
+ if (leftNumeric && rightNumeric) {
81
+ const difference = compareNumericIdentifier(leftValue, rightValue);
82
+ if (difference !== 0)
83
+ return difference;
84
+ continue;
85
+ }
86
+ if (leftNumeric !== rightNumeric)
87
+ return leftNumeric ? -1 : 1;
88
+ if (leftValue < rightValue)
89
+ return -1;
90
+ if (leftValue > rightValue)
91
+ return 1;
76
92
  }
77
- if (leftNumeric !== rightNumeric) return leftNumeric ? -1 : 1;
78
- if (leftValue < rightValue) return -1;
79
- if (leftValue > rightValue) return 1;
80
- }
81
- return 0;
93
+ return 0;
82
94
  }
83
-
84
95
  export function compareSkillVersions(left, right) {
85
- const leftVersion = typeof left === "string" ? parseSkillVersion(left, "left skillVersion") : left;
86
- const rightVersion = typeof right === "string" ? parseSkillVersion(right, "right skillVersion") : right;
87
- for (let index = 0; index < 3; index += 1) {
88
- const difference = compareNumericIdentifier(leftVersion.core[index], rightVersion.core[index]);
89
- if (difference !== 0) return difference;
90
- }
91
- return comparePrerelease(leftVersion.prerelease, rightVersion.prerelease);
96
+ const leftVersion = typeof left === "string" ? parseSkillVersion(left, "left skillVersion") : left;
97
+ const rightVersion = typeof right === "string" ? parseSkillVersion(right, "right skillVersion") : right;
98
+ for (let index = 0; index < 3; index += 1) {
99
+ const leftIdentifier = leftVersion.core[index];
100
+ const rightIdentifier = rightVersion.core[index];
101
+ if (leftIdentifier === undefined || rightIdentifier === undefined) {
102
+ throw new TypeError("skillVersion core must contain three identifiers");
103
+ }
104
+ const difference = compareNumericIdentifier(leftIdentifier, rightIdentifier);
105
+ if (difference !== 0)
106
+ return difference;
107
+ }
108
+ return comparePrerelease(leftVersion.prerelease, rightVersion.prerelease);
92
109
  }
93
-
94
110
  function resolveBundleFile(root, relativePath) {
95
- if (typeof relativePath !== "string" || relativePath.trim() === "") {
96
- throw new TypeError("skill manifest requiredFiles entries must be non-empty strings");
97
- }
98
- const segments = relativePath.split("/");
99
- if (relativePath !== relativePath.trim()
100
- || relativePath.includes("\\")
101
- || isAbsolute(relativePath)
102
- || segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
103
- throw new TypeError(`skill manifest contains unsafe required file ${JSON.stringify(relativePath)}`);
104
- }
105
- const target = resolve(root, ...segments);
106
- const fromRoot = relative(root, target);
107
- if (fromRoot === "" || fromRoot === ".." || fromRoot.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) {
108
- throw new TypeError(`skill manifest required file escapes its bundle: ${relativePath}`);
109
- }
110
- return target;
111
+ if (typeof relativePath !== "string" || relativePath.trim() === "") {
112
+ throw new TypeError("skill manifest requiredFiles entries must be non-empty strings");
113
+ }
114
+ const segments = relativePath.split("/");
115
+ if (relativePath !== relativePath.trim()
116
+ || relativePath.includes("\\")
117
+ || isAbsolute(relativePath)
118
+ || segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
119
+ throw new TypeError(`skill manifest contains unsafe required file ${JSON.stringify(relativePath)}`);
120
+ }
121
+ const target = resolve(root, ...segments);
122
+ const fromRoot = relative(root, target);
123
+ if (fromRoot === "" || fromRoot === ".." || fromRoot.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) {
124
+ throw new TypeError(`skill manifest required file escapes its bundle: ${relativePath}`);
125
+ }
126
+ return target;
111
127
  }
112
-
113
128
  function assertRealPathInside(rootRealPath, target, label) {
114
- const targetRealPath = realpathSync(target);
115
- const fromRoot = relative(rootRealPath, targetRealPath);
116
- if (fromRoot === "" || fromRoot === ".." || fromRoot.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) {
117
- throw new Error(`Odai skill bundle path escapes through a symlink: ${label}`);
118
- }
119
- return targetRealPath;
129
+ const targetRealPath = realpathSync(target);
130
+ const fromRoot = relative(rootRealPath, targetRealPath);
131
+ if (fromRoot === "" || fromRoot === ".." || fromRoot.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`)) {
132
+ throw new Error(`Odai skill bundle path escapes through a symlink: ${label}`);
133
+ }
134
+ return targetRealPath;
120
135
  }
121
-
122
136
  export function readSkillManifest(skillRoot) {
123
- const rootRealPath = realpathSync(skillRoot);
124
- const manifestPath = resolve(skillRoot, SKILL_MANIFEST_FILE);
125
- let parsed;
126
- try {
127
- const manifestRealPath = assertRealPathInside(rootRealPath, manifestPath, SKILL_MANIFEST_FILE);
128
- parsed = JSON.parse(readFileSync(manifestRealPath, "utf8"));
129
- } catch (error) {
130
- throw new Error(`cannot read Odai skill manifest ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`);
131
- }
132
- const manifest = assertPlainObject(parsed, `Odai skill manifest ${manifestPath}`);
133
- const unknown = Object.keys(manifest).filter((field) => !MANIFEST_FIELDS.has(field));
134
- if (unknown.length > 0) {
135
- throw new TypeError(`Odai skill manifest ${manifestPath} has unknown fields: ${unknown.join(", ")}`);
136
- }
137
- if (manifest.schemaVersion !== 1) {
138
- throw new TypeError(`Odai skill manifest ${manifestPath} has unsupported schemaVersion ${String(manifest.schemaVersion)}`);
139
- }
140
- if (manifest.name !== "odai") throw new TypeError(`Odai skill manifest ${manifestPath} must name odai`);
141
- const versionParts = parseSkillVersion(manifest.skillVersion, `Odai skill manifest ${manifestPath}.skillVersion`);
142
- if (!Number.isSafeInteger(manifest.runtimeContract) || manifest.runtimeContract <= 0) {
143
- throw new TypeError(`Odai skill manifest ${manifestPath}.runtimeContract must be a positive integer`);
144
- }
145
- if (!Array.isArray(manifest.requiredFiles) || manifest.requiredFiles.length === 0) {
146
- throw new TypeError(`Odai skill manifest ${manifestPath}.requiredFiles must be a non-empty array`);
147
- }
148
- const requiredFiles = manifest.requiredFiles.map((file) => {
149
- resolveBundleFile(skillRoot, file);
150
- return file;
151
- });
152
- if (new Set(requiredFiles).size !== requiredFiles.length) {
153
- throw new TypeError(`Odai skill manifest ${manifestPath}.requiredFiles contains duplicates`);
154
- }
155
- for (const required of REQUIRED_RUNTIME_FILES) {
156
- if (!requiredFiles.includes(required)) {
157
- throw new TypeError(`Odai skill manifest ${manifestPath} is missing required runtime file ${required}`);
137
+ const rootRealPath = realpathSync(skillRoot);
138
+ const manifestPath = resolve(skillRoot, SKILL_MANIFEST_FILE);
139
+ let parsed;
140
+ try {
141
+ const manifestRealPath = assertRealPathInside(rootRealPath, manifestPath, SKILL_MANIFEST_FILE);
142
+ parsed = JSON.parse(readFileSync(manifestRealPath, "utf8"));
143
+ }
144
+ catch (error) {
145
+ throw new Error(`cannot read Odai skill manifest ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`);
146
+ }
147
+ const manifest = assertPlainObject(parsed, `Odai skill manifest ${manifestPath}`);
148
+ const unknownFields = Object.keys(manifest).filter((field) => !MANIFEST_FIELDS.has(field));
149
+ if (unknownFields.length > 0) {
150
+ throw new TypeError(`Odai skill manifest ${manifestPath} has unknown fields: ${unknownFields.join(", ")}`);
151
+ }
152
+ if (manifest.schemaVersion !== 1) {
153
+ throw new TypeError(`Odai skill manifest ${manifestPath} has unsupported schemaVersion ${String(manifest.schemaVersion)}`);
154
+ }
155
+ if (manifest.name !== "odai")
156
+ throw new TypeError(`Odai skill manifest ${manifestPath} must name odai`);
157
+ if (typeof manifest.skillVersion !== "string") {
158
+ throw new TypeError(`Odai skill manifest ${manifestPath}.skillVersion must be a string`);
159
+ }
160
+ const skillVersion = manifest.skillVersion;
161
+ const versionParts = parseSkillVersion(skillVersion, `Odai skill manifest ${manifestPath}.skillVersion`);
162
+ if (typeof manifest.runtimeContract !== "number" || !Number.isSafeInteger(manifest.runtimeContract) || manifest.runtimeContract <= 0) {
163
+ throw new TypeError(`Odai skill manifest ${manifestPath}.runtimeContract must be a positive integer`);
158
164
  }
159
- }
160
- return Object.freeze({
161
- schemaVersion: 1,
162
- name: "odai",
163
- skillVersion: manifest.skillVersion,
164
- versionParts,
165
- runtimeContract: manifest.runtimeContract,
166
- requiredFiles: Object.freeze(requiredFiles),
167
- });
165
+ if (!Array.isArray(manifest.requiredFiles) || manifest.requiredFiles.length === 0) {
166
+ throw new TypeError(`Odai skill manifest ${manifestPath}.requiredFiles must be a non-empty array`);
167
+ }
168
+ const requiredFiles = manifest.requiredFiles.map((file) => {
169
+ resolveBundleFile(skillRoot, file);
170
+ if (typeof file !== "string")
171
+ throw new TypeError("skill manifest requiredFiles entries must be strings");
172
+ return file;
173
+ });
174
+ if (new Set(requiredFiles).size !== requiredFiles.length) {
175
+ throw new TypeError(`Odai skill manifest ${manifestPath}.requiredFiles contains duplicates`);
176
+ }
177
+ for (const required of REQUIRED_RUNTIME_FILES) {
178
+ if (!requiredFiles.includes(required)) {
179
+ throw new TypeError(`Odai skill manifest ${manifestPath} is missing required runtime file ${required}`);
180
+ }
181
+ }
182
+ return Object.freeze({
183
+ schemaVersion: 1,
184
+ name: "odai",
185
+ skillVersion,
186
+ versionParts,
187
+ runtimeContract: manifest.runtimeContract,
188
+ requiredFiles: Object.freeze(requiredFiles),
189
+ });
168
190
  }
169
-
170
191
  export function loadSkillBundle(skillPath, options = {}) {
171
- const entryPath = resolve(skillPath);
172
- const root = dirname(entryPath);
173
- const rootRealPath = realpathSync(root);
174
- const manifest = readSkillManifest(root);
175
- const digest = createHash("sha256");
176
- const contents = new Map();
177
- digest.update(JSON.stringify({
178
- schemaVersion: manifest.schemaVersion,
179
- name: manifest.name,
180
- skillVersion: manifest.skillVersion,
181
- runtimeContract: manifest.runtimeContract,
182
- requiredFiles: manifest.requiredFiles,
183
- }));
184
-
185
- for (const relativePath of [...manifest.requiredFiles].sort()) {
186
- const path = resolveBundleFile(root, relativePath);
187
- if (!existsSync(path)) throw new Error(`Odai skill bundle ${root} is missing ${relativePath}`);
188
- const realPath = assertRealPathInside(rootRealPath, path, relativePath);
189
- const content = readFileSync(realPath);
190
- contents.set(relativePath, content);
191
- digest.update("\0");
192
- digest.update(relativePath);
193
- digest.update("\0");
194
- digest.update(content);
195
- }
196
-
197
- const skillText = contents.get("SKILL.md").toString("utf8").trim();
198
- if (!skillText) throw new Error(`Odai canonical skill is empty: ${entryPath}`);
199
- const frontmatter = skillText.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u)?.[1];
200
- if (!frontmatter || !/^name:\s*odai\s*$/mu.test(frontmatter)) {
201
- throw new Error(`Odai canonical skill entry does not declare name odai: ${entryPath}`);
202
- }
203
-
204
- const roleContracts = Object.freeze(Object.fromEntries(["controller", "researcher", "planner", "executor", "reviewer", "frontend"].map((role) => {
205
- const relativePath = `assets/routing-roles/${role}.md`;
206
- const text = contents.get(relativePath).toString("utf8").trim();
207
- if (!text) throw new Error(`Odai canonical ${role} role is unavailable: ${resolve(root, relativePath)}`);
208
- return [role, text];
209
- })));
210
- const referenceContracts = Object.freeze(Object.fromEntries(["craft"].map((reference) => {
211
- const relativePath = `references/${reference}.md`;
212
- const text = contents.get(relativePath)?.toString("utf8").trim();
213
- if (!text) throw new Error(`Odai canonical ${reference} reference is unavailable: ${resolve(root, relativePath)}`);
214
- return [reference, text];
215
- })));
216
- const fileContents = Object.freeze(Object.fromEntries(
217
- [...contents].map(([relativePath, content]) => [relativePath, content.toString("base64")]),
218
- ));
219
-
220
- const bundle = {
221
- path: entryPath,
222
- root,
223
- source: typeof options.source === "string" && options.source ? options.source : "bundled",
224
- provider: typeof options.provider === "string" && options.provider ? options.provider : "odai-dsh-runtime",
225
- manifest,
226
- skillText,
227
- roleContracts,
228
- referenceContracts,
229
- digest: digest.digest("hex"),
230
- };
231
- Object.defineProperty(bundle, "fileContents", { value: fileContents, enumerable: false });
232
- return Object.freeze(bundle);
192
+ const entryPath = resolve(skillPath);
193
+ const root = dirname(entryPath);
194
+ const rootRealPath = realpathSync(root);
195
+ const manifest = readSkillManifest(root);
196
+ const digest = createHash("sha256");
197
+ const contents = new Map();
198
+ digest.update(JSON.stringify({
199
+ schemaVersion: manifest.schemaVersion,
200
+ name: manifest.name,
201
+ skillVersion: manifest.skillVersion,
202
+ runtimeContract: manifest.runtimeContract,
203
+ requiredFiles: manifest.requiredFiles,
204
+ }));
205
+ for (const relativePath of [...manifest.requiredFiles].sort()) {
206
+ const path = resolveBundleFile(root, relativePath);
207
+ if (!existsSync(path))
208
+ throw new Error(`Odai skill bundle ${root} is missing ${relativePath}`);
209
+ const realPath = assertRealPathInside(rootRealPath, path, relativePath);
210
+ const content = readFileSync(realPath);
211
+ contents.set(relativePath, content);
212
+ digest.update("\0");
213
+ digest.update(relativePath);
214
+ digest.update("\0");
215
+ digest.update(content);
216
+ }
217
+ const skillContent = contents.get("SKILL.md");
218
+ if (!skillContent)
219
+ throw new Error(`Odai skill bundle ${root} is missing SKILL.md`);
220
+ const skillText = skillContent.toString("utf8").trim();
221
+ if (!skillText)
222
+ throw new Error(`Odai canonical skill is empty: ${entryPath}`);
223
+ const frontmatter = skillText.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u)?.[1];
224
+ if (!frontmatter || !/^name:\s*odai\s*$/mu.test(frontmatter)) {
225
+ throw new Error(`Odai canonical skill entry does not declare name odai: ${entryPath}`);
226
+ }
227
+ const roleContracts = Object.freeze(Object.fromEntries(["controller", "researcher", "planner", "executor", "reviewer", "frontend"].map((role) => {
228
+ const relativePath = `assets/routing-roles/${role}.md`;
229
+ const content = contents.get(relativePath);
230
+ if (!content)
231
+ throw new Error(`Odai canonical ${role} role is unavailable: ${resolve(root, relativePath)}`);
232
+ const text = content.toString("utf8").trim();
233
+ if (!text)
234
+ throw new Error(`Odai canonical ${role} role is unavailable: ${resolve(root, relativePath)}`);
235
+ return [role, text];
236
+ })));
237
+ const referenceContracts = Object.freeze(Object.fromEntries(["craft"].map((reference) => {
238
+ const relativePath = `references/${reference}.md`;
239
+ const text = contents.get(relativePath)?.toString("utf8").trim();
240
+ if (!text)
241
+ throw new Error(`Odai canonical ${reference} reference is unavailable: ${resolve(root, relativePath)}`);
242
+ return [reference, text];
243
+ })));
244
+ const fileContents = Object.freeze(Object.fromEntries([...contents].map(([relativePath, content]) => [relativePath, content.toString("base64")])));
245
+ const bundle = {
246
+ path: entryPath,
247
+ root,
248
+ source: typeof options.source === "string" && options.source ? options.source : "bundled",
249
+ provider: typeof options.provider === "string" && options.provider ? options.provider : "odai-dsh-runtime",
250
+ manifest,
251
+ skillText,
252
+ roleContracts,
253
+ referenceContracts,
254
+ digest: digest.digest("hex"),
255
+ fileContents,
256
+ };
257
+ Object.defineProperty(bundle, "fileContents", { value: fileContents, enumerable: false });
258
+ return Object.freeze(bundle);
233
259
  }
234
-
235
260
  export function readSkillBundleFile(bundle, relativePath) {
236
- if (!bundle || typeof bundle !== "object" || !bundle.manifest?.requiredFiles?.includes(relativePath)) {
237
- throw new TypeError(`unknown Odai skill bundle file: ${String(relativePath)}`);
238
- }
239
- const encoded = bundle.fileContents?.[relativePath];
240
- if (typeof encoded !== "string") throw new Error(`Odai skill bundle snapshot is missing ${relativePath}`);
241
- return Buffer.from(encoded, "base64");
261
+ if (!bundle || typeof bundle !== "object" || !bundle.manifest?.requiredFiles?.includes(relativePath)) {
262
+ throw new TypeError(`unknown Odai skill bundle file: ${String(relativePath)}`);
263
+ }
264
+ const encoded = bundle.fileContents?.[relativePath];
265
+ if (typeof encoded !== "string")
266
+ throw new Error(`Odai skill bundle snapshot is missing ${relativePath}`);
267
+ return Buffer.from(encoded, "base64");
242
268
  }
243
-
244
269
  function fallbackSelection(mode, bundled, reasonCode, detail, candidate) {
245
- return Object.freeze({
246
- mode,
247
- status: "fallback",
248
- reasonCode,
249
- detail,
250
- bundle: bundled,
251
- ...(candidate ? { candidate } : {}),
252
- });
270
+ return Object.freeze({
271
+ mode,
272
+ status: "fallback",
273
+ reasonCode,
274
+ ...(detail === undefined ? {} : { detail }),
275
+ bundle: bundled,
276
+ ...(candidate ? { candidate } : {}),
277
+ });
253
278
  }
254
-
255
279
  function selected(mode, bundle, reasonCode, candidate) {
256
- return Object.freeze({
257
- mode,
258
- status: "selected",
259
- reasonCode,
260
- bundle,
261
- ...(candidate ? { candidate } : {}),
262
- });
280
+ return Object.freeze({
281
+ mode,
282
+ status: "selected",
283
+ reasonCode,
284
+ bundle,
285
+ ...(candidate ? { candidate } : {}),
286
+ });
263
287
  }
264
-
265
- export function chooseSkillBundle({ mode, bundled, candidate, candidateError } = {}) {
266
- if (!SKILL_SOURCE_MODES.includes(mode)) throw new TypeError(`unknown Odai skill source mode: ${String(mode)}`);
267
- if (!bundled) throw new TypeError("bundled Odai skill is required");
268
- if (mode === "bundled") return selected(mode, bundled, "bundled-configured");
269
- if (candidateError) {
270
- return fallbackSelection(mode, bundled, "external-invalid", candidateError instanceof Error ? candidateError.message : String(candidateError));
271
- }
272
- if (!candidate) {
273
- return mode === "auto"
274
- ? selected(mode, bundled, "external-not-installed")
275
- : fallbackSelection(mode, bundled, "user-source-missing", "no compatible user-level Odai skill is installed");
276
- }
277
- if (candidate.manifest.runtimeContract !== ODAI_RUNTIME_CONTRACT) {
278
- return fallbackSelection(
279
- mode,
280
- bundled,
281
- "runtime-contract-mismatch",
282
- `candidate runtimeContract ${candidate.manifest.runtimeContract} is incompatible with runtime contract ${ODAI_RUNTIME_CONTRACT}`,
283
- candidate,
284
- );
285
- }
286
-
287
- const versionOrder = compareSkillVersions(candidate.manifest.versionParts, bundled.manifest.versionParts);
288
- if (versionOrder === 0) {
289
- if (candidate.digest === bundled.digest) return selected(mode, bundled, "external-equivalent", candidate);
290
- return fallbackSelection(
291
- mode,
292
- bundled,
293
- "same-version-content-conflict",
294
- `candidate ${candidate.manifest.skillVersion} differs from the bundled content with the same version`,
295
- candidate,
296
- );
297
- }
298
-
299
- if (mode === "user") {
300
- if (!USER_SOURCES.has(candidate.source) && candidate.source !== "custom") {
301
- return fallbackSelection(mode, bundled, "user-source-invalid", `source ${candidate.source} is not user-level`, candidate);
288
+ export function chooseSkillBundle(options = {}) {
289
+ const { mode, bundled, candidate, candidateError } = options;
290
+ if (!isSkillSourceMode(mode))
291
+ throw new TypeError(`unknown Odai skill source mode: ${String(mode)}`);
292
+ if (!bundled)
293
+ throw new TypeError("bundled Odai skill is required");
294
+ if (mode === "bundled")
295
+ return selected(mode, bundled, "bundled-configured");
296
+ if (candidateError) {
297
+ return fallbackSelection(mode, bundled, "external-invalid", candidateError instanceof Error ? candidateError.message : String(candidateError));
298
+ }
299
+ if (!candidate) {
300
+ return mode === "auto"
301
+ ? selected(mode, bundled, "external-not-installed")
302
+ : fallbackSelection(mode, bundled, "user-source-missing", "no compatible user-level Odai skill is installed");
303
+ }
304
+ if (candidate.manifest.runtimeContract !== ODAI_RUNTIME_CONTRACT) {
305
+ return fallbackSelection(mode, bundled, "runtime-contract-mismatch", `candidate runtimeContract ${candidate.manifest.runtimeContract} is incompatible with runtime contract ${ODAI_RUNTIME_CONTRACT}`, candidate);
306
+ }
307
+ const versionOrder = compareSkillVersions(candidate.manifest.versionParts, bundled.manifest.versionParts);
308
+ if (versionOrder === 0) {
309
+ if (candidate.digest === bundled.digest)
310
+ return selected(mode, bundled, "external-equivalent", candidate);
311
+ return fallbackSelection(mode, bundled, "same-version-content-conflict", `candidate ${candidate.manifest.skillVersion} differs from the bundled content with the same version`, candidate);
312
+ }
313
+ if (mode === "user") {
314
+ if (!USER_SOURCES.has(candidate.source) && candidate.source !== "custom") {
315
+ return fallbackSelection(mode, bundled, "user-source-invalid", `source ${candidate.source} is not user-level`, candidate);
316
+ }
317
+ return selected(mode, candidate, "user-configured", candidate);
318
+ }
319
+ if (PROJECT_SOURCES.has(candidate.source))
320
+ return selected(mode, candidate, "project-scope-override", candidate);
321
+ if (USER_SOURCES.has(candidate.source)) {
322
+ return versionOrder > 0
323
+ ? selected(mode, candidate, "newer-user-skill", candidate)
324
+ : fallbackSelection(mode, bundled, "user-skill-older", `candidate ${candidate.manifest.skillVersion} is older than bundled ${bundled.manifest.skillVersion}`, candidate);
302
325
  }
303
- return selected(mode, candidate, "user-configured", candidate);
304
- }
305
- if (PROJECT_SOURCES.has(candidate.source)) return selected(mode, candidate, "project-scope-override", candidate);
306
- if (USER_SOURCES.has(candidate.source)) {
307
- return versionOrder > 0
308
- ? selected(mode, candidate, "newer-user-skill", candidate)
309
- : fallbackSelection(
310
- mode,
311
- bundled,
312
- "user-skill-older",
313
- `candidate ${candidate.manifest.skillVersion} is older than bundled ${bundled.manifest.skillVersion}`,
314
- candidate,
315
- );
316
- }
317
- return fallbackSelection(mode, bundled, "external-source-unsupported", `source ${candidate.source} cannot provide Odai governance`, candidate);
326
+ return fallbackSelection(mode, bundled, "external-source-unsupported", `source ${candidate.source} cannot provide Odai governance`, candidate);
318
327
  }
328
+ //# sourceMappingURL=skill-bundle.mjs.map