@senpi/runtime 3.0.6-dev.runtime-phase-2.20260630134511 → 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.
- package/dist/cli/guide-commands.d.ts.map +1 -1
- package/dist/cli/guide-commands.js +61 -0
- package/dist/cli/guide-commands.js.map +1 -1
- package/dist/cli/senpi-commands.d.ts +14 -0
- package/dist/cli/senpi-commands.d.ts.map +1 -1
- package/dist/cli/senpi-commands.js +390 -2
- package/dist/cli/senpi-commands.js.map +1 -1
- package/dist/dsl/plugin/index.d.ts.map +1 -1
- package/dist/dsl/plugin/index.js +6 -0
- package/dist/dsl/plugin/index.js.map +1 -1
- package/dist/health/types.d.ts +14 -0
- package/dist/health/types.d.ts.map +1 -1
- package/dist/health/types.js.map +1 -1
- package/dist/index.d.ts +19 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +183 -5
- package/dist/index.js.map +1 -1
- package/dist/risk/risk-audit-log.d.ts +7 -0
- package/dist/risk/risk-audit-log.d.ts.map +1 -1
- package/dist/risk/risk-audit-log.js +48 -1
- package/dist/risk/risk-audit-log.js.map +1 -1
- package/dist/runtime/runtime.d.ts +2 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +17 -3
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime-api/intake/scanner-registry.d.ts +3 -0
- package/dist/runtime-api/intake/scanner-registry.d.ts.map +1 -1
- package/dist/runtime-api/intake/scanner-registry.js +3 -0
- package/dist/runtime-api/intake/scanner-registry.js.map +1 -1
- package/dist/scanners/runtime-module.d.ts.map +1 -1
- package/dist/scanners/runtime-module.js +6 -0
- package/dist/scanners/runtime-module.js.map +1 -1
- package/dist/skills-manager/bootstrap.d.ts +12 -7
- package/dist/skills-manager/bootstrap.d.ts.map +1 -1
- package/dist/skills-manager/bootstrap.js +31 -20
- package/dist/skills-manager/bootstrap.js.map +1 -1
- package/dist/skills-manager/clone-groups.d.ts +20 -0
- package/dist/skills-manager/clone-groups.d.ts.map +1 -0
- package/dist/skills-manager/clone-groups.js +45 -0
- package/dist/skills-manager/clone-groups.js.map +1 -0
- package/dist/skills-manager/constants.d.ts +5 -33
- package/dist/skills-manager/constants.d.ts.map +1 -1
- package/dist/skills-manager/constants.js +11 -50
- package/dist/skills-manager/constants.js.map +1 -1
- package/dist/skills-manager/coordinator.d.ts +26 -119
- package/dist/skills-manager/coordinator.d.ts.map +1 -1
- package/dist/skills-manager/coordinator.js +145 -346
- package/dist/skills-manager/coordinator.js.map +1 -1
- package/dist/skills-manager/fetcher.d.ts +23 -2
- package/dist/skills-manager/fetcher.d.ts.map +1 -1
- package/dist/skills-manager/fetcher.js +36 -6
- package/dist/skills-manager/fetcher.js.map +1 -1
- package/dist/skills-manager/manifest.d.ts +160 -0
- package/dist/skills-manager/manifest.d.ts.map +1 -0
- package/dist/skills-manager/manifest.js +185 -0
- package/dist/skills-manager/manifest.js.map +1 -0
- package/dist/skills-manager/skills-manifest.json +16 -0
- package/dist/skills-manager/types.d.ts +61 -30
- package/dist/skills-manager/types.d.ts.map +1 -1
- package/dist/skills-manager/utils.d.ts +10 -26
- package/dist/skills-manager/utils.d.ts.map +1 -1
- package/dist/skills-manager/utils.js +18 -34
- package/dist/skills-manager/utils.js.map +1 -1
- package/dist/skills-manager/version-manager.d.ts +21 -91
- package/dist/skills-manager/version-manager.d.ts.map +1 -1
- package/dist/skills-manager/version-manager.js +48 -178
- package/dist/skills-manager/version-manager.js.map +1 -1
- package/dist/skills-manager/writer.d.ts +12 -1
- package/dist/skills-manager/writer.d.ts.map +1 -1
- package/dist/skills-manager/writer.js +50 -4
- package/dist/skills-manager/writer.js.map +1 -1
- package/dist/telemetry/event-store.d.ts +66 -0
- package/dist/telemetry/event-store.d.ts.map +1 -0
- package/dist/telemetry/event-store.js +202 -0
- package/dist/telemetry/event-store.js.map +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +7 -15
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/wallet-address.d.ts +3 -0
- package/dist/utils/wallet-address.d.ts.map +1 -0
- package/dist/utils/wallet-address.js +17 -0
- package/dist/utils/wallet-address.js.map +1 -0
- package/openclaw.plugin.json +18 -6
- 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
|
-
*
|
|
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
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* the
|
|
133
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
|
74
|
+
// a healthy mirror further down the path should still serve the version.
|
|
158
75
|
}
|
|
159
76
|
}
|
|
160
|
-
return
|
|
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
|
|
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
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
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
|
|
210
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
|
|
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,
|
|
145
|
+
const [localVersion, remoteVersion] = await Promise.all([
|
|
275
146
|
readLocalVersion(skillsDir, skillName, logger),
|
|
276
|
-
|
|
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"
|
|
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"
|
|
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"
|
|
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
|
|
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":"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
|
|
1
|
+
{"version":3,"file":"version-manager.js","sourceRoot":"","sources":["../../src/skills-manager/version-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|
|
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;
|
|
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
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,QAAgB,EAChB,SAAiB,EACjB,SAAiB,EACjB,MAAmB;
|
|
1
|
+
{"version":3,"file":"writer.js","sourceRoot":"","sources":["../../src/skills-manager/writer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local domain-event store: a per-strategy on-disk ring of the events `logger.event` emits, so the
|
|
3
|
+
* agent can query the trade narrative locally without a collector. Additive to the OTel collector
|
|
4
|
+
* emit — this is a second, independent sink.
|
|
5
|
+
*
|
|
6
|
+
* Layout: `{stateDir}/strategies/{address}/events/events.jsonl`, rotated to `events.1.jsonl` once it
|
|
7
|
+
* passes the size cap (a 2-file ring — at most ~2× the cap on disk per strategy). Writes are
|
|
8
|
+
* fire-and-forget and serialized per sink, so the hot `event()` path only enqueues — never blocks on
|
|
9
|
+
* I/O — and a rotation can't interleave with an append. Failures are swallowed: the event log must
|
|
10
|
+
* never disturb trading.
|
|
11
|
+
*/
|
|
12
|
+
import type { AttrValue } from "../utils/event-catalog.js";
|
|
13
|
+
import { type LogLevel } from "../utils/logger.js";
|
|
14
|
+
/** One persisted domain event: the catalogued name, severity, narrative body, call-time stamp, and the event's own capped scalar bag. Never the redact free-text slot. */
|
|
15
|
+
export interface StoredEvent {
|
|
16
|
+
name: string;
|
|
17
|
+
level: LogLevel;
|
|
18
|
+
body: string;
|
|
19
|
+
/** Call-time epoch ms (the same stamp the collector record carries). */
|
|
20
|
+
ts: number;
|
|
21
|
+
attrs: Record<string, AttrValue>;
|
|
22
|
+
}
|
|
23
|
+
/** Filters for {@link readEvents}; all optional. Time bounds are epoch ms, inclusive. */
|
|
24
|
+
export interface EventQuery {
|
|
25
|
+
name?: string;
|
|
26
|
+
asset?: string;
|
|
27
|
+
level?: LogLevel;
|
|
28
|
+
sinceMs?: number;
|
|
29
|
+
untilMs?: number;
|
|
30
|
+
/** Keep the last N after filtering (newest tail). */
|
|
31
|
+
limit?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface EventSink {
|
|
34
|
+
/** Enqueue one event for async, serialized, best-effort persistence. Returns immediately. */
|
|
35
|
+
append(event: StoredEvent): void;
|
|
36
|
+
/** Resolve once all currently-enqueued writes have settled (for shutdown drains and tests). */
|
|
37
|
+
flush(): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A per-strategy ring writer. Appends are serialized through one promise chain so the size check and
|
|
41
|
+
* rotation can't race a concurrent append; the caller's `append` only links the chain (no I/O).
|
|
42
|
+
*/
|
|
43
|
+
export declare function createEventStore(stateDir: string, address: string, maxBytes?: number): EventSink;
|
|
44
|
+
/** Register a runtime's event sink under its strategy wallet. Replaces any prior sink for that address. */
|
|
45
|
+
export declare function registerEventSink(address: string, sink: EventSink): void;
|
|
46
|
+
/** Drain a registered sink's queued writes (best-effort) before it is unregistered on shutdown. */
|
|
47
|
+
export declare function flushEventSink(address: string): Promise<void>;
|
|
48
|
+
/** Remove a runtime's event sink on shutdown. */
|
|
49
|
+
export declare function unregisterEventSink(address: string): void;
|
|
50
|
+
/** Test-only: drop all registered sinks. */
|
|
51
|
+
export declare function clearEventSinks(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Tap point for `logger.event`: route one event to its strategy's local ring. Resolves the wallet from
|
|
54
|
+
* the async-context identity (in-flow events) or the event's own `senpi.strategy.address`
|
|
55
|
+
* (lifecycle/intake events) and enqueues a best-effort write of the body + the event's own capped
|
|
56
|
+
* scalars. Stores every event — the redact free-text slot (never passed here) is the only thing the
|
|
57
|
+
* local store omits. A no-op when no sink is registered or the address is unknown; never throws.
|
|
58
|
+
*/
|
|
59
|
+
export declare function appendDomainEvent(name: string, level: LogLevel, body: string, attributes: Record<string, AttrValue> | undefined, timestampMs: number): void;
|
|
60
|
+
/**
|
|
61
|
+
* Read a strategy's stored events oldest-first, filtered, capped to the last `limit`. Reads the
|
|
62
|
+
* archive then the current file (both size-bounded by the ring), so memory stays bounded. Returns
|
|
63
|
+
* `[]` when nothing has been stored yet.
|
|
64
|
+
*/
|
|
65
|
+
export declare function readEvents(stateDir: string, address: string, query?: EventQuery): Promise<StoredEvent[]>;
|
|
66
|
+
//# sourceMappingURL=event-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-store.d.ts","sourceRoot":"","sources":["../../src/telemetry/event-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAKjE,0KAA0K;AAC1K,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,QAAQ,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CAClC;AAED,yFAAyF;AACzF,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,6FAA6F;IAC7F,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,+FAA+F;IAC/F,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AA+BD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAA6B,GACtC,SAAS,CA8CX;AAMD,2GAA2G;AAC3G,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI,CAExE;AAED,mGAAmG;AACnG,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnE;AAED,iDAAiD;AACjD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEzD;AAED,4CAA4C;AAC5C,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,QAAQ,EACf,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,SAAS,EACjD,WAAW,EAAE,MAAM,GAClB,IAAI,CAaN;AAmCD;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,UAAe,GACrB,OAAO,CAAC,WAAW,EAAE,CAAC,CAwBxB"}
|