@senpi/runtime 3.0.6-dev.runtime-phase-2.20260630134536 → 3.0.6-dev.runtime-phase-2.20260630182800

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 (48) hide show
  1. package/dist/cli/senpi-commands.d.ts.map +1 -1
  2. package/dist/cli/senpi-commands.js +4 -0
  3. package/dist/cli/senpi-commands.js.map +1 -1
  4. package/dist/index.d.ts +19 -6
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +0 -3
  7. package/dist/index.js.map +1 -1
  8. package/dist/skills-manager/bootstrap.d.ts +12 -7
  9. package/dist/skills-manager/bootstrap.d.ts.map +1 -1
  10. package/dist/skills-manager/bootstrap.js +31 -20
  11. package/dist/skills-manager/bootstrap.js.map +1 -1
  12. package/dist/skills-manager/clone-groups.d.ts +20 -0
  13. package/dist/skills-manager/clone-groups.d.ts.map +1 -0
  14. package/dist/skills-manager/clone-groups.js +45 -0
  15. package/dist/skills-manager/clone-groups.js.map +1 -0
  16. package/dist/skills-manager/constants.d.ts +5 -33
  17. package/dist/skills-manager/constants.d.ts.map +1 -1
  18. package/dist/skills-manager/constants.js +11 -50
  19. package/dist/skills-manager/constants.js.map +1 -1
  20. package/dist/skills-manager/coordinator.d.ts +26 -119
  21. package/dist/skills-manager/coordinator.d.ts.map +1 -1
  22. package/dist/skills-manager/coordinator.js +145 -346
  23. package/dist/skills-manager/coordinator.js.map +1 -1
  24. package/dist/skills-manager/fetcher.d.ts +23 -2
  25. package/dist/skills-manager/fetcher.d.ts.map +1 -1
  26. package/dist/skills-manager/fetcher.js +36 -6
  27. package/dist/skills-manager/fetcher.js.map +1 -1
  28. package/dist/skills-manager/manifest.d.ts +160 -0
  29. package/dist/skills-manager/manifest.d.ts.map +1 -0
  30. package/dist/skills-manager/manifest.js +185 -0
  31. package/dist/skills-manager/manifest.js.map +1 -0
  32. package/dist/skills-manager/skills-manifest.json +16 -0
  33. package/dist/skills-manager/types.d.ts +61 -30
  34. package/dist/skills-manager/types.d.ts.map +1 -1
  35. package/dist/skills-manager/utils.d.ts +10 -26
  36. package/dist/skills-manager/utils.d.ts.map +1 -1
  37. package/dist/skills-manager/utils.js +18 -34
  38. package/dist/skills-manager/utils.js.map +1 -1
  39. package/dist/skills-manager/version-manager.d.ts +21 -91
  40. package/dist/skills-manager/version-manager.d.ts.map +1 -1
  41. package/dist/skills-manager/version-manager.js +48 -178
  42. package/dist/skills-manager/version-manager.js.map +1 -1
  43. package/dist/skills-manager/writer.d.ts +12 -1
  44. package/dist/skills-manager/writer.d.ts.map +1 -1
  45. package/dist/skills-manager/writer.js +50 -4
  46. package/dist/skills-manager/writer.js.map +1 -1
  47. package/openclaw.plugin.json +18 -6
  48. package/package.json +3 -2
@@ -40,112 +40,29 @@ export function parseSkillMdVersion(content) {
40
40
  return versionMatch ? versionMatch[1].trim() : null;
41
41
  }
42
42
  /**
43
- * Extract the `description` value from a SKILL.md YAML frontmatter block.
44
- *
45
- * Skill descriptions can be authored a few different ways. This helper
46
- * handles the real-world shapes that appear in the senpi-skills repo today:
47
- *
48
- * 1. Single-line, double-quoted:
49
- * description: "Configure, deploy, ..."
50
- * 2. Single-line, single-quoted or unquoted:
51
- * description: Manage runtimes ...
52
- * 3. YAML block scalar (folded or literal, optional chomping indicator):
53
- * description: >
54
- * A short summary
55
- * continues on this line.
56
- * description: >- # strip trailing newlines (this PR's senpi-skills repo)
57
- * A short summary
58
- * description: | # literal — preserves newlines (collapsed to spaces here)
59
- * A short summary
60
- * description: |+ # keep trailing newlines (same handling)
61
- *
62
- * The block scalar header is `[>|][-+]?` per the YAML 1.2 spec:
63
- * `>` = folded, `|` = literal; `-` strips trailing newlines,
64
- * `+` keeps them, no indicator clips. For notification rendering we
65
- * always collapse to a single line joined by spaces, so the chomping
66
- * indicator only affects whether we match the block-scalar branch.
67
- *
68
- * Returns null when the field is absent or the frontmatter block is missing.
69
- *
70
- * Note: kept as a regex extractor rather than introducing a full YAML
71
- * parse here — `parseSkillMdVersion` uses the same approach, and skill
72
- * frontmatters are constrained enough that the regex covers the shapes we
73
- * actually encounter. If we ever adopt a full YAML parser for this file,
74
- * both helpers should switch together.
75
- */
76
- export function parseSkillMdDescription(content) {
77
- const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content.trim());
78
- if (!match)
79
- return null;
80
- const frontmatter = match[1];
81
- // Shape 3: block scalar — `description:` followed by a `>` or `|` header
82
- // (optionally with a `-` / `+` chomping indicator) on its own line, then
83
- // indented continuation lines. Stop at the first un-indented line or the
84
- // end of frontmatter. We collapse the indent and join lines into a single
85
- // string so the notification renderer doesn't emit raw YAML continuations.
86
- //
87
- // Pre-fix this regex was `[>|]` (no indicator). A SKILL.md authored with
88
- // `description: >-` (the YAML strip-trailing-newlines variant — what the
89
- // senpi-skills repo actually uses) didn't match, fell through to the
90
- // inline branch, and ended up capturing the literal "`>-`" as the value.
91
- // That's the bug report this fixes: the Telegram notification showed
92
- // `>-` instead of the real description.
93
- const foldedMatch = /(?:^|\n)description:[ \t]*[>|][-+]?[ \t]*\r?\n([\s\S]*?)(?=\n[^\s]|\n?$)/i.exec(frontmatter);
94
- if (foldedMatch) {
95
- const lines = foldedMatch[1]
96
- .split(/\r?\n/)
97
- .map((l) => l.replace(/^[ \t]+/, "").trim())
98
- .filter((l) => l.length > 0);
99
- if (lines.length > 0)
100
- return lines.join(" ");
101
- }
102
- // Shapes 1 + 2: single-line value. Allow optional surrounding quotes.
103
- // The value runs to the end of the line; trailing whitespace + matching
104
- // closing quote stripped.
105
- const inlineMatch = /(?:^|\n)description:[ \t]*(?:"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)'|([^\r\n]+?))[ \t]*(?:\r?\n|$)/i
106
- .exec(frontmatter);
107
- if (inlineMatch) {
108
- // Unescape `\"` and `\\` for double-quoted form; single-quoted is taken
109
- // literally per YAML (no escapes). Unquoted form passes through.
110
- const raw = inlineMatch[1] ?? inlineMatch[2] ?? inlineMatch[3] ?? "";
111
- const value = inlineMatch[1]
112
- ? raw.replace(/\\(["\\])/g, "$1")
113
- : raw;
114
- const trimmed = value.trim();
115
- return trimmed.length > 0 ? trimmed : null;
116
- }
117
- return null;
118
- }
119
- // `summarizeDescription` (sentence-cut + char-cap truncator) was removed
120
- // alongside the notification reformat in PR #78. The new nested-bullet
121
- // notification format renders the full description verbatim so users get
122
- // the complete context for the skill they're being notified about.
123
- // Telegram's own 4096-char message limit is the only remaining ceiling,
124
- // and skill descriptions in the senpi-skills repo are nowhere near that.
125
- /**
126
- * Read both the installed version and description for a skill from its local
127
- * SKILL.md. Shared core for `readLocalVersion` and `readLocalDescription` so a
128
- * tick that needs both pays a single file read per dir rather than two.
43
+ * Read the installed version for a skill from its local SKILL.md.
129
44
  *
130
- * Multi-dir search path: tries each entry in order and returns the first
131
- * readable SKILL.md's parsed fields. Either field may be null if absent in
132
- * the frontmatter — that's distinct from "no SKILL.md found at all" (which
133
- * returns nulls only after every dir has been tried).
45
+ * Accepts either a single dir (legacy callers) or an array of dirs (the
46
+ * multi-dir search path). When given an array, tries each entry in order and
47
+ * returns the first readable SKILL.md's parsed version — this mirrors the
48
+ * documented behaviour that `skillsDir[0]` is the *primary* (writes go here)
49
+ * and the remaining entries are read-only fallbacks scanned during version
50
+ * checks.
51
+ *
52
+ * The first readable SKILL.md wins even if its version field is absent — a
53
+ * present-but-versionless file is meaningful state (skill predates versioning),
54
+ * not a "not found" signal that should let the next dir override it. Returns
55
+ * null when no readable SKILL.md is found across any candidate dir, or when the
56
+ * version field is absent. Logs a warning per dir for unexpected errors
57
+ * (anything other than ENOENT).
134
58
  */
135
- export async function readLocalSkillMetadata(skillsDir, skillName, logger) {
59
+ export async function readLocalVersion(skillsDir, skillName, logger) {
136
60
  const dirs = typeof skillsDir === "string" ? [skillsDir] : skillsDir;
137
61
  for (const dir of dirs) {
138
62
  const filePath = join(dir, skillName, "SKILL.md");
139
63
  try {
140
64
  const content = await readFile(filePath, "utf8");
141
- // First readable SKILL.md wins, even if its fields are missing —
142
- // a present-but-versionless file is meaningful state (skill predates
143
- // versioning), not a "not found" signal that should let the next dir
144
- // override it.
145
- return {
146
- version: parseSkillMdVersion(content),
147
- description: parseSkillMdDescription(content),
148
- };
65
+ return parseSkillMdVersion(content);
149
66
  }
150
67
  catch (err) {
151
68
  if (err.code === "ENOENT") {
@@ -154,60 +71,35 @@ export async function readLocalSkillMetadata(skillsDir, skillName, logger) {
154
71
  logger?.warn(`[senpi-skills-manager] failed to read SKILL.md for "${skillName}" | path=${filePath} err=${formatErr(err)}`);
155
72
  // Non-ENOENT (permissions, I/O error) — log and keep trying the
156
73
  // remaining dirs rather than short-circuit; if one mirror is broken,
157
- // a healthy mirror further down the path should still serve metadata.
74
+ // a healthy mirror further down the path should still serve the version.
158
75
  }
159
76
  }
160
- return { version: null, description: null };
161
- }
162
- /**
163
- * Read the installed version for a skill from its local SKILL.md.
164
- *
165
- * Accepts either a single dir (legacy callers) or an array of dirs (new
166
- * multi-dir search path). When given an array, tries each entry in order and
167
- * returns the first non-null match — this mirrors the documented behaviour
168
- * that `skillsDir[0]` is the *primary* (writes go here) and the remaining
169
- * entries are read-only fallbacks scanned during version checks.
170
- *
171
- * Returns null when no readable SKILL.md is found across any of the
172
- * candidate dirs, or when the version field is absent. Logs a warning per
173
- * dir for unexpected errors (anything other than ENOENT).
174
- */
175
- export async function readLocalVersion(skillsDir, skillName, logger) {
176
- return (await readLocalSkillMetadata(skillsDir, skillName, logger)).version;
177
- }
178
- /**
179
- * Read the installed description for a skill from its local SKILL.md.
180
- * Multi-dir search path semantics identical to `readLocalVersion`.
181
- *
182
- * Returns null when no readable SKILL.md is found across any of the
183
- * candidate dirs, or when the description field is absent.
184
- */
185
- export async function readLocalDescription(skillsDir, skillName, logger) {
186
- return (await readLocalSkillMetadata(skillsDir, skillName, logger)).description;
77
+ return null;
187
78
  }
188
79
  /**
189
- * Fetch the remote SKILL.md for a skill and return both the version and the
190
- * description in one network round-trip.
191
- *
192
- * Notification text uses the description from the **remote** SKILL.md (not
193
- * the local one) so the "About:" line reflects the version the user is being
194
- * asked about — a description on disk for v1.x may be stale relative to the
195
- * v2.0.0 the major-update notification is announcing.
80
+ * Fetch the remote SKILL.md for a skill and return its version.
196
81
  *
197
- * Returns null fields when the skill does not exist in the remote repo
198
- * (HTTP 404) — that's a legitimate state ("skill removed or not yet
199
- * published"), not an error, so it should not pollute failure counters.
200
- * Both fields may also be null when the remote SKILL.md is present but
201
- * its frontmatter omits the corresponding field.
82
+ * Returns null when the skill does not exist in the remote repo (HTTP 404) —
83
+ * that's a legitimate state ("skill removed or not yet published"), not an
84
+ * error, so it should not pollute failure counters. Also null when the remote
85
+ * SKILL.md is present but its frontmatter omits the version field.
202
86
  *
203
87
  * Throws on transient/operational failures (network errors, timeouts, 5xx
204
88
  * responses, body-parse failures) so the caller's failure-recording path can
205
89
  * fire.
206
90
  *
91
+ * @param remotePath Subdirectory inside the repo holding the skill (manifest
92
+ * `path`); defaults to the skill name (monorepo convention).
207
93
  * @param fetchImpl Injectable for unit testing — defaults to global `fetch`.
208
94
  */
209
- export async function fetchRemoteSkillMetadata(skillName, rawBaseUrl = SKILLS_RAW_BASE_URL, fetchImpl = fetch) {
210
- const url = `${rawBaseUrl}/${encodeURIComponent(skillName)}/SKILL.md`;
95
+ export async function fetchRemoteVersion(skillName, rawBaseUrl = SKILLS_RAW_BASE_URL, fetchImpl = fetch, remotePath = skillName) {
96
+ // Each path segment is encoded so a nested `path` like "skills/foo" stays a
97
+ // valid URL.
98
+ const encodedPath = remotePath
99
+ .split("/")
100
+ .map((seg) => encodeURIComponent(seg))
101
+ .join("/");
102
+ const url = `${rawBaseUrl}/${encodedPath}/SKILL.md`;
211
103
  let res;
212
104
  try {
213
105
  res = await fetchImpl(url, { signal: AbortSignal.timeout(15_000) });
@@ -215,36 +107,21 @@ export async function fetchRemoteSkillMetadata(skillName, rawBaseUrl = SKILLS_RA
215
107
  catch (err) {
216
108
  throw new Error(`Network error fetching remote SKILL.md for "${skillName}": ${formatErr(err)}`);
217
109
  }
218
- // 404 = skill is not in the remote repo. Treated as "no remote metadata"
110
+ // 404 = skill is not in the remote repo. Treated as "no remote version"
219
111
  // rather than a failure so the caller can skip cleanly without escalating.
220
112
  if (res.status === 404) {
221
- return { version: null, description: null };
113
+ return null;
222
114
  }
223
115
  if (!res.ok) {
224
116
  throw new Error(`Failed to fetch remote SKILL.md for "${skillName}": HTTP ${res.status} ${res.statusText}`);
225
117
  }
226
118
  try {
227
- const text = await res.text();
228
- return {
229
- version: parseSkillMdVersion(text),
230
- description: parseSkillMdDescription(text),
231
- };
119
+ return parseSkillMdVersion(await res.text());
232
120
  }
233
121
  catch (err) {
234
122
  throw new Error(`Failed to read response body for "${skillName}": ${formatErr(err)}`);
235
123
  }
236
124
  }
237
- /**
238
- * Fetch the remote SKILL.md for a skill and return its version.
239
- *
240
- * Thin wrapper over {@link fetchRemoteSkillMetadata} kept for backward
241
- * compatibility with callers (and tests) that only need the version field.
242
- * New code that wants both version + description in a single round-trip
243
- * should call `fetchRemoteSkillMetadata` directly.
244
- */
245
- export async function fetchRemoteVersion(skillName, rawBaseUrl = SKILLS_RAW_BASE_URL, fetchImpl = fetch) {
246
- return (await fetchRemoteSkillMetadata(skillName, rawBaseUrl, fetchImpl)).version;
247
- }
248
125
  /**
249
126
  * Compare the local and remote versions for a skill and return a classified diff.
250
127
  *
@@ -256,44 +133,37 @@ export async function fetchRemoteVersion(skillName, rawBaseUrl = SKILLS_RAW_BASE
256
133
  * - Remote version is absent or unparseable → "unknown", caller should log and
257
134
  * skip.
258
135
  */
259
- export async function checkSkillVersion(skillsDir, skillName, rawBaseUrl = SKILLS_RAW_BASE_URL, fetchImpl = fetch, logger) {
260
- // Fetch the remote SKILL.md once and extract both version + description.
261
- // Description is reported back to the coordinator so notifications can
262
- // surface the *remote* (i.e. upcoming-version) description rather than the
263
- // local one, which may be stale relative to the version the notification
264
- // is announcing.
265
- //
266
- // Log the specific fetch failure for traceability, then re-throw so the
267
- // coordinator records it as a check failure (consecutiveFailures,
268
- // lastFailureReason). 404 is intentionally returned as null inside
269
- // fetchRemoteSkillMetadata and never reaches this catch.
270
- const remoteMetadataPromise = fetchRemoteSkillMetadata(skillName, rawBaseUrl, fetchImpl).catch((err) => {
136
+ export async function checkSkillVersion(skillsDir, skillName, rawBaseUrl = SKILLS_RAW_BASE_URL, fetchImpl = fetch, logger, remotePath = skillName) {
137
+ // Fetch the remote SKILL.md version. Log the specific fetch failure for
138
+ // traceability, then re-throw so the coordinator records it as a check
139
+ // failure (consecutiveFailures, lastFailureReason). 404 is intentionally
140
+ // returned as null inside fetchRemoteVersion and never reaches this catch.
141
+ const remoteVersionPromise = fetchRemoteVersion(skillName, rawBaseUrl, fetchImpl, remotePath).catch((err) => {
271
142
  logger?.warn(`[senpi-skills-manager] failed to fetch remote SKILL.md for "${skillName}" | err=${formatErr(err)}`);
272
143
  throw err;
273
144
  });
274
- const [localVersion, remoteMetadata] = await Promise.all([
145
+ const [localVersion, remoteVersion] = await Promise.all([
275
146
  readLocalVersion(skillsDir, skillName, logger),
276
- remoteMetadataPromise,
147
+ remoteVersionPromise,
277
148
  ]);
278
- const { version: remoteVersion, description: remoteDescription } = remoteMetadata;
279
149
  const normalizedRemote = normalizeVersion(remoteVersion ?? "");
280
150
  if (!remoteVersion || !parseSemver(normalizedRemote)) {
281
- return { localVersion, remoteVersion, updateKind: "unknown", remoteDescription };
151
+ return { localVersion, remoteVersion, updateKind: "unknown" };
282
152
  }
283
153
  // Skill has never had a version in SKILL.md (null) — predates the versioning
284
154
  // system or was installed before the field was added. Treat as a minor update
285
155
  // so it is auto-applied without requiring a manual major-version approval.
286
156
  if (localVersion === null) {
287
- return { localVersion, remoteVersion, updateKind: "minor", remoteDescription };
157
+ return { localVersion, remoteVersion, updateKind: "minor" };
288
158
  }
289
159
  const normalizedLocal = normalizeVersion(localVersion);
290
160
  // Local version string exists but is not valid semver even after normalisation
291
161
  // (e.g. "latest", "nightly"). We cannot safely compare, so skip.
292
162
  if (!parseSemver(normalizedLocal)) {
293
- return { localVersion, remoteVersion, updateKind: "unknown", remoteDescription };
163
+ return { localVersion, remoteVersion, updateKind: "unknown" };
294
164
  }
295
165
  // classifyUpdate returns AutoUpdateKind which is a strict subset of SkillUpdateKind
296
166
  const updateKind = classifyUpdate(normalizedLocal, normalizedRemote);
297
- return { localVersion, remoteVersion, updateKind, remoteDescription };
167
+ return { localVersion, remoteVersion, updateKind };
298
168
  }
299
169
  //# sourceMappingURL=version-manager.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version-manager.js","sourceRoot":"","sources":["../../src/skills-manager/version-manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"version-manager.js","sourceRoot":"","sources":["../../src/skills-manager/version-manager.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAC/E,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAErD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAS;IACxC,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe;IACjD,MAAM,KAAK,GAAG,6BAA6B,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IACjE,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7B,MAAM,YAAY,GAAG,uDAAuD,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC/F,OAAO,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACtD,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,SAAqC,EACrC,SAAiB,EACjB,MAAmB;IAEnB,MAAM,IAAI,GAAG,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACrE,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;QAClD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YACjD,OAAO,mBAAmB,CAAC,OAAO,CAAC,CAAC;QACtC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACrD,SAAS,CAAC,eAAe;YAC3B,CAAC;YACD,MAAM,EAAE,IAAI,CACV,uDAAuD,SAAS,YAAY,QAAQ,QAAQ,SAAS,CAAC,GAAG,CAAC,EAAE,CAC7G,CAAC;YACF,gEAAgE;YAChE,qEAAqE;YACrE,yEAAyE;QAC3E,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,SAAiB,EACjB,aAAqB,mBAAmB,EACxC,YAA0B,KAAK,EAC/B,aAAqB,SAAS;IAE9B,4EAA4E;IAC5E,aAAa;IACb,MAAM,WAAW,GAAG,UAAU;SAC3B,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;SACrC,IAAI,CAAC,GAAG,CAAC,CAAC;IACb,MAAM,GAAG,GAAG,GAAG,UAAU,IAAI,WAAW,WAAW,CAAC;IAEpD,IAAI,GAAa,CAAC;IAClB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACtE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,+CAA+C,SAAS,MAAM,SAAS,CAAC,GAAG,CAAC,EAAE,CAC/E,CAAC;IACJ,CAAC;IAED,wEAAwE;IACxE,2EAA2E;IAC3E,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,wCAAwC,SAAS,WAAW,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,CAC3F,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,OAAO,mBAAmB,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAC/C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,qCAAqC,SAAS,MAAM,SAAS,CAAC,GAAG,CAAC,EAAE,CACrE,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAqC,EACrC,SAAiB,EACjB,aAAqB,mBAAmB,EACxC,YAA0B,KAAK,EAC/B,MAAmB,EACnB,aAAqB,SAAS;IAE9B,wEAAwE;IACxE,uEAAuE;IACvE,yEAAyE;IACzE,2EAA2E;IAC3E,MAAM,oBAAoB,GAAG,kBAAkB,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC,KAAK,CACjG,CAAC,GAAG,EAAE,EAAE;QACN,MAAM,EAAE,IAAI,CACV,+DAA+D,SAAS,WAAW,SAAS,CAAC,GAAG,CAAC,EAAE,CACpG,CAAC;QACF,MAAM,GAAG,CAAC;IACZ,CAAC,CACF,CAAC;IACF,MAAM,CAAC,YAAY,EAAE,aAAa,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACtD,gBAAgB,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC;QAC9C,oBAAoB;KACrB,CAAC,CAAC;IAEH,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC;IAC/D,IAAI,CAAC,aAAa,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACrD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;IAChE,CAAC;IAED,6EAA6E;IAC7E,8EAA8E;IAC9E,2EAA2E;IAC3E,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;QAC1B,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;IAC9D,CAAC;IAED,MAAM,eAAe,GAAG,gBAAgB,CAAC,YAAY,CAAC,CAAC;IAEvD,+EAA+E;IAC/E,iEAAiE;IACjE,IAAI,CAAC,WAAW,CAAC,eAAe,CAAC,EAAE,CAAC;QAClC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;IAChE,CAAC;IAED,oFAAoF;IACpF,MAAM,UAAU,GAAG,cAAc,CAAC,eAAe,EAAE,gBAAgB,CAAoB,CAAC;IACxF,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC;AACrD,CAAC"}
@@ -24,5 +24,16 @@ import type { LoggerLike } from "./types.js";
24
24
  * @throws When the skill subdirectory is absent in the clone.
25
25
  * @throws When the staging copy fails.
26
26
  */
27
- export declare function installSkill(cloneDir: string, skillName: string, skillsDir: string, logger?: LoggerLike): Promise<void>;
27
+ export declare function installSkill(cloneDir: string, skillName: string, skillsDir: string, logger?: LoggerLike, options?: {
28
+ sourcePath?: string;
29
+ allowedSkills?: readonly string[];
30
+ /**
31
+ * The version we intend to install (the version the check read from the
32
+ * remote SKILL.md). When provided, the installed SKILL.md is re-read after
33
+ * the swap and the install is **rolled back** if it doesn't match — closing
34
+ * the gap between the CDN version check and the git clone (TOCTOU). Omit
35
+ * (e.g. bootstrap) to skip verification.
36
+ */
37
+ expectedVersion?: string | null;
38
+ }): Promise<void>;
28
39
  //# sourceMappingURL=writer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../src/skills-manager/writer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,IAAI,CAAC,CA6Ff"}
1
+ {"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../src/skills-manager/writer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAc7C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,UAAU,EACnB,OAAO,GAAE;IACP,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,GACL,OAAO,CAAC,IAAI,CAAC,CAsIf"}
@@ -6,7 +6,20 @@ import * as fs from "node:fs/promises";
6
6
  import { randomUUID } from "node:crypto";
7
7
  import { join } from "node:path";
8
8
  import { ALLOWED_SKILLS } from "./constants.js";
9
+ import { normalizeVersion, parseSkillMdVersion } from "./version-manager.js";
9
10
  import { formatErr } from "./utils.js";
11
+ /**
12
+ * Compare an installed SKILL.md version against the version we intended to
13
+ * install. Tolerant of two-part vs three-part forms (via `normalizeVersion`)
14
+ * and a leading `v`. A `null` installed version never matches a non-null
15
+ * expectation.
16
+ */
17
+ function versionsMatch(installed, expected) {
18
+ if (installed === null)
19
+ return false;
20
+ const norm = (v) => normalizeVersion(v.trim().replace(/^v/i, "")).toLowerCase();
21
+ return norm(installed) === norm(expected);
22
+ }
10
23
  /**
11
24
  * Copy a skill's subdirectory from `cloneDir` into `skillsDir/<skillName>/`,
12
25
  * replacing the existing copy via a crash-safe swap.
@@ -28,11 +41,17 @@ import { formatErr } from "./utils.js";
28
41
  * @throws When the skill subdirectory is absent in the clone.
29
42
  * @throws When the staging copy fails.
30
43
  */
31
- export async function installSkill(cloneDir, skillName, skillsDir, logger) {
32
- if (!ALLOWED_SKILLS.includes(skillName)) {
33
- throw new Error(`Skill "${skillName}" is not in the allowed skills list: ${ALLOWED_SKILLS.join(", ")}`);
44
+ export async function installSkill(cloneDir, skillName, skillsDir, logger, options = {}) {
45
+ // Allowlist defaults to ALLOWED_SKILLS (manifest-derived) but can be
46
+ // overridden by the coordinator with the exact set it's managing this run.
47
+ const allowedSkills = options.allowedSkills ?? ALLOWED_SKILLS;
48
+ if (!allowedSkills.includes(skillName)) {
49
+ throw new Error(`Skill "${skillName}" is not in the allowed skills list: ${allowedSkills.join(", ")}`);
34
50
  }
35
- const sourcePath = join(cloneDir, skillName);
51
+ // `sourcePath` is the subdir inside the clone to copy from (manifest `path`);
52
+ // it defaults to the skill name (monorepo convention). The install *target*
53
+ // is always `skillsDir/<skillName>` regardless of source layout.
54
+ const sourcePath = join(cloneDir, options.sourcePath ?? skillName);
36
55
  const targetPath = join(skillsDir, skillName);
37
56
  const stagingPath = join(skillsDir, `.tmp-${skillName}`);
38
57
  const backupPath = join(skillsDir, `.bak-${skillName}-${randomUUID()}`);
@@ -90,6 +109,33 @@ export async function installSkill(cloneDir, skillName, skillsDir, logger) {
90
109
  // On successful rename this path no longer exists; rm is still safe with force=true.
91
110
  await fs.rm(stagingPath, { recursive: true, force: true });
92
111
  }
112
+ // ── Post-install verification ─────────────────────────────────────────────
113
+ // The version we *decided* to install came from the raw-CDN SKILL.md, which
114
+ // can be cached/stale relative to the git clone we just swapped in. Re-read
115
+ // the installed SKILL.md and confirm it matches; on mismatch, roll back to the
116
+ // backup (or remove the bad install if there was none) and throw so the caller
117
+ // records a failure and retries next tick — never silently record a version we
118
+ // didn't actually install.
119
+ if (options.expectedVersion != null) {
120
+ let installedVersion = null;
121
+ try {
122
+ installedVersion = parseSkillMdVersion(await fs.readFile(join(targetPath, "SKILL.md"), "utf8"));
123
+ }
124
+ catch {
125
+ installedVersion = null; // missing/unreadable SKILL.md → treated as mismatch
126
+ }
127
+ if (!versionsMatch(installedVersion, options.expectedVersion)) {
128
+ logger?.warn(`[senpi-skills-manager] post-install verification failed, rolling back | skill=${skillName} expected=${options.expectedVersion} installed=${installedVersion ?? "none"}`);
129
+ await fs.rm(targetPath, { recursive: true, force: true });
130
+ if (movedTargetToBackup) {
131
+ await fs.rename(backupPath, targetPath).catch((restoreErr) => {
132
+ logger?.warn(`[senpi-skills-manager] failed to restore backup after verification failure | skill=${skillName} backup=${backupPath} err=${formatErr(restoreErr)}`);
133
+ });
134
+ }
135
+ throw new Error(`post-install verification failed for "${skillName}": expected version ${options.expectedVersion}, installed ${installedVersion ?? "none"}`);
136
+ }
137
+ logger?.info(`[senpi-skills-manager] post-install verification ok | skill=${skillName} version=${installedVersion}`);
138
+ }
93
139
  // Backup cleanup is best-effort once the new target is in place.
94
140
  if (movedTargetToBackup) {
95
141
  await fs.rm(backupPath, { recursive: true, force: true }).catch((err) => {
@@ -1 +1 @@
1
- {"version":3,"file":"writer.js","sourceRoot":"","sources":["../../src/skills-manager/writer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"writer.js","sourceRoot":"","sources":["../../src/skills-manager/writer.ts"],"names":[],"mappings":"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"}
@@ -85,12 +85,9 @@
85
85
  "skillsManager": {
86
86
  "type": "object",
87
87
  "additionalProperties": false,
88
- "description": "Skills manager configuration. Checks the senpi-skills repo for updates to declared skills after each plugin auto-update tick and applies patch/minor updates automatically (major updates are notify-only). Cadence is inherited from `autoUpdate.pollIntervalMinutes`; this block has no independent poll interval. Omit this block to use defaults.",
88
+ "description": "Skills manager configuration. The managed skill set and each skill's source (repo/branch/path) come from the checked-in manifest src/skills-manager/skills-manifest.json — the single source of truth, shipped with the runtime. Checks the source repo for updates after each plugin auto-update tick and applies patch/minor updates automatically; major updates are gated by default (detected, surfaced via `senpi skills status`, applied via a manual `senpi skills update <name>` — no notification). To authorize automatic major upgrades, set a per-skill `maxMajor` ceiling in the manifest (or `overrides`): the tick then auto-applies majors up to and including that number; bumping it in a reviewed change is the fleet-wide lever for adopting a new skill major. Cadence is inherited from `autoUpdate.pollIntervalMinutes`; this block has no independent poll interval. Omit this block to manage the full manifest set with defaults.",
89
89
  "default": {
90
- "enabled": true,
91
- "skills": [
92
- "senpi-trading-runtime"
93
- ]
90
+ "enabled": true
94
91
  },
95
92
  "properties": {
96
93
  "enabled": {
@@ -118,7 +115,22 @@
118
115
  "items": {
119
116
  "type": "string"
120
117
  },
121
- "description": "Explicit skill names to manage. Omitting the field falls back to the canonical default [\"senpi-trading-runtime\"]; passing an explicit empty array [] switches to discovery mode (scan subdirs under skillsDir)."
118
+ "description": "Deprecated and ignored. The managed skill set is defined solely by the manifest (src/skills-manager/skills-manifest.json). This field is tolerated for backward compatibility with existing configs but no longer affects which skills are managed. Use `overrides` to retarget a skill's repo/branch/path, or `enabled: false` to disable the manager."
119
+ },
120
+ "overrides": {
121
+ "type": "object",
122
+ "additionalProperties": {
123
+ "type": "object",
124
+ "additionalProperties": false,
125
+ "properties": {
126
+ "repo": { "type": "string" },
127
+ "branch": { "type": "string" },
128
+ "commit": { "type": "string", "description": "Pin to a commit SHA, or 'latest' to track the branch tip." },
129
+ "path": { "type": "string" },
130
+ "maxMajor": { "type": "integer", "minimum": 0, "description": "Auto-update major-version ceiling: the tick auto-applies updates up to and including this major and gates anything above. Unset gates all majors (manual update required)." }
131
+ }
132
+ },
133
+ "description": "Optional per-skill source overrides keyed by skill name, e.g. { \"senpi-strategy-ops\": { \"branch\": \"strategy-v2\" } }. Each entry may override repo, branch, and/or path. Branch precedence (highest first): per-skill env SENPI_SKILLS_BRANCH_<NAME> > this per-skill override > global env SENPI_SKILLS_BRANCH > manifest entry branch > manifest defaults.branch. So a per-skill override pins a skill even while the global env retargets the rest."
122
134
  }
123
135
  }
124
136
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@senpi/runtime",
3
- "version": "3.0.6-dev.runtime-phase-2.20260630134536",
3
+ "version": "3.0.6-dev.runtime-phase-2.20260630182800",
4
4
  "description": "Senpi runtime plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -13,8 +13,9 @@
13
13
  "scripts": {
14
14
  "setup:hooks": "sh -c 'git rev-parse --git-dir >/dev/null 2>&1 && git config core.hooksPath .githooks || true'",
15
15
  "prepare": "npm run setup:hooks",
16
- "build": "npm i && tsc && npm run copy:external-scanners",
16
+ "build": "npm i && tsc && npm run copy:external-scanners && npm run copy:skills-manifest",
17
17
  "copy:external-scanners": "node -e \"require('fs').rmSync('dist/scanners/external', { recursive: true, force: true }); require('fs').cpSync('src/scanners/external', 'dist/scanners/external', { recursive: true })\"",
18
+ "copy:skills-manifest": "node -e \"require('fs').cpSync('src/skills-manager/skills-manifest.json', 'dist/skills-manager/skills-manifest.json')\"",
18
19
  "typecheck:tests": "tsc -p tsconfig.tests.json --noEmit",
19
20
  "start": "node dist/index.js",
20
21
  "debug": "npm run build && node dist/debug.js",