@aiwayds/dsh-mcp-adapter 0.5.1 → 0.7.0
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/README.md +49 -3
- package/README.zh.md +50 -4
- package/lib/index.d.ts +275 -22
- package/lib/index.js +612 -70
- package/lib/index.js.map +1 -1
- package/lib/jev/backends.d.ts +35 -0
- package/lib/jev/backends.js +48 -0
- package/lib/jev/backends.js.map +1 -0
- package/lib/jev/client.d.ts +133 -0
- package/lib/jev/client.js +288 -0
- package/lib/jev/client.js.map +1 -0
- package/lib/jev/config.d.ts +82 -0
- package/lib/jev/config.js +55 -0
- package/lib/jev/config.js.map +1 -0
- package/lib/jev/dual.d.ts +42 -0
- package/lib/jev/dual.js +42 -0
- package/lib/jev/dual.js.map +1 -0
- package/lib/jev/gate.d.ts +51 -0
- package/lib/jev/gate.js +141 -0
- package/lib/jev/gate.js.map +1 -0
- package/lib/jev/log.d.ts +93 -0
- package/lib/jev/log.js +138 -0
- package/lib/jev/log.js.map +1 -0
- package/lib/jev/seams.d.ts +118 -0
- package/lib/jev/seams.js +364 -0
- package/lib/jev/seams.js.map +1 -0
- package/lib/jev/thresholds.d.ts +37 -0
- package/lib/jev/thresholds.js +44 -0
- package/lib/jev/thresholds.js.map +1 -0
- package/lib/rank.d.ts +157 -0
- package/lib/rank.js +327 -0
- package/lib/rank.js.map +1 -0
- package/lib/usage.d.ts +232 -0
- package/lib/usage.js +362 -0
- package/lib/usage.js.map +1 -0
- package/package.json +13 -13
package/lib/jev/seams.js
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The three System One seams (jev business wiring) — and ONLY these three
|
|
3
|
+
* places in the plugin may call out to a decision backend.
|
|
4
|
+
*
|
|
5
|
+
* WHERE THEY ARE ALLOWED TO RUN (red line ①, hot paths stay remote-free):
|
|
6
|
+
* - {@link askKeepSuggestions} — the background keep-suggestion batch, fired
|
|
7
|
+
* by the usage counter and awaited by nobody;
|
|
8
|
+
* - {@link rankListQuery} — `mcp_list {query}` only, i.e. when the MODEL
|
|
9
|
+
* explicitly asked for a ranked view;
|
|
10
|
+
* - {@link rankDidYouMean} — `mcp_call`'s unknown-tool branch, i.e. an error
|
|
11
|
+
* the model is going to read anyway.
|
|
12
|
+
* The assemble waterfall, `mcp_list` without a query, and a SUCCESSFUL
|
|
13
|
+
* dispatch never appear in this file's call graph.
|
|
14
|
+
*
|
|
15
|
+
* FAIL-OPEN (red line ②): every export returns a "no opinion" value on ANY
|
|
16
|
+
* failure — disabled, timeout, 5xx, bad JSON, a thrown hook, an unparseable
|
|
17
|
+
* answer. None of them throws. The caller's lexical order then stands, which
|
|
18
|
+
* is the pre-seam behavior; a jev outage is indistinguishable from jev being
|
|
19
|
+
* off.
|
|
20
|
+
*
|
|
21
|
+
* CALIBRATION: the batch in {@link askKeepSuggestions} is the one seam that
|
|
22
|
+
* reads a band (thresholds.ts, kind 'suggest') — and only as a DISPLAY label
|
|
23
|
+
* on a suggestion row, and only for a PRIMARY probability. The two ranking
|
|
24
|
+
* seams log their probabilities with band 'record': a rank is relative
|
|
25
|
+
* ordering, and adopting/vetoing thresholds ported from another plugin's
|
|
26
|
+
* protocol would be a category error. An laya probability is not calibrated at
|
|
27
|
+
* all (dual.ts: relative use only), so a pace-maker answer NEVER takes a band
|
|
28
|
+
* and NEVER becomes a suggestion row — see {@link askKeepSuggestions}.
|
|
29
|
+
*
|
|
30
|
+
* @module jev/seams
|
|
31
|
+
*/
|
|
32
|
+
import { jevAskDual } from "./dual.js";
|
|
33
|
+
import { band } from "./thresholds.js";
|
|
34
|
+
import { digestFor } from "./log.js";
|
|
35
|
+
import { RANK_CANDIDATE_LIMIT } from "../rank.js";
|
|
36
|
+
import { suggestionBasis } from "../usage.js";
|
|
37
|
+
/** A probability is usable only when it is a finite 0..1 number. */
|
|
38
|
+
export function noulProbability(answer) {
|
|
39
|
+
if (typeof answer !== 'object' || answer === null)
|
|
40
|
+
return undefined;
|
|
41
|
+
const p = answer.noul;
|
|
42
|
+
return typeof p === 'number' && Number.isFinite(p) && p >= 0 && p <= 1 ? p : undefined;
|
|
43
|
+
}
|
|
44
|
+
/** The option-probability map of a `choice` answer, when present and usable. */
|
|
45
|
+
function choiceProbabilities(answer) {
|
|
46
|
+
if (typeof answer !== 'object' || answer === null)
|
|
47
|
+
return undefined;
|
|
48
|
+
const raw = answer.probabilities;
|
|
49
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
|
|
50
|
+
return undefined;
|
|
51
|
+
const out = {};
|
|
52
|
+
for (const [option, value] of Object.entries(raw)) {
|
|
53
|
+
if (typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1)
|
|
54
|
+
out[option] = value;
|
|
55
|
+
}
|
|
56
|
+
return Object.keys(out).length === 0 ? undefined : out;
|
|
57
|
+
}
|
|
58
|
+
/** The single winning option of a `choice` answer, when present. */
|
|
59
|
+
function choiceWinner(answer) {
|
|
60
|
+
if (typeof answer !== 'object' || answer === null)
|
|
61
|
+
return undefined;
|
|
62
|
+
const choice = answer.choice;
|
|
63
|
+
return typeof choice === 'string' && choice !== '' ? choice : undefined;
|
|
64
|
+
}
|
|
65
|
+
const RANK_QUESTION_ID = 'r1';
|
|
66
|
+
/**
|
|
67
|
+
* The ranking question both rank seams share (one batched choice request
|
|
68
|
+
* each). The option KEY is the tool name, so the answer maps back onto the
|
|
69
|
+
* candidate pool without an index that could drift.
|
|
70
|
+
*/
|
|
71
|
+
function rankQuestion(instructions, candidates) {
|
|
72
|
+
const criteria = {};
|
|
73
|
+
for (const candidate of candidates) {
|
|
74
|
+
const blurb = candidate.description.replace(/\s+/gu, ' ').trim().slice(0, 200);
|
|
75
|
+
criteria[candidate.name] = blurb === '' ? candidate.name : `${candidate.name}: ${blurb}`;
|
|
76
|
+
}
|
|
77
|
+
return { type: 'choice', instructions, criteria };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Turn one jev ranking answer into an order. A full probability map sorts by
|
|
81
|
+
* probability descending (ties keep the lexical order); a bare `choice` field
|
|
82
|
+
* only moves the winner to the front; anything unparseable reports "no
|
|
83
|
+
* opinion" so the caller keeps the order it already had.
|
|
84
|
+
*
|
|
85
|
+
* @param candidates - the pool the question was asked about, in lexical order.
|
|
86
|
+
* @param answer - the raw answer value for {@link RANK_QUESTION_ID}.
|
|
87
|
+
* @returns the reordered names, or undefined for "no opinion".
|
|
88
|
+
*/
|
|
89
|
+
export function orderFromChoice(candidates, answer) {
|
|
90
|
+
const names = candidates.map(candidate => candidate.name);
|
|
91
|
+
const index = new Map(names.map((name, position) => [name, position]));
|
|
92
|
+
const raw = choiceProbabilities(answer);
|
|
93
|
+
// A probability map that scores NONE of the candidates says nothing about
|
|
94
|
+
// their order — treat it as no opinion, not as "all equal".
|
|
95
|
+
const probabilities = raw === undefined
|
|
96
|
+
? undefined
|
|
97
|
+
: Object.fromEntries(Object.entries(raw).filter(([name]) => index.has(name)));
|
|
98
|
+
if (probabilities !== undefined && Object.keys(probabilities).length > 0) {
|
|
99
|
+
return [...names].sort((left, right) => {
|
|
100
|
+
const a = probabilities[left] ?? -1;
|
|
101
|
+
const b = probabilities[right] ?? -1;
|
|
102
|
+
return (b - a) || ((index.get(left) ?? 0) - (index.get(right) ?? 0));
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
const winner = choiceWinner(answer);
|
|
106
|
+
if (winner === undefined || !index.has(winner))
|
|
107
|
+
return undefined;
|
|
108
|
+
return [winner, ...names.filter(name => name !== winner)];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Log one verdict row per candidate the ranking answer scored. `band: 'record'`
|
|
112
|
+
* and `agree: 'n/a'` are deliberate: a relative order has no adopt/veto
|
|
113
|
+
* threshold to reconcile against, and inventing one would poison the
|
|
114
|
+
* calibration analysis with a band nothing acts on.
|
|
115
|
+
*/
|
|
116
|
+
async function logRankVerdicts(log, lane, state, instructions, candidates, answer, backend, degraded) {
|
|
117
|
+
const probabilities = choiceProbabilities(answer);
|
|
118
|
+
if (probabilities === undefined)
|
|
119
|
+
return;
|
|
120
|
+
const at = new Date().toISOString();
|
|
121
|
+
const rows = candidates
|
|
122
|
+
.map(candidate => ({ candidate, p: probabilities[candidate.name] }))
|
|
123
|
+
.filter((entry) => entry.p !== undefined)
|
|
124
|
+
.map(entry => ({
|
|
125
|
+
at,
|
|
126
|
+
lane,
|
|
127
|
+
questionId: RANK_QUESTION_ID,
|
|
128
|
+
qtype: 'choice',
|
|
129
|
+
ref: `tool:${entry.candidate.name}`,
|
|
130
|
+
digest: digestFor(state, RANK_QUESTION_ID, instructions),
|
|
131
|
+
probability: entry.p,
|
|
132
|
+
band: 'record',
|
|
133
|
+
agree: 'n/a',
|
|
134
|
+
backend,
|
|
135
|
+
degraded,
|
|
136
|
+
}));
|
|
137
|
+
await log.logVerdicts(rows);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Seam B: rerank `mcp_list {query}` candidates by relevance.
|
|
141
|
+
*
|
|
142
|
+
* The incoming order is the LEXICAL one (see ../rank.ts) and is also the
|
|
143
|
+
* fail-open result: any failure, disabled switch, or unusable answer returns
|
|
144
|
+
* `undefined`, which the caller reads as "keep what you had".
|
|
145
|
+
*
|
|
146
|
+
* A DEGRADED answer (the local laya pace-maker took over because the primary
|
|
147
|
+
* backend failed) is usable here: relative ordering is exactly what a rank
|
|
148
|
+
* is, and jev/dual.ts's contract allows degraded results for relative use
|
|
149
|
+
* only — which is all this seam does.
|
|
150
|
+
*
|
|
151
|
+
* @param query - the model's query text.
|
|
152
|
+
* @param ordered - lexical order (best first), already capped by the caller.
|
|
153
|
+
* @param env - the live jev runtime.
|
|
154
|
+
* @returns the jev order, or undefined for "no opinion".
|
|
155
|
+
*/
|
|
156
|
+
export async function rankListQuery(query, ordered, env) {
|
|
157
|
+
const candidates = ordered.slice(0, RANK_CANDIDATE_LIMIT);
|
|
158
|
+
if (candidates.length < 2)
|
|
159
|
+
return undefined;
|
|
160
|
+
const instructions = `下面这些 MCP 工具里,哪一个与检索 query 最相关?请选出相关性最高的那一个。query:${query}`;
|
|
161
|
+
const state = [
|
|
162
|
+
'任务背景:模型正在通过 mcp_list 浏览被折叠出常驻 prompt 的 MCP 工具,需要按与当前任务的相关性挑选。',
|
|
163
|
+
`检索 query:${query}`,
|
|
164
|
+
`候选工具数:${candidates.length}`,
|
|
165
|
+
].join('\n');
|
|
166
|
+
return rankSeam('list-rank', state, instructions, candidates, env);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Seam C: rerank the near-miss candidates of an unregistered tool name.
|
|
170
|
+
*
|
|
171
|
+
* Same fail-open contract as {@link rankListQuery}. Only runs when the lexical
|
|
172
|
+
* prefilter found MORE THAN ONE candidate — a single lexical hit needs no
|
|
173
|
+
* ranking, so the cold branch stays free when it has nothing to offer.
|
|
174
|
+
*
|
|
175
|
+
* @param tool - the name dispatch could not resolve.
|
|
176
|
+
* @param ordered - lexical near-miss order (best first).
|
|
177
|
+
* @param env - the live jev runtime.
|
|
178
|
+
* @returns the jev order, or undefined for "no opinion".
|
|
179
|
+
*/
|
|
180
|
+
export async function rankDidYouMean(tool, ordered, env) {
|
|
181
|
+
const candidates = ordered.slice(0, RANK_CANDIDATE_LIMIT);
|
|
182
|
+
if (candidates.length < 2)
|
|
183
|
+
return undefined;
|
|
184
|
+
const instructions = `模型刚刚尝试调用 MCP 工具「${tool}」,但这个名字没有注册。哪一个候选最可能就是它想调用的工具?`;
|
|
185
|
+
const state = [
|
|
186
|
+
'任务背景:模型通过 mcp_call 分发被折叠出常驻 prompt 的 MCP 工具,工具名拼错时会走到 not-registered 分支。',
|
|
187
|
+
`未注册的工具名:${tool}`,
|
|
188
|
+
`候选工具数:${candidates.length}`,
|
|
189
|
+
].join('\n');
|
|
190
|
+
return rankSeam('didyoumean', state, instructions, candidates, env);
|
|
191
|
+
}
|
|
192
|
+
/** Shared body of both ranking seams (one batched choice request each). */
|
|
193
|
+
async function rankSeam(lane, state, instructions, candidates, env) {
|
|
194
|
+
try {
|
|
195
|
+
if (!env.enabled())
|
|
196
|
+
return undefined;
|
|
197
|
+
const dual = await jevAskDual({
|
|
198
|
+
state,
|
|
199
|
+
questions: { [RANK_QUESTION_ID]: rankQuestion(instructions, candidates) },
|
|
200
|
+
config: env.config(),
|
|
201
|
+
lane,
|
|
202
|
+
// fail-open is real here: the caller's lexical order IS the fallback.
|
|
203
|
+
fallback: true,
|
|
204
|
+
log: env.log(),
|
|
205
|
+
});
|
|
206
|
+
if (!dual.ok)
|
|
207
|
+
return undefined;
|
|
208
|
+
const answer = dual.answers[RANK_QUESTION_ID];
|
|
209
|
+
const order = orderFromChoice(candidates, answer);
|
|
210
|
+
if (order === undefined)
|
|
211
|
+
return undefined;
|
|
212
|
+
// Telemetry must never turn a good answer into a failed seam.
|
|
213
|
+
await logRankVerdicts(env.log(), lane, state, instructions, candidates, answer, dual.source, dual.source === 'laya').catch(() => undefined);
|
|
214
|
+
return order;
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// contained — the seam has no throw path, by contract
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/** The keep-suggestion question for one candidate (two-sided noul criteria). */
|
|
222
|
+
function keepQuestion(candidate) {
|
|
223
|
+
const instructions = `候选 MCP 工具「${candidate.tool}」当前不在常驻 prompt 里(已折叠,模型每次要用都得先 mcp_list 查到它)。`
|
|
224
|
+
+ `记录到的使用情况:${suggestionBasis(candidate)}。描述:${candidate.description}。`
|
|
225
|
+
+ '该工具值得在每次对话中都常驻 prompt(即加入 keep 名单)吗?';
|
|
226
|
+
return {
|
|
227
|
+
type: 'noul',
|
|
228
|
+
instructions,
|
|
229
|
+
criteria: {
|
|
230
|
+
true: `值得常驻——「${candidate.tool}」被反复调用,把它的 schema 常驻 prompt 能省掉每次查找与展开的成本`,
|
|
231
|
+
false: `不值得常驻——「${candidate.tool}」的调用频次或不可替代性不足以抵消常驻 schema 的 token 成本,保持折叠即可`,
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/** The state text the keep-suggestion batch is asked against. */
|
|
236
|
+
function keepState(candidates) {
|
|
237
|
+
const lines = candidates.map(candidate => [
|
|
238
|
+
`- ${candidate.tool}: ${suggestionBasis(candidate)}`,
|
|
239
|
+
candidate.description === '' ? '' : ` ${candidate.description}`,
|
|
240
|
+
].filter(line => line !== '').join('\n'));
|
|
241
|
+
return [
|
|
242
|
+
'任务背景:模型通过 mcp_list/mcp_call 使用被折叠出常驻 prompt 的 MCP 工具,每个折叠工具的 schema 都要按需展开。',
|
|
243
|
+
`候选工具数:${candidates.length}(记录到的调用情况与工具描述)`,
|
|
244
|
+
...lines,
|
|
245
|
+
].join('\n');
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Seam A: ask, in ONE batched noul request, whether each folded-and-used tool
|
|
249
|
+
* earns a permanent place in every prompt.
|
|
250
|
+
*
|
|
251
|
+
* Fire-and-forget by contract: the caller never awaits this on a hot path.
|
|
252
|
+
* `null` means "this round produced nothing" (disabled, call failed, no
|
|
253
|
+
* candidates, unusable answers, DEGRADED primary) — the caller then keeps the
|
|
254
|
+
* PREVIOUS batch in suggestions.json, and the usage record is unaffected
|
|
255
|
+
* either way.
|
|
256
|
+
*
|
|
257
|
+
* ABSOLUTE SCORES ONLY FROM THE PRIMARY: a suggestion row is a calibrated
|
|
258
|
+
* probability plus a band plus an actionable "keep it resident" line that a
|
|
259
|
+
* human reads in `/mcp suggest`. When the primary failed and the local laya
|
|
260
|
+
* pace-maker took over, that answer is usable for RELATIVE ranking only
|
|
261
|
+
* (dual.ts) and laya's negatives sit in the same band as its positives — so a
|
|
262
|
+
* degraded round returns `null` instead of rendering uncalibrated numbers. The
|
|
263
|
+
* answer is still logged (band 'record', degraded=true): the laya-vs-jev bench
|
|
264
|
+
* and the fallback-rate health metric must not lose a round.
|
|
265
|
+
*
|
|
266
|
+
* @param candidates - folded tools with recorded calls, most-used first.
|
|
267
|
+
* @param env - the live jev runtime.
|
|
268
|
+
* @returns one row per answered candidate, or null.
|
|
269
|
+
*/
|
|
270
|
+
export async function askKeepSuggestions(candidates, env) {
|
|
271
|
+
const batch = candidates.slice(0, RANK_CANDIDATE_LIMIT);
|
|
272
|
+
if (batch.length === 0)
|
|
273
|
+
return null;
|
|
274
|
+
const state = keepState(batch);
|
|
275
|
+
const questions = {};
|
|
276
|
+
const meta = [];
|
|
277
|
+
for (const candidate of batch) {
|
|
278
|
+
const qid = `c${meta.length + 1}`;
|
|
279
|
+
const question = keepQuestion(candidate);
|
|
280
|
+
questions[qid] = question;
|
|
281
|
+
meta.push({ qid, candidate, instructions: question.instructions });
|
|
282
|
+
}
|
|
283
|
+
try {
|
|
284
|
+
if (!env.enabled())
|
|
285
|
+
return null;
|
|
286
|
+
const dual = await jevAskDual({
|
|
287
|
+
state,
|
|
288
|
+
questions,
|
|
289
|
+
config: env.config(),
|
|
290
|
+
lane: 'usage-suggest',
|
|
291
|
+
// Nothing to fall back to: suggestions are additive display, and a
|
|
292
|
+
// failed round simply leaves the previous batch in place.
|
|
293
|
+
fallback: true,
|
|
294
|
+
log: env.log(),
|
|
295
|
+
});
|
|
296
|
+
if (!dual.ok)
|
|
297
|
+
return null;
|
|
298
|
+
// The driving source produces the rows; the pace-maker (when it answered
|
|
299
|
+
// alongside a healthy primary) contributes comparison rows only, exactly
|
|
300
|
+
// as §6.2 prescribes.
|
|
301
|
+
const driving = {
|
|
302
|
+
answers: dual.answers,
|
|
303
|
+
backend: dual.source,
|
|
304
|
+
degraded: dual.source === 'laya',
|
|
305
|
+
};
|
|
306
|
+
const sources = [driving];
|
|
307
|
+
if (dual.source === 'primary' && dual.layaResult?.ok === true) {
|
|
308
|
+
sources.push({ answers: dual.layaResult.answers, backend: 'laya', degraded: false });
|
|
309
|
+
}
|
|
310
|
+
const at = new Date().toISOString();
|
|
311
|
+
const records = [];
|
|
312
|
+
const rows = [];
|
|
313
|
+
for (const source of sources) {
|
|
314
|
+
for (const entry of meta) {
|
|
315
|
+
const probability = noulProbability(source.answers[entry.qid]);
|
|
316
|
+
if (probability === undefined)
|
|
317
|
+
continue; // unparseable answer: no fabricated row
|
|
318
|
+
// A pace-maker probability is uncalibrated, so it never takes a band
|
|
319
|
+
// (`band()` would be a category error) and never becomes a
|
|
320
|
+
// suggestion row — whichever way it got here: a comparison row
|
|
321
|
+
// beside a healthy primary, or a degraded takeover.
|
|
322
|
+
const fromPaceMaker = source.backend !== 'primary';
|
|
323
|
+
const verdictBand = fromPaceMaker ? 'record' : band(probability, 'suggest');
|
|
324
|
+
rows.push({
|
|
325
|
+
at,
|
|
326
|
+
lane: 'usage-suggest',
|
|
327
|
+
questionId: entry.qid,
|
|
328
|
+
qtype: 'noul',
|
|
329
|
+
ref: `tool:${entry.candidate.tool}`,
|
|
330
|
+
digest: digestFor(state, entry.qid, entry.instructions),
|
|
331
|
+
probability,
|
|
332
|
+
band: verdictBand,
|
|
333
|
+
// Candidates are folded by construction, so there is no legacy
|
|
334
|
+
// "it was already kept" disposition to reconcile against.
|
|
335
|
+
agree: 'n/a',
|
|
336
|
+
backend: source.backend,
|
|
337
|
+
degraded: source.degraded,
|
|
338
|
+
});
|
|
339
|
+
if (fromPaceMaker)
|
|
340
|
+
continue; // telemetry/bench row only — never a suggestion
|
|
341
|
+
records.push({
|
|
342
|
+
tool: entry.candidate.tool,
|
|
343
|
+
probability,
|
|
344
|
+
band: verdictBand,
|
|
345
|
+
basis: suggestionBasis(entry.candidate),
|
|
346
|
+
_at: at,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
await env.log().logVerdicts(rows).catch(() => undefined);
|
|
351
|
+
// A degraded round produced verdict rows and nothing else: no primary
|
|
352
|
+
// drove it, so there is no calibrated number to show and no record to
|
|
353
|
+
// write — the previous batch stands (see the module doc).
|
|
354
|
+
if (records.length === 0)
|
|
355
|
+
return null;
|
|
356
|
+
records.sort((left, right) => right.probability - left.probability || left.tool.localeCompare(right.tool));
|
|
357
|
+
return records;
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
// contained — a broken batch must never reach the dispatch path
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
//# sourceMappingURL=seams.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seams.js","sourceRoot":"","sources":["../../src/jev/seams.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System One decision bands (design 2026-09-25 §5), ported from
|
|
3
|
+
* dsh-topics-memory `src/jev/thresholds.ts` with the band kind renamed
|
|
4
|
+
* `rerank` → `suggest` for this plugin's `/mcp suggest` seam.
|
|
5
|
+
*
|
|
6
|
+
* !!! CALIBRATION NOTICE — READ BEFORE ACTING ON A BAND !!!
|
|
7
|
+
* The numbers below are PLACEHOLDERS copied over from topics-memory to keep
|
|
8
|
+
* the file's shape identical for diffing. They were calibrated for THAT
|
|
9
|
+
* plugin's own question protocol and corpus; this plugin's protocol differs
|
|
10
|
+
* (different candidates, different instructions, batch shape), so the
|
|
11
|
+
* absolute probabilities move. Every value MUST be re-swept against this
|
|
12
|
+
* plugin's own protocol before any band is treated as a calibration
|
|
13
|
+
* conclusion — do NOT carry over topics-memory's numbers.
|
|
14
|
+
*
|
|
15
|
+
* WHERE A BAND IS READ TODAY: exactly one place — seam A (`jev/seams.ts`
|
|
16
|
+
* askKeepSuggestions) reads `band(p, 'suggest')` as the DISPLAY label on a
|
|
17
|
+
* `/mcp suggest` row that already carries the raw probability. Nothing
|
|
18
|
+
* auto-acts on a band, and a pace-maker (laya) probability never takes one:
|
|
19
|
+
* uncalibrated scores are logged as band 'record' and produce no suggestion
|
|
20
|
+
* row, so a degraded round yields no band at all. Until this plugin's own
|
|
21
|
+
* re-sweep lands, a band must be read as "above the placeholder cutoff", not
|
|
22
|
+
* as "adopt/veto is justified".
|
|
23
|
+
*
|
|
24
|
+
* @module jev/thresholds
|
|
25
|
+
*/
|
|
26
|
+
/** `/mcp suggest` (建议补的 mcp 工具): adopt ≥ 0.60, strong-veto < 0.10. */
|
|
27
|
+
export declare const SUGGEST_ADOPT = 0.6;
|
|
28
|
+
export declare const SUGGEST_FALLBACK = 0.1;
|
|
29
|
+
/** Second band kind, kept for shape parity with the source file and for the
|
|
30
|
+
* lanes that are not calibrated yet — the values are placeholders too. */
|
|
31
|
+
export declare const MERGE_PAIR_ADOPT = 0.5;
|
|
32
|
+
export declare const MERGE_PAIR_FALLBACK = 0.15;
|
|
33
|
+
export type JevBand = 'adopt' | 'record' | 'fallback';
|
|
34
|
+
export type JevBandKind = 'suggest' | 'mergePair';
|
|
35
|
+
/** Map a noul probability to its band: adopt → act on it, record → only log
|
|
36
|
+
* it, fallback → hard veto back to the legacy behavior. */
|
|
37
|
+
export declare function band(probability: number, kind: JevBandKind): JevBand;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System One decision bands (design 2026-09-25 §5), ported from
|
|
3
|
+
* dsh-topics-memory `src/jev/thresholds.ts` with the band kind renamed
|
|
4
|
+
* `rerank` → `suggest` for this plugin's `/mcp suggest` seam.
|
|
5
|
+
*
|
|
6
|
+
* !!! CALIBRATION NOTICE — READ BEFORE ACTING ON A BAND !!!
|
|
7
|
+
* The numbers below are PLACEHOLDERS copied over from topics-memory to keep
|
|
8
|
+
* the file's shape identical for diffing. They were calibrated for THAT
|
|
9
|
+
* plugin's own question protocol and corpus; this plugin's protocol differs
|
|
10
|
+
* (different candidates, different instructions, batch shape), so the
|
|
11
|
+
* absolute probabilities move. Every value MUST be re-swept against this
|
|
12
|
+
* plugin's own protocol before any band is treated as a calibration
|
|
13
|
+
* conclusion — do NOT carry over topics-memory's numbers.
|
|
14
|
+
*
|
|
15
|
+
* WHERE A BAND IS READ TODAY: exactly one place — seam A (`jev/seams.ts`
|
|
16
|
+
* askKeepSuggestions) reads `band(p, 'suggest')` as the DISPLAY label on a
|
|
17
|
+
* `/mcp suggest` row that already carries the raw probability. Nothing
|
|
18
|
+
* auto-acts on a band, and a pace-maker (laya) probability never takes one:
|
|
19
|
+
* uncalibrated scores are logged as band 'record' and produce no suggestion
|
|
20
|
+
* row, so a degraded round yields no band at all. Until this plugin's own
|
|
21
|
+
* re-sweep lands, a band must be read as "above the placeholder cutoff", not
|
|
22
|
+
* as "adopt/veto is justified".
|
|
23
|
+
*
|
|
24
|
+
* @module jev/thresholds
|
|
25
|
+
*/
|
|
26
|
+
/** `/mcp suggest` (建议补的 mcp 工具): adopt ≥ 0.60, strong-veto < 0.10. */
|
|
27
|
+
export const SUGGEST_ADOPT = 0.6;
|
|
28
|
+
export const SUGGEST_FALLBACK = 0.1;
|
|
29
|
+
/** Second band kind, kept for shape parity with the source file and for the
|
|
30
|
+
* lanes that are not calibrated yet — the values are placeholders too. */
|
|
31
|
+
export const MERGE_PAIR_ADOPT = 0.5;
|
|
32
|
+
export const MERGE_PAIR_FALLBACK = 0.15;
|
|
33
|
+
/** Map a noul probability to its band: adopt → act on it, record → only log
|
|
34
|
+
* it, fallback → hard veto back to the legacy behavior. */
|
|
35
|
+
export function band(probability, kind) {
|
|
36
|
+
const floor = kind === 'suggest' ? SUGGEST_FALLBACK : MERGE_PAIR_FALLBACK;
|
|
37
|
+
const adopt = kind === 'suggest' ? SUGGEST_ADOPT : MERGE_PAIR_ADOPT;
|
|
38
|
+
if (probability >= adopt)
|
|
39
|
+
return 'adopt';
|
|
40
|
+
if (probability >= floor)
|
|
41
|
+
return 'record';
|
|
42
|
+
return 'fallback';
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=thresholds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thresholds.js","sourceRoot":"","sources":["../../src/jev/thresholds.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,sEAAsE;AACtE,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAA;AAChC,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,CAAA;AAEnC;2EAC2E;AAC3E,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,CAAA;AACnC,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,CAAA;AAKvC;4DAC4D;AAC5D,MAAM,UAAU,IAAI,CAAC,WAAmB,EAAE,IAAiB;IACzD,MAAM,KAAK,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,mBAAmB,CAAA;IACzE,MAAM,KAAK,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,gBAAgB,CAAA;IACnE,IAAI,WAAW,IAAI,KAAK;QAAE,OAAO,OAAO,CAAA;IACxC,IAAI,WAAW,IAAI,KAAK;QAAE,OAAO,QAAQ,CAAA;IACzC,OAAO,UAAU,CAAA;AACnB,CAAC"}
|
package/lib/rank.d.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lexical pre-ranking for the two model-facing search seams (`mcp_list
|
|
3
|
+
* {query}` and `mcp_call`'s unknown-tool branch).
|
|
4
|
+
*
|
|
5
|
+
* WHY A SEPARATE MODULE: these are the pure, dependency-free halves of seams
|
|
6
|
+
* B and C. They must stay callable from `node --test` without the meta-tool
|
|
7
|
+
* definitions, AND from `jev/seams.ts` (which reranks whatever this module
|
|
8
|
+
* produced) — so they cannot live in src/index.ts without an import cycle
|
|
9
|
+
* (index.ts → jev/seams.ts → index.ts).
|
|
10
|
+
*
|
|
11
|
+
* CONTRACT (red line ②): everything here is deterministic, synchronous and
|
|
12
|
+
* offline. A jev answer is only ever a REORDER of what these functions already
|
|
13
|
+
* produced (see {@link mergeNameOrder}); any jev failure degrades to the
|
|
14
|
+
* order computed below, so this file is the fail-open baseline of both seams.
|
|
15
|
+
*
|
|
16
|
+
* SCRIPT: the lexical prefilter runs over the ASCII tokens when the query has
|
|
17
|
+
* any, and falls back to a CJK substring match when it has none (see
|
|
18
|
+
* {@link cjkQuery}) — the model asks this plugin's questions in Chinese.
|
|
19
|
+
*
|
|
20
|
+
* @module rank
|
|
21
|
+
*/
|
|
22
|
+
/** Cap on the candidate list handed to a jev batch (both ranking seams). */
|
|
23
|
+
export declare const RANK_CANDIDATE_LIMIT = 40;
|
|
24
|
+
/** How many ranked tools a `mcp_list {query}` answer actually shows. */
|
|
25
|
+
export declare const LIST_RESULT_LIMIT = 10;
|
|
26
|
+
/** How many near-misses the unknown-tool error names. */
|
|
27
|
+
export declare const DID_YOU_MEAN_LIMIT = 3;
|
|
28
|
+
/** Max edit distance a near-miss may have to be offered. */
|
|
29
|
+
export declare const DID_YOU_MEAN_MAX_DISTANCE = 3;
|
|
30
|
+
/** The minimal shape ranking needs from a tool. */
|
|
31
|
+
export interface RankableEntry {
|
|
32
|
+
readonly name: string;
|
|
33
|
+
readonly description: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* What the ranking seams pass around: a tool plus the blurb that goes into a
|
|
37
|
+
* decision question (or into the lexical score). Named separately from
|
|
38
|
+
* {@link RankableEntry} because it crosses the seam boundary (index.ts builds
|
|
39
|
+
* the pools, jev/seams.ts asks about them).
|
|
40
|
+
*/
|
|
41
|
+
export type RankCandidate = RankableEntry;
|
|
42
|
+
/**
|
|
43
|
+
* Lowercase alphanumeric tokens of one string, split on non-alphanumeric runs
|
|
44
|
+
* AND on camelCase / letter-digit boundaries — so `create_issue`,
|
|
45
|
+
* `createIssue` and `createissue` all reduce to overlapping tokens. Tool
|
|
46
|
+
* naming conventions vary per MCP vendor; the search seam must not depend on
|
|
47
|
+
* one of them.
|
|
48
|
+
*
|
|
49
|
+
* ASCII ONLY, on purpose: the token weights in {@link scoreQueryMatch} are
|
|
50
|
+
* calibrated on token equality, and a CJK query has no ASCII tokens to
|
|
51
|
+
* compare. Chinese/other CJK text is matched by the substring fallback
|
|
52
|
+
* ({@link cjkQuery}) instead, and that fallback is a SEPARATE path so the
|
|
53
|
+
* ASCII ranking cannot drift.
|
|
54
|
+
*/
|
|
55
|
+
export declare function tokenize(text: string): string[];
|
|
56
|
+
/**
|
|
57
|
+
* Lexical score of one entry against a tokenized query. 0 means "no match at
|
|
58
|
+
* all" and is what the rankers filter on — an unmatched entry is never shown
|
|
59
|
+
* as a result, however long its description is.
|
|
60
|
+
*
|
|
61
|
+
* Weights: exact full-name equality dominates; a query token found in the
|
|
62
|
+
* NAME beats one found only in the DESCRIPTION; a whole-phrase substring hit
|
|
63
|
+
* beats a scattered per-token hit.
|
|
64
|
+
*
|
|
65
|
+
* @param entry - the tool being scored.
|
|
66
|
+
* @param tokens - query tokens from {@link tokenize}.
|
|
67
|
+
* @param queryCompact - the whole query squashed to alphanumerics.
|
|
68
|
+
* @returns the score (0 = no match).
|
|
69
|
+
*/
|
|
70
|
+
export declare function scoreQueryMatch(entry: RankableEntry, tokens: readonly string[], queryCompact: string): number;
|
|
71
|
+
/**
|
|
72
|
+
* Rank entries against a free-text query: best score first, name ascending as
|
|
73
|
+
* the deterministic tie-break, entries with no lexical match dropped, at most
|
|
74
|
+
* `limit` survivors.
|
|
75
|
+
*
|
|
76
|
+
* A query with no ASCII token falls back to the CJK substring prefilter
|
|
77
|
+
* ({@link cjkQuery}) — the model writes this plugin's queries in Chinese, and
|
|
78
|
+
* "no tokens" must not read as "nothing matched". A query with neither ASCII
|
|
79
|
+
* nor CJK content (blank, punctuation) still matches nothing.
|
|
80
|
+
*
|
|
81
|
+
* Pure and synchronous — this is the order a failed/failed-open jev rerank
|
|
82
|
+
* degrades to, and the only order produced when `jevEnabled` is false.
|
|
83
|
+
*
|
|
84
|
+
* @param query - the model's raw query text.
|
|
85
|
+
* @param entries - the catalog entries to score (input is never mutated).
|
|
86
|
+
* @param limit - max survivors; omit for "every match".
|
|
87
|
+
* @returns the surviving entries, best first.
|
|
88
|
+
*/
|
|
89
|
+
export declare function rankByQuery<T extends RankableEntry>(query: string, entries: readonly T[], limit?: number): T[];
|
|
90
|
+
/**
|
|
91
|
+
* Reorder a list of NAMES by an external ranking (the jev answer). Names the
|
|
92
|
+
* ranking does not mention — and names it repeats — keep their original
|
|
93
|
+
* relative order at the tail, so a short or partial answer degrades to "jev's
|
|
94
|
+
* prefix + lexical remainder" instead of dropping candidates. Names that were
|
|
95
|
+
* not in `ordered` at all are ignored.
|
|
96
|
+
*
|
|
97
|
+
* @param ordered - the lexical order being permuted.
|
|
98
|
+
* @param ranking - tool names, best first, as returned by the reranker.
|
|
99
|
+
* @returns a new array; `ordered` is never mutated.
|
|
100
|
+
*/
|
|
101
|
+
export declare function mergeNameOrder(ordered: readonly string[], ranking: readonly string[]): string[];
|
|
102
|
+
/**
|
|
103
|
+
* Levenshtein distance between two names, or `undefined` when it is provably
|
|
104
|
+
* greater than `max`. The length pre-check plus the per-row minimum make the
|
|
105
|
+
* common "clearly not a near-miss" case O(max) instead of O(n·m); names are
|
|
106
|
+
* short anyway, but the pool can be a full catalog and this runs inside a
|
|
107
|
+
* dispatch error path.
|
|
108
|
+
*
|
|
109
|
+
* @param a - first name (case-insensitive).
|
|
110
|
+
* @param b - second name (case-insensitive).
|
|
111
|
+
* @param max - distances above this report "no".
|
|
112
|
+
* @returns the distance, or undefined when it exceeds `max`.
|
|
113
|
+
*/
|
|
114
|
+
export declare function editDistanceWithin(a: string, b: string, max: number): number | undefined;
|
|
115
|
+
/** Where one near-miss landed: a typo of the name, or merely a shared token. */
|
|
116
|
+
export type DidYouMeanTier = 'edit' | 'token';
|
|
117
|
+
/** One scored near-miss candidate. */
|
|
118
|
+
export interface DidYouMeanCandidate {
|
|
119
|
+
/** The candidate's registered tool name. */
|
|
120
|
+
name: string;
|
|
121
|
+
/** How it matched: within the edit distance, or on a shared token. */
|
|
122
|
+
tier: DidYouMeanTier;
|
|
123
|
+
/** Edit distance (0 for token-only matches, which are not distance-graded). */
|
|
124
|
+
distance: number;
|
|
125
|
+
/** Tokens shared with the mistyped name. */
|
|
126
|
+
shared: number;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Lexical near-miss candidates for one unregistered tool name: everything
|
|
130
|
+
* within {@link DID_YOU_MEAN_MAX_DISTANCE} edits, plus anything sharing a
|
|
131
|
+
* name token, best first. This is the always-on floor of the unknown-tool
|
|
132
|
+
* branch — it runs whether or not jev is enabled, needs no network, and is
|
|
133
|
+
* the order that stands when a jev rerank fails.
|
|
134
|
+
*
|
|
135
|
+
* @param tool - the name dispatch could not resolve.
|
|
136
|
+
* @param pool - candidate names (production: the visible prefix tools).
|
|
137
|
+
* @param limit - max candidates (default {@link DID_YOU_MEAN_LIMIT}).
|
|
138
|
+
* @param ignoreTokens - tokens that must NOT count as a shared-token match.
|
|
139
|
+
* The caller's fold prefix goes here: every candidate in the pool starts
|
|
140
|
+
* with it, so "shares mcp" is not evidence of anything.
|
|
141
|
+
* @returns the candidates, best first (edit tier, then fewest edits, then
|
|
142
|
+
* most shared tokens, then shorter, then alphabetical).
|
|
143
|
+
*/
|
|
144
|
+
export declare function didYouMeanCandidates(tool: string, pool: readonly string[], limit?: number, ignoreTokens?: readonly string[]): DidYouMeanCandidate[];
|
|
145
|
+
/** Which ordering produced a suggestion list — shown in the error text. */
|
|
146
|
+
export type DidYouMeanSource = 'lexical' | 'jev';
|
|
147
|
+
/**
|
|
148
|
+
* The suffix appended to the unknown-tool error. The base message stays
|
|
149
|
+
* byte-identical to the pre-seam one, so a caller that only knew the old text
|
|
150
|
+
* still matches it; the suggestion is an addition, and its provenance is
|
|
151
|
+
* always stated (an unranked guess must not look like a ranked one).
|
|
152
|
+
*
|
|
153
|
+
* @param names - suggested tool names, best first.
|
|
154
|
+
* @param source - who produced the order.
|
|
155
|
+
* @returns `''` when there is nothing to suggest.
|
|
156
|
+
*/
|
|
157
|
+
export declare function didYouMeanMessage(names: readonly string[], source: DidYouMeanSource): string;
|