@senpi/runtime 3.0.149 → 3.0.153
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 +5 -0
- package/dist/cli/guide-commands.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -1
- package/dist/index.js.map +1 -1
- package/dist/prompt/session-context-seed-hook.d.ts +7 -0
- package/dist/prompt/session-context-seed-hook.d.ts.map +1 -1
- package/dist/prompt/session-context-seed-hook.js +1 -1
- package/dist/prompt/session-context-seed-hook.js.map +1 -1
- package/dist/prompt/skill-router-hook.d.ts +24 -0
- package/dist/prompt/skill-router-hook.d.ts.map +1 -0
- package/dist/prompt/skill-router-hook.js +362 -0
- package/dist/prompt/skill-router-hook.js.map +1 -0
- package/dist/skill-router/catalog.d.ts +30 -0
- package/dist/skill-router/catalog.d.ts.map +1 -0
- package/dist/skill-router/catalog.js +131 -0
- package/dist/skill-router/catalog.js.map +1 -0
- package/dist/skill-router/classify-client.d.ts +80 -0
- package/dist/skill-router/classify-client.d.ts.map +1 -0
- package/dist/skill-router/classify-client.js +283 -0
- package/dist/skill-router/classify-client.js.map +1 -0
- package/dist/skill-router/prompt.d.ts +36 -0
- package/dist/skill-router/prompt.d.ts.map +1 -0
- package/dist/skill-router/prompt.js +318 -0
- package/dist/skill-router/prompt.js.map +1 -0
- package/dist/skill-router/resolve.d.ts +41 -0
- package/dist/skill-router/resolve.d.ts.map +1 -0
- package/dist/skill-router/resolve.js +43 -0
- package/dist/skill-router/resolve.js.map +1 -0
- package/dist/skills-manager/version-manager.d.ts +14 -0
- package/dist/skills-manager/version-manager.d.ts.map +1 -1
- package/dist/skills-manager/version-manager.js +31 -3
- package/dist/skills-manager/version-manager.js.map +1 -1
- package/dist/telemetry/spans/span-hooks.d.ts.map +1 -1
- package/dist/telemetry/spans/span-hooks.js +9 -0
- package/dist/telemetry/spans/span-hooks.js.map +1 -1
- package/dist/utils/event-catalog.d.ts +84 -0
- package/dist/utils/event-catalog.d.ts.map +1 -1
- package/dist/utils/event-catalog.js +7 -0
- package/dist/utils/event-catalog.js.map +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type WarnLogger } from "../skill-router/classify-client.js";
|
|
2
|
+
import { type SessionContextSeedHookPluginApi } from "./session-context-seed-hook.js";
|
|
3
|
+
/** The plugin API surface this hook needs (the seed hook's, with `timeoutMs`). */
|
|
4
|
+
export type SkillRouterHookPluginApi = SessionContextSeedHookPluginApi;
|
|
5
|
+
export type SkillRouterMode = "off" | "shadow" | "on";
|
|
6
|
+
/** What registration did: nothing, the real handler in a mode, or the error-emitting stub. */
|
|
7
|
+
export type SkillRouterRegistration = SkillRouterMode | "stub";
|
|
8
|
+
export interface RegisterSkillRouterHookOptions {
|
|
9
|
+
api: SkillRouterHookPluginApi;
|
|
10
|
+
/** The box's per-user Senpi token, resolved like the OTel user token in `register()`. */
|
|
11
|
+
token: string | undefined;
|
|
12
|
+
/** The skills manager's search path, `resolveSkillsManagerConfig(...).skillsDir`. */
|
|
13
|
+
searchPath: readonly string[];
|
|
14
|
+
/** The plugin logger, for the one-time configuration warnings. */
|
|
15
|
+
logger: WarnLogger;
|
|
16
|
+
/** Where the router's env vars are read from, once. Defaults to `process.env`. */
|
|
17
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
18
|
+
/** Test seams. */
|
|
19
|
+
fetch?: typeof fetch;
|
|
20
|
+
now?: () => number;
|
|
21
|
+
manifestNames?: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
export declare function registerSkillRouterHook(options: RegisterSkillRouterHookOptions): SkillRouterRegistration;
|
|
24
|
+
//# sourceMappingURL=skill-router-hook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-router-hook.d.ts","sourceRoot":"","sources":["../../src/prompt/skill-router-hook.ts"],"names":[],"mappings":"AA8BA,OAAO,EAA4C,KAAK,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAG/G,OAAO,EAAgB,KAAK,+BAA+B,EAAE,MAAM,gCAAgC,CAAC;AAIpG,kFAAkF;AAClF,MAAM,MAAM,wBAAwB,GAAG,+BAA+B,CAAC;AAEvE,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,IAAI,CAAC;AAEtD,8FAA8F;AAC9F,MAAM,MAAM,uBAAuB,GAAG,eAAe,GAAG,MAAM,CAAC;AAE/D,MAAM,WAAW,8BAA8B;IAC7C,GAAG,EAAE,wBAAwB,CAAC;IAC9B,yFAAyF;IACzF,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,qFAAqF;IACrF,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,kEAAkE;IAClE,MAAM,EAAE,UAAU,CAAC;IACnB,kFAAkF;IAClF,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IACnD,kBAAkB;IAClB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAMD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,8BAA8B,GAAG,uBAAuB,CA2BxG"}
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* registerSkillRouterHook: the per-turn skill pointer (ticket skill-router/06).
|
|
3
|
+
*
|
|
4
|
+
* On an eligible user turn the `before_prompt_build` handler sends the user's message (envelope
|
|
5
|
+
* stripped, `preparePromptForClassify`) and the names and versions of the installed skills to
|
|
6
|
+
* senpi-backend's classify endpoint, narrows the server's `fired` list to what this box may point
|
|
7
|
+
* at (`resolveInjectable`), and, in mode `on` only, returns a short `prependContext` naming at most
|
|
8
|
+
* two skills. Every eligible turn emits one `skill_router.decision`; a failure also emits one
|
|
9
|
+
* `skill_router.error`.
|
|
10
|
+
*
|
|
11
|
+
* `SENPI_SKILL_ROUTER` (`off` | `shadow` | `on`, default `off`) is read once at registration:
|
|
12
|
+
* - `off` or an unknown value registers nothing;
|
|
13
|
+
* - `shadow` classifies and logs in the background and returns at once, so it never delays a turn;
|
|
14
|
+
* - `on` awaits the decision (the client's own 1,500 ms budget) and injects the pointer.
|
|
15
|
+
* A box in `shadow` or `on` with no bearer or a bad `SENPI_SKILL_ROUTER_URL` registers a stub that
|
|
16
|
+
* emits one `skill_router.error` on its first eligible turn and never calls the endpoint.
|
|
17
|
+
*
|
|
18
|
+
* Non-negotiables: the handler never throws (the shadow path included) and any failure means no
|
|
19
|
+
* injection; no prompt text, prompt hash, token or URL override is ever logged; nothing from the
|
|
20
|
+
* response body is interpolated into the pointer (names are the catalog's strings, scores are
|
|
21
|
+
* validated numbers formatted here); the bearer goes only to the validated `*.senpi.ai` origin.
|
|
22
|
+
*/
|
|
23
|
+
import { createLogger } from "../utils/logger.js";
|
|
24
|
+
import { EVENT_NAME, eventAttrs, } from "../utils/event-catalog.js";
|
|
25
|
+
import { createSkillCatalog, skillsForRequest } from "../skill-router/catalog.js";
|
|
26
|
+
import { createClassifyClient, resolveClassifyUrl } from "../skill-router/classify-client.js";
|
|
27
|
+
import { preparePromptForClassify } from "../skill-router/prompt.js";
|
|
28
|
+
import { resolveInjectable, resolveLocalFloor } from "../skill-router/resolve.js";
|
|
29
|
+
import { isSeedPrompt } from "./session-context-seed-hook.js";
|
|
30
|
+
const telemetry = createLogger("[senpi-runtime][skill-router]");
|
|
31
|
+
const MODE_ENV_NAME = "SENPI_SKILL_ROUTER";
|
|
32
|
+
/** OpenClaw's per-hook timeout, a backstop above the client's own 1,500 ms abort. */
|
|
33
|
+
const HOOK_TIMEOUT_MS = 2000;
|
|
34
|
+
export function registerSkillRouterHook(options) {
|
|
35
|
+
const env = options.env ?? process.env;
|
|
36
|
+
const mode = resolveMode(env[MODE_ENV_NAME], options.logger);
|
|
37
|
+
if (mode === "off")
|
|
38
|
+
return "off";
|
|
39
|
+
const url = resolveClassifyUrl(env.SENPI_SKILL_ROUTER_URL, options.logger);
|
|
40
|
+
const token = typeof options.token === "string" && options.token.length > 0 ? options.token : undefined;
|
|
41
|
+
if (token === undefined) {
|
|
42
|
+
options.logger.warn("[senpi-runtime][skill-router] no Senpi token (apiKey, SENPI_AUTH_TOKEN or SENPI_API_KEY) is set; the skill router is disabled");
|
|
43
|
+
}
|
|
44
|
+
if (url === undefined || token === undefined) {
|
|
45
|
+
registerStub(options.api, mode, url === undefined ? "bad_url" : "no_bearer");
|
|
46
|
+
return "stub";
|
|
47
|
+
}
|
|
48
|
+
const handler = createRouterHandler({
|
|
49
|
+
mode,
|
|
50
|
+
client: createClassifyClient({ url, token, fetch: options.fetch, now: options.now }),
|
|
51
|
+
catalog: createSkillCatalog({ searchPath: options.searchPath }),
|
|
52
|
+
floor: resolveLocalFloor(env.SENPI_SKILL_ROUTER_FLOOR, options.logger),
|
|
53
|
+
manifestNames: options.manifestNames,
|
|
54
|
+
now: options.now ?? Date.now,
|
|
55
|
+
});
|
|
56
|
+
options.api.on("before_prompt_build", handler, { timeoutMs: HOOK_TIMEOUT_MS });
|
|
57
|
+
return mode;
|
|
58
|
+
}
|
|
59
|
+
function resolveMode(raw, logger) {
|
|
60
|
+
const value = raw?.trim().toLowerCase() ?? "";
|
|
61
|
+
if (value === "shadow" || value === "on")
|
|
62
|
+
return value;
|
|
63
|
+
if (value !== "" && value !== "off") {
|
|
64
|
+
logger.warn(`[senpi-runtime][skill-router] ${MODE_ENV_NAME} is not off, shadow or on; the skill router is off`);
|
|
65
|
+
}
|
|
66
|
+
return "off";
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* A misconfigured box in `shadow` or `on`: emit one `skill_router.error` on the first eligible turn
|
|
70
|
+
* (not at registration, where the OTel SDK may not be up yet and the record could be lost), then do
|
|
71
|
+
* nothing forever. It never calls the endpoint.
|
|
72
|
+
*/
|
|
73
|
+
function registerStub(api, mode, code) {
|
|
74
|
+
let reported = false;
|
|
75
|
+
api.on("before_prompt_build", (event, agentCtx) => {
|
|
76
|
+
try {
|
|
77
|
+
if (reported)
|
|
78
|
+
return undefined;
|
|
79
|
+
const turn = readEligibleTurn(event, agentCtx);
|
|
80
|
+
if (turn === undefined)
|
|
81
|
+
return undefined;
|
|
82
|
+
reported = true;
|
|
83
|
+
emitError({ ...joinAttrs(turn, mode), "senpi.skill_router.status": 0, "senpi.skill_router.code": code });
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Telemetry must never break prompt building.
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}, { timeoutMs: HOOK_TIMEOUT_MS });
|
|
90
|
+
}
|
|
91
|
+
/** One decision per OpenClaw `runId`: 2026.5.7 can build the prompt more than once per run. */
|
|
92
|
+
const RUN_MEMO_MAX_ENTRIES = 100;
|
|
93
|
+
const RUN_MEMO_TTL_MS = 10 * 60 * 1000;
|
|
94
|
+
function createRouterHandler(deps) {
|
|
95
|
+
// Search-path indexes already reported as `catalog_unreadable` (once per directory per process).
|
|
96
|
+
const reportedDirs = new Set();
|
|
97
|
+
const decisions = createRunMemo(deps.now);
|
|
98
|
+
return async (event, agentCtx) => {
|
|
99
|
+
try {
|
|
100
|
+
const turn = readEligibleTurn(event, agentCtx);
|
|
101
|
+
if (turn === undefined)
|
|
102
|
+
return undefined;
|
|
103
|
+
// A repeat attempt for the same run reuses the decision: no second call, no second event.
|
|
104
|
+
let decision = turn.runId === undefined ? undefined : decisions.get(turn.runId);
|
|
105
|
+
if (decision === undefined) {
|
|
106
|
+
decision = decideOnce(deps, reportedDirs, turn);
|
|
107
|
+
if (turn.runId !== undefined)
|
|
108
|
+
decisions.set(turn.runId, decision);
|
|
109
|
+
}
|
|
110
|
+
// Shadow injects nothing, so it doesn't wait: the call and its telemetry finish in the background.
|
|
111
|
+
if (deps.mode === "shadow") {
|
|
112
|
+
// decideOnce never rejects; the catch is a last guard against an unhandled rejection.
|
|
113
|
+
decision.catch(() => undefined);
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
const pointer = await decision;
|
|
117
|
+
return pointer === undefined ? undefined : { prependContext: pointer };
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
logHandlerFailure(err);
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* {@link decide}, made total: it never rejects, so the background shadow path can't surface an
|
|
127
|
+
* unhandled rejection. A throw (none is expected: every helper it calls is documented never-throw)
|
|
128
|
+
* still records the turn's one decision, as `error` / `hook_error`, unless one was already emitted,
|
|
129
|
+
* and one `skill_router.error` so the failure counts in the error rate. Those emits are guarded
|
|
130
|
+
* too: a throwing logger must not turn into a rejection.
|
|
131
|
+
*/
|
|
132
|
+
async function decideOnce(deps, reportedDirs, turn) {
|
|
133
|
+
const progress = { decided: false };
|
|
134
|
+
try {
|
|
135
|
+
return await decide(deps, reportedDirs, turn, progress);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
logHandlerFailure(err);
|
|
139
|
+
try {
|
|
140
|
+
const join = joinAttrs(turn, deps.mode);
|
|
141
|
+
emitError({ ...join, "senpi.skill_router.status": 0, "senpi.skill_router.code": "hook_error" });
|
|
142
|
+
if (!progress.decided) {
|
|
143
|
+
emitDecisionEvent({
|
|
144
|
+
...join,
|
|
145
|
+
"senpi.skill_router.outcome": "error",
|
|
146
|
+
"senpi.skill_router.code": "hook_error",
|
|
147
|
+
"senpi.skill_router.envelope_stripped": false,
|
|
148
|
+
"senpi.skill_router.injected": false,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Telemetry must never break prompt building.
|
|
154
|
+
}
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** Only the error's class: a message could quote the prompt, and nothing here may log it. */
|
|
159
|
+
function logHandlerFailure(err) {
|
|
160
|
+
try {
|
|
161
|
+
telemetry.warn("skill router handler failed", undefined, {
|
|
162
|
+
"error.type": err instanceof Error ? err.name : typeof err,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Logging must never break prompt building.
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/** Classify one turn and emit its telemetry. Resolves to the pointer text in `on`, else `undefined`. */
|
|
170
|
+
async function decide(deps, reportedDirs, turn, progress) {
|
|
171
|
+
const join = joinAttrs(turn, deps.mode);
|
|
172
|
+
const emitDecision = (attrs) => {
|
|
173
|
+
progress.decided = true;
|
|
174
|
+
emitDecisionEvent(attrs);
|
|
175
|
+
};
|
|
176
|
+
const prepared = preparePromptForClassify(turn.prompt);
|
|
177
|
+
if (!prepared.ok) {
|
|
178
|
+
// The skip reason is the outcome (reset, inter-session, empty, and any reason the hygiene adds).
|
|
179
|
+
emitDecision({
|
|
180
|
+
...join,
|
|
181
|
+
"senpi.skill_router.outcome": prepared.reason,
|
|
182
|
+
"senpi.skill_router.envelope_stripped": prepared.envelopeStripped,
|
|
183
|
+
"senpi.skill_router.injected": false,
|
|
184
|
+
});
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
const promptAttrs = {
|
|
188
|
+
"senpi.skill_router.prompt_length": prepared.length,
|
|
189
|
+
"senpi.skill_router.prompt_truncated": prepared.truncated,
|
|
190
|
+
"senpi.skill_router.envelope_stripped": prepared.envelopeStripped,
|
|
191
|
+
};
|
|
192
|
+
const snapshot = await deps.catalog.read();
|
|
193
|
+
for (const index of snapshot.unreadableDirs) {
|
|
194
|
+
if (reportedDirs.has(index))
|
|
195
|
+
continue;
|
|
196
|
+
reportedDirs.add(index);
|
|
197
|
+
emitError({
|
|
198
|
+
...join,
|
|
199
|
+
"senpi.skill_router.status": 0,
|
|
200
|
+
"senpi.skill_router.code": "catalog_unreadable",
|
|
201
|
+
"senpi.skill_router.dir_index": index,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
const sent = skillsForRequest(snapshot, deps.manifestNames);
|
|
205
|
+
if (sent.length === 0) {
|
|
206
|
+
emitDecision({
|
|
207
|
+
...join,
|
|
208
|
+
...promptAttrs,
|
|
209
|
+
"senpi.skill_router.outcome": "no_skills",
|
|
210
|
+
"senpi.skill_router.skills_sent": 0,
|
|
211
|
+
"senpi.skill_router.injected": false,
|
|
212
|
+
});
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
const result = await deps.client.classify({ prompt: prepared.prompt, skills: sent });
|
|
216
|
+
if (!result.ok) {
|
|
217
|
+
if (result.code === "backoff") {
|
|
218
|
+
// An open Retry-After window: no call was made, so the decision alone records it.
|
|
219
|
+
emitDecision({
|
|
220
|
+
...join,
|
|
221
|
+
...promptAttrs,
|
|
222
|
+
"senpi.skill_router.outcome": "backoff",
|
|
223
|
+
"senpi.skill_router.code": result.code,
|
|
224
|
+
"senpi.skill_router.skills_sent": sent.length,
|
|
225
|
+
"senpi.skill_router.retry_after_sec": result.retryAfterSec,
|
|
226
|
+
"senpi.skill_router.latency_ms": result.latencyMs,
|
|
227
|
+
"senpi.skill_router.injected": false,
|
|
228
|
+
});
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
emitError({
|
|
232
|
+
...join,
|
|
233
|
+
"senpi.skill_router.status": result.status,
|
|
234
|
+
"senpi.skill_router.code": result.code,
|
|
235
|
+
"senpi.skill_router.reason": result.reason,
|
|
236
|
+
"senpi.skill_router.latency_ms": result.latencyMs,
|
|
237
|
+
"senpi.skill_router.retry_after_sec": result.retryAfterSec,
|
|
238
|
+
});
|
|
239
|
+
emitDecision({
|
|
240
|
+
...join,
|
|
241
|
+
...promptAttrs,
|
|
242
|
+
"senpi.skill_router.outcome": "error",
|
|
243
|
+
"senpi.skill_router.code": result.code,
|
|
244
|
+
"senpi.skill_router.skills_sent": sent.length,
|
|
245
|
+
"senpi.skill_router.latency_ms": result.latencyMs,
|
|
246
|
+
"senpi.skill_router.injected": false,
|
|
247
|
+
});
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
const { injectable } = resolveInjectable({ result, sent, catalog: snapshot, floor: deps.floor });
|
|
251
|
+
const pointer = deps.mode === "on" ? formatPointer(injectable) : undefined;
|
|
252
|
+
emitDecision({
|
|
253
|
+
...join,
|
|
254
|
+
...promptAttrs,
|
|
255
|
+
"senpi.skill_router.outcome": "classified",
|
|
256
|
+
"senpi.skill_router.decision_id": result.decisionId,
|
|
257
|
+
"senpi.skill_router.criteria_version": result.criteriaVersion,
|
|
258
|
+
"senpi.skill_router.model": result.model,
|
|
259
|
+
"senpi.skill_router.skills_sent": sent.length,
|
|
260
|
+
"senpi.skill_router.unknown_count": result.unknownCount,
|
|
261
|
+
"senpi.skill_router.ignored_count": result.ignored,
|
|
262
|
+
"senpi.skill_router.unanswered_count": result.unansweredCount,
|
|
263
|
+
"senpi.skill_router.scores": JSON.stringify(result.scores),
|
|
264
|
+
"senpi.skill_router.fired": JSON.stringify(result.fired),
|
|
265
|
+
"senpi.skill_router.injectable": JSON.stringify(injectable.map((s) => s.name)),
|
|
266
|
+
"senpi.skill_router.floor": deps.floor,
|
|
267
|
+
"senpi.skill_router.injected": pointer !== undefined,
|
|
268
|
+
"senpi.skill_router.latency_ms": result.latencyMs,
|
|
269
|
+
"senpi.skill_router.server_latency_ms": result.serverLatencyMs,
|
|
270
|
+
"senpi.skill_router.usage_input_tokens": result.usage.inputTokens,
|
|
271
|
+
"senpi.skill_router.usage_output_tokens": result.usage.outputTokens,
|
|
272
|
+
"senpi.skill_router.usage_cost_usd": result.usage.costUsd,
|
|
273
|
+
});
|
|
274
|
+
return pointer;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* The pointer the model sees (PRD, "What it injects"). The names are the catalog's own strings and
|
|
278
|
+
* the scores validated numbers, formatted here; nothing from the response body is interpolated.
|
|
279
|
+
*/
|
|
280
|
+
function formatPointer(injectable) {
|
|
281
|
+
if (injectable.length === 0)
|
|
282
|
+
return undefined;
|
|
283
|
+
const matches = injectable.map((s) => `${s.name} (${s.score.toFixed(2)})`).join(", ");
|
|
284
|
+
const instruction = injectable.length === 1
|
|
285
|
+
? "Read that skill's SKILL.md and follow it before calling any tools. Do not answer from raw tool output or general knowledge where the skill defines the procedure."
|
|
286
|
+
: "Read those skills' SKILL.md files and follow them before calling any tools. Do not answer from raw tool output or general knowledge where the skills define the procedure.";
|
|
287
|
+
return `[senpi skill routing] This question matches: ${matches}.\n${instruction}`;
|
|
288
|
+
}
|
|
289
|
+
function emitDecisionEvent(attrs) {
|
|
290
|
+
telemetry.event(EVENT_NAME.skillRouterDecision, "info", {
|
|
291
|
+
body: "Skill router decision",
|
|
292
|
+
attributes: eventAttrs(attrs),
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
function emitError(attrs) {
|
|
296
|
+
telemetry.event(EVENT_NAME.skillRouterError, "warn", {
|
|
297
|
+
body: "Skill router error",
|
|
298
|
+
attributes: eventAttrs(attrs),
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
/** A small TTL map, capped by evicting the oldest insert. */
|
|
302
|
+
function createRunMemo(now) {
|
|
303
|
+
const entries = new Map();
|
|
304
|
+
return {
|
|
305
|
+
get(key) {
|
|
306
|
+
const entry = entries.get(key);
|
|
307
|
+
if (entry === undefined)
|
|
308
|
+
return undefined;
|
|
309
|
+
if (entry.expiresAt <= now()) {
|
|
310
|
+
entries.delete(key);
|
|
311
|
+
return undefined;
|
|
312
|
+
}
|
|
313
|
+
return entry.value;
|
|
314
|
+
},
|
|
315
|
+
set(key, value) {
|
|
316
|
+
const at = now();
|
|
317
|
+
for (const [k, e] of entries)
|
|
318
|
+
if (e.expiresAt <= at)
|
|
319
|
+
entries.delete(k);
|
|
320
|
+
while (entries.size >= RUN_MEMO_MAX_ENTRIES) {
|
|
321
|
+
const oldest = entries.keys().next().value;
|
|
322
|
+
if (oldest === undefined)
|
|
323
|
+
break;
|
|
324
|
+
entries.delete(oldest);
|
|
325
|
+
}
|
|
326
|
+
entries.set(key, { value, expiresAt: at + RUN_MEMO_TTL_MS });
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* A turn the router looks at (OpenClaw 2026.5.7 `before_prompt_build`, ctx at
|
|
332
|
+
* `selection-BeP8qtCb.js:7924-7935`): a user-triggered turn (not `heartbeat`, `cron`, `memory` or
|
|
333
|
+
* `manual`), outside a subagent or cron session, with a non-empty prompt that is not a
|
|
334
|
+
* session-context seed. Only `event.prompt` is read, never `event.messages`.
|
|
335
|
+
*/
|
|
336
|
+
function readEligibleTurn(event, agentCtx) {
|
|
337
|
+
const ctx = asRecord(agentCtx);
|
|
338
|
+
if (ctx?.trigger !== "user")
|
|
339
|
+
return undefined;
|
|
340
|
+
const sessionKey = nonEmptyString(ctx.sessionKey);
|
|
341
|
+
if (sessionKey !== undefined && (sessionKey.includes("subagent:") || sessionKey.includes("cron:"))) {
|
|
342
|
+
return undefined;
|
|
343
|
+
}
|
|
344
|
+
const prompt = asRecord(event)?.prompt;
|
|
345
|
+
if (typeof prompt !== "string" || prompt.length === 0 || isSeedPrompt(prompt))
|
|
346
|
+
return undefined;
|
|
347
|
+
return { prompt, sessionKey, runId: nonEmptyString(ctx.runId) };
|
|
348
|
+
}
|
|
349
|
+
function joinAttrs(turn, mode) {
|
|
350
|
+
return {
|
|
351
|
+
"gen_ai.conversation.id": turn.sessionKey,
|
|
352
|
+
"senpi.openclaw.run_id": turn.runId,
|
|
353
|
+
"senpi.skill_router.mode": mode,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
function asRecord(v) {
|
|
357
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : undefined;
|
|
358
|
+
}
|
|
359
|
+
function nonEmptyString(v) {
|
|
360
|
+
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
361
|
+
}
|
|
362
|
+
//# sourceMappingURL=skill-router-hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-router-hook.js","sourceRoot":"","sources":["../../src/prompt/skill-router-hook.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type SkillRef } from "./classify-client.js";
|
|
2
|
+
export interface InstalledSkill {
|
|
3
|
+
/** The top-level frontmatter `name`, else the directory name. */
|
|
4
|
+
name: string;
|
|
5
|
+
/** From `parseSkillMdVersion`; absent when the frontmatter has none. */
|
|
6
|
+
version?: string;
|
|
7
|
+
/** Absolute path of the skill's `SKILL.md`. */
|
|
8
|
+
skillMdPath: string;
|
|
9
|
+
}
|
|
10
|
+
export interface CatalogSnapshot {
|
|
11
|
+
/** Installed skills by name, in search-path order. */
|
|
12
|
+
skills: ReadonlyMap<string, InstalledSkill>;
|
|
13
|
+
/** Indexes into the search path whose `readdir` failed (a missing directory is not a failure). */
|
|
14
|
+
unreadableDirs: readonly number[];
|
|
15
|
+
}
|
|
16
|
+
export interface SkillCatalog {
|
|
17
|
+
read(): Promise<CatalogSnapshot>;
|
|
18
|
+
}
|
|
19
|
+
export declare function createSkillCatalog(options: {
|
|
20
|
+
searchPath: readonly string[];
|
|
21
|
+
}): SkillCatalog;
|
|
22
|
+
/**
|
|
23
|
+
* The skill list sent with a classify call: names matching the server's pattern, deduped, at
|
|
24
|
+
* most 40. Manifest skills come first (manifest order), then the rest in name order, so a
|
|
25
|
+
* managed skill is never the one cut. User-installed skills are still sent: the server doesn't
|
|
26
|
+
* route them in v1 but counts them as `unknown`, the demand signal for a v2 hybrid. An empty
|
|
27
|
+
* list means no call.
|
|
28
|
+
*/
|
|
29
|
+
export declare function skillsForRequest(snapshot: CatalogSnapshot, manifestNames?: readonly string[]): SkillRef[];
|
|
30
|
+
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/skill-router/catalog.ts"],"names":[],"mappings":"AA+BA,OAAO,EAAkC,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErF,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,sDAAsD;IACtD,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC5C,kGAAkG;IAClG,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;CAClC;AAgBD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAAE,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAAG,YAAY,CA2D3F;AASD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,eAAe,EACzB,aAAa,GAAE,SAAS,MAAM,EAA4B,GACzD,QAAQ,EAAE,CAgBZ"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The installed-skill catalog the skill router classifies against (ticket skill-router/05).
|
|
3
|
+
*
|
|
4
|
+
* Under D3 option A the server holds the routing criteria, so the box reads only each skill's
|
|
5
|
+
* `name` and `version` (plus its `SKILL.md` path, for telemetry and tests), never its
|
|
6
|
+
* `description` or a `routing:` field.
|
|
7
|
+
*
|
|
8
|
+
* It scans the skills manager's search path (`resolveSkillsManagerConfig(...).skillsDir`,
|
|
9
|
+
* passed in by the hook), with the skills manager's rules: immediate subdirectories, hidden
|
|
10
|
+
* (`.tmp-*` staging, `.bak-*` backup) ones skipped, merged across the path, first found wins.
|
|
11
|
+
* Parsed entries are cached in memory and validated on every read by a `readdir` of each
|
|
12
|
+
* directory and a `stat` of each `SKILL.md`, so an install by the skills manager's tick, a
|
|
13
|
+
* manual `senpi skills update` or anything else that writes there shows up on the next turn.
|
|
14
|
+
*
|
|
15
|
+
* Limitation (accepted for v1): OpenClaw 2026.5.7 also loads skills from `<workspace>/skills`,
|
|
16
|
+
* `<workspace>/.agents/skills` and `~/.agents/skills` (`workspace-B4eaH2KK.js:698-740`), and a
|
|
17
|
+
* workspace skill overrides a managed one of the same name. Those directories are not scanned,
|
|
18
|
+
* so a skill the agent installs there is not sent, and if one shadows a managed name,
|
|
19
|
+
* `skillMdPath` points at the managed file rather than the one OpenClaw loaded.
|
|
20
|
+
*
|
|
21
|
+
* `read()` never throws: an unreadable directory or file skips what it holds, and the snapshot
|
|
22
|
+
* reports which search-path entries failed `readdir`, by index (never the path text).
|
|
23
|
+
*/
|
|
24
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
25
|
+
import { resolve } from "node:path";
|
|
26
|
+
import { getManifestSkillNames } from "../skills-manager/manifest.js";
|
|
27
|
+
import { extractSkillMdFrontmatter, parseSkillMdName, parseSkillMdVersion, } from "../skills-manager/version-manager.js";
|
|
28
|
+
import { MAX_SKILLS, SKILL_NAME_PATTERN } from "./classify-client.js";
|
|
29
|
+
/**
|
|
30
|
+
* OpenClaw 2026.5.7 skips a larger `SKILL.md` (`DEFAULT_MAX_SKILL_FILE_BYTES`,
|
|
31
|
+
* `workspace-B4eaH2KK.js:310`, applied at :604-615), so it is not installed as far as the model
|
|
32
|
+
* is concerned, and it bounds what this reads.
|
|
33
|
+
*/
|
|
34
|
+
const MAX_SKILL_MD_BYTES = 256_000;
|
|
35
|
+
export function createSkillCatalog(options) {
|
|
36
|
+
const searchPath = [...options.searchPath];
|
|
37
|
+
const cache = new Map();
|
|
38
|
+
return {
|
|
39
|
+
async read() {
|
|
40
|
+
const skills = new Map();
|
|
41
|
+
const unreadableDirs = [];
|
|
42
|
+
const claimedDirNames = new Set();
|
|
43
|
+
const seenPaths = new Set();
|
|
44
|
+
for (const [index, dir] of searchPath.entries()) {
|
|
45
|
+
let dirNames;
|
|
46
|
+
try {
|
|
47
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
48
|
+
dirNames = entries
|
|
49
|
+
.filter((e) => e.isDirectory() && !e.name.startsWith("."))
|
|
50
|
+
.map((e) => e.name)
|
|
51
|
+
.sort();
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
if (err?.code !== "ENOENT")
|
|
55
|
+
unreadableDirs.push(index);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
for (const dirName of dirNames) {
|
|
59
|
+
if (claimedDirNames.has(dirName))
|
|
60
|
+
continue;
|
|
61
|
+
const skillMdPath = resolve(dir, dirName, "SKILL.md");
|
|
62
|
+
let info;
|
|
63
|
+
try {
|
|
64
|
+
info = await stat(skillMdPath);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
continue; // no SKILL.md here: not a skill, and it claims nothing
|
|
68
|
+
}
|
|
69
|
+
// First found wins: this directory holds the name even if its file turns out bad.
|
|
70
|
+
claimedDirNames.add(dirName);
|
|
71
|
+
if (!info.isFile() || info.size > MAX_SKILL_MD_BYTES)
|
|
72
|
+
continue;
|
|
73
|
+
seenPaths.add(skillMdPath);
|
|
74
|
+
let cached = cache.get(skillMdPath);
|
|
75
|
+
if (!cached || cached.mtimeMs !== info.mtimeMs || cached.size !== info.size) {
|
|
76
|
+
let content;
|
|
77
|
+
try {
|
|
78
|
+
content = await readFile(skillMdPath, "utf8");
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
cache.delete(skillMdPath);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
cached = { mtimeMs: info.mtimeMs, size: info.size, skill: parseSkill(content, dirName) };
|
|
85
|
+
cache.set(skillMdPath, cached);
|
|
86
|
+
}
|
|
87
|
+
if (!cached.skill || skills.has(cached.skill.name))
|
|
88
|
+
continue;
|
|
89
|
+
skills.set(cached.skill.name, { ...cached.skill, skillMdPath });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const path of cache.keys())
|
|
93
|
+
if (!seenPaths.has(path))
|
|
94
|
+
cache.delete(path);
|
|
95
|
+
return { skills, unreadableDirs };
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function parseSkill(content, dirName) {
|
|
100
|
+
if (extractSkillMdFrontmatter(content) === null)
|
|
101
|
+
return null;
|
|
102
|
+
const name = parseSkillMdName(content) ?? dirName;
|
|
103
|
+
const version = parseSkillMdVersion(content);
|
|
104
|
+
return version === null ? { name } : { name, version };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The skill list sent with a classify call: names matching the server's pattern, deduped, at
|
|
108
|
+
* most 40. Manifest skills come first (manifest order), then the rest in name order, so a
|
|
109
|
+
* managed skill is never the one cut. User-installed skills are still sent: the server doesn't
|
|
110
|
+
* route them in v1 but counts them as `unknown`, the demand signal for a v2 hybrid. An empty
|
|
111
|
+
* list means no call.
|
|
112
|
+
*/
|
|
113
|
+
export function skillsForRequest(snapshot, manifestNames = getManifestSkillNames()) {
|
|
114
|
+
const rank = new Map(manifestNames.map((name, i) => [name, i]));
|
|
115
|
+
const byRank = (s) => rank.get(s.name) ?? Number.POSITIVE_INFINITY;
|
|
116
|
+
const candidates = [...snapshot.skills.values()]
|
|
117
|
+
.filter((s) => SKILL_NAME_PATTERN.test(s.name))
|
|
118
|
+
.sort((a, b) => byRank(a) - byRank(b) || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
119
|
+
const seen = new Set();
|
|
120
|
+
const sent = [];
|
|
121
|
+
for (const s of candidates) {
|
|
122
|
+
if (seen.has(s.name))
|
|
123
|
+
continue;
|
|
124
|
+
seen.add(s.name);
|
|
125
|
+
sent.push(s.version === undefined ? { name: s.name } : { name: s.name, version: s.version });
|
|
126
|
+
if (sent.length === MAX_SKILLS)
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
return sent;
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/skill-router/catalog.ts"],"names":[],"mappings":"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"}
|