@ladderkitlab/hub-kb 0.1.0 → 0.2.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/dist/main.js CHANGED
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/main.ts
4
- import fs3 from "node:fs/promises";
5
- import path3 from "node:path";
4
+ import path2 from "node:path";
6
5
  import { createHash as createHash2 } from "node:crypto";
7
6
  import { fileURLToPath } from "node:url";
8
7
 
@@ -41,141 +40,10 @@ function tokenize(n, cfg) {
41
40
  }
42
41
  return [...out];
43
42
  }
44
- var QUESTION_WORDS = ["\u4EC0\u4E48\u610F\u601D", "\u4E3A\u4EC0\u4E48", "\u600E\u4E48\u6837", "\u4EC0\u4E48\u6837", "\u600E\u4E48", "\u4EC0\u4E48", "\u600E\u6837", "\u4E3A\u5565", "\u5982\u4F55", "\u662F\u5426", "\u54EA\u4E9B", "\u54EA\u4E2A", "\u54EA\u91CC", "\u5417", "\u5462", "\u5565", "\u548B"];
45
- var UNKNOWN_TOKEN_WEIGHT = 0.5;
46
- function questionTokens(n, cfg) {
47
- const out = /* @__PURE__ */ new Set();
48
- const k = cfg.tokenizer.cjk_ngram;
49
- for (const run2 of cjkRuns(n)) {
50
- const mark = new Array(run2.length).fill(false);
51
- for (const w of QUESTION_WORDS) for (let i = run2.indexOf(w); i >= 0; i = run2.indexOf(w, i + 1)) mark.fill(true, i, i + w.length);
52
- for (let i = 0; i + k <= run2.length; i++) if (mark.slice(i, i + k).some(Boolean)) out.add(run2.slice(i, i + k));
53
- if (run2.length < k && mark.some(Boolean)) out.add(run2);
54
- }
55
- return out;
56
- }
57
- function dimMatchAny(d, vs, cfg) {
58
- if (!vs?.some(Boolean)) return cfg.filters.applicability_context_missing === "drop" && d !== "any" ? null : 1;
59
- if (d === "any") return 1;
60
- if (d === "unknown") return cfg.filters.unknown_applicability_multiplier;
61
- return vs.some((v) => v && d.includes(v)) ? 1 : null;
62
- }
63
- var dimMatch = (d, v, cfg) => dimMatchAny(d, v === void 0 ? void 0 : [v], cfg);
64
- function intersect(sets) {
65
- const common = sets.length ? sets[0].filter((v) => sets.every((s) => s.includes(v))) : [];
66
- return common.length ? common : void 0;
67
- }
68
- var MAX_SEARCH_RESULTS = 5;
69
- function osMentions(n, terms, groups) {
70
- const os = /* @__PURE__ */ new Set();
71
- const mentions = [];
72
- const byWord = /* @__PURE__ */ new Map();
73
- for (const [id, words] of Object.entries(terms ?? {})) for (const w of words) byWord.set(w, [id]);
74
- for (const g of Object.values(groups ?? {})) for (const w of g.aliases) byWord.set(w, g.members);
75
- if (byWord.size === 0) return { text: n, os, mentions };
76
- const alt = [...byWord.keys()].sort((a, b) => b.length - a.length).join("|");
77
- const text = n.replace(new RegExp(`(?<![a-z0-9_])(${alt})(?:\\s*\\d+)?(?![a-z0-9_])`, "g"), (m, w) => {
78
- const set = byWord.get(w);
79
- mentions.push(set);
80
- if (set.length === 1) os.add(set[0]);
81
- return /^[a-z0-9 ]+$/.test(w) ? " " : m === w ? w : `${w} `;
82
- });
83
- return { text, os, mentions };
84
- }
85
- var MAX_GAP = 12;
86
- var MIN_FIXED = 3;
87
- var wordSeq = (n) => n.match(/[a-z0-9_]+|[一-鿿]+/g) ?? [];
88
- var segAt = (q, seg, i) => seg.every((w, j) => q[i + j] === w);
89
- function matchSegments(q, segs, from, first) {
90
- if (segs.length === 0) return true;
91
- const [seg, ...rest2] = segs;
92
- const last = first ? q.length - seg.length : Math.min(q.length - seg.length, from + MAX_GAP);
93
- for (let i = from; i <= last; i++) if (segAt(q, seg, i) && matchSegments(q, rest2, i + seg.length, false)) return true;
94
- return false;
95
- }
96
- var fixedWords = (p) => p.segments.reduce((a, s) => a + s.length, 0);
97
- function errorLineHits(patterns, n) {
98
- const q = wordSeq(n);
99
- return (patterns ?? []).filter((p) => fixedWords(p) >= MIN_FIXED && matchSegments(q, p.segments, 0, true)).sort((a, b) => fixedWords(b) - fixedWords(a));
100
- }
101
- function explicitContext(ctx) {
102
- return { client: ctx.client?.trim() ? ctx.client : void 0, os: ctx.os?.trim() ? ctx.os : void 0 };
103
- }
104
- function search(index, query, ctx, t2s) {
105
- const cfg = index.config;
106
- const normalized = normalize(query, cfg, t2s);
107
- const mentioned = osMentions(normalized, index.os_terms, index.os_groups);
108
- const clients = osMentions(mentioned.text, index.client_terms);
109
- const q = tokenize(clients.text, cfg);
110
- if (q.length === 0) return { results: [], abstained: true };
111
- const { os: ctxOs, client: ctxClient } = explicitContext(ctx);
112
- const os = ctxOs !== void 0 ? [ctxOs] : intersect(mentioned.mentions);
113
- const client = ctxClient ?? (clients.os.size === 1 ? [...clients.os][0] : void 0);
114
- const hits = errorLineHits(index.error_patterns, normalized).filter((h) => dimMatch(h.client ?? "any", client, cfg) !== null && dimMatchAny(h.os ?? "any", os, cfg) !== null);
115
- if (hits.length) {
116
- const byId = new Map(index.docs.map((d) => [d.id, d]));
117
- const lines = [];
118
- const add = (id) => {
119
- const d = byId.get(id);
120
- if (!d || lines.some((r) => r.id === id)) return;
121
- const mc = dimMatch(d.client, client, cfg), mo = dimMatchAny(d.os, os, cfg);
122
- if (mc === null || mo === null) return;
123
- lines.push({ id, type: d.type, score: (cfg.weights.error_messages ?? 0) * mc * mo * (d.stale ? cfg.freshness.stale_multiplier : 1) });
124
- };
125
- for (const h of hits) {
126
- if (h.maps_to !== "unmapped") add(h.maps_to);
127
- add(h.owner);
128
- }
129
- if (lines.length) {
130
- const results2 = lines.slice(0, Math.min(cfg.max_results, MAX_SEARCH_RESULTS));
131
- const abstained2 = results2[0].score < cfg.abstain.min_score;
132
- return { results: abstained2 ? [] : results2, abstained: abstained2 };
133
- }
134
- }
135
- const latin = q.filter((t) => /^[a-z0-9_]+$/.test(t));
136
- const question = questionTokens(clients.text, cfg);
137
- const candidates = index.docs.flatMap((d) => {
138
- const mc = dimMatch(d.client, client, cfg), mo = dimMatchAny(d.os, os, cfg);
139
- return mc === null || mo === null ? [] : [{ d, mc, mo, tokens: new Set(Object.entries(d.fields).filter(([f]) => cfg.weights[f]).flatMap(([, toks]) => toks)) }];
140
- });
141
- const n = candidates.length;
142
- const weigh = (has) => new Map(q.map((t) => {
143
- const df = candidates.filter((c) => c.tokens.has(t)).length;
144
- const shared = candidates.filter((c) => has(c, t)).length;
145
- return [t, question.has(t) || n > 1 && shared === n ? 0 : df === 0 ? UNKNOWN_TOKEN_WEIGHT : 1];
146
- }));
147
- let weight = weigh((c, t) => c.tokens.has(t));
148
- if (q.every((t) => weight.get(t) === 0 || !candidates.some((c) => c.tokens.has(t)))) {
149
- const strong = new Map(candidates.map((c) => [c, new Set(Object.entries(c.d.fields).filter(([f]) => f !== "body" && cfg.weights[f]).flatMap(([, toks]) => toks))]));
150
- weight = weigh((c, t) => strong.get(c).has(t));
151
- }
152
- const total = q.reduce((a, t) => a + weight.get(t), 0);
153
- if (total === 0) return { results: [], abstained: true };
154
- const scored = [];
155
- for (const { d, mc, mo } of candidates) {
156
- const matched = /* @__PURE__ */ new Set();
157
- let s = 0;
158
- for (const [field2, toks] of Object.entries(d.fields)) {
159
- const w = cfg.weights[field2] ?? 0;
160
- if (!w) continue;
161
- const set = new Set(toks);
162
- let hit = 0;
163
- for (const t of q) if (set.has(t)) {
164
- hit++;
165
- matched.add(t);
166
- }
167
- s += w * (hit / q.length);
168
- }
169
- if ([...matched].reduce((a, t) => a + weight.get(t), 0) / total < cfg.abstain.min_token_coverage) continue;
170
- if (latin.some((t) => !matched.has(t))) continue;
171
- s *= mc * mo;
172
- if (d.stale) s *= cfg.freshness.stale_multiplier;
173
- if (s > 0) scored.push({ id: d.id, type: d.type, score: s, lv: d.last_verified });
174
- }
175
- scored.sort((a, b) => b.score - a.score || (b.lv > a.lv ? 1 : b.lv < a.lv ? -1 : 0) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
176
- const results = scored.slice(0, Math.min(cfg.max_results, MAX_SEARCH_RESULTS)).map(({ id, type, score }) => ({ id, type, score }));
177
- const abstained = results.length === 0 || results[0].score < cfg.abstain.min_score;
178
- return { results: abstained ? [] : results, abstained };
43
+ function freshnessAt(e, now) {
44
+ if (!e.fresh_windows) return e.freshness;
45
+ const t = now.getTime();
46
+ return e.fresh_windows.some(([from, until]) => (from === null || t >= Date.parse(from)) && t < Date.parse(until)) ? "current" : "stale";
179
47
  }
180
48
 
181
49
  // ../redact/context.ts
@@ -185,6 +53,11 @@ var MAX_DEPTH = 64;
185
53
  var BLOCK_PREFIX = /[\s,,。::]/;
186
54
  var isKeyChar = (c) => c !== void 0 && /[A-Za-z0-9_-]/.test(c);
187
55
  var isNewline = (c) => c === "\n" || c === "\r";
56
+ var JSON_ESC = { '"': '"', "\\": "\\", "/": "/", b: "\b", f: "\f", n: "\n", r: "\r", t: " " };
57
+ function decodeJsonKey(raw) {
58
+ if (!raw.includes("\\")) return raw;
59
+ return raw.replace(/\\(u[0-9A-Fa-f]{4}|.)/g, (m, e) => e.length === 5 ? String.fromCharCode(parseInt(e.slice(1), 16)) : JSON_ESC[e] ?? m);
60
+ }
188
61
  function configRegions(s) {
189
62
  const t = s.text;
190
63
  const n = s.n;
@@ -226,7 +99,17 @@ function configRegions(s) {
226
99
  }
227
100
  s.budget.tick(j - i);
228
101
  const closed = j < n && t[j] === c;
229
- lastKey = depth > 0 ? { value: t.slice(i + 1, j) } : null;
102
+ const keyText = c === '"' ? decodeJsonKey(t.slice(i + 1, j)) : t.slice(i + 1, j);
103
+ lastKey = depth > 0 ? { value: keyText } : null;
104
+ if (depth === 0 && closed && (i === 0 || BLOCK_PREFIX.test(t[i - 1])) && ROOTS.has(keyText.toLowerCase())) {
105
+ let k = j + 1;
106
+ while (k < n && /\s/.test(t[k])) k++;
107
+ s.budget.tick(k - j);
108
+ if (t[k] === ":") {
109
+ out.push({ index: i === 0 ? 0 : i - 1, end: n });
110
+ return out;
111
+ }
112
+ }
230
113
  i = closed ? j + 1 : j;
231
114
  continue;
232
115
  }
@@ -477,6 +360,16 @@ function isContent(s, raw) {
477
360
  }
478
361
  return decoded.trim() !== "";
479
362
  }
363
+ function hasRawValueContent(s, start, end) {
364
+ let raw = "";
365
+ for (let k = start; k < end; ) {
366
+ s.budget.tick();
367
+ const a = placeholderAt(s.text, k);
368
+ if (a > 0 && k + a <= end) k += a;
369
+ else raw += s.text[k++];
370
+ }
371
+ return s.budget.exhausted || isContent(s, raw);
372
+ }
480
373
  function interpretUrlValue(s, start) {
481
374
  const cached = s.values.get(start);
482
375
  if (cached) {
@@ -562,9 +455,11 @@ var PROSE_CREDENTIAL_NAMES = ["token", "secret", "password", "pwd", "session_id"
562
455
  var PROSE_KEY_NAME = "key";
563
456
  var PROSE_KEY_MIN_LENGTH = 8;
564
457
  var AUTH_SCHEME_NAME = "bearer";
458
+ var AUTH_HEADER_NAME = "authorization";
565
459
  var PROXY_URI_SCHEMES = ["ss", "ssr", "vmess", "vless", "trojan", "hysteria", "hysteria2", "hy2", "tuic", "anytls"];
460
+ var SOCKS_URI_SCHEMES = ["socks", "socks4", "socks4a", "socks5", "socks5h"];
566
461
  var SCHEMES_LONGEST_FIRST = [...PROXY_URI_SCHEMES].sort((a, b) => b.length - a.length);
567
- var PROSE_NAMES_LONGEST_FIRST = [...PROSE_CREDENTIAL_NAMES, PROSE_KEY_NAME, AUTH_SCHEME_NAME].sort((a, b) => b.length - a.length);
462
+ var PROSE_NAMES_LONGEST_FIRST = [...PROSE_CREDENTIAL_NAMES, PROSE_KEY_NAME, AUTH_SCHEME_NAME, AUTH_HEADER_NAME].sort((a, b) => b.length - a.length);
568
463
  var PROSE_NAME_FIRST = new Set(PROSE_NAMES_LONGEST_FIRST.map((n) => n[0]));
569
464
  var isAsciiName = (name) => name.charCodeAt(0) < 128;
570
465
  var SEPARATORS = "=:\uFF1A";
@@ -572,6 +467,18 @@ var isAlnumCode = (x) => x >= 48 && x <= 57 || x >= 65 && x <= 90 || x >= 97 &&
572
467
  var isAlnumAt = (t, i) => i >= 0 && i < t.length && isAlnumCode(t.charCodeAt(i));
573
468
  var isConnector = (c) => c === "_" || c === "-" || c === ".";
574
469
  var isTokenCharAt = (t, i) => i < t.length && (isAlnumCode(t.charCodeAt(i)) || t[i] === "_" || t[i] === "-");
470
+ var isB64TokenCharAt = (t, i) => isTokenCharAt(t, i) || i < t.length && ".~+/".includes(t[i]);
471
+ var isUpperCode = (x) => x >= 65 && x <= 90;
472
+ var isLowerOrDigitCode = (x) => x >= 97 && x <= 122 || x >= 48 && x <= 57;
473
+ var camelBoundaryAt = (t, i) => i > 0 && isUpperCode(t.charCodeAt(i)) && isLowerOrDigitCode(t.charCodeAt(i - 1));
474
+ var B64_LONG_MIN = 40;
475
+ function b64LongEnd(s, k) {
476
+ const t = s.text;
477
+ let j = k;
478
+ while (j < s.n && (isAlnumAt(t, j) || t[j] === "+" || t[j] === "/")) j++;
479
+ s.budget.tick(j - k + 1);
480
+ return j - k >= B64_LONG_MIN ? j : -1;
481
+ }
575
482
  function compoundNameStart(text, nameStart, s) {
576
483
  let start = nameStart;
577
484
  for (; ; ) {
@@ -613,7 +520,8 @@ function keyThresholdEnd(s, from) {
613
520
  let c = 0;
614
521
  while (c < PROSE_KEY_MIN_LENGTH && isTokenCharAt(t, k + c)) c++;
615
522
  s.budget.tick(k - from + c + 1);
616
- return c < PROSE_KEY_MIN_LENGTH ? -1 : k + PROSE_KEY_MIN_LENGTH;
523
+ if (c >= PROSE_KEY_MIN_LENGTH) return k + PROSE_KEY_MIN_LENGTH;
524
+ return b64LongEnd(s, k) < 0 ? -1 : k + PROSE_KEY_MIN_LENGTH;
617
525
  }
618
526
  function proseNameAt(text, i) {
619
527
  if (!PROSE_NAME_FIRST.has(text[i].toLowerCase())) return null;
@@ -652,7 +560,8 @@ function proseNamedHits(s) {
652
560
  break;
653
561
  }
654
562
  const name = proseNameAt(t, i);
655
- if (!name || isAsciiName(name) && isAlnumAt(t, i - 1)) {
563
+ const camel = name !== null && isAsciiName(name) && isAlnumAt(t, i - 1) && camelBoundaryAt(t, i);
564
+ if (!name || isAsciiName(name) && isAlnumAt(t, i - 1) && !camel) {
656
565
  i++;
657
566
  continue;
658
567
  }
@@ -661,7 +570,7 @@ function proseNamedHits(s) {
661
570
  let k = nameEnd;
662
571
  while (k < n && isInlineWs(t[k])) k++;
663
572
  let c = 0;
664
- while (c < PROSE_KEY_MIN_LENGTH && isTokenCharAt(t, k + c)) c++;
573
+ while (c < PROSE_KEY_MIN_LENGTH && isB64TokenCharAt(t, k + c)) c++;
665
574
  s.budget.tick(k - nameEnd + c + 1);
666
575
  if (k === nameEnd || c < PROSE_KEY_MIN_LENGTH) {
667
576
  i++;
@@ -700,9 +609,43 @@ function proseNamedHits(s) {
700
609
  i++;
701
610
  continue;
702
611
  }
703
- headEnd = k + 1;
612
+ let first = k;
613
+ if (name === AUTH_HEADER_NAME) {
614
+ let w = k;
615
+ while (w < n && /[A-Za-z]/.test(t[w])) w++;
616
+ let v = w;
617
+ while (v < n && isInlineWs(t[v])) v++;
618
+ s.budget.tick(v - k + 1);
619
+ if (w > k && v > w) {
620
+ const v0 = v;
621
+ for (; ; ) {
622
+ const a = placeholderAt(t, v);
623
+ if (a > 0) {
624
+ v += a;
625
+ continue;
626
+ }
627
+ if (v < n && isInlineWs(t[v])) {
628
+ v++;
629
+ continue;
630
+ }
631
+ break;
632
+ }
633
+ s.budget.tick(v - v0 + 1);
634
+ if (v >= n || !isPrintableAscii(t.charCodeAt(v)) || isWs(t[v])) {
635
+ i++;
636
+ continue;
637
+ }
638
+ first = v;
639
+ }
640
+ }
641
+ headEnd = first + 1;
704
642
  }
705
- const start = compoundNameStart(t, i, s);
643
+ let runStart = i;
644
+ if (camel) {
645
+ while (runStart > 0 && isAlnumAt(t, runStart - 1)) runStart--;
646
+ s.budget.tick(i - runStart + 1);
647
+ }
648
+ const start = compoundNameStart(t, runStart, s);
706
649
  let urlEnd = -1;
707
650
  if (t[j] === "=" && j === nameEnd && inUrlParamName(s, start)) {
708
651
  const v = interpretUrlValue(s, sepEnd);
@@ -748,6 +691,44 @@ function urlCredentialSpans(s) {
748
691
  break;
749
692
  }
750
693
  const c = t[i];
694
+ if (c === "%" && i + 2 < n) {
695
+ const head = t.slice(i, i + 3).toUpperCase();
696
+ if (head === "%3F" || head === "%26" || head === "%23") {
697
+ let j = i + 3;
698
+ while (j < n) {
699
+ if (PARAM_NAME_CHAR(t, j)) {
700
+ j++;
701
+ continue;
702
+ }
703
+ if (t[j] === "%" && isHex(t[j + 1]) && isHex(t[j + 2]) && !["%3D", "%26", "%3F", "%23"].includes(t.slice(j, j + 3).toUpperCase())) {
704
+ j += 3;
705
+ continue;
706
+ }
707
+ break;
708
+ }
709
+ s.budget.tick(j - i + 1);
710
+ if (j > i + 3 && t.slice(j, j + 3).toUpperCase() === "%3D") {
711
+ const cls2 = urlNameClass(t.slice(i + 3, j).replace(/%25/gi, "%"));
712
+ if (!cls2) {
713
+ i = j;
714
+ continue;
715
+ }
716
+ const vStart = j + 3;
717
+ let k = vStart;
718
+ while (k < n && t[k] !== "&" && t[k] !== "#" && !isWs(t[k]) && !(t[k] === "%" && t.slice(k, k + 3).toUpperCase() === "%26")) k++;
719
+ s.budget.tick(k - vStart + 1);
720
+ if (k > vStart && hasRawValueContent(s, vStart, k)) {
721
+ out.push({ index: cls2 === "url" ? vStart : i + 3, end: k });
722
+ i = k;
723
+ continue;
724
+ }
725
+ i = k > vStart ? k : j;
726
+ continue;
727
+ }
728
+ }
729
+ i++;
730
+ continue;
731
+ }
751
732
  if (c !== "?" && c !== "&" && c !== "#" && c !== ";") {
752
733
  i++;
753
734
  continue;
@@ -795,6 +776,15 @@ function httpHeadAt(t, i) {
795
776
  const head = t.slice(i, i + 8).toLowerCase();
796
777
  return head.startsWith("https://") ? 8 : head.startsWith("http://") ? 7 : 0;
797
778
  }
779
+ var SOCKS_HEADS = [...SOCKS_URI_SCHEMES].sort((x, y) => y.length - x.length).map((x) => `${x}://`);
780
+ function userinfoHeadAt(t, i) {
781
+ const h = httpHeadAt(t, i);
782
+ if (h) return h;
783
+ if (t[i] !== "s" && t[i] !== "S") return 0;
784
+ const head = t.slice(i, i + 10).toLowerCase();
785
+ for (const p of SOCKS_HEADS) if (head.startsWith(p)) return p.length;
786
+ return 0;
787
+ }
798
788
  function valueLimits(s, creds = urlCredentialSpans(s)) {
799
789
  const t = s.text;
800
790
  const ends = creds.filter((sp) => sp.end < s.n && (t[sp.end] === "&" || t[sp.end] === "#" || t[sp.end] === ";" || CJK_STOPS.includes(t[sp.end])));
@@ -894,12 +884,24 @@ function userinfoSpans(s) {
894
884
  s.unfinished = Math.min(s.unfinished, i);
895
885
  break;
896
886
  }
897
- const len = httpHeadAt(t, i);
887
+ const len = userinfoHeadAt(t, i);
898
888
  if (!len) {
899
889
  i++;
900
890
  continue;
901
891
  }
902
892
  const from = i + len;
893
+ if (!httpHeadAt(t, i)) {
894
+ let j2 = from;
895
+ let at2 = -1;
896
+ while (j2 < n && !isWs(t[j2]) && t[j2] !== "?" && t[j2] !== "#") {
897
+ if (t[j2] === "@") at2 = j2;
898
+ j2++;
899
+ }
900
+ s.budget.tick(j2 - i);
901
+ if (at2 > from && userinfoHasContent(s, from, at2)) out.push({ index: from, end: at2 });
902
+ i = j2 > from ? j2 : from;
903
+ continue;
904
+ }
903
905
  let j = from;
904
906
  let at = -1;
905
907
  while (j < n && !isWs(t[j]) && t[j] !== "/" && t[j] !== "?" && t[j] !== "#") {
@@ -1172,8 +1174,12 @@ var RULES = [
1172
1174
  ];
1173
1175
  var RULE_NAMES = RULES.map(([name]) => name);
1174
1176
  var TOKEN_ORDER = RULE_NAMES.indexOf("token");
1177
+ function foldFullwidth(text) {
1178
+ if (!/[\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF1D]/.test(text)) return text;
1179
+ return text.replace(/[\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF1D]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) - 65248));
1180
+ }
1175
1181
  function sensitiveMatches(text, opts = {}) {
1176
- const s = newScan(text, opts.budget);
1182
+ const s = newScan(foldFullwidth(text), opts.budget);
1177
1183
  const out = [];
1178
1184
  let exhaustedAt = -1;
1179
1185
  for (let order = 0; order < RULES.length; order++) {
@@ -1252,8 +1258,8 @@ var USAGE_RULES = [
1252
1258
  "",
1253
1259
  "1. \u8C03 `list_entries`\uFF0C`query` \u7528\u7528\u6237\u539F\u8BDD\uFF1B\u539F\u8BDD\u8D85\u8FC7 200 \u5B57\u65F6\uFF0C\u622A\u53D6\u6700\u80FD\u4EE3\u8868\u95EE\u9898\u7684\u4E00\u6BB5\u2014\u2014\u6709\u62A5\u9519\u539F\u6587\u5C31\u53D6\u62A5\u9519\u539F\u6587\u2014\u2014\u4E0D\u6539\u5199\u63AA\u8F9E\u3002\u8C03\u7528\u672C skill \u65F6\u4F20\u5165\u7684\u53C2\u6570\u662F\u4F60\u81EA\u5DF1\u7684\u8F6C\u8FF0\uFF0C\u4E0D\u80FD\u4EE3\u66FF\u7528\u6237\u539F\u8BDD\u3002\u7528\u6237\u9644\u4E86\u622A\u56FE\u3001\u4F60\u770B\u5F97\u5230\u65F6\uFF0C\u628A\u56FE\u91CC\u7684\u62A5\u9519\u539F\u6587\u9010\u5B57\u3001\u8FDE\u540C\u5BA2\u6237\u7AEF\u540D\u79F0\u63A5\u5728\u539F\u8BDD\u540E\u9762\u4E00\u8D77\u4F5C\u4E3A `query`\uFF1B\u8D85\u8FC7 200 \u5B57\u65F6\u4F18\u5148\u4FDD\u7559\u62A5\u9519\u539F\u6587\u3002",
1254
1260
  `2. \u5148\u5224\u65AD\u95EE\u9898\u662F\u5426\u5C5E\u4E8E\u7B2C 4 \u6B65\u300C\u76F8\u90BB\u4F46\u4E0D\u505A\u300D\u7684\u90A3\u51E0\u7C7B\u2014\u2014\u770B\u95EE\u9898\u8981\u7684\u7B54\u6848\u5728\u54EA\u4E00\u4FA7\uFF0C\u4E0D\u770B\u95EE\u9898\u91CC\u63D0\u5230\u4E86\u4EC0\u4E48\uFF1A\u8981\u7684\u662F\u5BA2\u6237\u7AEF\u600E\u4E48\u7528\u3001\u600E\u4E48\u8BBE\u7F6E\u3001\u600E\u4E48\u6392\u9519\uFF0C\u6216\u5BA2\u6237\u7AEF\u600E\u4E48\u53D6\u7528\u3001\u663E\u793A\u670D\u52A1\u65B9\u7ED9\u7684\u8BA2\u9605\u3001\u6D41\u91CF\u4FE1\u606F\u4E0E\u8282\u70B9\u51ED\u636E\uFF0C\u662F\u77E5\u8BC6\u5E93\u7684\u4E8B\uFF0C\u8D77\u56E0\u5728\u670D\u52A1\u5546\u6216\u670D\u52A1\u7AEF\u4E5F\u4E00\u6837\uFF1B\u8981\u7684\u662F\u670D\u52A1\u5546\uFF08\u542B\u5BBD\u5E26\u8FD0\u8425\u5546\uFF09\u81EA\u5DF1\u7684\u89C4\u5219\u6216\u51B3\u5B9A\uFF08\u5982\u5957\u9910\u3001\u8BA1\u8D39\u3001\u8D26\u53F7\u4E0E\u552E\u540E\uFF09\uFF0C\u6216\u53EA\u95EE\u5B83\u7684\u7EBF\u8DEF\u3001\u7F51\u7EDC\u662F\u4E0D\u662F\u51FA\u4E86\u6545\u969C\uFF0C\u6216\u670D\u52A1\u7AEF\u4E0E\u4E2D\u8F6C\u7EBF\u8DEF\u65B9\u6848\u672C\u8EAB\uFF08\u600E\u4E48\u642D\u5EFA\u3001\u6548\u679C\u4E0E\u53D6\u820D\u3001\u9009\u54EA\u79CD\uFF09\uFF0C\u5C31\u7B97\u76F8\u90BB\uFF0C\u95EE\u6CD5\u518D\u6280\u672F\u3001\u63D0\u5230\u7684\u5BA2\u6237\u7AEF\u6216\u73B0\u8C61\u518D\u5177\u4F53\u4E5F\u4E00\u6837\uFF1B\u5E26\u7740\u5BA2\u6237\u7AEF\u91CC\u7684\u4F7F\u7528\u5F02\u5E38\u95EE\u662F\u4E0D\u662F\u670D\u52A1\u5546\u6216\u7F51\u7EDC\u90A3\u8FB9\u51FA\u4E86\u95EE\u9898\u7684\uFF0C\u8981\u7684\u662F\u6392\u9519\uFF0C\u5F52\u77E5\u8BC6\u5E93\u3002\u662F\u5C31\u4E0D\u6311\u6761\u76EE\u3001\u4E0D\u8BFB\u6B63\u6587\uFF0C\u76F4\u63A5\u56DE\u300C${OUT_OF_SCOPE_REPLY}\u300D\uFF0C\u9664\u8FD9\u4E00\u53E5\u5916\u4E0D\u8F93\u51FA\u4EFB\u4F55\u6587\u5B57\uFF08\u7B2C 1 \u6B65\u7684 \`list_entries\` \u7167\u8C03\uFF09\u3002\u95EE\u9898\u542B\u7CCA\u2014\u2014\u4E0D\u8FFD\u95EE\u5C31\u4E0D\u77E5\u9053\u5177\u4F53\u95EE\u7684\u662F\u4EC0\u4E48\uFF0C\u4F8B\u5982\u53EA\u8BF4\u300C\u7528\u4E0D\u4E86\u4E86\u300D\uFF0C\u6216\u8BF4\u4E86\u300C\u6709\u4E00\u5904\u7A7A\u767D\u300D\u5374\u6CA1\u8BF4\u662F\u54EA\u4E00\u5904\uFF0C\u6216\u53EA\u8BF4\u62A5\u9519\u53CD\u590D\u5F39\u51FA\u5374\u6CA1\u7ED9\u51FA\u539F\u6587\u2014\u2014\u65F6\uFF0C\u4E0D\u6311\u6761\u76EE\u3001\u4E0D\u8BFB\u6B63\u6587\uFF0C\u53EA\u7528\u4E00\u53E5\u8BDD\u8FFD\u95EE\u7528\u7684\u662F\u4EC0\u4E48\u5BA2\u6237\u7AEF\u3001\u9047\u5230\u4E86\u4EC0\u4E48\u73B0\u8C61\u3001\u662F\u54EA\u4E00\u5904\u3002\u7528\u6237\u53EA\u8BF4\u9644\u4E86\u622A\u56FE\u3001\u6CA1\u5199\u51FA\u73B0\u8C61\u6216\u62A5\u9519\u539F\u6587\uFF0C\u800C\u4F60\u770B\u4E0D\u5230\u56FE\u65F6\uFF0C\u4E5F\u6309\u542B\u7CCA\u5904\u7406\uFF1A\u4E0D\u6311\u6761\u76EE\u3001\u4E0D\u8BFB\u6B63\u6587\uFF0C\u53EA\u7528\u4E00\u53E5\u8BDD\u8BF7\u7528\u6237\u628A\u56FE\u91CC\u7684\u62A5\u9519\u6587\u5B57\u590D\u5236\u8FC7\u6765\uFF0C\u4E0D\u95EE\u522B\u7684\uFF1B\u95EE\u9898\u672C\u8EAB\u5DF2\u5199\u6E05\u73B0\u8C61\u6216\u62A5\u9519\u7684\uFF0C\u770B\u4E0D\u5230\u56FE\u4E5F\u7167\u5E38\u5F80\u4E0B\u8D70\uFF0C\u4E0D\u7B97\u542B\u7CCA\u3002\u770B\u5F97\u5230\u622A\u56FE\u65F6\uFF0C\u6309\u56FE\u91CC\u7684\u73B0\u8C61\u5904\u7406\uFF0C\u4E0D\u7B97\u542B\u7CCA\uFF1B\u770B\u5F97\u51FA\u4E0E\u4EE3\u7406\u5BA2\u6237\u7AEF\u6709\u5173\u65F6\u7167\u5E38\u5148\u8C03 \`list_entries\`\uFF0C\u770B\u4E0D\u51FA\u65F6\u76F4\u63A5\u8FFD\u95EE\u3002\u5426\u5219\u4ECE\u76EE\u5F55\u6311 1\u20133 \u6761\u53EF\u80FD\u76F8\u5173\u7684\u6761\u76EE\uFF1B\u4E00\u6761\u90FD\u4E0D\u6CBE\u8FB9\u5C31\u8DF3\u5230\u7B2C 4 \u6B65\u3002`,
1255
- "3. \u7528 `get_entry` \u9010\u6761\u8BFB\u6B63\u6587\uFF1B\u8FD4\u56DE\u300C\u6761\u76EE\u4E0D\u5B58\u5728\u300D\u65F6\uFF0C\u91CD\u65B0\u8C03\u4E00\u6B21 `list_entries` \u53D6\u5F53\u524D\u76EE\u5F55\uFF0C\u4ECD\u6CA1\u6709\u5C31\u6309\u7B2C 4 \u6B65\u300C\u6CA1\u6709\u6761\u76EE\u80FD\u56DE\u7B54\u300D\u5904\u7406\u3002",
1256
- "4. \u56DE\u7B54\uFF08\u56DE\u7B54\u6B63\u6587\u4E0D\u5199\u6761\u76EE id\uFF0C\u4F9D\u636E\u653E\u5728\u672B\u5C3E\u7684\u6761\u76EE\u9875\u9762\u94FE\u63A5\u91CC\uFF1B\u5F15\u7528\u6761\u76EE\u65F6\uFF0C\u4F5C\u4E3A\u4F9D\u636E\u7684\u53EA\u80FD\u662F\u8BFB\u8FC7\u6B63\u6587\u7684\u6761\u76EE\uFF1B\u6761\u76EE\u6B63\u6587\u91CC\u63D0\u5230\u7684\u5176\u4ED6\u6761\u76EE\u53EF\u4EE5\u70B9\u540D\u4F9B\u7528\u6237\u5EF6\u4F38\u9605\u8BFB\uFF0C\u4F46\u4E0D\u5F53\u4F5C\u4F9D\u636E\uFF1B\u76EE\u5F55\u91CC\u770B\u5230\u4F46\u6CA1\u8BFB\u8FC7\u6B63\u6587\u7684\u6761\u76EE\u4E0D\u70B9\u540D\u3002\u8BFB\u8FC7\u6B63\u6587\u540E\u8FD8\u9700\u8981\u8FFD\u95EE\u7684\uFF0C\u4E5F\u5148\u6309\u4E0B\u9762\u7684\u4E09\u79CD\u56DE\u7B54\u4E4B\u4E00\u4F5C\u7B54\uFF0C\u518D\u8FFD\u95EE\uFF09\u3002\u56DE\u7B54\u8981\u77ED\uFF0C\u7EC6\u8282\u7B49\u7528\u6237\u8FFD\u95EE\u518D\u7ED9\u3002\u7B2C\u4E00\u6B21\u56DE\u7B54\u8981\u80FD\u4E00\u773C\u770B\u5B8C\uFF1A\u4E0D\u8D85\u8FC7 8 \u884C\u3001300 \u5B57\uFF08\u672B\u5C3E\u7684\u6761\u76EE\u94FE\u63A5\u4E0D\u7B97\uFF09\uFF0C\u6BCF\u884C\u53EA\u5199\u4E00\u53E5\u8BDD\uFF1B\u653E\u4E0D\u4E0B\u7684\u7559\u7ED9\u8FFD\u95EE\u3002\u5199\u6CD5\uFF1A\u7528\u81EA\u7136\u7684\u51E0\u6BB5\u8BDD\uFF0C\u4E0D\u52A0\u300C\u7ED3\u8BBA\u300D\u300C\u786E\u8BA4\u300D\u300C\u4F9D\u636E\u300D\u8FD9\u7C7B\u5C0F\u6807\u9898\u6216\u6807\u7B7E\uFF0C\u4E0D\u7528\u8868\u683C\u3001\u5D4C\u5957\u5217\u8868\u548C\u7C97\u4F53\uFF1B\u4E00\u53E5\u53EA\u8BF4\u4E00\u4EF6\u4E8B\uFF1B\u7528\u7948\u4F7F\u53E5\uFF1B\u540C\u4E00\u4E2A\u4E1C\u897F\u59CB\u7EC8\u7528\u540C\u4E00\u4E2A\u8BCD\uFF1B\u4E0D\u7528\u300C\u5927\u6982\u300D\u300C\u4E00\u4E9B\u300D\u8FD9\u7C7B\u542B\u7CCA\u7684\u8BCD\uFF1B\u6309\u94AE\u540D\u3001\u83DC\u5355\u540D\u3001\u62A5\u9519\u539F\u6587\u548C\u547D\u4EE4\u7167\u539F\u6837\u5199\uFF1B\u4E0D\u4E3B\u52A8\u8BB2\u539F\u7406\uFF0C\u7528\u6237\u95EE\u4E3A\u4EC0\u4E48\u518D\u8BB2\u3002\u7528\u4E86\u6761\u76EE\u5185\u5BB9\u7684\u56DE\u7B54\uFF08\u80FD\u5B8C\u6574\u56DE\u7B54\u3001\u53EA\u80FD\u90E8\u5206\u56DE\u7B54\uFF09\uFF0C\u77E5\u9053\u672C\u77E5\u8BC6\u5E93\u4E3B\u7AD9\u5730\u5740\u65F6\uFF08\u89C1\u672C skill \u7684\u300C\u56DE\u9000\u300D\u4E00\u8282\uFF09\uFF0C\u5FC5\u987B\u5728\u672B\u5C3E\u5355\u72EC\u4E00\u884C\u7ED9\u51FA\u7528\u5230\u7684\u6761\u76EE\u9875\u9762\u94FE\u63A5 `<\u4E3B\u7AD9>/<type>/<id>/`\uFF0C`<type>` \u53D6\u76EE\u5F55\u91CC\u7684\u6761\u76EE\u7C7B\u578B\uFF1B\u4E0D\u77E5\u9053\u5C31\u4E0D\u7ED9\u3002\u7528\u6237\u8FFD\u95EE\u7EC6\u8282\u65F6\uFF0C\u6309\u6761\u76EE\u7ED9\u51FA\u5B8C\u6574\u6B65\u9AA4\uFF0C\u4E0D\u53D7\u4E0B\u9762\u7684\u957F\u5EA6\u9650\u5236\uFF1A",
1261
+ "3. \u7528 `get_entry` \u9010\u6761\u8BFB\u6B63\u6587\uFF1B\u8FD4\u56DE\u91CC\u6CE8\u660E\u6761\u76EE\u5206\u4E86\u51E0\u6BB5\u65F6\uFF0C\u6309\u6BB5\u6807\u9898\u7528 `part` \u53C2\u6570\u518D\u8BFB\u4E0E\u95EE\u9898\u6709\u5173\u7684\u6BB5\u3002\u8FD4\u56DE\u300C\u6761\u76EE\u4E0D\u5B58\u5728\u300D\u65F6\uFF0C\u91CD\u65B0\u8C03\u4E00\u6B21 `list_entries` \u53D6\u5F53\u524D\u76EE\u5F55\uFF0C\u4ECD\u6CA1\u6709\u5C31\u6309\u7B2C 4 \u6B65\u300C\u6CA1\u6709\u6761\u76EE\u80FD\u56DE\u7B54\u300D\u5904\u7406\u3002",
1262
+ "4. \u56DE\u7B54\uFF08\u56DE\u7B54\u6B63\u6587\u4E0D\u5199\u6761\u76EE id\uFF0C\u4F9D\u636E\u653E\u5728\u672B\u5C3E\u7684\u6761\u76EE\u9875\u9762\u94FE\u63A5\u91CC\uFF1B\u5F15\u7528\u6761\u76EE\u65F6\uFF0C\u4F5C\u4E3A\u4F9D\u636E\u7684\u53EA\u80FD\u662F\u8BFB\u8FC7\u6B63\u6587\u7684\u6761\u76EE\uFF1B\u6761\u76EE\u6B63\u6587\u91CC\u63D0\u5230\u7684\u5176\u4ED6\u6761\u76EE\u53EF\u4EE5\u70B9\u540D\u4F9B\u7528\u6237\u5EF6\u4F38\u9605\u8BFB\uFF0C\u4F46\u4E0D\u5F53\u4F5C\u4F9D\u636E\uFF1B\u76EE\u5F55\u91CC\u770B\u5230\u4F46\u6CA1\u8BFB\u8FC7\u6B63\u6587\u7684\u6761\u76EE\u4E0D\u70B9\u540D\u3002\u8BFB\u8FC7\u6B63\u6587\u540E\u8FD8\u9700\u8981\u8FFD\u95EE\u7684\uFF0C\u4E5F\u5148\u6309\u4E0B\u9762\u7684\u4E09\u79CD\u56DE\u7B54\u4E4B\u4E00\u4F5C\u7B54\uFF0C\u518D\u8FFD\u95EE\uFF09\u3002\u56DE\u7B54\u8981\u77ED\uFF0C\u7EC6\u8282\u7B49\u7528\u6237\u8FFD\u95EE\u518D\u7ED9\u3002\u7B2C\u4E00\u6B21\u56DE\u7B54\u8981\u80FD\u4E00\u773C\u770B\u5B8C\uFF1A\u4E0D\u8D85\u8FC7 8 \u884C\u3001300 \u5B57\uFF08\u672B\u5C3E\u7684\u6761\u76EE\u94FE\u63A5\u4E0D\u7B97\uFF09\uFF0C\u6BCF\u884C\u53EA\u5199\u4E00\u53E5\u8BDD\uFF1B\u653E\u4E0D\u4E0B\u7684\u7559\u7ED9\u8FFD\u95EE\u3002\u5199\u6CD5\uFF1A\u7528\u81EA\u7136\u7684\u51E0\u6BB5\u8BDD\uFF0C\u4E0D\u52A0\u300C\u7ED3\u8BBA\u300D\u300C\u786E\u8BA4\u300D\u300C\u4F9D\u636E\u300D\u8FD9\u7C7B\u5C0F\u6807\u9898\u6216\u6807\u7B7E\uFF0C\u4E0D\u7528\u8868\u683C\u3001\u5D4C\u5957\u5217\u8868\u548C\u7C97\u4F53\uFF1B\u4E00\u53E5\u53EA\u8BF4\u4E00\u4EF6\u4E8B\uFF1B\u7528\u7948\u4F7F\u53E5\uFF1B\u540C\u4E00\u4E2A\u4E1C\u897F\u59CB\u7EC8\u7528\u540C\u4E00\u4E2A\u8BCD\uFF1B\u4E0D\u7528\u300C\u5927\u6982\u300D\u300C\u4E00\u4E9B\u300D\u8FD9\u7C7B\u542B\u7CCA\u7684\u8BCD\uFF1B\u6309\u94AE\u540D\u3001\u83DC\u5355\u540D\u3001\u62A5\u9519\u539F\u6587\u548C\u547D\u4EE4\u7167\u539F\u6837\u5199\uFF1B\u4E0D\u4E3B\u52A8\u8BB2\u539F\u7406\uFF0C\u7528\u6237\u95EE\u4E3A\u4EC0\u4E48\u518D\u8BB2\u3002\u7528\u4E86\u6761\u76EE\u5185\u5BB9\u7684\u56DE\u7B54\uFF08\u80FD\u5B8C\u6574\u56DE\u7B54\u3001\u53EA\u80FD\u90E8\u5206\u56DE\u7B54\uFF09\uFF0C`get_entry` \u8FD4\u56DE\u7684\u6B63\u6587\u7B2C\u4E00\u884C\u5199\u7740\u300C\u6761\u76EE\u9875\u9762\uFF1A\u300D\u5730\u5740\u65F6\uFF0C\u5FC5\u987B\u5728\u672B\u5C3E\u5355\u72EC\u4E00\u884C\u7ED9\u51FA\u7528\u5230\u7684\u6761\u76EE\u9875\u9762\u94FE\u63A5\uFF0C\u7167\u6284\u90A3\u4E2A\u5730\u5740\uFF1B\u6CA1\u6709\u8FD9\u4E00\u884C\u800C\u672C skill \u7684\u300C\u56DE\u9000\u300D\u4E00\u8282\u7ED9\u4E86\u4E3B\u7AD9\u5730\u5740\u65F6\uFF0C\u6309 `<\u4E3B\u7AD9>/<type>/<id>/` \u62FC\u51FA\uFF0C`<type>` \u53D6\u76EE\u5F55\u91CC\u7684\u6761\u76EE\u7C7B\u578B\uFF1B\u4E24\u6837\u90FD\u6CA1\u6709\u5C31\u4E0D\u7ED9\u3002\u7528\u6237\u8FFD\u95EE\u7EC6\u8282\u65F6\uFF0C\u6309\u6761\u76EE\u7ED9\u51FA\u5B8C\u6574\u6B65\u9AA4\uFF0C\u4E0D\u53D7\u4E0B\u9762\u7684\u957F\u5EA6\u9650\u5236\uFF1A",
1257
1263
  " - \u80FD\u5B8C\u6574\u56DE\u7B54\uFF1A\u8981\u52A8\u624B\u64CD\u4F5C\u7684\u95EE\u9898\uFF0C\u5148\u7528\u4E00\u53E5\u8BDD\u8BF4\u662F\u4EC0\u4E48\u60C5\u51B5\u3001\u8BE5\u505A\u4EC0\u4E48\uFF0C\u518D\u5217\u6700\u591A 3 \u6B65\uFF0C\u6BCF\u6B65\u4E00\u4E2A\u52A8\u4F5C\uFF0C\u7136\u540E\u7528\u4E00\u53E5\u8BDD\u8BF4\u505A\u5B8C\u540E\u770B\u5230\u4EC0\u4E48\u5C31\u8BF4\u660E\u597D\u4E86\uFF08\u53D6\u81EA\u6761\u76EE\u7684\u300C\u9A8C\u8BC1\u300D\uFF09\uFF1B\u95EE\u300C\u80FD\u4E0D\u80FD\u300D\u300C\u662F\u4E0D\u662F\u300D\u300C\u662F\u4EC0\u4E48\u300D\u7684\u95EE\u9898\uFF0C\u76F4\u63A5\u7528 2\u20133 \u53E5\u8BDD\u56DE\u7B54\uFF0C\u4E0D\u5217\u6B65\u9AA4\uFF0C\u4E5F\u4E0D\u5199\u600E\u4E48\u786E\u8BA4\u3002\u6761\u76EE\u6709\u591A\u4E2A\u5206\u652F\u65F6\uFF0C\u6309\u7528\u6237\u8BF4\u7684\u73B0\u8C61\u6311\u4E00\u652F\uFF1B\u7528\u6237\u6CA1\u8BF4\u73B0\u8C61\uFF0C\u6311\u6700\u5E38\u89C1\u7684\u4E00\u652F\uFF1B\u5176\u4F59\u5206\u652F\u4E0D\u5C55\u5F00\uFF0C\u53EA\u5728\u6700\u540E\u4E00\u884C\u70B9\u540D 2\u20133 \u79CD\u522B\u7684\u60C5\u51B5\u8BA9\u7528\u6237\u9009\uFF08\u4F8B\u5982\u300C\u5982\u679C\u4F60\u770B\u5230\u7684\u662F A \u6216 B\uFF0C\u544A\u8BC9\u6211\u662F\u54EA\u4E2A\u300D\uFF09\uFF1B\u6CA1\u6709\u522B\u7684\u5206\u652F\u65F6\uFF0C\u6700\u540E\u4E00\u53E5\u544A\u8BC9\u7528\u6237\u8FD8\u80FD\u600E\u4E48\u7EE7\u7EED\u3002\u4E0D\u5199\u7528\u6237\u6CA1\u95EE\u7684\u63D0\u9192\u548C\u6CE8\u610F\u4E8B\u9879\uFF1B",
1258
1264
  ` - \u53EA\u80FD\u90E8\u5206\u56DE\u7B54\uFF1A\u6761\u76EE\u5BF9\u8FD9\u4E2A\u95EE\u9898\u6709\u5B9E\u8D28\u5E2E\u52A9\uFF08\u7B54\u6848\u6216\u6392\u67E5\u6B65\u9AA4\u7684\u4E00\u90E8\u5206\uFF09\u3001\u4F46\u7B54\u4E0D\u5168\u2014\u2014\u56DE\u7B54\u91CC\u7528\u4E86\u6761\u76EE\u91CC\u7684\u6392\u67E5\u6B65\u9AA4\uFF0C\u5C31\u6309\u8FD9\u4E00\u79CD\u4F5C\u7B54\uFF1B\u6761\u76EE\u8986\u76D6\u7684\u90E8\u5206\u6309\u4E0A\u9762\u7684\u77ED\u7B54\u5199\uFF0C\u5E76\u4EE5\u56FA\u5B9A\u524D\u7F00\u300C${PARTIAL_PREFIX}\u300D\u7528\u4E00\u53E5\u8BDD\u5199\u51FA\u7F3A\u7684\u90E8\u5206\uFF08\u524D\u7F00\u5355\u72EC\u8D77\u4E00\u884C\u5199\u5728\u884C\u9996\uFF0C\u4E0D\u5199\u6210\u6807\u9898\uFF09\uFF1B\u7F3A\u7684\u90E8\u5206\u8981\u7528\u81EA\u5DF1\u7684\u77E5\u8BC6\u8865\uFF0C\u5C31\u5728\u524D\u7F00\u540E\u5148\u5199\u56FA\u5B9A\u6807\u6CE8\u300C${NOT_KB_LABEL}\u300D\u518D\u8865\uFF0C\u81EA\u5DF1\u8865\u7684\u53EA\u5199 1\u20132 \u53E5\uFF1B`,
1259
1265
  ` - \u6CA1\u6709\u6761\u76EE\u80FD\u56DE\u7B54\uFF08\u4E3B\u9898\u76F8\u8FD1\u3001\u5374\u7B54\u4E0D\u4E86\u7528\u6237\u5177\u4F53\u95EE\u9898\u7684\u6761\u76EE\u4E5F\u7B97\u6CA1\u6709\uFF09\uFF1A\u56DE\u7B54\u4EE5\u56FA\u5B9A\u6807\u6CE8\u300C${NOT_KB_LABEL}\u300D\u5F00\u5934\u2014\u2014\u8981\u81EA\u7B54\uFF0C\u5C31\u5728\u6807\u6CE8\u540E\u7528\u81EA\u5DF1\u7684\u77E5\u8BC6\u56DE\u7B54\uFF0C\u53EA\u5199 1\u20132 \u53E5\uFF0C\u5E76\u4E0E\u6761\u76EE\u5185\u5BB9\u5206\u5F00\uFF1B\u4E0D\u81EA\u7B54\uFF0C\u5C31\u5728\u6807\u6CE8\u540E\u8BF4\u660E\u77E5\u8BC6\u5E93\u6CA1\u6709\u8986\u76D6\uFF1B`,
@@ -1263,34 +1269,179 @@ var USAGE_RULES = [
1263
1269
  var SKILL_DESCRIPTION = `\u4E2D\u7ACB\u7684\u4EE3\u7406\u5BA2\u6237\u7AEF\u77E5\u8BC6\u5E93\u3002\u7528\u6237\u95EE Clash Verge Rev\u3001Clash Party\u3001FlClash\u3001v2rayN\u3001v2rayNG\u3001Clash Meta for Android\u3001sing-box\u3001Karing\u3001Shadowrocket \u7B49\u4EE3\u7406\u5BA2\u6237\u7AEF\u6216 mihomo\u3001Xray \u5185\u6838\u7684\u95EE\u9898\u65F6\u4F7F\u7528\uFF1A\u8BA2\u9605\u3001\u8282\u70B9\u3001\u534F\u8BAE\u3001\u5206\u6D41\u89C4\u5219\u3001TUN\uFF08\u865A\u62DF\u7F51\u5361\uFF09\u3001\u7CFB\u7EDF\u4EE3\u7406\u662F\u4EC0\u4E48\uFF0C\u5BA2\u6237\u7AEF\u4E0E\u5185\u6838\u7684\u7248\u672C\u3001\u66F4\u65B0\u3001\u4E0B\u8F7D\uFF08\u542B\u9884\u53D1\u5E03\u4E0E autobuild\uFF09\u600E\u4E48\u9009\uFF0C\u60F3\u5B8C\u6210\u5BFC\u5165\u8BA2\u9605\u3001\u5207\u6362\u8282\u70B9\u7B49\u64CD\u4F5C\uFF0C\u6216\u9047\u5230\u8282\u70B9\u4E3A\u7A7A\u3001\u8BA2\u9605\u66F4\u65B0\u5931\u8D25\u3001\u8FDE\u4E0A\u4F46\u6253\u4E0D\u5F00\u7F51\u9875\u7B49\u95EE\u9898\uFF08\u9644\u622A\u56FE\u6C42\u52A9\u4E5F\u7B97\uFF09\uFF1B\u7528\u6237\u95EE${ADJACENT_SCOPE.join("\u3001")}\u65F6\u540C\u6837\u4F7F\u7528\uFF08\u6309\u89C4\u5219\u53EA\u56DE\u4E00\u53E5\u56FA\u5B9A\u8BDD\uFF09\u3002\u53EA\u8BFB\uFF0C\u4E0D\u8BFB\u53D6\u7528\u6237\u673A\u5668\u72B6\u6001\uFF0C\u4E0D\u5199\u5165\u3002`;
1264
1270
 
1265
1271
  // ../runtime-core/src/mcp.ts
1272
+ function siteOriginOf(manifest) {
1273
+ const v = typeof manifest === "object" && manifest !== null ? manifest.site_origin : void 0;
1274
+ if (typeof v !== "string" || !/^https:\/\/[A-Za-z0-9.-]+(:[0-9]+)?$/.test(v)) return void 0;
1275
+ try {
1276
+ if (new URL(v).port === "0") return void 0;
1277
+ } catch {
1278
+ return void 0;
1279
+ }
1280
+ return v;
1281
+ }
1266
1282
  var ID = /^[a-z0-9]+(-[a-z0-9]+)*$/;
1267
1283
  var MAX_BYTES = 131072;
1268
1284
  var MAX_ID_UNITS = 256;
1269
1285
  function channelOf(v, cfg) {
1270
1286
  return typeof v === "string" && (cfg.channels ?? []).includes(v) ? v : null;
1271
1287
  }
1288
+ var LIST_ENTRIES_BUDGET = 38e3;
1272
1289
  var RESULT_SIZE_CHARS = 131072;
1273
- var LIST_ENTRIES_DESCRIPTION = `\u4ECE\u8FD9\u91CC\u5F00\u59CB\uFF1A\u8FD4\u56DE\u77E5\u8BC6\u5E93\u7684\u6761\u76EE\u76EE\u5F55\uFF08\u6BCF\u884C\u4E00\u6761\uFF1Aid | \u7C7B\u578B | \u6807\u9898 | \u522B\u540D\u2026\uFF09\uFF0C\u518D\u7528 get_entry \u8BFB\u6B63\u6587\u3002query \u586B\u7528\u6237\u539F\u8BDD\uFF081\u2013200 \u5B57\uFF09\u3002
1290
+ var LIST_ENTRIES_DESCRIPTION = `\u4ECE\u8FD9\u91CC\u5F00\u59CB\uFF1A\u8FD4\u56DE\u77E5\u8BC6\u5E93\u7684\u6761\u76EE\u76EE\u5F55\uFF08\u6BCF\u884C\u4E00\u6761\uFF1Aid | \u7C7B\u578B | \u6807\u9898\uFF0C\u4E0E query \u76F8\u5173\u7684\u6761\u76EE\u540E\u9762\u8FD8\u5217\u51FA\u522B\u540D\uFF09\uFF0C\u518D\u7528 get_entry \u8BFB\u6B63\u6587\u3002query \u586B\u7528\u6237\u539F\u8BDD\uFF081\u2013200 \u5B57\uFF09\u3002
1274
1291
 
1275
1292
  ${USAGE_RULES}`;
1276
1293
  var TOOLS = [
1277
- { name: "search", description: "\u6309\u7528\u6237\u539F\u8BDD\u68C0\u7D22\u4EE3\u7406\u5BA2\u6237\u7AEF\u77E5\u8BC6\u5E93\u6761\u76EE\u3002\u53EF\u9009 client\u3001os \u7F29\u5C0F\u8303\u56F4\u3002", inputSchema: { type: "object", properties: { query: { type: "string", minLength: 1, maxLength: 200 }, client: { type: "string" }, os: { type: "string" }, channel: { type: "string", maxLength: 32 } }, required: ["query"], additionalProperties: false } },
1278
1294
  { name: "list_entries", description: LIST_ENTRIES_DESCRIPTION, inputSchema: { type: "object", properties: { query: { type: "string", minLength: 1, maxLength: 200 } }, required: ["query"], additionalProperties: false } },
1279
- { name: "get_entry", description: "\u6309\u6761\u76EE id \u53D6\u56DE\u5B8C\u6574\u5185\u5BB9\uFF08Markdown\uFF09\u3002", inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"], additionalProperties: false } }
1295
+ { name: "get_entry", description: "\u6309\u6761\u76EE id \u53D6\u56DE\u5185\u5BB9\uFF08Markdown\uFF09\u3002\u957F\u6761\u76EE\u5206\u6BB5\u8FD4\u56DE\uFF1A\u6B63\u6587\u524D\u9762\u7684\u8BF4\u660E\u5199\u660E\u5171\u51E0\u6BB5\u548C\u5404\u6BB5\u6807\u9898\uFF0C\u7528 part \u53C2\u6570\u8BFB\u5176\u4F59\u5404\u6BB5\u3002", inputSchema: { type: "object", properties: { id: { type: "string" }, part: { type: "integer", minimum: 1 } }, required: ["id"], additionalProperties: false } }
1280
1296
  ];
1281
1297
  var MCP_TOOLS = ["list_entries", "get_entry"].map((n) => ({ ...TOOLS.find((t) => t.name === n), name: n, _meta: { "anthropic/maxResultSizeChars": RESULT_SIZE_CHARS } }));
1282
- function catalogOf(projection) {
1298
+ function aliasOrder(ids, index, query, t2s) {
1299
+ const score = /* @__PURE__ */ new Map();
1300
+ if (index.docs.length > 0) {
1301
+ const qt = [...new Set(tokens(query, index.config, t2s))];
1302
+ const n = index.docs.length;
1303
+ const df = /* @__PURE__ */ new Map();
1304
+ for (const d of index.docs) for (const t of new Set(Object.values(d.fields).flat())) df.set(t, (df.get(t) ?? 0) + 1);
1305
+ for (const d of index.docs) {
1306
+ let s = 0;
1307
+ for (const [f, toks] of Object.entries(d.fields)) {
1308
+ const w = index.config.weights[f] ?? 0;
1309
+ if (!w) continue;
1310
+ const set = new Set(toks);
1311
+ for (const t of qt) if (set.has(t)) s += w * Math.log(1 + n / (df.get(t) ?? 1));
1312
+ }
1313
+ score.set(d.id, s);
1314
+ }
1315
+ }
1316
+ return [...ids].sort((a, b) => (score.get(b) ?? 0) - (score.get(a) ?? 0) || (a < b ? -1 : a > b ? 1 : 0));
1317
+ }
1318
+ function catalogOf(projection, index, query, t2s) {
1283
1319
  const ids = Object.keys(projection.entries).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
1284
- return { content_version: projection.content_version, entries: ids.map((id) => {
1320
+ const entries = ids.map((id) => {
1285
1321
  const e = projection.entries[id];
1286
- return { id: e.id, type: e.type, title: e.title, aliases: [...e.aliases] };
1287
- }) };
1322
+ return { id: e.id, type: e.type, title: e.title };
1323
+ });
1324
+ const byId = new Map(entries.map((e) => [e.id, e]));
1325
+ const base = { content_version: projection.content_version, entries };
1326
+ let size = bytes(catalogText(base)) + bytes(JSON.stringify(base));
1327
+ for (const id of aliasOrder(ids, index, query, t2s)) {
1328
+ const e = byId.get(id);
1329
+ const aliases = projection.entries[id]?.aliases ?? [];
1330
+ if (!e || aliases.length === 0) continue;
1331
+ const extra = aliases.reduce((a, x) => a + bytes(" | " + field(x)), 0) + bytes(`,"aliases":${JSON.stringify(aliases)}`);
1332
+ if (size + extra > LIST_ENTRIES_BUDGET) break;
1333
+ e.aliases = [...aliases];
1334
+ size += extra;
1335
+ }
1336
+ return { content_version: projection.content_version, entries };
1288
1337
  }
1289
1338
  var field = (s) => s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r\n|\r|\n/g, " ");
1290
1339
  function catalogText(c) {
1291
- return [`content_version: ${c.content_version}`, ...c.entries.map((e) => [e.id, e.type, e.title, ...e.aliases].map(field).join(" | "))].join("\n");
1340
+ return [`content_version: ${c.content_version}`, ...c.entries.map((e) => [e.id, e.type, e.title, ...e.aliases ?? []].map(field).join(" | "))].join("\n");
1292
1341
  }
1293
1342
  var bytes = (s) => new TextEncoder().encode(s).length;
1343
+ var PART_BYTES = 17e3;
1344
+ var FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})/;
1345
+ var FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \t]*\r?\n?$/;
1346
+ function linesWithFence(s, start = null) {
1347
+ const lines = [];
1348
+ let fence = start;
1349
+ for (const line of s.match(/[^\n]*\n|[^\n]+$/g) ?? []) {
1350
+ lines.push({ line, inFence: fence !== null });
1351
+ if (fence === null) {
1352
+ const m = FENCE_OPEN.exec(line);
1353
+ if (m) fence = { ch: m[1][0], len: m[1].length };
1354
+ } else {
1355
+ const m = FENCE_CLOSE.exec(line);
1356
+ if (m && m[1][0] === fence.ch && m[1].length >= fence.len) fence = null;
1357
+ }
1358
+ }
1359
+ return { lines, end: fence };
1360
+ }
1361
+ function groupLines(s, isStart) {
1362
+ const ls = linesWithFence(s).lines;
1363
+ const out = [];
1364
+ ls.forEach((l, i) => {
1365
+ if (i === 0 || isStart(ls, i)) out.push(l.line);
1366
+ else out[out.length - 1] += l.line;
1367
+ });
1368
+ return out;
1369
+ }
1370
+ var cost = (s) => bytes(JSON.stringify(s)) - 2;
1371
+ function codePointChunks(s, limit) {
1372
+ const out = [];
1373
+ let cur = "";
1374
+ let n = 0;
1375
+ for (const cp of s) {
1376
+ const b = cost(cp);
1377
+ if (n + b > limit && cur) {
1378
+ out.push(cur);
1379
+ cur = "";
1380
+ n = 0;
1381
+ }
1382
+ cur += cp;
1383
+ n += b;
1384
+ }
1385
+ if (cur) out.push(cur);
1386
+ return out;
1387
+ }
1388
+ function pack(units, limit, split) {
1389
+ const out = [];
1390
+ let cur = "";
1391
+ let n = 0;
1392
+ const add = (u) => {
1393
+ const b = cost(u);
1394
+ if (n + b <= limit) {
1395
+ cur += u;
1396
+ n += b;
1397
+ return;
1398
+ }
1399
+ if (cur) out.push(cur);
1400
+ cur = u;
1401
+ n = b;
1402
+ };
1403
+ for (const u of units) {
1404
+ if (cost(u) <= limit) add(u);
1405
+ else for (const piece of split(u)) add(piece);
1406
+ }
1407
+ if (cur || out.length === 0) out.push(cur);
1408
+ return out;
1409
+ }
1410
+ function entryParts(markdown, limit) {
1411
+ const byLine = (u) => pack(groupLines(u, () => true), limit, (x) => codePointChunks(x, limit));
1412
+ const byParagraph = (u) => pack(groupLines(u, (ls, i) => !ls[i - 1].inFence && ls[i - 1].line.trim() === ""), limit, byLine);
1413
+ return pack(groupLines(markdown, (ls, i) => !ls[i].inFence && ls[i].line.startsWith("## ")), limit, byParagraph);
1414
+ }
1415
+ function partHeadings(segs) {
1416
+ const clip = (line) => {
1417
+ const cps = [...line];
1418
+ return cps.length > 40 ? cps.slice(0, 40).join("") + "\u2026" : line;
1419
+ };
1420
+ const { lines } = linesWithFence(segs.join(""));
1421
+ const starts = [];
1422
+ let off = 0;
1423
+ for (const l of lines) {
1424
+ starts.push(off);
1425
+ off += l.line.length;
1426
+ }
1427
+ let lastH2 = "";
1428
+ let segStart = 0;
1429
+ let i = 0;
1430
+ return segs.map((seg) => {
1431
+ const segEnd = segStart + seg.length;
1432
+ while (i < lines.length && starts[i] + lines[i].line.length <= segStart) i++;
1433
+ let heading;
1434
+ if (i < lines.length && starts[i] === segStart) {
1435
+ let k = i;
1436
+ while (k < lines.length && starts[k] < segEnd && lines[k].line.trim() === "") k++;
1437
+ if (k < lines.length && starts[k] < segEnd && !lines[k].inFence && /^#{1,2} /.test(lines[k].line)) heading = lines[k].line.trim();
1438
+ }
1439
+ const label = heading ?? (lastH2 ? `${lastH2}\uFF08\u7EED\uFF09` : seg.split("\n").find((l) => l.trim() !== "")?.trim() ?? "");
1440
+ for (let k = i; k < lines.length && starts[k] < segEnd; k++) if (starts[k] >= segStart && !lines[k].inFence && lines[k].line.startsWith("## ")) lastH2 = lines[k].line.trim();
1441
+ segStart = segEnd;
1442
+ return clip(label);
1443
+ });
1444
+ }
1294
1445
  function vocabulary(index) {
1295
1446
  const v = /* @__PURE__ */ new Set();
1296
1447
  for (const d of index.docs) for (const toks of Object.values(d.fields)) for (const t of toks) v.add(t);
@@ -1328,47 +1479,38 @@ async function handleToolCall(name, args, deps, measure = (r) => bytes(JSON.stri
1328
1479
  if (!tool) return { ok: false, code: -32602, message: "unknown tool" };
1329
1480
  const props = tool.inputSchema.properties;
1330
1481
  for (const k of Object.keys(a)) if (!(k in props)) return { ok: false, code: -32602, message: "unexpected argument" };
1482
+ const typeOk = (v, p) => p.type === "integer" ? Number.isInteger(v) && (p.minimum === void 0 || v >= p.minimum) : typeof v === p.type;
1331
1483
  for (const [k, v] of Object.entries(a)) {
1332
1484
  const p = props[k];
1333
- if (v !== void 0 && (typeof v !== p.type || p.maxLength !== void 0 && v.length > p.maxLength)) return { ok: false, code: -32602, message: `argument ${k} must be ${p.type}${p.maxLength ? ` \u2264${p.maxLength}` : ""}` };
1485
+ if (v !== void 0 && (!typeOk(v, p) || p.maxLength !== void 0 && v.length > p.maxLength)) return { ok: false, code: -32602, message: `argument ${k} must be ${p.type}${p.maxLength ? ` \u2264${p.maxLength}` : ""}${p.minimum !== void 0 ? ` \u2265${p.minimum}` : ""}` };
1334
1486
  }
1335
1487
  for (const k of tool.inputSchema.required) if (a[k] === void 0) return { ok: false, code: -32602, message: `missing argument ${k}` };
1336
- if (name === "search") {
1337
- const query = typeof a.query === "string" ? a.query : "";
1338
- if (query.length < 1 || query.length > 200) return { ok: false, code: -32602, message: "query must be 1..200 chars" };
1339
- const ctx = { client: typeof a.client === "string" ? a.client : void 0, os: typeof a.os === "string" ? a.os : void 0 };
1340
- const cleanQuery = queryText(query, deps.redact);
1341
- const r = search(deps.index, cleanQuery, ctx, deps.t2s);
1342
- const results = r.results.map((x) => ({ id: x.id, type: x.type, title: deps.projection.entries[x.id]?.title ?? x.id, score: Number(x.score.toFixed(4)) }));
1343
- const top = results[0] ? deps.projection.entries[results[0].id] : void 0;
1344
- await logQuery(deps, cleanQuery, { tool: "search", intent: top?.intents[0] ?? null, top_id: results[0]?.id ?? null, top_type: results[0]?.type ?? null, abstained: r.abstained ? 1 : 0, channel: channelOf(a.channel, deps.index.config) });
1345
- return { ok: true, result: { results, abstained: r.abstained, content_version: deps.index.content_version, sponsored: null } };
1346
- }
1347
1488
  if (name === "list_entries") {
1348
1489
  const query = typeof a.query === "string" ? a.query : "";
1349
1490
  if (query.length < 1 || query.length > 200) return { ok: false, code: -32602, message: "query must be 1..200 chars" };
1350
1491
  await logQuery(deps, queryText(query, deps.redact), { tool: "list_entries", intent: null, top_id: null, top_type: null, abstained: 0, channel: channelOf(deps.channel, deps.index.config) });
1351
- return { ok: true, result: catalogOf(deps.projection) };
1492
+ return { ok: true, result: catalogOf(deps.projection, deps.index, query, deps.t2s) };
1352
1493
  }
1353
1494
  if (name === "get_entry") {
1354
1495
  const id = typeof a.id === "string" ? a.id : "";
1355
1496
  if (!ID.test(id)) return { ok: false, code: -32602, message: "bad id" };
1356
1497
  const e = Object.hasOwn(deps.projection.entries, id) ? deps.projection.entries[id] : void 0;
1357
1498
  if (!e) return { ok: false, code: -32004, message: "entry not found" };
1358
- const make = (md, truncated) => ({ id: e.id, type: e.type, title: e.title, markdown: md, truncated, last_verified_at: e.last_verified_at, freshness: e.freshness, content_version: deps.projection.content_version, sponsored: null });
1359
- if (measure(make("", true)) > MAX_BYTES) return { ok: false, code: -32603, message: "entry too large" };
1360
- let result = make(e.markdown, false);
1361
- if (measure(result) > MAX_BYTES) {
1362
- const cps = [...e.markdown];
1363
- if (cps.length === 0) return { ok: false, code: -32603, message: "entry too large" };
1364
- let lo = 0, hi = cps.length - 1;
1365
- while (lo < hi) {
1366
- const mid = Math.ceil((lo + hi) / 2);
1367
- if (measure(make(cps.slice(0, mid).join(""), true)) <= MAX_BYTES) lo = mid;
1368
- else hi = mid - 1;
1369
- }
1370
- result = make(cps.slice(0, lo).join(""), true);
1371
- }
1499
+ const segs = entryParts(e.markdown, PART_BYTES);
1500
+ const part = typeof a.part === "number" ? a.part : 1;
1501
+ if (part > segs.length) return { ok: false, code: -32602, message: "part out of range" };
1502
+ const headings = partHeadings(segs);
1503
+ const others = headings.map((h, i) => `${i + 1}\u300C${h}\u300D`).filter((_, i) => i + 1 !== part).join("\u3001");
1504
+ const url = deps.siteOrigin ? `${deps.siteOrigin}/${e.type}/${e.id}/` : void 0;
1505
+ const freshness = freshnessAt(e, deps.now?.() ?? /* @__PURE__ */ new Date());
1506
+ const staleNote = freshness === "stale" ? `\uFF08\u8FD9\u6761\u5185\u5BB9\u6700\u540E\u9A8C\u8BC1\u4E8E ${e.last_verified_at}\uFF0C\u5DF2\u8D85\u8FC7\u6709\u6548\u671F\uFF0C\u53EF\u80FD\u5DF2\u8FC7\u671F\u3002\uFF09` : "";
1507
+ const lead = [url ? `\u6761\u76EE\u9875\u9762\uFF1A${url}` : "", staleNote, segs.length === 1 ? "" : `\uFF08\u672C\u6761\u76EE\u5206 ${segs.length} \u6BB5\uFF0C\u8FD9\u662F\u7B2C ${part} \u6BB5\u3002\u5176\u4F59\u5404\u6BB5\uFF1A${others}\uFF1B\u7528 get_entry \u7684 part \u53C2\u6570\u8BFB\u3002\uFF09`].filter(Boolean).join("\n");
1508
+ const md0 = lead ? `${lead}
1509
+
1510
+ ${segs[part - 1]}` : segs[part - 1];
1511
+ const make = (md, truncated) => ({ id: e.id, type: e.type, title: e.title, ...url ? { url } : {}, markdown: md, truncated, part, parts: segs.length, part_headings: headings, last_verified_at: e.last_verified_at, freshness, content_version: deps.projection.content_version, sponsored: null });
1512
+ const result = make(md0, false);
1513
+ if (measure(result) > MAX_BYTES) return { ok: false, code: -32603, message: "entry too large" };
1372
1514
  return { ok: true, result };
1373
1515
  }
1374
1516
  return { ok: false, code: -32602, message: "unknown tool" };
@@ -1391,7 +1533,7 @@ function metaVersion(m) {
1391
1533
  return m.params._meta[PV];
1392
1534
  }
1393
1535
  function acceptedId(rawId) {
1394
- if (rawId !== void 0 && rawId !== null && typeof rawId !== "number" && (typeof rawId !== "string" || rawId.length > MAX_ID_UNITS || credentialRules(rawId).length > 0)) return false;
1536
+ if (rawId !== void 0 && rawId !== null && typeof rawId !== "number" && (typeof rawId !== "string" || rawId.length > MAX_ID_UNITS || credentialRules(rawId).some((r) => r !== "uuid"))) return false;
1395
1537
  return rawId;
1396
1538
  }
1397
1539
  function modernMetaError(meta) {
@@ -1486,12 +1628,14 @@ async function loadDepsFromDir(dir, extra) {
1486
1628
  if (!want || want.bytes !== buf.length || want.sha256 !== createHash("sha256").update(buf).digest("hex")) throw new Error(`${f} does not match release-manifest.json in ${dir}`);
1487
1629
  return JSON.parse(buf.toString("utf8"));
1488
1630
  }));
1489
- return { index, projection, t2s: makeTableT2s(table), salt: extra.salt, sha256: extra.sha256, redact: redactSensitive };
1631
+ const siteOrigin = siteOriginOf(manifest);
1632
+ return { index, projection, t2s: makeTableT2s(table), salt: extra.salt, sha256: extra.sha256, redact: redactSensitive, ...siteOrigin ? { siteOrigin } : {} };
1490
1633
  }
1491
1634
  var TOO_LARGE = Symbol("request too large");
1635
+ var BAD_UTF8 = Symbol("invalid utf-8");
1492
1636
  async function* boundedLines(input, maxBytes = MAX_JSON_BODY_BYTES) {
1493
1637
  const enc = new TextEncoder();
1494
- const dec = new TextDecoder();
1638
+ const dec = new TextDecoder("utf-8", { fatal: true });
1495
1639
  let parts = [];
1496
1640
  let size = 0;
1497
1641
  let skipping = false;
@@ -1504,7 +1648,12 @@ async function* boundedLines(input, maxBytes = MAX_JSON_BODY_BYTES) {
1504
1648
  }
1505
1649
  parts = [];
1506
1650
  size = 0;
1507
- const line = dec.decode(buf);
1651
+ let line;
1652
+ try {
1653
+ line = dec.decode(buf);
1654
+ } catch {
1655
+ return BAD_UTF8;
1656
+ }
1508
1657
  return line.endsWith("\r") ? line.slice(0, -1) : line;
1509
1658
  };
1510
1659
  for await (const raw of input) {
@@ -1541,13 +1690,18 @@ async function createStdioServer(deps, io) {
1541
1690
  await io.write(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32600, message: "request too large" } }));
1542
1691
  continue;
1543
1692
  }
1693
+ const parseError = JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "parse error" } });
1694
+ if (line === BAD_UTF8) {
1695
+ await io.write(parseError);
1696
+ continue;
1697
+ }
1544
1698
  const trimmed = line.trim();
1545
1699
  if (!trimmed) continue;
1546
1700
  let msg;
1547
1701
  try {
1548
1702
  msg = JSON.parse(trimmed);
1549
1703
  } catch {
1550
- await io.write(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "parse error" } }));
1704
+ await io.write(parseError);
1551
1705
  continue;
1552
1706
  }
1553
1707
  const r = await handleJsonRpc(msg, deps);
@@ -1555,45 +1709,6 @@ async function createStdioServer(deps, io) {
1555
1709
  }
1556
1710
  }
1557
1711
 
1558
- // src/static.ts
1559
- import http from "node:http";
1560
- import fs2 from "node:fs/promises";
1561
- import path2 from "node:path";
1562
- var TYPES = { ".html": "text/html; charset=utf-8", ".js": "text/javascript", ".css": "text/css", ".json": "application/json", ".svg": "image/svg+xml", ".png": "image/png", ".webp": "image/webp", ".mp4": "video/mp4", ".webm": "video/webm", ".txt": "text/plain; charset=utf-8" };
1563
- function createStaticServer(root) {
1564
- return http.createServer(async (req, res) => {
1565
- let p;
1566
- try {
1567
- p = decodeURIComponent((req.url ?? "/").split("?")[0]);
1568
- } catch {
1569
- res.writeHead(400);
1570
- res.end("bad request");
1571
- return;
1572
- }
1573
- if (p.endsWith("/")) p += "index.html";
1574
- const candidate = path2.resolve(root, `.${p}`);
1575
- if (candidate !== root && !candidate.startsWith(root + path2.sep)) {
1576
- res.writeHead(403);
1577
- res.end();
1578
- return;
1579
- }
1580
- const file = await fs2.realpath(candidate).catch(() => null);
1581
- if (!file || file !== root && !file.startsWith(root + path2.sep)) {
1582
- res.writeHead(404);
1583
- res.end("not found");
1584
- return;
1585
- }
1586
- try {
1587
- const data = await fs2.readFile(file);
1588
- res.writeHead(200, { "content-type": TYPES[path2.extname(file)] ?? "application/octet-stream" });
1589
- res.end(data);
1590
- } catch {
1591
- res.writeHead(404);
1592
- res.end("not found");
1593
- }
1594
- });
1595
- }
1596
-
1597
1712
  // src/exit.ts
1598
1713
  var FALLBACK = "\u52A0\u8F7D\u6216\u8FD0\u884C\u5931\u8D25\uFF08\u8BE6\u60C5\u4E0D\u56DE\u663E\uFF09";
1599
1714
  var FLUSH_TIMEOUT_MS = 2e3;
@@ -1646,8 +1761,8 @@ var fail = makeFail({ error: (m) => console.error(m), exit: (c) => process.exit(
1646
1761
  installProcessHandlers(process, fail);
1647
1762
  async function run() {
1648
1763
  if (cmd === "mcp") {
1649
- const defaultAssets = path3.join(path3.dirname(fileURLToPath(import.meta.url)), "..", "snapshot");
1650
- const deps = await loadDepsFromDir(path3.resolve(opt("assets", defaultAssets)), { salt: "local", sha256: async (s) => createHash2("sha256").update(s).digest("hex") });
1764
+ const defaultAssets = path2.join(path2.dirname(fileURLToPath(import.meta.url)), "..", "snapshot");
1765
+ const deps = await loadDepsFromDir(path2.resolve(opt("assets", defaultAssets)), { salt: "local", sha256: async (s) => createHash2("sha256").update(s).digest("hex") });
1651
1766
  const it = boundedLines(process.stdin)[Symbol.asyncIterator]();
1652
1767
  await createStdioServer(deps, { readLine: async () => {
1653
1768
  const n = await it.next();
@@ -1667,12 +1782,8 @@ async function run() {
1667
1782
  process.stdout.once("error", onError);
1668
1783
  }
1669
1784
  }) });
1670
- } else if (cmd === "open") {
1671
- const root = await fs3.realpath(path3.resolve(opt("dir", "./site/dist")));
1672
- const port = Number(opt("port", "4321"));
1673
- createStaticServer(root).on("error", fail).listen(port, "127.0.0.1", () => console.log(`http://127.0.0.1:${port}/`));
1674
1785
  } else {
1675
- console.log("usage: hub-kb mcp [--assets <dir>, default: bundled snapshot/] | hub-kb open [--dir ./site/dist] [--port 4321]");
1786
+ console.log("usage: hub-kb mcp [--assets <dir>, default: bundled snapshot/]");
1676
1787
  process.exit(1);
1677
1788
  }
1678
1789
  }