@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.
- package/dist/__tests__/temp-dir.d.ts +4 -0
- package/dist/__tests__/temp-dir.js +20 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +9 -0
- package/dist/cli/memory.d.ts +1 -0
- package/dist/cli/memory.js +77 -0
- package/dist/kit-cli.d.ts +2 -0
- package/dist/kit-cli.js +16205 -0
- package/dist/kit-server.js +677 -187
- package/dist/memory/__tests__/digest-llama-local.test.d.ts +1 -0
- package/dist/memory/__tests__/digest-llama-local.test.js +33 -0
- package/dist/memory/__tests__/digest-pending.test.d.ts +1 -0
- package/dist/memory/__tests__/digest-pending.test.js +164 -0
- package/dist/memory/__tests__/digest-processor.test.d.ts +1 -0
- package/dist/memory/__tests__/digest-processor.test.js +34 -0
- package/dist/memory/__tests__/digest-worker.test.d.ts +1 -0
- package/dist/memory/__tests__/digest-worker.test.js +46 -0
- package/dist/memory/__tests__/embedder.test.d.ts +1 -0
- package/dist/memory/__tests__/embedder.test.js +14 -0
- package/dist/memory/__tests__/indexer.test.js +7 -12
- package/dist/memory/__tests__/lockfile.test.d.ts +1 -0
- package/dist/memory/__tests__/lockfile.test.js +81 -0
- package/dist/memory/__tests__/store.test.js +15 -3
- package/dist/memory/chunker.js +3 -5
- package/dist/memory/constants.d.ts +5 -0
- package/dist/memory/constants.js +12 -0
- package/dist/memory/digest/constants.d.ts +14 -0
- package/dist/memory/digest/constants.js +15 -0
- package/dist/memory/digest/files.d.ts +7 -0
- package/dist/memory/digest/files.js +45 -0
- package/dist/memory/digest/model-registry.d.ts +6 -0
- package/dist/memory/digest/model-registry.js +48 -0
- package/dist/memory/digest/processor.d.ts +7 -0
- package/dist/memory/digest/processor.js +171 -0
- package/dist/memory/digest/providers/llama-local.d.ts +7 -0
- package/dist/memory/digest/providers/llama-local.js +136 -0
- package/dist/memory/digest/types.d.ts +85 -0
- package/dist/memory/digest/types.js +1 -0
- package/dist/memory/digest/worker.d.ts +3 -0
- package/dist/memory/digest/worker.js +51 -0
- package/dist/memory/embedder.d.ts +2 -0
- package/dist/memory/embedder.js +12 -4
- package/dist/memory/index.d.ts +1 -1
- package/dist/memory/index.js +1 -1
- package/dist/memory/indexer.d.ts +5 -2
- package/dist/memory/indexer.js +15 -37
- package/dist/memory/store.js +6 -18
- package/dist/memory/types.d.ts +2 -1
- package/dist/memory/types.js +1 -9
- package/dist/tools/__tests__/cli-digest-pending.test.d.ts +1 -0
- package/dist/tools/__tests__/cli-digest-pending.test.js +51 -0
- package/dist/tools/__tests__/cli-memory.test.d.ts +1 -0
- package/dist/tools/__tests__/cli-memory.test.js +22 -0
- package/dist/tools/__tests__/digest-init.test.d.ts +1 -0
- package/dist/tools/__tests__/digest-init.test.js +32 -0
- package/dist/tools/__tests__/memory.test.js +28 -6
- package/dist/tools/agent.js +2 -13
- package/dist/tools/config.d.ts +4 -4
- package/dist/tools/config.js +21 -13
- package/dist/tools/core.js +1 -3
- package/dist/tools/handoffs.js +1 -2
- package/dist/tools/integration.js +2 -8
- package/dist/tools/memory.d.ts +1 -1
- package/dist/tools/memory.js +29 -3
- package/dist/tools/security.js +2 -6
- package/dist/utils/args.d.ts +7 -0
- package/dist/utils/args.js +31 -0
- package/dist/utils/async.d.ts +1 -0
- package/dist/utils/async.js +15 -0
- package/dist/utils/credentials.d.ts +3 -3
- package/dist/utils/credentials.js +5 -7
- package/dist/utils/files.d.ts +8 -0
- package/dist/utils/files.js +77 -0
- package/dist/utils/hash.d.ts +1 -0
- package/dist/utils/hash.js +4 -0
- package/dist/utils/json.d.ts +2 -0
- package/dist/utils/json.js +13 -0
- package/dist/utils/parser.js +2 -6
- package/dist/utils/paths.d.ts +4 -0
- package/dist/utils/paths.js +6 -0
- package/package.json +7 -5
package/dist/kit-server.js
CHANGED
|
@@ -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 =
|
|
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
|
|
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(
|
|
3233
|
-
let input =
|
|
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 [
|
|
3433
|
-
wsComponent.path =
|
|
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
|
|
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,
|
|
3598
|
+
function resolveComponent(base, relative4, options, skipNormalization) {
|
|
3599
3599
|
const target = {};
|
|
3600
3600
|
if (!skipNormalization) {
|
|
3601
3601
|
base = parse3(serialize(base, options), options);
|
|
3602
|
-
|
|
3602
|
+
relative4 = parse3(serialize(relative4, options), options);
|
|
3603
3603
|
}
|
|
3604
3604
|
options = options || {};
|
|
3605
|
-
if (!options.tolerant &&
|
|
3606
|
-
target.scheme =
|
|
3607
|
-
target.userinfo =
|
|
3608
|
-
target.host =
|
|
3609
|
-
target.port =
|
|
3610
|
-
target.path = removeDotSegments(
|
|
3611
|
-
target.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 (
|
|
3614
|
-
target.userinfo =
|
|
3615
|
-
target.host =
|
|
3616
|
-
target.port =
|
|
3617
|
-
target.path = removeDotSegments(
|
|
3618
|
-
target.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 (!
|
|
3620
|
+
if (!relative4.path) {
|
|
3621
3621
|
target.path = base.path;
|
|
3622
|
-
if (
|
|
3623
|
-
target.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 (
|
|
3629
|
-
target.path = removeDotSegments(
|
|
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 = "/" +
|
|
3632
|
+
target.path = "/" + relative4.path;
|
|
3633
3633
|
} else if (!base.path) {
|
|
3634
|
-
target.path =
|
|
3634
|
+
target.path = relative4.path;
|
|
3635
3635
|
} else {
|
|
3636
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
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 =
|
|
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 =
|
|
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:
|
|
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,
|
|
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,
|
|
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:
|
|
22175
|
-
const fullPath = [...
|
|
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,
|
|
22291
|
+
constructor(parent, value, path15, key) {
|
|
22292
22292
|
this._cachedPath = [];
|
|
22293
22293
|
this.parent = parent;
|
|
22294
22294
|
this.data = value;
|
|
22295
|
-
this._path =
|
|
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,
|
|
25934
|
-
if (!
|
|
25933
|
+
function getElementAtPath(obj, path15) {
|
|
25934
|
+
if (!path15)
|
|
25935
25935
|
return obj;
|
|
25936
|
-
return
|
|
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(
|
|
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(
|
|
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((
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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(
|
|
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((
|
|
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((
|
|
35875
|
+
return new Promise((resolve5) => {
|
|
35876
35876
|
const json = serializeMessage(message);
|
|
35877
35877
|
if (this._stdout.write(json)) {
|
|
35878
|
-
|
|
35878
|
+
resolve5();
|
|
35879
35879
|
} else {
|
|
35880
|
-
this._stdout.once("drain",
|
|
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
|
|
35888
|
-
import * as
|
|
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
|
|
35903
|
-
import * as
|
|
35902
|
+
import * as fs2 from "fs";
|
|
35903
|
+
import * as path2 from "path";
|
|
35904
35904
|
|
|
35905
|
-
// src/memory/
|
|
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 :
|
|
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 =
|
|
35928
|
-
if (
|
|
35996
|
+
const settingsPath = path2.join(projectDir, ".agent-kit", "settings.json");
|
|
35997
|
+
if (fs2.existsSync(settingsPath)) {
|
|
35929
35998
|
try {
|
|
35930
|
-
return JSON.parse(
|
|
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
|
-
// ~/.
|
|
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
|
|
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 =
|
|
36100
|
+
const relativePath = path3.join(".agent-kit", "handoffs", featureSlug, `${canonicalType}.md`);
|
|
36022
36101
|
return {
|
|
36023
36102
|
featureSlug,
|
|
36024
36103
|
canonicalType,
|
|
36025
|
-
filePath:
|
|
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 =
|
|
36051
|
-
if (!
|
|
36052
|
-
|
|
36129
|
+
const handoffDir = path4.dirname(location.filePath);
|
|
36130
|
+
if (!fs3.existsSync(handoffDir)) {
|
|
36131
|
+
fs3.mkdirSync(handoffDir, { recursive: true });
|
|
36053
36132
|
}
|
|
36054
|
-
|
|
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
|
|
36067
|
-
import * as
|
|
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
|
|
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 =
|
|
36077
|
-
const root =
|
|
36078
|
-
if (resolved !== root && !resolved.startsWith(root +
|
|
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(
|
|
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 =
|
|
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 =
|
|
36162
|
-
|
|
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 =
|
|
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
|
-
|
|
36171
|
-
const logStream =
|
|
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:
|
|
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 =
|
|
36194
|
-
if (
|
|
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 =
|
|
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((
|
|
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)
|
|
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
|
|
36360
|
+
import { writeFileSync as writeFileSync4 } from "fs";
|
|
36296
36361
|
|
|
36297
36362
|
// src/utils/credentials.ts
|
|
36298
|
-
import * as
|
|
36363
|
+
import * as fs5 from "fs";
|
|
36364
|
+
|
|
36365
|
+
// src/utils/paths.ts
|
|
36299
36366
|
import * as os from "os";
|
|
36300
|
-
import * as
|
|
36301
|
-
var
|
|
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 (!
|
|
36398
|
+
if (!fs5.existsSync(CREDENTIALS_FILE)) {
|
|
36327
36399
|
cachedCredentials = {};
|
|
36328
36400
|
return cachedCredentials;
|
|
36329
36401
|
}
|
|
36330
36402
|
try {
|
|
36331
|
-
const stat =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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({
|
|
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
|
|
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
|
|
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
|
|
36968
|
+
const existingChunkIds = this.store.hashesBySource(source);
|
|
36912
36969
|
let text;
|
|
36913
36970
|
try {
|
|
36914
|
-
text =
|
|
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:
|
|
36974
|
+
return { indexed: 0, deleted: 0, skipped: existingChunkIds.size };
|
|
36918
36975
|
}
|
|
36919
36976
|
const allChunks = chunkMarkdown(text, source, this.config);
|
|
36920
|
-
const
|
|
36921
|
-
const toIndex = allChunks.filter((
|
|
36922
|
-
const toDelete = [...
|
|
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:
|
|
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 (!
|
|
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 =
|
|
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 / (
|
|
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 / (
|
|
37065
|
+
existing.bm25 = 1 / (RRF_K + rank + 1);
|
|
37012
37066
|
} else {
|
|
37013
|
-
scoreMap.set(r.id, { dense: 0, bm25: 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 /
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|