@hanhnd/agent-kit 1.0.30 → 1.0.32

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.
Files changed (81) hide show
  1. package/dist/__tests__/temp-dir.d.ts +4 -0
  2. package/dist/__tests__/temp-dir.js +20 -0
  3. package/dist/cli/index.d.ts +1 -0
  4. package/dist/cli/index.js +9 -0
  5. package/dist/cli/memory.d.ts +1 -0
  6. package/dist/cli/memory.js +77 -0
  7. package/dist/kit-cli.d.ts +2 -0
  8. package/dist/kit-cli.js +16205 -0
  9. package/dist/kit-server.js +677 -187
  10. package/dist/memory/__tests__/digest-llama-local.test.d.ts +1 -0
  11. package/dist/memory/__tests__/digest-llama-local.test.js +33 -0
  12. package/dist/memory/__tests__/digest-pending.test.d.ts +1 -0
  13. package/dist/memory/__tests__/digest-pending.test.js +164 -0
  14. package/dist/memory/__tests__/digest-processor.test.d.ts +1 -0
  15. package/dist/memory/__tests__/digest-processor.test.js +34 -0
  16. package/dist/memory/__tests__/digest-worker.test.d.ts +1 -0
  17. package/dist/memory/__tests__/digest-worker.test.js +46 -0
  18. package/dist/memory/__tests__/embedder.test.d.ts +1 -0
  19. package/dist/memory/__tests__/embedder.test.js +14 -0
  20. package/dist/memory/__tests__/indexer.test.js +7 -12
  21. package/dist/memory/__tests__/lockfile.test.d.ts +1 -0
  22. package/dist/memory/__tests__/lockfile.test.js +81 -0
  23. package/dist/memory/__tests__/store.test.js +15 -3
  24. package/dist/memory/chunker.js +3 -5
  25. package/dist/memory/constants.d.ts +5 -0
  26. package/dist/memory/constants.js +12 -0
  27. package/dist/memory/digest/constants.d.ts +14 -0
  28. package/dist/memory/digest/constants.js +15 -0
  29. package/dist/memory/digest/files.d.ts +7 -0
  30. package/dist/memory/digest/files.js +45 -0
  31. package/dist/memory/digest/model-registry.d.ts +6 -0
  32. package/dist/memory/digest/model-registry.js +48 -0
  33. package/dist/memory/digest/processor.d.ts +7 -0
  34. package/dist/memory/digest/processor.js +171 -0
  35. package/dist/memory/digest/providers/llama-local.d.ts +7 -0
  36. package/dist/memory/digest/providers/llama-local.js +136 -0
  37. package/dist/memory/digest/types.d.ts +85 -0
  38. package/dist/memory/digest/types.js +1 -0
  39. package/dist/memory/digest/worker.d.ts +3 -0
  40. package/dist/memory/digest/worker.js +51 -0
  41. package/dist/memory/embedder.d.ts +2 -0
  42. package/dist/memory/embedder.js +12 -4
  43. package/dist/memory/index.d.ts +1 -1
  44. package/dist/memory/index.js +1 -1
  45. package/dist/memory/indexer.d.ts +5 -2
  46. package/dist/memory/indexer.js +15 -37
  47. package/dist/memory/store.js +6 -18
  48. package/dist/memory/types.d.ts +2 -1
  49. package/dist/memory/types.js +1 -9
  50. package/dist/tools/__tests__/cli-digest-pending.test.d.ts +1 -0
  51. package/dist/tools/__tests__/cli-digest-pending.test.js +51 -0
  52. package/dist/tools/__tests__/cli-memory.test.d.ts +1 -0
  53. package/dist/tools/__tests__/cli-memory.test.js +22 -0
  54. package/dist/tools/__tests__/digest-init.test.d.ts +1 -0
  55. package/dist/tools/__tests__/digest-init.test.js +32 -0
  56. package/dist/tools/__tests__/memory.test.js +28 -6
  57. package/dist/tools/agent.js +2 -13
  58. package/dist/tools/config.d.ts +4 -4
  59. package/dist/tools/config.js +21 -13
  60. package/dist/tools/core.js +1 -3
  61. package/dist/tools/handoffs.js +1 -2
  62. package/dist/tools/integration.js +2 -8
  63. package/dist/tools/memory.d.ts +1 -1
  64. package/dist/tools/memory.js +29 -3
  65. package/dist/tools/security.js +2 -6
  66. package/dist/utils/args.d.ts +7 -0
  67. package/dist/utils/args.js +31 -0
  68. package/dist/utils/async.d.ts +1 -0
  69. package/dist/utils/async.js +15 -0
  70. package/dist/utils/credentials.d.ts +3 -3
  71. package/dist/utils/credentials.js +5 -7
  72. package/dist/utils/files.d.ts +8 -0
  73. package/dist/utils/files.js +77 -0
  74. package/dist/utils/hash.d.ts +1 -0
  75. package/dist/utils/hash.js +4 -0
  76. package/dist/utils/json.d.ts +2 -0
  77. package/dist/utils/json.js +13 -0
  78. package/dist/utils/parser.js +2 -6
  79. package/dist/utils/paths.d.ts +4 -0
  80. package/dist/utils/paths.js +6 -0
  81. package/package.json +7 -5
@@ -2987,7 +2987,7 @@ var require_compile = __commonJS({
2987
2987
  const schOrFunc = root.refs[ref];
2988
2988
  if (schOrFunc)
2989
2989
  return schOrFunc;
2990
- let _sch = resolve3.call(this, root, ref);
2990
+ let _sch = resolve5.call(this, root, ref);
2991
2991
  if (_sch === void 0) {
2992
2992
  const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
2993
2993
  const { schemaId } = this.opts;
@@ -3014,7 +3014,7 @@ var require_compile = __commonJS({
3014
3014
  function sameSchemaEnv(s1, s2) {
3015
3015
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3016
3016
  }
3017
- function resolve3(root, ref) {
3017
+ function resolve5(root, ref) {
3018
3018
  let sch;
3019
3019
  while (typeof (sch = this.refs[ref]) == "string")
3020
3020
  ref = sch;
@@ -3229,8 +3229,8 @@ var require_utils = __commonJS({
3229
3229
  }
3230
3230
  return ind;
3231
3231
  }
3232
- function removeDotSegments(path11) {
3233
- let input = path11;
3232
+ function removeDotSegments(path15) {
3233
+ let input = path15;
3234
3234
  const output = [];
3235
3235
  let nextSlash = -1;
3236
3236
  let len = 0;
@@ -3429,8 +3429,8 @@ var require_schemes = __commonJS({
3429
3429
  wsComponent.secure = void 0;
3430
3430
  }
3431
3431
  if (wsComponent.resourceName) {
3432
- const [path11, query] = wsComponent.resourceName.split("?");
3433
- wsComponent.path = path11 && path11 !== "/" ? path11 : void 0;
3432
+ const [path15, query] = wsComponent.resourceName.split("?");
3433
+ wsComponent.path = path15 && path15 !== "/" ? path15 : void 0;
3434
3434
  wsComponent.query = query;
3435
3435
  wsComponent.resourceName = void 0;
3436
3436
  }
@@ -3589,55 +3589,55 @@ var require_fast_uri = __commonJS({
3589
3589
  }
3590
3590
  return uri;
3591
3591
  }
3592
- function resolve3(baseURI, relativeURI, options) {
3592
+ function resolve5(baseURI, relativeURI, options) {
3593
3593
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3594
3594
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
3595
3595
  schemelessOptions.skipEscape = true;
3596
3596
  return serialize(resolved, schemelessOptions);
3597
3597
  }
3598
- function resolveComponent(base, relative3, options, skipNormalization) {
3598
+ function resolveComponent(base, relative4, options, skipNormalization) {
3599
3599
  const target = {};
3600
3600
  if (!skipNormalization) {
3601
3601
  base = parse3(serialize(base, options), options);
3602
- relative3 = parse3(serialize(relative3, options), options);
3602
+ relative4 = parse3(serialize(relative4, options), options);
3603
3603
  }
3604
3604
  options = options || {};
3605
- if (!options.tolerant && relative3.scheme) {
3606
- target.scheme = relative3.scheme;
3607
- target.userinfo = relative3.userinfo;
3608
- target.host = relative3.host;
3609
- target.port = relative3.port;
3610
- target.path = removeDotSegments(relative3.path || "");
3611
- target.query = relative3.query;
3605
+ if (!options.tolerant && relative4.scheme) {
3606
+ target.scheme = relative4.scheme;
3607
+ target.userinfo = relative4.userinfo;
3608
+ target.host = relative4.host;
3609
+ target.port = relative4.port;
3610
+ target.path = removeDotSegments(relative4.path || "");
3611
+ target.query = relative4.query;
3612
3612
  } else {
3613
- if (relative3.userinfo !== void 0 || relative3.host !== void 0 || relative3.port !== void 0) {
3614
- target.userinfo = relative3.userinfo;
3615
- target.host = relative3.host;
3616
- target.port = relative3.port;
3617
- target.path = removeDotSegments(relative3.path || "");
3618
- target.query = relative3.query;
3613
+ if (relative4.userinfo !== void 0 || relative4.host !== void 0 || relative4.port !== void 0) {
3614
+ target.userinfo = relative4.userinfo;
3615
+ target.host = relative4.host;
3616
+ target.port = relative4.port;
3617
+ target.path = removeDotSegments(relative4.path || "");
3618
+ target.query = relative4.query;
3619
3619
  } else {
3620
- if (!relative3.path) {
3620
+ if (!relative4.path) {
3621
3621
  target.path = base.path;
3622
- if (relative3.query !== void 0) {
3623
- target.query = relative3.query;
3622
+ if (relative4.query !== void 0) {
3623
+ target.query = relative4.query;
3624
3624
  } else {
3625
3625
  target.query = base.query;
3626
3626
  }
3627
3627
  } else {
3628
- if (relative3.path[0] === "/") {
3629
- target.path = removeDotSegments(relative3.path);
3628
+ if (relative4.path[0] === "/") {
3629
+ target.path = removeDotSegments(relative4.path);
3630
3630
  } else {
3631
3631
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
3632
- target.path = "/" + relative3.path;
3632
+ target.path = "/" + relative4.path;
3633
3633
  } else if (!base.path) {
3634
- target.path = relative3.path;
3634
+ target.path = relative4.path;
3635
3635
  } else {
3636
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
3636
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative4.path;
3637
3637
  }
3638
3638
  target.path = removeDotSegments(target.path);
3639
3639
  }
3640
- target.query = relative3.query;
3640
+ target.query = relative4.query;
3641
3641
  }
3642
3642
  target.userinfo = base.userinfo;
3643
3643
  target.host = base.host;
@@ -3645,7 +3645,7 @@ var require_fast_uri = __commonJS({
3645
3645
  }
3646
3646
  target.scheme = base.scheme;
3647
3647
  }
3648
- target.fragment = relative3.fragment;
3648
+ target.fragment = relative4.fragment;
3649
3649
  return target;
3650
3650
  }
3651
3651
  function equal(uriA, uriB, options) {
@@ -3816,7 +3816,7 @@ var require_fast_uri = __commonJS({
3816
3816
  var fastUri = {
3817
3817
  SCHEMES,
3818
3818
  normalize,
3819
- resolve: resolve3,
3819
+ resolve: resolve5,
3820
3820
  resolveComponent,
3821
3821
  equal,
3822
3822
  serialize,
@@ -6792,12 +6792,12 @@ var require_dist = __commonJS({
6792
6792
  throw new Error(`Unknown format "${name}"`);
6793
6793
  return f;
6794
6794
  };
6795
- function addFormats(ajv, list, fs7, exportName) {
6795
+ function addFormats(ajv, list, fs11, exportName) {
6796
6796
  var _a;
6797
6797
  var _b;
6798
6798
  (_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
6799
6799
  for (const f of list)
6800
- ajv.addFormat(f, fs7[f]);
6800
+ ajv.addFormat(f, fs11[f]);
6801
6801
  }
6802
6802
  module.exports = exports = formatsPlugin;
6803
6803
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -22171,8 +22171,8 @@ function getErrorMap() {
22171
22171
 
22172
22172
  // ../node_modules/zod/v3/helpers/parseUtil.js
22173
22173
  var makeIssue = (params) => {
22174
- const { data, path: path11, errorMaps, issueData } = params;
22175
- const fullPath = [...path11, ...issueData.path || []];
22174
+ const { data, path: path15, errorMaps, issueData } = params;
22175
+ const fullPath = [...path15, ...issueData.path || []];
22176
22176
  const fullIssue = {
22177
22177
  ...issueData,
22178
22178
  path: fullPath
@@ -22288,11 +22288,11 @@ var errorUtil;
22288
22288
 
22289
22289
  // ../node_modules/zod/v3/types.js
22290
22290
  var ParseInputLazyPath = class {
22291
- constructor(parent, value, path11, key) {
22291
+ constructor(parent, value, path15, key) {
22292
22292
  this._cachedPath = [];
22293
22293
  this.parent = parent;
22294
22294
  this.data = value;
22295
- this._path = path11;
22295
+ this._path = path15;
22296
22296
  this._key = key;
22297
22297
  }
22298
22298
  get path() {
@@ -25930,10 +25930,10 @@ function assignProp(target, prop, value) {
25930
25930
  configurable: true
25931
25931
  });
25932
25932
  }
25933
- function getElementAtPath(obj, path11) {
25934
- if (!path11)
25933
+ function getElementAtPath(obj, path15) {
25934
+ if (!path15)
25935
25935
  return obj;
25936
- return path11.reduce((acc, key) => acc?.[key], obj);
25936
+ return path15.reduce((acc, key) => acc?.[key], obj);
25937
25937
  }
25938
25938
  function promiseAllObject(promisesObj) {
25939
25939
  const keys = Object.keys(promisesObj);
@@ -26253,11 +26253,11 @@ function aborted(x, startIndex = 0) {
26253
26253
  }
26254
26254
  return false;
26255
26255
  }
26256
- function prefixIssues(path11, issues) {
26256
+ function prefixIssues(path15, issues) {
26257
26257
  return issues.map((iss) => {
26258
26258
  var _a;
26259
26259
  (_a = iss).path ?? (_a.path = []);
26260
- iss.path.unshift(path11);
26260
+ iss.path.unshift(path15);
26261
26261
  return iss;
26262
26262
  });
26263
26263
  }
@@ -33768,7 +33768,7 @@ var Protocol = class {
33768
33768
  return;
33769
33769
  }
33770
33770
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
33771
- await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
33771
+ await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
33772
33772
  options?.signal?.throwIfAborted();
33773
33773
  }
33774
33774
  } catch (error2) {
@@ -33785,7 +33785,7 @@ var Protocol = class {
33785
33785
  */
33786
33786
  request(request, resultSchema, options) {
33787
33787
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
33788
- return new Promise((resolve3, reject) => {
33788
+ return new Promise((resolve5, reject) => {
33789
33789
  const earlyReject = (error2) => {
33790
33790
  reject(error2);
33791
33791
  };
@@ -33863,7 +33863,7 @@ var Protocol = class {
33863
33863
  if (!parseResult.success) {
33864
33864
  reject(parseResult.error);
33865
33865
  } else {
33866
- resolve3(parseResult.data);
33866
+ resolve5(parseResult.data);
33867
33867
  }
33868
33868
  } catch (error2) {
33869
33869
  reject(error2);
@@ -34124,12 +34124,12 @@ var Protocol = class {
34124
34124
  }
34125
34125
  } catch {
34126
34126
  }
34127
- return new Promise((resolve3, reject) => {
34127
+ return new Promise((resolve5, reject) => {
34128
34128
  if (signal.aborted) {
34129
34129
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
34130
34130
  return;
34131
34131
  }
34132
- const timeoutId = setTimeout(resolve3, interval);
34132
+ const timeoutId = setTimeout(resolve5, interval);
34133
34133
  signal.addEventListener("abort", () => {
34134
34134
  clearTimeout(timeoutId);
34135
34135
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -35229,7 +35229,7 @@ var McpServer = class {
35229
35229
  let task = createTaskResult.task;
35230
35230
  const pollInterval = task.pollInterval ?? 5e3;
35231
35231
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
35232
- await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
35232
+ await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
35233
35233
  const updatedTask = await extra.taskStore.getTask(taskId);
35234
35234
  if (!updatedTask) {
35235
35235
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -35872,20 +35872,20 @@ var StdioServerTransport = class {
35872
35872
  this.onclose?.();
35873
35873
  }
35874
35874
  send(message) {
35875
- return new Promise((resolve3) => {
35875
+ return new Promise((resolve5) => {
35876
35876
  const json = serializeMessage(message);
35877
35877
  if (this._stdout.write(json)) {
35878
- resolve3();
35878
+ resolve5();
35879
35879
  } else {
35880
- this._stdout.once("drain", resolve3);
35880
+ this._stdout.once("drain", resolve5);
35881
35881
  }
35882
35882
  });
35883
35883
  }
35884
35884
  };
35885
35885
 
35886
35886
  // src/tools/core.ts
35887
- import * as fs2 from "fs";
35888
- import * as path3 from "path";
35887
+ import * as fs3 from "fs";
35888
+ import * as path4 from "path";
35889
35889
 
35890
35890
  // src/utils/utils.ts
35891
35891
  function getWorkspaceRoot() {
@@ -35899,10 +35899,10 @@ function mcpJson(obj) {
35899
35899
  }
35900
35900
 
35901
35901
  // src/tools/config.ts
35902
- import * as fs from "fs";
35903
- import * as path from "path";
35902
+ import * as fs2 from "fs";
35903
+ import * as path2 from "path";
35904
35904
 
35905
- // src/memory/types.ts
35905
+ // src/memory/constants.ts
35906
35906
  var DEFAULT_MEMORY_CONFIG = {
35907
35907
  enabled: false,
35908
35908
  wikiDir: "",
@@ -35912,6 +35912,75 @@ var DEFAULT_MEMORY_CONFIG = {
35912
35912
  embeddingModel: "Xenova/bge-small-en-v1.5",
35913
35913
  vectorDimension: 384
35914
35914
  };
35915
+ var LOCK_RETRY_MS = 50;
35916
+ var LOCK_TIMEOUT_MS = 500;
35917
+ var RRF_K = 60;
35918
+
35919
+ // src/utils/files.ts
35920
+ import * as fs from "node:fs";
35921
+ import * as path from "node:path";
35922
+ function atomicWriteTextFile(filePath, content) {
35923
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
35924
+ const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
35925
+ fs.writeFileSync(tempPath, content, "utf8");
35926
+ fs.renameSync(tempPath, filePath);
35927
+ }
35928
+ function atomicWriteJsonFile(filePath, value) {
35929
+ atomicWriteTextFile(filePath, `${JSON.stringify(value, null, 2)}
35930
+ `);
35931
+ }
35932
+ function tryWriteFileExclusive(filePath, content) {
35933
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
35934
+ try {
35935
+ fs.writeFileSync(filePath, content, { flag: "wx" });
35936
+ return true;
35937
+ } catch (err) {
35938
+ if (err.code === "EEXIST") return false;
35939
+ throw err;
35940
+ }
35941
+ }
35942
+ async function acquireLock(lockPath, timeoutMs, retryMs) {
35943
+ const deadline = Date.now() + timeoutMs;
35944
+ while (Date.now() < deadline) {
35945
+ try {
35946
+ if (tryWriteFileExclusive(lockPath, String(process.pid))) return true;
35947
+ } catch {
35948
+ }
35949
+ await new Promise((r) => setTimeout(r, retryMs));
35950
+ }
35951
+ return false;
35952
+ }
35953
+ function releaseLock(lockPath, opts = {}) {
35954
+ try {
35955
+ fs.unlinkSync(lockPath);
35956
+ } catch (err) {
35957
+ if (opts.ignoreErrors || err.code === "ENOENT") return;
35958
+ throw err;
35959
+ }
35960
+ }
35961
+ function tryAcquireProcessLock(lockPath) {
35962
+ const lockContent = JSON.stringify({ pid: process.pid });
35963
+ if (tryWriteFileExclusive(lockPath, lockContent)) return true;
35964
+ let pid;
35965
+ try {
35966
+ const raw = fs.readFileSync(lockPath, "utf8");
35967
+ pid = JSON.parse(raw).pid;
35968
+ } catch {
35969
+ }
35970
+ if (pid !== void 0) {
35971
+ try {
35972
+ process.kill(pid, 0);
35973
+ return false;
35974
+ } catch (err) {
35975
+ if (err.code !== "ESRCH") return false;
35976
+ }
35977
+ }
35978
+ try {
35979
+ fs.unlinkSync(lockPath);
35980
+ } catch {
35981
+ }
35982
+ return tryWriteFileExclusive(lockPath, lockContent);
35983
+ }
35915
35984
 
35916
35985
  // src/tools/config.ts
35917
35986
  function resolveMemoryConfig(settings2, workspaceRoot2) {
@@ -35920,20 +35989,37 @@ function resolveMemoryConfig(settings2, workspaceRoot2) {
35920
35989
  ...DEFAULT_MEMORY_CONFIG,
35921
35990
  ...override,
35922
35991
  enabled: override.enabled ?? DEFAULT_MEMORY_CONFIG.enabled,
35923
- wikiDir: typeof override.wikiDir === "string" && override.wikiDir.length > 0 ? override.wikiDir : path.join(workspaceRoot2, ".agent-kit", "wiki")
35992
+ wikiDir: typeof override.wikiDir === "string" && override.wikiDir.length > 0 ? override.wikiDir : path2.join(workspaceRoot2, ".agent-kit", "wiki")
35924
35993
  };
35925
35994
  }
35926
35995
  function loadProjectSettings(projectDir) {
35927
- const settingsPath = path.join(projectDir, ".agent-kit", "settings.json");
35928
- if (fs.existsSync(settingsPath)) {
35996
+ const settingsPath = path2.join(projectDir, ".agent-kit", "settings.json");
35997
+ if (fs2.existsSync(settingsPath)) {
35929
35998
  try {
35930
- return JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
35999
+ return JSON.parse(fs2.readFileSync(settingsPath, "utf-8"));
35931
36000
  } catch (error2) {
35932
36001
  console.error(`[agent-kit] Warning: Failed to parse ${settingsPath}:`, error2);
35933
36002
  }
35934
36003
  }
35935
36004
  return {};
35936
36005
  }
36006
+ function writeProjectSettings(workspaceRoot2, patch) {
36007
+ const settingsPath = path2.join(workspaceRoot2, ".agent-kit", "settings.json");
36008
+ const current = loadProjectSettings(workspaceRoot2);
36009
+ atomicWriteJsonFile(settingsPath, { ...current, ...patch });
36010
+ }
36011
+ function resolveConversationDigestConfig(settings2) {
36012
+ const digest = settings2.memory?.conversationDigest;
36013
+ if (!digest?.initialized || typeof digest.modelId !== "string" || typeof digest.initializedAt !== "string") {
36014
+ return void 0;
36015
+ }
36016
+ return {
36017
+ enabled: digest.enabled !== false,
36018
+ initialized: true,
36019
+ modelId: digest.modelId,
36020
+ initializedAt: digest.initializedAt
36021
+ };
36022
+ }
35937
36023
  var FORBIDDEN_FILES = [
35938
36024
  ".env",
35939
36025
  ".bashrc",
@@ -35948,7 +36034,7 @@ var FORBIDDEN_FILES = [
35948
36034
  ".netrc",
35949
36035
  ".gitconfig",
35950
36036
  "credentials"
35951
- // ~/.claude/credentials file
36037
+ // ~/.agent-kit/credentials file
35952
36038
  ];
35953
36039
  var FORBIDDEN_PATTERNS = [
35954
36040
  /^\.env$/i,
@@ -35965,17 +36051,10 @@ var FORBIDDEN_PATTERNS = [
35965
36051
  /secrets\.json$/i,
35966
36052
  /secret\.json$/i
35967
36053
  ];
35968
- var FORBIDDEN_DIRS = [
35969
- ".git",
35970
- ".ssh",
35971
- ".aws",
35972
- ".kube",
35973
- ".gnupg",
35974
- ".docker"
35975
- ];
36054
+ var FORBIDDEN_DIRS = [".git", ".ssh", ".aws", ".kube", ".gnupg", ".docker"];
35976
36055
 
35977
36056
  // src/tools/handoffs.ts
35978
- import * as path2 from "path";
36057
+ import * as path3 from "path";
35979
36058
  var HANDOFF_TYPES = [
35980
36059
  "brainstorm",
35981
36060
  "clarification",
@@ -36018,11 +36097,11 @@ function resolveHandoffPath(input) {
36018
36097
  content: input.content,
36019
36098
  type: canonicalType
36020
36099
  });
36021
- const relativePath = path2.join(".agent-kit", "handoffs", featureSlug, `${canonicalType}.md`);
36100
+ const relativePath = path3.join(".agent-kit", "handoffs", featureSlug, `${canonicalType}.md`);
36022
36101
  return {
36023
36102
  featureSlug,
36024
36103
  canonicalType,
36025
- filePath: path2.join(input.workspaceRoot, relativePath),
36104
+ filePath: path3.join(input.workspaceRoot, relativePath),
36026
36105
  relativePath
36027
36106
  };
36028
36107
  }
@@ -36047,11 +36126,11 @@ function registerCoreTools(server2) {
36047
36126
  slug,
36048
36127
  content
36049
36128
  });
36050
- const handoffDir = path3.dirname(location.filePath);
36051
- if (!fs2.existsSync(handoffDir)) {
36052
- fs2.mkdirSync(handoffDir, { recursive: true });
36129
+ const handoffDir = path4.dirname(location.filePath);
36130
+ if (!fs3.existsSync(handoffDir)) {
36131
+ fs3.mkdirSync(handoffDir, { recursive: true });
36053
36132
  }
36054
- fs2.writeFileSync(location.filePath, content, "utf8");
36133
+ fs3.writeFileSync(location.filePath, content, "utf8");
36055
36134
  return mcpText(`\u2705 Saved to: ${location.filePath}`);
36056
36135
  } catch (error2) {
36057
36136
  return mcpText(`Error saving handoff: ${error2}`);
@@ -36063,28 +36142,28 @@ function registerCoreTools(server2) {
36063
36142
  // src/tools/agent.ts
36064
36143
  import { spawn } from "child_process";
36065
36144
  import { randomUUID } from "crypto";
36066
- import * as fs3 from "fs";
36067
- import * as path5 from "path";
36145
+ import * as fs4 from "fs";
36146
+ import * as path6 from "path";
36068
36147
 
36069
36148
  // src/tools/security.ts
36070
36149
  import { execFileSync } from "child_process";
36071
- import * as path4 from "path";
36150
+ import * as path5 from "path";
36072
36151
  function sanitize(input) {
36073
36152
  return String(input).replace(/[;&|`$<>\\]/g, "").trim().slice(0, 500);
36074
36153
  }
36075
36154
  function validatePath(filePath, baseDir = process.cwd()) {
36076
- const resolved = path4.resolve(baseDir, filePath);
36077
- const root = path4.resolve(baseDir);
36078
- if (resolved !== root && !resolved.startsWith(root + path4.sep)) {
36155
+ const resolved = path5.resolve(baseDir, filePath);
36156
+ const root = path5.resolve(baseDir);
36157
+ if (resolved !== root && !resolved.startsWith(root + path5.sep)) {
36079
36158
  throw new Error(`Path traversal detected: ${filePath}`);
36080
36159
  }
36081
- const segments = resolved.split(path4.sep);
36160
+ const segments = resolved.split(path5.sep);
36082
36161
  for (const segment of segments) {
36083
36162
  if (FORBIDDEN_DIRS.some((d) => segment.toLowerCase() === d.toLowerCase())) {
36084
36163
  throw new Error(`Access to sensitive directory is FORBIDDEN: ${segment}`);
36085
36164
  }
36086
36165
  }
36087
- const fileName = path4.basename(resolved);
36166
+ const fileName = path5.basename(resolved);
36088
36167
  const isExactForbidden = FORBIDDEN_FILES.some((f) => fileName.toLowerCase() === f.toLowerCase());
36089
36168
  const isPatternForbidden = FORBIDDEN_PATTERNS.some((re) => re.test(fileName));
36090
36169
  if (isExactForbidden || isPatternForbidden) {
@@ -36142,36 +36221,27 @@ function agentArgs(agent, prompt, workspaceRoot2) {
36142
36221
  case "claude":
36143
36222
  return ["--dangerously-skip-permissions", "-p", prompt];
36144
36223
  case "codex":
36145
- return [
36146
- "--ask-for-approval",
36147
- "never",
36148
- "--sandbox",
36149
- "workspace-write",
36150
- "--cd",
36151
- workspaceRoot2,
36152
- "exec",
36153
- prompt
36154
- ];
36224
+ return ["--ask-for-approval", "never", "--sandbox", "workspace-write", "--cd", workspaceRoot2, "exec", prompt];
36155
36225
  }
36156
36226
  }
36157
36227
  function taskSlug(task) {
36158
36228
  return task.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 6).join("-").slice(0, 48);
36159
36229
  }
36160
36230
  function initAgentLog(workspaceRoot2, slug, agent) {
36161
- const logsDir = path5.join(workspaceRoot2, ".agent-kit", "logs", "agents");
36162
- fs3.mkdirSync(logsDir, { recursive: true });
36231
+ const logsDir = path6.join(workspaceRoot2, ".agent-kit", "logs", "agents");
36232
+ fs4.mkdirSync(logsDir, { recursive: true });
36163
36233
  const now = (/* @__PURE__ */ new Date()).toISOString();
36164
- const logFile = path5.join(logsDir, `delegate-${now}-${slug}.log`);
36234
+ const logFile = path6.join(logsDir, `delegate-${now}-${slug}.log`);
36165
36235
  const header = `Agent: ${agent}
36166
36236
  Date: ${now}
36167
36237
  ${"\u2500".repeat(60)}
36168
36238
 
36169
36239
  `;
36170
- fs3.writeFileSync(logFile, header, "utf-8");
36171
- const logStream = fs3.createWriteStream(logFile, { flags: "a" });
36240
+ fs4.writeFileSync(logFile, header, "utf-8");
36241
+ const logStream = fs4.createWriteStream(logFile, { flags: "a" });
36172
36242
  logStream.on("error", () => {
36173
36243
  });
36174
- return { logPath: path5.relative(workspaceRoot2, logFile), logStream };
36244
+ return { logPath: path6.relative(workspaceRoot2, logFile), logStream };
36175
36245
  }
36176
36246
  function registerAgentTools(server2) {
36177
36247
  server2.tool(
@@ -36179,9 +36249,7 @@ function registerAgentTools(server2) {
36179
36249
  "!Important: Trigger only when user explicitly asks to delegate a task to an external agent CLI (gemini, claude, or codex). The task can be a direct message or a path to a handoff file (.agent-kit/handoffs/feature-slug/plan.md).",
36180
36250
  {
36181
36251
  agent: external_exports.enum(["gemini", "claude", "codex"]).describe('The agent CLI to invoke: "gemini", "claude", or "codex"'),
36182
- task: external_exports.string().describe(
36183
- 'Task message or path to a handoff file (e.g., ".agent-kit/handoffs/feature-slug/plan.md")'
36184
- )
36252
+ task: external_exports.string().describe('Task message or path to a handoff file (e.g., ".agent-kit/handoffs/feature-slug/plan.md")')
36185
36253
  },
36186
36254
  async ({ agent, task }, extra) => {
36187
36255
  const workspaceRoot2 = getWorkspaceRoot();
@@ -36190,11 +36258,11 @@ function registerAgentTools(server2) {
36190
36258
  let killChild;
36191
36259
  try {
36192
36260
  let prompt = task;
36193
- const resolvedTask = path5.resolve(workspaceRoot2, task);
36194
- if (fs3.existsSync(resolvedTask) && fs3.statSync(resolvedTask).isFile()) {
36261
+ const resolvedTask = path6.resolve(workspaceRoot2, task);
36262
+ if (fs4.existsSync(resolvedTask) && fs4.statSync(resolvedTask).isFile()) {
36195
36263
  try {
36196
36264
  validatePath(task, workspaceRoot2);
36197
- prompt = fs3.readFileSync(resolvedTask, "utf-8");
36265
+ prompt = fs4.readFileSync(resolvedTask, "utf-8");
36198
36266
  } catch (err) {
36199
36267
  return mcpText(`Error reading task file: ${err}`);
36200
36268
  }
@@ -36247,7 +36315,7 @@ function registerAgentTools(server2) {
36247
36315
  };
36248
36316
  if (extra.signal.aborted) killChild();
36249
36317
  else extra.signal.addEventListener("abort", killChild);
36250
- await new Promise((resolve3, reject) => {
36318
+ await new Promise((resolve5, reject) => {
36251
36319
  child.stdout?.on("data", (data) => {
36252
36320
  job.chunks.push(data.toString());
36253
36321
  job.logStream.write(data);
@@ -36257,11 +36325,8 @@ function registerAgentTools(server2) {
36257
36325
  job.logStream.write(data);
36258
36326
  });
36259
36327
  child.on("close", (code, signal) => {
36260
- if (code === 0) resolve3();
36261
- else
36262
- reject(
36263
- new Error(`Process exited with code ${code}${signal ? ` (signal: ${signal})` : ""}`)
36264
- );
36328
+ if (code === 0) resolve5();
36329
+ else reject(new Error(`Process exited with code ${code}${signal ? ` (signal: ${signal})` : ""}`));
36265
36330
  });
36266
36331
  child.on("error", reject);
36267
36332
  });
@@ -36292,13 +36357,20 @@ function registerAgentTools(server2) {
36292
36357
  }
36293
36358
 
36294
36359
  // src/tools/integration.ts
36295
- import { writeFileSync as writeFileSync3 } from "fs";
36360
+ import { writeFileSync as writeFileSync4 } from "fs";
36296
36361
 
36297
36362
  // src/utils/credentials.ts
36298
- import * as fs4 from "fs";
36363
+ import * as fs5 from "fs";
36364
+
36365
+ // src/utils/paths.ts
36299
36366
  import * as os from "os";
36300
- import * as path6 from "path";
36301
- var CREDENTIALS_FILE = path6.join(os.homedir(), ".claude", "credentials");
36367
+ import * as path7 from "path";
36368
+ var AGENT_KIT_HOME = path7.join(os.homedir(), ".agent-kit");
36369
+ var CREDENTIALS_FILE = path7.join(AGENT_KIT_HOME, "credentials");
36370
+ var MODEL_CACHE_DIR = path7.join(AGENT_KIT_HOME, "cache", "models");
36371
+ var FASTEMBED_CACHE_DIR = path7.join(MODEL_CACHE_DIR, "fastembed");
36372
+
36373
+ // src/utils/credentials.ts
36302
36374
  var cachedCredentials = null;
36303
36375
  function parseIni(content) {
36304
36376
  const result = {};
@@ -36323,12 +36395,12 @@ function parseIni(content) {
36323
36395
  }
36324
36396
  function loadCredentials() {
36325
36397
  if (cachedCredentials !== null) return cachedCredentials;
36326
- if (!fs4.existsSync(CREDENTIALS_FILE)) {
36398
+ if (!fs5.existsSync(CREDENTIALS_FILE)) {
36327
36399
  cachedCredentials = {};
36328
36400
  return cachedCredentials;
36329
36401
  }
36330
36402
  try {
36331
- const stat = fs4.statSync(CREDENTIALS_FILE);
36403
+ const stat = fs5.statSync(CREDENTIALS_FILE);
36332
36404
  const mode = stat.mode & 511;
36333
36405
  if (mode !== 384) {
36334
36406
  process.stderr.write(
@@ -36338,7 +36410,7 @@ function loadCredentials() {
36338
36410
  }
36339
36411
  } catch {
36340
36412
  }
36341
- const content = fs4.readFileSync(CREDENTIALS_FILE, "utf-8");
36413
+ const content = fs5.readFileSync(CREDENTIALS_FILE, "utf-8");
36342
36414
  const ini = parseIni(content);
36343
36415
  const profile = process.env.KIT_PROFILE ?? "default";
36344
36416
  cachedCredentials = ini[profile] ?? {};
@@ -36467,9 +36539,7 @@ ${renderNodes(node.content, listDepth, node.type).trim()}
36467
36539
  for (const row of rows) {
36468
36540
  if (row.type !== "tableRow") continue;
36469
36541
  const cells = row.content || [];
36470
- const rendered = cells.map(
36471
- (c) => renderNodes(c.content).trim().replace(/\n+/g, " ").replace(/\|/g, "\\|")
36472
- );
36542
+ const rendered = cells.map((c) => renderNodes(c.content).trim().replace(/\n+/g, " ").replace(/\|/g, "\\|"));
36473
36543
  result += "| " + rendered.join(" | ") + " |\n";
36474
36544
  if (isFirstRow) {
36475
36545
  result += "| " + cells.map(() => "---").join(" | ") + " |\n";
@@ -36538,9 +36608,7 @@ function registerIntegrationTools(server2) {
36538
36608
  "Get Bitbucket PR details and optionally the diff. Accepts a full PR URL or a numeric PR ID with workspace + repoSlug.",
36539
36609
  {
36540
36610
  input: external_exports.string().describe("Bitbucket PR URL or numeric PR ID"),
36541
- workspace: external_exports.string().optional().describe(
36542
- "Bitbucket workspace slug (required for numeric ID if BITBUCKET_DEFAULT_WORKSPACE not set)"
36543
- ),
36611
+ workspace: external_exports.string().optional().describe("Bitbucket workspace slug (required for numeric ID if BITBUCKET_DEFAULT_WORKSPACE not set)"),
36544
36612
  repoSlug: external_exports.string().optional().describe("Bitbucket repo slug (required for numeric ID)"),
36545
36613
  includeDiff: external_exports.boolean().optional().default(true).describe("Include unified diff in response")
36546
36614
  },
@@ -36565,9 +36633,7 @@ function registerIntegrationTools(server2) {
36565
36633
  );
36566
36634
  }
36567
36635
  if (!repo || !prId) {
36568
- return mcpText(
36569
- `\u274C Could not parse PR URL. Expected: bitbucket.org/{ws}/{repo}/pull-requests/{id}`
36570
- );
36636
+ return mcpText(`\u274C Could not parse PR URL. Expected: bitbucket.org/{ws}/{repo}/pull-requests/{id}`);
36571
36637
  }
36572
36638
  const safeWs = sanitize(ws);
36573
36639
  const safeRepo = sanitize(repo);
@@ -36599,7 +36665,7 @@ ${diff}
36599
36665
  } else {
36600
36666
  const filePath = `/tmp/kit-pr-${prId}-${Date.now()}.diff`;
36601
36667
  try {
36602
- writeFileSync3(filePath, diff, "utf8");
36668
+ writeFileSync4(filePath, diff, "utf8");
36603
36669
  output += `
36604
36670
 
36605
36671
  ### Diff
@@ -36672,12 +36738,9 @@ ${ticket.fields.labels?.join(", ") || "None"}`;
36672
36738
  }
36673
36739
 
36674
36740
  // src/tools/memory.ts
36675
- import * as path10 from "path";
36741
+ import * as path14 from "path";
36676
36742
 
36677
36743
  // src/memory/embedder.ts
36678
- import * as os2 from "os";
36679
- import * as path7 from "path";
36680
- var DEFAULT_CACHE_DIR = path7.join(os2.homedir(), ".cache", "fastembed");
36681
36744
  var EmbedderError = class extends Error {
36682
36745
  constructor(message, cause) {
36683
36746
  super(message);
@@ -36714,6 +36777,11 @@ var Embedder = class {
36714
36777
  isReady() {
36715
36778
  return this.pipeline !== null;
36716
36779
  }
36780
+ async initialize() {
36781
+ if (!this.pipeline) {
36782
+ await this.loadModel();
36783
+ }
36784
+ }
36717
36785
  async loadModel() {
36718
36786
  try {
36719
36787
  const { EmbeddingModel, FlagEmbedding } = await import("fastembed");
@@ -36723,7 +36791,11 @@ var Embedder = class {
36723
36791
  "Xenova/all-MiniLM-L6-v2": EmbeddingModel.AllMiniLML6V2
36724
36792
  };
36725
36793
  const model = modelMap[this.modelName] ?? EmbeddingModel.BGESmallENV15;
36726
- const embedder = await FlagEmbedding.init({ model, cacheDir: DEFAULT_CACHE_DIR, showDownloadProgress: false });
36794
+ const embedder = await FlagEmbedding.init({
36795
+ model,
36796
+ cacheDir: FASTEMBED_CACHE_DIR,
36797
+ showDownloadProgress: false
36798
+ });
36727
36799
  this.pipeline = async (texts) => {
36728
36800
  const result = [];
36729
36801
  for await (const batch of embedder.embed(texts, texts.length)) {
@@ -36740,13 +36812,18 @@ var Embedder = class {
36740
36812
  };
36741
36813
 
36742
36814
  // src/memory/indexer.ts
36743
- import * as fs5 from "fs";
36815
+ import * as fs6 from "fs";
36744
36816
  import * as path8 from "path";
36745
36817
 
36818
+ // src/utils/hash.ts
36819
+ import { createHash } from "node:crypto";
36820
+ function sha256Hex(content) {
36821
+ return createHash("sha256").update(content).digest("hex");
36822
+ }
36823
+
36746
36824
  // src/memory/chunker.ts
36747
- import { createHash } from "crypto";
36748
36825
  function computeChunkId(content) {
36749
- return createHash("sha256").update(content).digest("hex").slice(0, 16);
36826
+ return sha256Hex(content).slice(0, 16);
36750
36827
  }
36751
36828
  function cleanContentForEmbedding(text) {
36752
36829
  return text.replace(/<!--[\s\S]*?-->/g, "");
@@ -36876,26 +36953,6 @@ ${sectionText}` : sectionText;
36876
36953
  }
36877
36954
 
36878
36955
  // src/memory/indexer.ts
36879
- var LOCK_RETRY_MS = 50;
36880
- var LOCK_TIMEOUT_MS = 500;
36881
- async function acquireLock(lockPath) {
36882
- const deadline = Date.now() + LOCK_TIMEOUT_MS;
36883
- while (Date.now() < deadline) {
36884
- try {
36885
- fs5.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
36886
- return true;
36887
- } catch {
36888
- await new Promise((r) => setTimeout(r, LOCK_RETRY_MS));
36889
- }
36890
- }
36891
- return false;
36892
- }
36893
- function releaseLock(lockPath) {
36894
- try {
36895
- fs5.unlinkSync(lockPath);
36896
- } catch {
36897
- }
36898
- }
36899
36956
  var MemoryIndexer = class {
36900
36957
  constructor(store, embedder, config2) {
36901
36958
  this.store = store;
@@ -36908,25 +36965,25 @@ var MemoryIndexer = class {
36908
36965
  }
36909
36966
  async indexFileRelativeTo(absolutePath, sourceRoot) {
36910
36967
  const source = path8.relative(sourceRoot, absolutePath);
36911
- const existingHashes = this.store.hashesBySource(source);
36968
+ const existingChunkIds = this.store.hashesBySource(source);
36912
36969
  let text;
36913
36970
  try {
36914
- text = fs5.readFileSync(absolutePath, "utf8");
36971
+ text = fs6.readFileSync(absolutePath, "utf8");
36915
36972
  } catch (err) {
36916
36973
  console.warn(`[memory-indexer] Cannot read file ${absolutePath}:`, err);
36917
- return { indexed: 0, deleted: 0, skipped: existingHashes.size };
36974
+ return { indexed: 0, deleted: 0, skipped: existingChunkIds.size };
36918
36975
  }
36919
36976
  const allChunks = chunkMarkdown(text, source, this.config);
36920
- const newHashes = new Set(allChunks.map((c) => c.id));
36921
- const toIndex = allChunks.filter((c) => !existingHashes.has(c.id));
36922
- const toDelete = [...existingHashes].filter((h) => !newHashes.has(h));
36977
+ const currentChunkIds = new Set(allChunks.map((chunk) => chunk.id));
36978
+ const toIndex = allChunks.filter((chunk) => !existingChunkIds.has(chunk.id));
36979
+ const toDelete = [...existingChunkIds].filter((id) => !currentChunkIds.has(id));
36923
36980
  let embeddings = [];
36924
36981
  if (toIndex.length > 0) {
36925
36982
  try {
36926
36983
  embeddings = await this.embedder.embed(toIndex.map((c) => c.content));
36927
36984
  } catch (err) {
36928
36985
  console.warn(`[memory-indexer] Embedding failed for ${absolutePath}:`, err);
36929
- return { indexed: 0, deleted: 0, skipped: existingHashes.size };
36986
+ return { indexed: 0, deleted: 0, skipped: existingChunkIds.size };
36930
36987
  }
36931
36988
  }
36932
36989
  if (toIndex.length > 0) {
@@ -36943,14 +37000,14 @@ var MemoryIndexer = class {
36943
37000
  }
36944
37001
  async indexDirectory(rootDir, opts = {}) {
36945
37002
  const totals = { indexed: 0, deleted: 0, skipped: 0 };
36946
- if (!fs5.existsSync(rootDir)) return totals;
37003
+ if (!fs6.existsSync(rootDir)) return totals;
36947
37004
  const relativeBase = opts.relativeBase ?? this.config.wikiDir;
36948
37005
  const excludeFiles = new Set(opts.excludeFiles ?? []);
36949
37006
  const files = [];
36950
37007
  const walk = (dirPath) => {
36951
37008
  let entries;
36952
37009
  try {
36953
- entries = fs5.readdirSync(dirPath, { withFileTypes: true });
37010
+ entries = fs6.readdirSync(dirPath, { withFileTypes: true });
36954
37011
  } catch (err) {
36955
37012
  console.warn(`[memory-indexer] Cannot scan directory ${dirPath}:`, err);
36956
37013
  return;
@@ -36967,9 +37024,7 @@ var MemoryIndexer = class {
36967
37024
  }
36968
37025
  };
36969
37026
  walk(rootDir);
36970
- const currentSources = new Set(
36971
- files.map((file) => path8.relative(relativeBase, file))
36972
- );
37027
+ const currentSources = new Set(files.map((file) => path8.relative(relativeBase, file)));
36973
37028
  for (const stale of this.store.indexedSources()) {
36974
37029
  if (!currentSources.has(stale)) {
36975
37030
  this.store.deleteBySource(stale);
@@ -37000,22 +37055,21 @@ var MemoryIndexer = class {
37000
37055
  if (bm25Ids.size > 0) {
37001
37056
  denseResults = denseResults.filter((result) => bm25Ids.has(result.id));
37002
37057
  }
37003
- const k = 60;
37004
37058
  const scoreMap = /* @__PURE__ */ new Map();
37005
37059
  denseResults.forEach((r, rank) => {
37006
- scoreMap.set(r.id, { dense: 1 / (k + rank + 1), bm25: 0 });
37060
+ scoreMap.set(r.id, { dense: 1 / (RRF_K + rank + 1), bm25: 0 });
37007
37061
  });
37008
37062
  bm25Results.forEach((r, rank) => {
37009
37063
  const existing = scoreMap.get(r.id);
37010
37064
  if (existing) {
37011
- existing.bm25 = 1 / (k + rank + 1);
37065
+ existing.bm25 = 1 / (RRF_K + rank + 1);
37012
37066
  } else {
37013
- scoreMap.set(r.id, { dense: 0, bm25: 1 / (k + rank + 1) });
37067
+ scoreMap.set(r.id, { dense: 0, bm25: 1 / (RRF_K + rank + 1) });
37014
37068
  }
37015
37069
  });
37016
37070
  const hasDense = this.store.vecAvailable && denseResults.length > 0;
37017
37071
  const numRetrievers = hasDense ? 2 : 1;
37018
- const maxScore = numRetrievers / 61;
37072
+ const maxScore = numRetrievers / (RRF_K + 1);
37019
37073
  const ranked = [...scoreMap.entries()].map(([id, scores]) => ({
37020
37074
  id,
37021
37075
  totalScore: scores.dense + scores.bm25,
@@ -37036,7 +37090,7 @@ var MemoryIndexer = class {
37036
37090
  const retriever = r.hasDense && r.hasBm25 ? "both" : r.hasDense ? "dense" : "bm25";
37037
37091
  let contentSource = "file";
37038
37092
  try {
37039
- chunk.content = fs5.readFileSync(path8.join(this.config.wikiDir, chunk.source), "utf8");
37093
+ chunk.content = fs6.readFileSync(path8.join(this.config.wikiDir, chunk.source), "utf8");
37040
37094
  } catch {
37041
37095
  contentSource = "fallback";
37042
37096
  }
@@ -37049,22 +37103,23 @@ var MemoryIndexer = class {
37049
37103
  const rawDir = path8.join(this.config.wikiDir, "raw");
37050
37104
  const savePath = path8.join(rawDir, `conv_save_${datePart}.md`);
37051
37105
  const lockPath = `${savePath}.lock`;
37052
- fs5.mkdirSync(rawDir, { recursive: true });
37053
- const acquired = await acquireLock(lockPath);
37106
+ fs6.mkdirSync(rawDir, { recursive: true });
37107
+ const acquired = await acquireLock(lockPath, LOCK_TIMEOUT_MS, LOCK_RETRY_MS);
37054
37108
  if (!acquired) {
37055
37109
  console.warn("[memory-indexer] Could not acquire write lock, writing anyway (fail-open)");
37056
37110
  }
37057
37111
  try {
37058
- fs5.appendFileSync(savePath, `
37112
+ fs6.appendFileSync(savePath, `
37059
37113
  ${content}
37060
37114
  `, "utf8");
37061
37115
  } finally {
37062
- if (acquired) releaseLock(lockPath);
37116
+ if (acquired) releaseLock(lockPath, { ignoreErrors: true });
37063
37117
  }
37064
37118
  return { indexed: 0, deleted: 0, skipped: 0 };
37065
37119
  }
37066
37120
  async startupIndex() {
37067
37121
  try {
37122
+ await this.embedder.initialize();
37068
37123
  await this.indexDirectory(path8.join(this.config.wikiDir, "compiled"), {
37069
37124
  relativeBase: this.config.wikiDir,
37070
37125
  excludeFiles: ["index.md", "log.md"]
@@ -37083,7 +37138,7 @@ ${content}
37083
37138
  // src/memory/store.ts
37084
37139
  var import_stopword = __toESM(require_stopword_cjs(), 1);
37085
37140
  import Database from "better-sqlite3";
37086
- import * as fs6 from "fs";
37141
+ import * as fs7 from "fs";
37087
37142
  import * as path9 from "path";
37088
37143
  var StoreError = class extends Error {
37089
37144
  constructor(message, cause) {
@@ -37095,7 +37150,7 @@ var StoreError = class extends Error {
37095
37150
  var MemoryStore = class {
37096
37151
  constructor(dbPath, config2) {
37097
37152
  this.config = config2;
37098
- fs6.mkdirSync(path9.dirname(dbPath), { recursive: true });
37153
+ fs7.mkdirSync(path9.dirname(dbPath), { recursive: true });
37099
37154
  this.db = this.openDatabase(dbPath);
37100
37155
  }
37101
37156
  db;
@@ -37111,7 +37166,7 @@ var MemoryStore = class {
37111
37166
  } catch (err) {
37112
37167
  console.error("[memory-store] DB open failed, recreating:", err);
37113
37168
  try {
37114
- fs6.unlinkSync(dbPath);
37169
+ fs7.unlinkSync(dbPath);
37115
37170
  } catch {
37116
37171
  }
37117
37172
  const db = new Database(dbPath);
@@ -37326,8 +37381,415 @@ var MemoryStore = class {
37326
37381
  }
37327
37382
  };
37328
37383
 
37384
+ // src/memory/digest/processor.ts
37385
+ import * as fs10 from "node:fs";
37386
+ import * as path13 from "node:path";
37387
+
37388
+ // src/memory/digest/constants.ts
37389
+ import * as path10 from "node:path";
37390
+ var DEFAULT_DIGEST_MODEL_ID = "qwen2.5-1.5b-instruct-q4";
37391
+ var DEFAULT_DIGEST_MAX_INPUT_CHARS = 3e3;
37392
+ var DEFAULT_DIGEST_TIMEOUT_MS = 12e4;
37393
+ var KIT_DIR = ".agent-kit";
37394
+ var WIKI_DIR = path10.join(KIT_DIR, "wiki");
37395
+ var WIKI_RAW_DIR = path10.join(WIKI_DIR, "raw");
37396
+ var PROVISIONAL_DIGEST_DIR = path10.join(WIKI_DIR, "compiled", "provisional", "conversation-digests");
37397
+ var DIGEST_LOCKFILE_REL_PATH = path10.join(KIT_DIR, "digest-worker.lock");
37398
+ var LLAMA_CONTEXT_SIZE = 4096;
37399
+ var LLAMA_MAX_GENERATED_TOKENS = 512;
37400
+ var LLAMA_TEMPERATURE = 0.1;
37401
+
37402
+ // src/memory/digest/files.ts
37403
+ import * as fs8 from "node:fs";
37404
+ import * as path11 from "node:path";
37405
+ function defaultProvisionalDigestDir(workspaceRoot2) {
37406
+ return path11.join(workspaceRoot2, PROVISIONAL_DIGEST_DIR);
37407
+ }
37408
+ function readConversationDigestInput(workspaceRoot2, inputPath) {
37409
+ const absoluteInput = path11.isAbsolute(inputPath) ? inputPath : path11.resolve(workspaceRoot2, inputPath);
37410
+ const content = fs8.readFileSync(absoluteInput, "utf8");
37411
+ return {
37412
+ sourcePath: path11.relative(workspaceRoot2, absoluteInput),
37413
+ content,
37414
+ contentHash: sha256Hex(content)
37415
+ };
37416
+ }
37417
+ function provisionalDigestPath(outDir, input) {
37418
+ const safeName = path11.basename(input.sourcePath).replace(/\.[^.]+$/, "").replace(/[^a-z0-9._-]+/gi, "-");
37419
+ return path11.join(outDir, `${input.contentHash.slice(0, 16)}-${safeName}.md`);
37420
+ }
37421
+ function findExistingProvisionalDigest(outDir, input) {
37422
+ const markdownPath = provisionalDigestPath(outDir, input);
37423
+ return fs8.existsSync(markdownPath) ? markdownPath : void 0;
37424
+ }
37425
+ function writeProvisionalDigestFile(outDir, input, markdown) {
37426
+ const markdownPath = provisionalDigestPath(outDir, input);
37427
+ atomicWriteTextFile(markdownPath, markdown);
37428
+ return markdownPath;
37429
+ }
37430
+ function writeConversationDigestSettings(workspaceRoot2, digest) {
37431
+ const current = loadProjectSettings(workspaceRoot2);
37432
+ const memory = current.memory ?? {};
37433
+ writeProjectSettings(workspaceRoot2, {
37434
+ memory: {
37435
+ ...memory,
37436
+ enabled: memory.enabled ?? true,
37437
+ conversationDigest: digest
37438
+ }
37439
+ });
37440
+ }
37441
+
37442
+ // src/memory/digest/model-registry.ts
37443
+ var DigestModelRegistryError = class extends Error {
37444
+ constructor(message) {
37445
+ super(message);
37446
+ this.name = "DigestModelRegistryError";
37447
+ }
37448
+ };
37449
+ var DIGEST_MODEL_REGISTRY = {
37450
+ "qwen2.5-0.5b-instruct-q4": {
37451
+ id: "qwen2.5-0.5b-instruct-q4",
37452
+ ggufUri: "hf:bartowski/Qwen2.5-0.5B-Instruct-GGUF/Qwen2.5-0.5B-Instruct-Q4_K_M.gguf",
37453
+ approxSizeBytes: 4e8,
37454
+ license: "Apache-2.0",
37455
+ sourceUrl: "https://huggingface.co/bartowski/Qwen2.5-0.5B-Instruct-GGUF",
37456
+ enabled: true
37457
+ },
37458
+ "qwen2.5-1.5b-instruct-q4": {
37459
+ id: "qwen2.5-1.5b-instruct-q4",
37460
+ ggufUri: "hf:bartowski/Qwen2.5-1.5B-Instruct-GGUF/Qwen2.5-1.5B-Instruct-Q4_K_M.gguf",
37461
+ approxSizeBytes: 1e9,
37462
+ license: "Apache-2.0",
37463
+ sourceUrl: "https://huggingface.co/bartowski/Qwen2.5-1.5B-Instruct-GGUF",
37464
+ enabled: true
37465
+ },
37466
+ "qwen2.5-3b-instruct-q4": {
37467
+ id: "qwen2.5-3b-instruct-q4",
37468
+ ggufUri: "hf:bartowski/Qwen2.5-3B-Instruct-GGUF/Qwen2.5-3B-Instruct-Q4_K_M.gguf",
37469
+ approxSizeBytes: 19e8,
37470
+ license: "Apache-2.0",
37471
+ sourceUrl: "https://huggingface.co/bartowski/Qwen2.5-3B-Instruct-GGUF",
37472
+ enabled: true
37473
+ }
37474
+ };
37475
+ function getDigestModelSpec(modelId) {
37476
+ if (!modelId.trim()) {
37477
+ throw new DigestModelRegistryError("Digest model id is required");
37478
+ }
37479
+ const spec = DIGEST_MODEL_REGISTRY[modelId];
37480
+ if (!spec) {
37481
+ throw new DigestModelRegistryError(`Unknown digest model: ${modelId}`);
37482
+ }
37483
+ if (spec.enabled !== true) {
37484
+ throw new DigestModelRegistryError(`Digest model is disabled: ${modelId}`);
37485
+ }
37486
+ if (!spec.ggufUri || !spec.sourceUrl) {
37487
+ throw new DigestModelRegistryError(`Digest model spec is incomplete: ${modelId}`);
37488
+ }
37489
+ return spec;
37490
+ }
37491
+
37492
+ // src/memory/digest/providers/llama-local.ts
37493
+ import * as fs9 from "node:fs";
37494
+ import * as path12 from "node:path";
37495
+ import { getLlama, LlamaChatSession, createModelDownloader } from "node-llama-cpp";
37496
+
37497
+ // src/utils/async.ts
37498
+ async function withTimeout(promise, timeoutMs, createError) {
37499
+ let timer;
37500
+ const timeout = new Promise((_resolve, reject) => {
37501
+ timer = setTimeout(() => {
37502
+ reject(createError());
37503
+ }, timeoutMs);
37504
+ });
37505
+ try {
37506
+ return await Promise.race([promise, timeout]);
37507
+ } finally {
37508
+ if (timer) clearTimeout(timer);
37509
+ }
37510
+ }
37511
+
37512
+ // src/memory/digest/providers/llama-local.ts
37513
+ var LlamaLocalDigestProviderError = class extends Error {
37514
+ constructor(message) {
37515
+ super(message);
37516
+ this.name = "LlamaLocalDigestProviderError";
37517
+ }
37518
+ };
37519
+ function titleFromSource(sourcePath) {
37520
+ return path12.basename(sourcePath).replace(/\.[^.]+$/, "").replace(/[_-]+/g, " ");
37521
+ }
37522
+ function trimConversationExport(content, maxInputChars) {
37523
+ const trimmed = content.trim();
37524
+ if (trimmed.length <= maxInputChars) return trimmed;
37525
+ return [
37526
+ "[Earlier conversation content omitted to fit the local digest context window.]",
37527
+ trimmed.slice(-maxInputChars)
37528
+ ].join("\n\n");
37529
+ }
37530
+ function buildPrompt(input, maxInputChars) {
37531
+ const conversationExport = trimConversationExport(input.content, maxInputChars);
37532
+ const sourceName = path12.basename(input.sourcePath);
37533
+ return [
37534
+ {
37535
+ role: "system",
37536
+ content: [
37537
+ "# ROLE",
37538
+ "You write short project-memory wiki pages from developer transcripts.",
37539
+ "Your output is temporary recall to prevent blind steps.",
37540
+ "",
37541
+ "# RULES",
37542
+ "1. Read the end of the export FIRST to discover the ultimate resolution.",
37543
+ "2. What the user says LAST ALWAYS overrides earlier agreements.",
37544
+ "3. Capture absolute engineering conclusions, never conversational pleasantries.",
37545
+ "4. Return Markdown only. Do not wrap output in code blocks (```markdown ... ```)."
37546
+ ].join("\n")
37547
+ },
37548
+ {
37549
+ role: "user",
37550
+ content: [
37551
+ "Generate a high-level project memory page. STRICTLY adhere to this architectural layout:",
37552
+ "",
37553
+ "<layout>",
37554
+ "# Digest: " + titleFromSource(input.sourcePath),
37555
+ "- **Ultimate Core Resolution**: [Describe the final working state of the feature and what problem it solves in 1-2 sentences. No conversational filler.]",
37556
+ "- **Architectural Changes**: [List 3-4 high-level engineering decisions made (e.g., shifts in hook timing, data isolation, atomic locks). Focus on systemic changes, NOT individual files.]",
37557
+ "- **Component State**: [Briefly state how the Plugin side and the MCP/CLI side now interact based on the final decision.]",
37558
+ "- **Considered & Rejected**: [List concepts proposed but discarded (e.g., initial hooks location, mocking strategies). Omit if empty.]",
37559
+ "</layout>",
37560
+ "",
37561
+ "# CRITICAL RULES:",
37562
+ "- DO NOT list individual file paths, line numbers, or specific test cases (EXCLUDE lists of files).",
37563
+ "- Focus entirely on the SYSTEM DESIGN and ARCHITECTURE that the next engineer needs to know.",
37564
+ "- Keep output under 300 tokens. Stop immediately after the last valid section.",
37565
+ "",
37566
+ "Source path: " + input.sourcePath,
37567
+ '<conversation_export format="memory-kit">',
37568
+ conversationExport,
37569
+ "</conversation_export>"
37570
+ ].join("\n")
37571
+ }
37572
+ ];
37573
+ }
37574
+ function stripCodeFence(text) {
37575
+ const trimmed = text.trim();
37576
+ const match = trimmed.match(/^```(?:markdown|md)?\s*\n([\s\S]*?)\n```$/i);
37577
+ return match ? match[1].trim() : trimmed;
37578
+ }
37579
+ function sanitizeConversationDigestMarkdown(generatedText) {
37580
+ return stripCodeFence(generatedText).trim() + "\n";
37581
+ }
37582
+ async function createLlamaLocalDigestProvider(modelId) {
37583
+ const spec = getDigestModelSpec(modelId);
37584
+ const modelCacheDir = path12.join(MODEL_CACHE_DIR, "llama");
37585
+ fs9.mkdirSync(modelCacheDir, { recursive: true });
37586
+ let modelFilePath;
37587
+ try {
37588
+ const downloader = await createModelDownloader({
37589
+ modelUri: spec.ggufUri,
37590
+ dirPath: modelCacheDir
37591
+ });
37592
+ modelFilePath = downloader.entrypointFilePath;
37593
+ if (!fs9.existsSync(modelFilePath)) {
37594
+ await downloader.download();
37595
+ }
37596
+ } catch (err) {
37597
+ const message = err instanceof Error ? err.message : String(err);
37598
+ throw new LlamaLocalDigestProviderError(`Failed to download model: ${message}`);
37599
+ }
37600
+ let llama;
37601
+ let model;
37602
+ try {
37603
+ llama = await getLlama();
37604
+ model = await llama.loadModel({ modelPath: modelFilePath });
37605
+ } catch (err) {
37606
+ const message = err instanceof Error ? err.message : String(err);
37607
+ throw new LlamaLocalDigestProviderError(`Failed to load model: ${message}`);
37608
+ }
37609
+ return {
37610
+ id: "llama-local",
37611
+ async dispose() {
37612
+ await model.dispose();
37613
+ },
37614
+ async generateDigestMarkdown(input, options) {
37615
+ const messages = buildPrompt(input, options.maxInputChars);
37616
+ const systemContent = messages[0].content;
37617
+ const userContent = messages[1].content;
37618
+ const context = await model.createContext({ contextSize: LLAMA_CONTEXT_SIZE });
37619
+ try {
37620
+ const session = new LlamaChatSession({
37621
+ contextSequence: context.getSequence(),
37622
+ systemPrompt: systemContent
37623
+ });
37624
+ const response = await withTimeout(
37625
+ session.prompt(userContent, {
37626
+ maxTokens: LLAMA_MAX_GENERATED_TOKENS,
37627
+ temperature: LLAMA_TEMPERATURE
37628
+ }),
37629
+ options.timeoutMs,
37630
+ () => new LlamaLocalDigestProviderError(`Llama provider timed out after ${options.timeoutMs}ms`)
37631
+ );
37632
+ return sanitizeConversationDigestMarkdown(response);
37633
+ } finally {
37634
+ await context.dispose();
37635
+ }
37636
+ }
37637
+ };
37638
+ }
37639
+
37640
+ // src/memory/digest/processor.ts
37641
+ function digestLockPath(workspaceRoot2) {
37642
+ return path13.join(workspaceRoot2, DIGEST_LOCKFILE_REL_PATH);
37643
+ }
37644
+ async function indexProvisionalDigestFile(workspaceRoot2, markdownPath) {
37645
+ try {
37646
+ const settings2 = loadProjectSettings(workspaceRoot2);
37647
+ const config2 = resolveMemoryConfig(settings2, workspaceRoot2);
37648
+ const store = new MemoryStore(path13.join(config2.wikiDir, "index.db"), config2);
37649
+ const embedder = { embed: async (_texts) => [], initialize: async () => {
37650
+ } };
37651
+ const indexer = new MemoryIndexer(store, embedder, config2);
37652
+ await indexer.indexFile(markdownPath);
37653
+ return { indexed: true };
37654
+ } catch (err) {
37655
+ const message = err instanceof Error ? err.message : String(err);
37656
+ return { indexed: false, error: message };
37657
+ }
37658
+ }
37659
+ async function digestConversationFile(options) {
37660
+ const outDir = options.outDir ? path13.resolve(options.workspaceRoot, options.outDir) : defaultProvisionalDigestDir(options.workspaceRoot);
37661
+ const input = readConversationDigestInput(options.workspaceRoot, options.inputPath);
37662
+ const existingPath = findExistingProvisionalDigest(outDir, input);
37663
+ if (existingPath) {
37664
+ const indexResult = await indexProvisionalDigestFile(options.workspaceRoot, existingPath);
37665
+ return {
37666
+ markdown: existingPath,
37667
+ status: "provisional",
37668
+ contentHash: input.contentHash,
37669
+ skipped: true,
37670
+ ...indexResult
37671
+ };
37672
+ }
37673
+ const provider = await createLlamaLocalDigestProvider(options.modelId);
37674
+ try {
37675
+ const markdown = await provider.generateDigestMarkdown(input, {
37676
+ modelId: options.modelId,
37677
+ maxInputChars: options.maxInputChars ?? DEFAULT_DIGEST_MAX_INPUT_CHARS,
37678
+ timeoutMs: options.timeoutMs ?? DEFAULT_DIGEST_TIMEOUT_MS
37679
+ });
37680
+ const markdownPath = writeProvisionalDigestFile(outDir, input, markdown);
37681
+ const indexResult = await indexProvisionalDigestFile(options.workspaceRoot, markdownPath);
37682
+ return {
37683
+ markdown: markdownPath,
37684
+ status: "provisional",
37685
+ contentHash: input.contentHash,
37686
+ skipped: false,
37687
+ ...indexResult
37688
+ };
37689
+ } finally {
37690
+ await provider.dispose?.();
37691
+ }
37692
+ }
37693
+ async function digestPendingConversations({
37694
+ workspaceRoot: workspaceRoot2,
37695
+ digestFn = digestConversationFile
37696
+ }) {
37697
+ const digestConfig = resolveConversationDigestConfig(loadProjectSettings(workspaceRoot2));
37698
+ if (!digestConfig || digestConfig.enabled === false) {
37699
+ return { ok: true, initialized: false, action: "noop", reason: "not-initialized" };
37700
+ }
37701
+ const lockPath = digestLockPath(workspaceRoot2);
37702
+ if (!tryAcquireProcessLock(lockPath)) {
37703
+ return { ok: true, initialized: true, action: "noop", reason: "locked" };
37704
+ }
37705
+ try {
37706
+ const rawDir = path13.join(workspaceRoot2, WIKI_RAW_DIR);
37707
+ let files;
37708
+ try {
37709
+ files = fs10.readdirSync(rawDir).filter((f) => f.startsWith("conv_") && f.endsWith(".md")).map((f) => path13.join(rawDir, f)).sort((a, b) => fs10.statSync(a).mtimeMs - fs10.statSync(b).mtimeMs);
37710
+ } catch (err) {
37711
+ if (err.code === "ENOENT") {
37712
+ files = [];
37713
+ } else {
37714
+ throw err;
37715
+ }
37716
+ }
37717
+ const outDir = defaultProvisionalDigestDir(workspaceRoot2);
37718
+ const candidates = files.filter((f) => {
37719
+ try {
37720
+ const input = readConversationDigestInput(workspaceRoot2, f);
37721
+ return !findExistingProvisionalDigest(outDir, input);
37722
+ } catch {
37723
+ return false;
37724
+ }
37725
+ });
37726
+ if (candidates.length === 0) {
37727
+ return { ok: true, initialized: true, action: "noop", reason: "no-pending" };
37728
+ }
37729
+ let count = 0;
37730
+ let skipped = 0;
37731
+ let errors = 0;
37732
+ for (const filePath of candidates) {
37733
+ try {
37734
+ const result = await digestFn({
37735
+ workspaceRoot: workspaceRoot2,
37736
+ inputPath: filePath,
37737
+ modelId: digestConfig.modelId
37738
+ });
37739
+ if (result.skipped) {
37740
+ skipped++;
37741
+ } else {
37742
+ count++;
37743
+ }
37744
+ } catch {
37745
+ errors++;
37746
+ }
37747
+ }
37748
+ return { ok: true, initialized: true, action: "digested", count, skipped, errors };
37749
+ } catch (err) {
37750
+ const message = err instanceof Error ? err.message : String(err);
37751
+ return { ok: false, initialized: true, action: "error", error: message };
37752
+ } finally {
37753
+ releaseLock(lockPath);
37754
+ }
37755
+ }
37756
+ async function initializeConversationDigestModel(input) {
37757
+ try {
37758
+ getDigestModelSpec(input.modelId);
37759
+ if (input.allowDownload !== true) {
37760
+ return {
37761
+ initialized: false,
37762
+ modelId: input.modelId,
37763
+ error: "Digest model initialization requires explicit download permission"
37764
+ };
37765
+ }
37766
+ const loadedProvider = await createLlamaLocalDigestProvider(input.modelId);
37767
+ await loadedProvider?.dispose?.();
37768
+ const initializedAt = (/* @__PURE__ */ new Date()).toISOString();
37769
+ writeConversationDigestSettings(input.workspaceRoot, {
37770
+ enabled: input.enabled ?? true,
37771
+ initialized: true,
37772
+ modelId: input.modelId,
37773
+ initializedAt
37774
+ });
37775
+ await digestPendingConversations({ workspaceRoot: input.workspaceRoot });
37776
+ return {
37777
+ initialized: true,
37778
+ modelId: input.modelId,
37779
+ initializedAt
37780
+ };
37781
+ } catch (err) {
37782
+ const message = err instanceof Error ? err.message : String(err);
37783
+ return {
37784
+ initialized: false,
37785
+ modelId: input.modelId,
37786
+ error: message
37787
+ };
37788
+ }
37789
+ }
37790
+
37329
37791
  // src/tools/memory.ts
37330
- function registerMemoryToolHandlers(server2, indexer, store, config2) {
37792
+ function registerMemoryToolHandlers(server2, indexer, store, config2, workspaceRoot2 = process.cwd()) {
37331
37793
  server2.tool(
37332
37794
  "kit_memory_search",
37333
37795
  "Search persistent memory for context relevant to the current question. Returns semantically relevant results using hybrid dense+BM25+RRF search.",
@@ -37377,14 +37839,42 @@ ${content}`;
37377
37839
  }
37378
37840
  }
37379
37841
  );
37842
+ server2.tool(
37843
+ "kit_memory_digest_init",
37844
+ "Initialize or toggle local conversation digesting. Downloads/cache-loads the pinned local model only when explicitly invoked.",
37845
+ {
37846
+ model_id: external_exports.string().optional().describe("Digest model id"),
37847
+ enabled: external_exports.boolean().optional().describe("Enable or disable conversation digesting")
37848
+ },
37849
+ async ({ model_id, enabled }) => {
37850
+ try {
37851
+ const existingConfig = resolveConversationDigestConfig(loadProjectSettings(workspaceRoot2));
37852
+ const result = await initializeConversationDigestModel({
37853
+ workspaceRoot: workspaceRoot2,
37854
+ modelId: model_id ?? existingConfig?.modelId ?? DEFAULT_DIGEST_MODEL_ID,
37855
+ allowDownload: true,
37856
+ enabled
37857
+ });
37858
+ return mcpJson({
37859
+ initialized: result.initialized,
37860
+ model_id: result.modelId,
37861
+ initialized_at: result.initializedAt,
37862
+ error: result.error
37863
+ });
37864
+ } catch (err) {
37865
+ const message = err instanceof Error ? err.message : String(err);
37866
+ return mcpJson({ initialized: false, error: message });
37867
+ }
37868
+ }
37869
+ );
37380
37870
  }
37381
37871
  function registerMemoryTools(server2, settings2, workspaceRoot2) {
37382
37872
  if (settings2.memory?.enabled !== true) return null;
37383
37873
  const config2 = resolveMemoryConfig(settings2, workspaceRoot2);
37384
- const store = new MemoryStore(path10.join(config2.wikiDir, "index.db"), config2);
37874
+ const store = new MemoryStore(path14.join(config2.wikiDir, "index.db"), config2);
37385
37875
  const embedder = new Embedder(config2.embeddingModel);
37386
37876
  const indexer = new MemoryIndexer(store, embedder, config2);
37387
- registerMemoryToolHandlers(server2, indexer, store, config2);
37877
+ registerMemoryToolHandlers(server2, indexer, store, config2, workspaceRoot2);
37388
37878
  return indexer;
37389
37879
  }
37390
37880