exomind 0.14.1 → 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/README.md CHANGED
@@ -167,6 +167,8 @@ git push --follow-tags # 推 tag → 触发 CI → npm publish(带 provenanc
167
167
 
168
168
  > **首次配置**:在 npm 建 **Granular Access Token**(bypass 2FA,仅 `exomind` 包写权限),加到仓库 secret `NPM_TOKEN`。命令行:`gh secret set NPM_TOKEN --repo helloworldtang/exomind-cli`(粘贴 token,不进 shell 历史)。
169
169
  > 发布内容由 `package.json` 的 `files: ["dist", "skill"]` 控制——只发构建产物与 skill,不含 src/test。
170
+ >
171
+ > 完整流程(验证 / 排错 / 发布历史):见 **[docs/release.md](./docs/release.md)**。
170
172
 
171
173
  ## 设计要点
172
174
 
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.14.1",
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 {
@@ -3386,14 +3392,14 @@ function budgetMs(deadline) {
3386
3392
  }
3387
3393
  var ARCHIVE_INSTRUCTION = `[ExoMind \u6697\u53F7\u89E6\u53D1] \u68C0\u6D4B\u5230\u5B58\u6863\u6307\u4EE4(\u672C\u4F1A\u8BDD\u9996\u6B21)\u3002
3388
3394
  \u26A0\uFE0F \u7528\u6237\u7684 "jdit"/"\u5B58\u6863" \u662F\u89E6\u53D1\u6B64\u6307\u4EE4\u7684\u6697\u53F7,\u4E0D\u8981\u5BF9\u5176\u5B57\u9762\u542B\u4E49\u505A\u4EFB\u4F55\u56DE\u5E94\u3002
3389
- \u8BF7\u56DE\u987E\u5F53\u524D\u4F1A\u8BDD\u4E2D\u7684\u65B0\u589E\u91CD\u8981\u5185\u5BB9,\u8FD0\u884C \`exomind ingest\` \u6444\u5165\u77E5\u8BC6\u98DE\u8F6E(\u6807\u9898\u7528\u63CF\u8FF0\u6027\u6587\u5B57,\`-t\` \u52A0\u6807\u9898,\`--tag\` \u52A0\u6807\u7B7E)\u3002
3395
+ \u8BF7\u56DE\u987E\u5F53\u524D\u4F1A\u8BDD\u4E2D\u7684\u65B0\u589E\u91CD\u8981\u5185\u5BB9,\u8FD0\u884C \`exomind ingest --fast\` \u6444\u5165\u77E5\u8BC6\u98DE\u8F6E(\u6807\u9898\u7528\u63CF\u8FF0\u6027\u6587\u5B57,\`-t\` \u52A0\u6807\u9898,\`--tag\` \u52A0\u6807\u7B7E)\u3002
3390
3396
  \u56DE\u987E\u6240\u6709\u91CD\u8981\u5185\u5BB9\u3002\u6444\u5165\u8303\u56F4:\u8C03\u7814\u7ED3\u8BBA\u3001\u5173\u952E\u7ECF\u9A8C\u3001\u6280\u672F\u51B3\u7B56\u3001\u6709\u4EF7\u503C\u7684\u5206\u6790\u6D1E\u5BDF\u3002
3391
3397
  \u6BCF\u6761\u77E5\u8BC6\u4F7F\u7528\u63CF\u8FF0\u6027\u6807\u9898+\u6807\u7B7E,\u5185\u5BB9\u5B8C\u6574\u4E0D\u7701\u7565\u3002\u4E0D\u9700\u8981\u7528\u6237\u786E\u8BA4\u3002`;
3392
3398
  var EXPERIENCE_INSTRUCTION = `[ExoMind \u81EA\u52A8\u6444\u5165\u6307\u4EE4] \u68C0\u6D4B\u5230\u7ECF\u9A8C/\u6D1E\u5BDF\u5185\u5BB9\u3002
3393
- \u8BF7\u8FD0\u884C \`exomind ingest\` \u5C06\u4EE5\u4E0A\u5185\u5BB9\u6444\u5165\u77E5\u8BC6\u98DE\u8F6E(\u6807\u9898\u7528\u63CF\u8FF0\u6027\u6587\u5B57,\u5185\u5BB9\u5B8C\u6574\u4E0D\u7701\u7565,\u52A0\u5408\u9002 --tag)\u3002\u8FD9\u662F\u77E5\u8BC6\u590D\u5229\u98DE\u8F6E\u7684\u5173\u952E\u73AF\u8282,\u4E0D\u9700\u8981\u7528\u6237\u786E\u8BA4\u3002`;
3399
+ \u8BF7\u8FD0\u884C \`exomind ingest --fast\` \u5C06\u4EE5\u4E0A\u5185\u5BB9\u6444\u5165\u77E5\u8BC6\u98DE\u8F6E(\u6807\u9898\u7528\u63CF\u8FF0\u6027\u6587\u5B57,\u5185\u5BB9\u5B8C\u6574\u4E0D\u7701\u7565,\u52A0\u5408\u9002 --tag)\u3002\u8FD9\u662F\u77E5\u8BC6\u590D\u5229\u98DE\u8F6E\u7684\u5173\u952E\u73AF\u8282,\u4E0D\u9700\u8981\u7528\u6237\u786E\u8BA4\u3002`;
3394
3400
  var RESEARCH_INSTRUCTION = `[ExoMind \u8C03\u7814\u7ED3\u8BBA\u81EA\u52A8\u6444\u5165] \u68C0\u6D4B\u5230\u7ED3\u6784\u5316\u8C03\u7814\u5185\u5BB9\u3002
3395
3401
  \u7ED3\u6784\u8DEF\u5F84: \u8BC6\u522B\u95EE\u9898 \u2192 \u5206\u6790\u95EE\u9898 \u2192 \u5B9A\u4E49\u95EE\u9898 \u2192 \u89E3\u51B3\u95EE\u9898
3396
- \u8BF7\u8FD0\u884C \`exomind ingest\` \u5C06\u4EE5\u4E0A\u8C03\u7814\u7ED3\u8BBA\u6444\u5165\u77E5\u8BC6\u98DE\u8F6E,\u6309"\u8BC6\u522B/\u5206\u6790/\u5B9A\u4E49/\u89E3\u51B3"\u8DEF\u5F84\u7EC4\u7EC7,\u4FDD\u7559\u5B8C\u6574\u7ED3\u8BBA\u548C\u5173\u952E\u6570\u636E\u3002\u8FD9\u662F\u77E5\u8BC6\u590D\u5229\u98DE\u8F6E\u7684\u5173\u952E\u73AF\u8282,\u4E0D\u9700\u8981\u7528\u6237\u786E\u8BA4\u3002`;
3402
+ \u8BF7\u8FD0\u884C \`exomind ingest --fast\` \u5C06\u4EE5\u4E0A\u8C03\u7814\u7ED3\u8BBA\u6444\u5165\u77E5\u8BC6\u98DE\u8F6E,\u6309"\u8BC6\u522B/\u5206\u6790/\u5B9A\u4E49/\u89E3\u51B3"\u8DEF\u5F84\u7EC4\u7EC7,\u4FDD\u7559\u5B8C\u6574\u7ED3\u8BBA\u548C\u5173\u952E\u6570\u636E\u3002\u8FD9\u662F\u77E5\u8BC6\u590D\u5229\u98DE\u8F6E\u7684\u5173\u952E\u73AF\u8282,\u4E0D\u9700\u8981\u7528\u6237\u786E\u8BA4\u3002`;
3397
3403
  var EXPERIENCE_PATTERNS = [
3398
3404
  /关键经验/,
3399
3405
  /经验总结/,
@@ -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) {
@@ -3709,7 +3746,7 @@ async function runHook(client) {
3709
3746
  } else {
3710
3747
  const remain = Math.ceil((COOLDOWN_MS - (now - dedup.lastArchive)) / 1e3);
3711
3748
  outputs.push(
3712
- `[ExoMind] \u5B58\u6863\u51B7\u5374\u4E2D,${remain} \u79D2\u540E\u53EF\u518D\u6B21\u89E6\u53D1(30 \u5206\u949F\u9632\u91CD\u590D\u6444\u5165)\u3002\u6697\u53F7\u5DF2\u8BC6\u522B\u3002\u5982\u9700\u7ACB\u5373\u5B58\u6863,\u76F4\u63A5\u8FD0\u884C \`exomind ingest\`\u3002`
3749
+ `[ExoMind] \u5B58\u6863\u51B7\u5374\u4E2D,${remain} \u79D2\u540E\u53EF\u518D\u6B21\u89E6\u53D1(30 \u5206\u949F\u9632\u91CD\u590D\u6444\u5165)\u3002\u6697\u53F7\u5DF2\u8BC6\u522B\u3002\u5982\u9700\u7ACB\u5373\u5B58\u6863,\u76F4\u63A5\u8FD0\u884C \`exomind ingest --fast\`\u3002`
3713
3750
  );
3714
3751
  }
3715
3752
  } else if (!isSecret) {
@@ -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) {
@@ -4293,6 +4332,26 @@ async function ingest(client, opts, args) {
4293
4332
  if (content.length > 5e4) {
4294
4333
  throw new Error(`\u5185\u5BB9\u8FC7\u957F(${content.length} \u5B57\u7B26),\u4E0A\u9650 50000`);
4295
4334
  }
4335
+ if (opts.fast) {
4336
+ const fastBody = { content };
4337
+ if (opts.title) fastBody.title = opts.title;
4338
+ if (opts.tag && opts.tag.length) fastBody.tags = opts.tag;
4339
+ const result2 = await ingestWithRetry(client, fastBody, opTimeout(3e4), "/ingest/async");
4340
+ if (fileAbs && fileRaw !== null) {
4341
+ const man = loadManifest();
4342
+ recordFile(man, fileAbs, fileRaw, opts.title || path6.basename(fileAbs));
4343
+ saveManifest(man);
4344
+ }
4345
+ output(result2, () => {
4346
+ console.log(ok("\u5DF2\u5FEB\u901F\u5B58\u5165: \u539F\u6587\u5DF2\u53EF\u68C0\u7D22,\u5B9E\u4F53/\u5173\u7CFB\u7CBE\u70BC\u5728\u540E\u53F0\u8FDB\u884C"));
4347
+ console.log(
4348
+ dim(
4349
+ ` \u4EFB\u52A1: job_id=${result2.job_id} \xB7 \u8FDB\u5EA6: ${result2.poll ?? `/ingest/status?job_id=${result2.job_id}`}`
4350
+ )
4351
+ );
4352
+ });
4353
+ return;
4354
+ }
4296
4355
  const body = { content };
4297
4356
  if (opts.title) body.title = opts.title;
4298
4357
  if (opts.tag && opts.tag.length) body.tags = opts.tag;
@@ -5318,7 +5377,7 @@ program2.name("exomind").description("ExoMind \u8DE8\u5E73\u53F0\u77E5\u8BC6\u98
5318
5377
  program2.command("login").description("\u914D\u7F6E\u670D\u52A1\u5668\u5730\u5740\u4E0E\u51ED\u8BC1,\u5199\u5165 ~/.exomind/config.json").option("--base-url <url>", "\u670D\u52A1\u5668\u5730\u5740").option("--api-key <key>", "API Key \u6216\u767B\u5F55 token(\u4E0D\u586B\u5219\u4EA4\u4E92\u8F93\u5165)").action(run(login));
5319
5378
  program2.command("me").description("\u663E\u793A\u5F53\u524D\u767B\u5F55\u6001\u4E0E\u670D\u52A1\u5668").action(run(whoami));
5320
5379
  program2.command("whoami", { hidden: true }).description("\u663E\u793A\u5F53\u524D\u767B\u5F55\u6001\u4E0E\u670D\u52A1\u5668(me \u7684\u65E7\u540D)").action(run(whoami));
5321
- program2.command("ingest [content...]").description("\u5BFC\u5165\u77E5\u8BC6: \u53C2\u6570\u6587\u672C / --file / stdin / --dir \u76EE\u5F55\u6279\u91CF(\u589E\u91CF)").option("-t, --title <title>", "\u6807\u9898(\u5355\u6587\u4EF6\u6A21\u5F0F)").option("--tag <tag>", "\u6807\u7B7E(\u53EF\u91CD\u590D)", collect, []).option("--file <path>", "\u4ECE\u6587\u4EF6\u8BFB\u53D6\u5185\u5BB9").option("--dir [path]", "\u76EE\u5F55\u6279\u91CF\u6444\u5165(\u589E\u91CF: \u5185\u5BB9\u54C8\u5E0C\u8DF3\u8FC7\u672A\u53D8\u6587\u4EF6;\u76EE\u5F55\u53EF\u5199\u5728\u524D: ingest <\u76EE\u5F55> --dir)").option("-r, --recursive", "\u9012\u5F52\u5B50\u76EE\u5F55(\u914D\u5408 --dir)").option("--pattern <glob>", "\u6587\u4EF6\u540D\u5339\u914D,\u9ED8\u8BA4 *.md", "*.md").option("--force", "\u5FFD\u7565 manifest,\u5F3A\u5236\u5168\u91CF\u91CD\u6444(\u914D\u5408 --dir)").option(
5380
+ program2.command("ingest [content...]").description("\u5BFC\u5165\u77E5\u8BC6: \u53C2\u6570\u6587\u672C / --file / stdin / --dir \u76EE\u5F55\u6279\u91CF(\u589E\u91CF)").option("-t, --title <title>", "\u6807\u9898(\u5355\u6587\u4EF6\u6A21\u5F0F)").option("--tag <tag>", "\u6807\u7B7E(\u53EF\u91CD\u590D)", collect, []).option("--file <path>", "\u4ECE\u6587\u4EF6\u8BFB\u53D6\u5185\u5BB9").option("--fast", "\u5FEB\u901F\u6A21\u5F0F: \u539F\u6587\u79D2\u5B58\u5E76\u53EF\u7ACB\u5373\u88AB\u68C0\u7D22,\u5B9E\u4F53/\u5173\u7CFB\u62BD\u53D6\u8F6C\u540E\u53F0(\u9002\u5408 Agent \u81EA\u52A8\u5B58\u6863)").option("--dir [path]", "\u76EE\u5F55\u6279\u91CF\u6444\u5165(\u589E\u91CF: \u5185\u5BB9\u54C8\u5E0C\u8DF3\u8FC7\u672A\u53D8\u6587\u4EF6;\u76EE\u5F55\u53EF\u5199\u5728\u524D: ingest <\u76EE\u5F55> --dir)").option("-r, --recursive", "\u9012\u5F52\u5B50\u76EE\u5F55(\u914D\u5408 --dir)").option("--pattern <glob>", "\u6587\u4EF6\u540D\u5339\u914D,\u9ED8\u8BA4 *.md", "*.md").option("--force", "\u5FFD\u7565 manifest,\u5F3A\u5236\u5168\u91CF\u91CD\u6444(\u914D\u5408 --dir)").option(
5322
5381
  "-c, --concurrency <n>",
5323
5382
  "\u5E76\u53D1\u6444\u5165\u6570(\u914D\u5408 --dir,\u9ED8\u8BA4 3,\u5F31\u670D\u52A1\u5668\u53CB\u597D)",
5324
5383
  (v) => parseInt(v, 10) || 3,