exomind 0.15.0 → 0.16.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/cli.js CHANGED
@@ -3119,7 +3119,7 @@ var {
3119
3119
  // package.json
3120
3120
  var package_default = {
3121
3121
  name: "exomind",
3122
- version: "0.15.0",
3122
+ version: "0.16.0",
3123
3123
  description: "ExoMind \u8DE8\u5E73\u53F0\u547D\u4EE4\u884C\u5BA2\u6237\u7AEF \u2014 \u901A\u8FC7 REST \u4E0E ExoMind \u77E5\u8BC6\u98DE\u8F6E\u4EA4\u4E92(ingest/query/search/review),\u66FF\u4EE3 Windows \u4E0D\u53EF\u7528\u7684 MCP \u5BA2\u6237\u7AEF\u3002",
3124
3124
  bin: {
3125
3125
  exomind: "dist/cli.js"
@@ -3302,6 +3302,12 @@ function saveConfig(cfg) {
3302
3302
  } catch {
3303
3303
  }
3304
3304
  }
3305
+ function invalidateKeywordCache() {
3306
+ try {
3307
+ fs.unlinkSync(CACHE_KEYWORDS);
3308
+ } catch {
3309
+ }
3310
+ }
3305
3311
  function resolveConfig(overrides) {
3306
3312
  const base = loadConfig();
3307
3313
  return {
@@ -3632,13 +3638,27 @@ async function getEntityDesc(client, name, deadline = Number.POSITIVE_INFINITY)
3632
3638
  return { name, description: "" };
3633
3639
  }
3634
3640
  }
3641
+ var SHORT_ASCII = /^[a-z0-9._#+-]{2,4}$/;
3642
+ function escapeRegExp(s) {
3643
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3644
+ }
3645
+ function hitsPrompt(promptLower, candLower) {
3646
+ if (SHORT_ASCII.test(candLower)) {
3647
+ try {
3648
+ return new RegExp(`(?<![a-z0-9])${escapeRegExp(candLower)}(?![a-z0-9])`).test(promptLower);
3649
+ } catch {
3650
+ return promptLower.includes(candLower);
3651
+ }
3652
+ }
3653
+ return promptLower.includes(candLower);
3654
+ }
3635
3655
  function matchEntities(prompt2, candidates) {
3636
3656
  const lower = prompt2.toLowerCase();
3637
3657
  const hits = /* @__PURE__ */ new Set();
3638
3658
  for (const name of candidates) {
3639
3659
  const nl = name.toLowerCase().trim();
3640
3660
  if (nl.length < 2) continue;
3641
- if (lower.includes(nl)) hits.add(name);
3661
+ if (hitsPrompt(lower, nl)) hits.add(name);
3642
3662
  }
3643
3663
  return [...hits].sort((a, b) => b.length - a.length);
3644
3664
  }
@@ -3664,22 +3684,39 @@ ${desc}
3664
3684
  }
3665
3685
  async function buildContext(client, msg, dedup, now, deadline = Number.POSITIVE_INFINITY) {
3666
3686
  const index = await getKeywordIndex(client, deadline);
3667
- if (!index) return "";
3687
+ if (!index) {
3688
+ return '[ExoMind] \u77E5\u8BC6\u98DE\u8F6E\u4E0A\u4E0B\u6587\u672C\u6B21\u672A\u6CE8\u5165(\u5173\u952E\u8BCD\u8868\u62C9\u53D6\u8D85\u65F6\u6216\u5931\u8D25);\u9700\u8981\u53CD\u67E5\u53EF\u8FD0\u884C `exomind search "\u5173\u952E\u8BCD"`\u3002';
3689
+ }
3668
3690
  const candidates = [...index.names || [], ...index.aliases || []];
3669
3691
  const matched = matchEntities(msg, candidates);
3670
3692
  if (!matched.length) return "";
3671
3693
  const picked = [];
3694
+ const pickedCanonical = /* @__PURE__ */ new Set();
3695
+ const cooled = [];
3672
3696
  for (const name of matched) {
3673
3697
  if (picked.length >= 3) break;
3674
3698
  if (budgetMs(deadline) <= 200) break;
3675
3699
  const canonical = name;
3676
- if (dedup.injected[canonical] && now - dedup.injected[canonical] < COOLDOWN_MS) continue;
3700
+ if (dedup.injected[canonical] && now - dedup.injected[canonical] < COOLDOWN_MS) {
3701
+ cooled.push(name);
3702
+ continue;
3703
+ }
3677
3704
  const desc = await getEntityDesc(client, name, deadline);
3678
3705
  if (desc.description || desc.relationships?.length) {
3679
- picked.push(desc);
3680
3706
  dedup.injected[canonical] = now;
3707
+ const norm = (desc.name || name).toLowerCase();
3708
+ if (pickedCanonical.has(norm)) continue;
3709
+ pickedCanonical.add(norm);
3710
+ picked.push(desc);
3681
3711
  }
3682
3712
  }
3713
+ if (!picked.length && cooled.length) {
3714
+ const remainMin = Math.ceil(
3715
+ (COOLDOWN_MS - (now - Math.max(...cooled.map((n) => dedup.injected[n] ?? 0)))) / 6e4
3716
+ );
3717
+ const sample = cooled.slice(0, 2).map((n) => `"${n}"`).join("\u3001");
3718
+ return `[ExoMind] ${sample} \u7B49\u5173\u952E\u8BCD\u7684\u4E0A\u4E0B\u6587\u5DF2\u6CE8\u5165\u8FC7,30 \u5206\u949F\u53BB\u91CD\u51B7\u5374\u8FD8\u5269 ${remainMin} \u5206\u949F,\u672C\u6B21\u8DF3\u8FC7;\u9700\u8981\u8BE6\u60C5\u53EF\u8FD0\u884C \`exomind entity "${cooled[0]}"\`\u3002`;
3719
+ }
3683
3720
  return contextBlock(picked);
3684
3721
  }
3685
3722
  async function runHook(client) {
@@ -4070,7 +4107,9 @@ async function ingestWithRetry(client, payload, timeoutMs, url = "/ingest") {
4070
4107
  let transientAttempts = 0;
4071
4108
  while (true) {
4072
4109
  try {
4073
- return await client.post(url, payload, { timeoutMs });
4110
+ const res = await client.post(url, payload, { timeoutMs });
4111
+ invalidateKeywordCache();
4112
+ return res;
4074
4113
  } catch (e) {
4075
4114
  if (!(e instanceof ApiError)) throw e;
4076
4115
  if (e.status === 429) {