@hanhnd/agent-kit 1.0.28 → 1.0.29
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/kit-server.js +840 -85
- package/dist/memory/__tests__/chunker.test.d.ts +1 -0
- package/dist/memory/__tests__/chunker.test.js +105 -0
- package/dist/memory/__tests__/indexer.test.d.ts +1 -0
- package/dist/memory/__tests__/indexer.test.js +332 -0
- package/dist/memory/__tests__/store.test.d.ts +1 -0
- package/dist/memory/__tests__/store.test.js +133 -0
- package/dist/memory/chunker.d.ts +4 -0
- package/dist/memory/chunker.js +147 -0
- package/dist/memory/embedder.d.ts +13 -0
- package/dist/memory/embedder.js +68 -0
- package/dist/memory/index.d.ts +5 -0
- package/dist/memory/index.js +4 -0
- package/dist/memory/indexer.d.ts +20 -0
- package/dist/memory/indexer.js +222 -0
- package/dist/memory/store.d.ts +29 -0
- package/dist/memory/store.js +268 -0
- package/dist/memory/types.d.ts +30 -0
- package/dist/memory/types.js +9 -0
- package/dist/tools/__tests__/memory.test.d.ts +1 -0
- package/dist/tools/__tests__/memory.test.js +185 -0
- package/dist/tools/config.d.ts +7 -0
- package/dist/tools/config.js +15 -0
- package/dist/tools/memory.d.ts +16 -0
- package/dist/tools/memory.js +75 -0
- package/dist/utils/utils.js +3 -1
- package/package.json +8 -3
package/dist/kit-server.js
CHANGED
|
@@ -6,7 +6,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var
|
|
9
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
10
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
11
|
+
}) : x)(function(x) {
|
|
12
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
13
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
14
|
+
});
|
|
15
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
10
16
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
11
17
|
};
|
|
12
18
|
var __export = (target, all) => {
|
|
@@ -3223,8 +3229,8 @@ var require_utils = __commonJS({
|
|
|
3223
3229
|
}
|
|
3224
3230
|
return ind;
|
|
3225
3231
|
}
|
|
3226
|
-
function removeDotSegments(
|
|
3227
|
-
let input =
|
|
3232
|
+
function removeDotSegments(path11) {
|
|
3233
|
+
let input = path11;
|
|
3228
3234
|
const output = [];
|
|
3229
3235
|
let nextSlash = -1;
|
|
3230
3236
|
let len = 0;
|
|
@@ -3423,8 +3429,8 @@ var require_schemes = __commonJS({
|
|
|
3423
3429
|
wsComponent.secure = void 0;
|
|
3424
3430
|
}
|
|
3425
3431
|
if (wsComponent.resourceName) {
|
|
3426
|
-
const [
|
|
3427
|
-
wsComponent.path =
|
|
3432
|
+
const [path11, query] = wsComponent.resourceName.split("?");
|
|
3433
|
+
wsComponent.path = path11 && path11 !== "/" ? path11 : void 0;
|
|
3428
3434
|
wsComponent.query = query;
|
|
3429
3435
|
wsComponent.resourceName = void 0;
|
|
3430
3436
|
}
|
|
@@ -3589,49 +3595,49 @@ var require_fast_uri = __commonJS({
|
|
|
3589
3595
|
schemelessOptions.skipEscape = true;
|
|
3590
3596
|
return serialize(resolved, schemelessOptions);
|
|
3591
3597
|
}
|
|
3592
|
-
function resolveComponent(base,
|
|
3598
|
+
function resolveComponent(base, relative3, options, skipNormalization) {
|
|
3593
3599
|
const target = {};
|
|
3594
3600
|
if (!skipNormalization) {
|
|
3595
3601
|
base = parse3(serialize(base, options), options);
|
|
3596
|
-
|
|
3602
|
+
relative3 = parse3(serialize(relative3, options), options);
|
|
3597
3603
|
}
|
|
3598
3604
|
options = options || {};
|
|
3599
|
-
if (!options.tolerant &&
|
|
3600
|
-
target.scheme =
|
|
3601
|
-
target.userinfo =
|
|
3602
|
-
target.host =
|
|
3603
|
-
target.port =
|
|
3604
|
-
target.path = removeDotSegments(
|
|
3605
|
-
target.query =
|
|
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;
|
|
3606
3612
|
} else {
|
|
3607
|
-
if (
|
|
3608
|
-
target.userinfo =
|
|
3609
|
-
target.host =
|
|
3610
|
-
target.port =
|
|
3611
|
-
target.path = removeDotSegments(
|
|
3612
|
-
target.query =
|
|
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
3619
|
} else {
|
|
3614
|
-
if (!
|
|
3620
|
+
if (!relative3.path) {
|
|
3615
3621
|
target.path = base.path;
|
|
3616
|
-
if (
|
|
3617
|
-
target.query =
|
|
3622
|
+
if (relative3.query !== void 0) {
|
|
3623
|
+
target.query = relative3.query;
|
|
3618
3624
|
} else {
|
|
3619
3625
|
target.query = base.query;
|
|
3620
3626
|
}
|
|
3621
3627
|
} else {
|
|
3622
|
-
if (
|
|
3623
|
-
target.path = removeDotSegments(
|
|
3628
|
+
if (relative3.path[0] === "/") {
|
|
3629
|
+
target.path = removeDotSegments(relative3.path);
|
|
3624
3630
|
} else {
|
|
3625
3631
|
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
|
|
3626
|
-
target.path = "/" +
|
|
3632
|
+
target.path = "/" + relative3.path;
|
|
3627
3633
|
} else if (!base.path) {
|
|
3628
|
-
target.path =
|
|
3634
|
+
target.path = relative3.path;
|
|
3629
3635
|
} else {
|
|
3630
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
3636
|
+
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
|
|
3631
3637
|
}
|
|
3632
3638
|
target.path = removeDotSegments(target.path);
|
|
3633
3639
|
}
|
|
3634
|
-
target.query =
|
|
3640
|
+
target.query = relative3.query;
|
|
3635
3641
|
}
|
|
3636
3642
|
target.userinfo = base.userinfo;
|
|
3637
3643
|
target.host = base.host;
|
|
@@ -3639,7 +3645,7 @@ var require_fast_uri = __commonJS({
|
|
|
3639
3645
|
}
|
|
3640
3646
|
target.scheme = base.scheme;
|
|
3641
3647
|
}
|
|
3642
|
-
target.fragment =
|
|
3648
|
+
target.fragment = relative3.fragment;
|
|
3643
3649
|
return target;
|
|
3644
3650
|
}
|
|
3645
3651
|
function equal(uriA, uriB, options) {
|
|
@@ -6786,12 +6792,12 @@ var require_dist = __commonJS({
|
|
|
6786
6792
|
throw new Error(`Unknown format "${name}"`);
|
|
6787
6793
|
return f;
|
|
6788
6794
|
};
|
|
6789
|
-
function addFormats(ajv, list,
|
|
6795
|
+
function addFormats(ajv, list, fs7, exportName) {
|
|
6790
6796
|
var _a;
|
|
6791
6797
|
var _b;
|
|
6792
6798
|
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
6793
6799
|
for (const f of list)
|
|
6794
|
-
ajv.addFormat(f,
|
|
6800
|
+
ajv.addFormat(f, fs7[f]);
|
|
6795
6801
|
}
|
|
6796
6802
|
module.exports = exports = formatsPlugin;
|
|
6797
6803
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -7277,8 +7283,8 @@ function getErrorMap() {
|
|
|
7277
7283
|
|
|
7278
7284
|
// ../node_modules/zod/v3/helpers/parseUtil.js
|
|
7279
7285
|
var makeIssue = (params) => {
|
|
7280
|
-
const { data, path:
|
|
7281
|
-
const fullPath = [...
|
|
7286
|
+
const { data, path: path11, errorMaps, issueData } = params;
|
|
7287
|
+
const fullPath = [...path11, ...issueData.path || []];
|
|
7282
7288
|
const fullIssue = {
|
|
7283
7289
|
...issueData,
|
|
7284
7290
|
path: fullPath
|
|
@@ -7394,11 +7400,11 @@ var errorUtil;
|
|
|
7394
7400
|
|
|
7395
7401
|
// ../node_modules/zod/v3/types.js
|
|
7396
7402
|
var ParseInputLazyPath = class {
|
|
7397
|
-
constructor(parent, value,
|
|
7403
|
+
constructor(parent, value, path11, key) {
|
|
7398
7404
|
this._cachedPath = [];
|
|
7399
7405
|
this.parent = parent;
|
|
7400
7406
|
this.data = value;
|
|
7401
|
-
this._path =
|
|
7407
|
+
this._path = path11;
|
|
7402
7408
|
this._key = key;
|
|
7403
7409
|
}
|
|
7404
7410
|
get path() {
|
|
@@ -11036,10 +11042,10 @@ function assignProp(target, prop, value) {
|
|
|
11036
11042
|
configurable: true
|
|
11037
11043
|
});
|
|
11038
11044
|
}
|
|
11039
|
-
function getElementAtPath(obj,
|
|
11040
|
-
if (!
|
|
11045
|
+
function getElementAtPath(obj, path11) {
|
|
11046
|
+
if (!path11)
|
|
11041
11047
|
return obj;
|
|
11042
|
-
return
|
|
11048
|
+
return path11.reduce((acc, key) => acc?.[key], obj);
|
|
11043
11049
|
}
|
|
11044
11050
|
function promiseAllObject(promisesObj) {
|
|
11045
11051
|
const keys = Object.keys(promisesObj);
|
|
@@ -11359,11 +11365,11 @@ function aborted(x, startIndex = 0) {
|
|
|
11359
11365
|
}
|
|
11360
11366
|
return false;
|
|
11361
11367
|
}
|
|
11362
|
-
function prefixIssues(
|
|
11368
|
+
function prefixIssues(path11, issues) {
|
|
11363
11369
|
return issues.map((iss) => {
|
|
11364
11370
|
var _a;
|
|
11365
11371
|
(_a = iss).path ?? (_a.path = []);
|
|
11366
|
-
iss.path.unshift(
|
|
11372
|
+
iss.path.unshift(path11);
|
|
11367
11373
|
return iss;
|
|
11368
11374
|
});
|
|
11369
11375
|
}
|
|
@@ -20990,12 +20996,12 @@ var StdioServerTransport = class {
|
|
|
20990
20996
|
};
|
|
20991
20997
|
|
|
20992
20998
|
// src/tools/core.ts
|
|
20993
|
-
import * as
|
|
20994
|
-
import * as
|
|
20999
|
+
import * as fs2 from "fs";
|
|
21000
|
+
import * as path3 from "path";
|
|
20995
21001
|
|
|
20996
21002
|
// src/utils/utils.ts
|
|
20997
21003
|
function getWorkspaceRoot() {
|
|
20998
|
-
return process.env.WORKSPACE_DIR || process.env.
|
|
21004
|
+
return process.env.WORKSPACE_DIR || process.env.CLAUDE_PROJECT_DIR || process.env.CODEX_PROJECT_DIR || process.env.GEMINI_PROJECT_DIR || process.env.CLAUDE_WORKSPACE || process.env.CODEX_WORKSPACE || process.env.GEMINI_WORKSPACE || process.env.INIT_CWD || process.env.PWD || process.cwd();
|
|
20999
21005
|
}
|
|
21000
21006
|
function mcpText(text) {
|
|
21001
21007
|
return { content: [{ type: "text", text }] };
|
|
@@ -21005,6 +21011,41 @@ function mcpJson(obj) {
|
|
|
21005
21011
|
}
|
|
21006
21012
|
|
|
21007
21013
|
// src/tools/config.ts
|
|
21014
|
+
import * as fs from "fs";
|
|
21015
|
+
import * as path from "path";
|
|
21016
|
+
|
|
21017
|
+
// src/memory/types.ts
|
|
21018
|
+
var DEFAULT_MEMORY_CONFIG = {
|
|
21019
|
+
enabled: false,
|
|
21020
|
+
wikiDir: "",
|
|
21021
|
+
topK: 5,
|
|
21022
|
+
chunkSize: 1500,
|
|
21023
|
+
overlapLines: 2,
|
|
21024
|
+
embeddingModel: "Xenova/bge-small-en-v1.5",
|
|
21025
|
+
vectorDimension: 384
|
|
21026
|
+
};
|
|
21027
|
+
|
|
21028
|
+
// src/tools/config.ts
|
|
21029
|
+
function resolveMemoryConfig(settings2, workspaceRoot2) {
|
|
21030
|
+
const override = settings2.memory ?? {};
|
|
21031
|
+
return {
|
|
21032
|
+
...DEFAULT_MEMORY_CONFIG,
|
|
21033
|
+
...override,
|
|
21034
|
+
enabled: override.enabled ?? DEFAULT_MEMORY_CONFIG.enabled,
|
|
21035
|
+
wikiDir: typeof override.wikiDir === "string" && override.wikiDir.length > 0 ? override.wikiDir : path.join(workspaceRoot2, ".agent-kit", "wiki")
|
|
21036
|
+
};
|
|
21037
|
+
}
|
|
21038
|
+
function loadProjectSettings(projectDir) {
|
|
21039
|
+
const settingsPath = path.join(projectDir, ".agent-kit", "settings.json");
|
|
21040
|
+
if (fs.existsSync(settingsPath)) {
|
|
21041
|
+
try {
|
|
21042
|
+
return JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
21043
|
+
} catch (error2) {
|
|
21044
|
+
console.error(`[agent-kit] Warning: Failed to parse ${settingsPath}:`, error2);
|
|
21045
|
+
}
|
|
21046
|
+
}
|
|
21047
|
+
return {};
|
|
21048
|
+
}
|
|
21008
21049
|
var FORBIDDEN_FILES = [
|
|
21009
21050
|
".env",
|
|
21010
21051
|
".bashrc",
|
|
@@ -21046,7 +21087,7 @@ var FORBIDDEN_DIRS = [
|
|
|
21046
21087
|
];
|
|
21047
21088
|
|
|
21048
21089
|
// src/tools/handoffs.ts
|
|
21049
|
-
import * as
|
|
21090
|
+
import * as path2 from "path";
|
|
21050
21091
|
var HANDOFF_TYPES = [
|
|
21051
21092
|
"brainstorm",
|
|
21052
21093
|
"clarification",
|
|
@@ -21089,11 +21130,11 @@ function resolveHandoffPath(input) {
|
|
|
21089
21130
|
content: input.content,
|
|
21090
21131
|
type: canonicalType
|
|
21091
21132
|
});
|
|
21092
|
-
const relativePath =
|
|
21133
|
+
const relativePath = path2.join(".agent-kit", "handoffs", featureSlug, `${canonicalType}.md`);
|
|
21093
21134
|
return {
|
|
21094
21135
|
featureSlug,
|
|
21095
21136
|
canonicalType,
|
|
21096
|
-
filePath:
|
|
21137
|
+
filePath: path2.join(input.workspaceRoot, relativePath),
|
|
21097
21138
|
relativePath
|
|
21098
21139
|
};
|
|
21099
21140
|
}
|
|
@@ -21118,11 +21159,11 @@ function registerCoreTools(server2) {
|
|
|
21118
21159
|
slug,
|
|
21119
21160
|
content
|
|
21120
21161
|
});
|
|
21121
|
-
const handoffDir =
|
|
21122
|
-
if (!
|
|
21123
|
-
|
|
21162
|
+
const handoffDir = path3.dirname(location.filePath);
|
|
21163
|
+
if (!fs2.existsSync(handoffDir)) {
|
|
21164
|
+
fs2.mkdirSync(handoffDir, { recursive: true });
|
|
21124
21165
|
}
|
|
21125
|
-
|
|
21166
|
+
fs2.writeFileSync(location.filePath, content, "utf8");
|
|
21126
21167
|
return mcpText(`\u2705 Saved to: ${location.filePath}`);
|
|
21127
21168
|
} catch (error2) {
|
|
21128
21169
|
return mcpText(`Error saving handoff: ${error2}`);
|
|
@@ -21134,28 +21175,28 @@ function registerCoreTools(server2) {
|
|
|
21134
21175
|
// src/tools/agent.ts
|
|
21135
21176
|
import { spawn } from "child_process";
|
|
21136
21177
|
import { randomUUID } from "crypto";
|
|
21137
|
-
import * as
|
|
21138
|
-
import * as
|
|
21178
|
+
import * as fs3 from "fs";
|
|
21179
|
+
import * as path5 from "path";
|
|
21139
21180
|
|
|
21140
21181
|
// src/tools/security.ts
|
|
21141
21182
|
import { execFileSync } from "child_process";
|
|
21142
|
-
import * as
|
|
21183
|
+
import * as path4 from "path";
|
|
21143
21184
|
function sanitize(input) {
|
|
21144
21185
|
return String(input).replace(/[;&|`$<>\\]/g, "").trim().slice(0, 500);
|
|
21145
21186
|
}
|
|
21146
21187
|
function validatePath(filePath, baseDir = process.cwd()) {
|
|
21147
|
-
const resolved =
|
|
21148
|
-
const root =
|
|
21149
|
-
if (resolved !== root && !resolved.startsWith(root +
|
|
21188
|
+
const resolved = path4.resolve(baseDir, filePath);
|
|
21189
|
+
const root = path4.resolve(baseDir);
|
|
21190
|
+
if (resolved !== root && !resolved.startsWith(root + path4.sep)) {
|
|
21150
21191
|
throw new Error(`Path traversal detected: ${filePath}`);
|
|
21151
21192
|
}
|
|
21152
|
-
const segments = resolved.split(
|
|
21193
|
+
const segments = resolved.split(path4.sep);
|
|
21153
21194
|
for (const segment of segments) {
|
|
21154
21195
|
if (FORBIDDEN_DIRS.some((d) => segment.toLowerCase() === d.toLowerCase())) {
|
|
21155
21196
|
throw new Error(`Access to sensitive directory is FORBIDDEN: ${segment}`);
|
|
21156
21197
|
}
|
|
21157
21198
|
}
|
|
21158
|
-
const fileName =
|
|
21199
|
+
const fileName = path4.basename(resolved);
|
|
21159
21200
|
const isExactForbidden = FORBIDDEN_FILES.some((f) => fileName.toLowerCase() === f.toLowerCase());
|
|
21160
21201
|
const isPatternForbidden = FORBIDDEN_PATTERNS.some((re) => re.test(fileName));
|
|
21161
21202
|
if (isExactForbidden || isPatternForbidden) {
|
|
@@ -21206,7 +21247,7 @@ var INSTALL_HINTS = {
|
|
|
21206
21247
|
claude: "Install Claude CLI: npm install -g @anthropic-ai/claude-code",
|
|
21207
21248
|
codex: "Install Codex CLI: npm install -g @openai/codex"
|
|
21208
21249
|
};
|
|
21209
|
-
function agentArgs(agent, prompt,
|
|
21250
|
+
function agentArgs(agent, prompt, workspaceRoot2) {
|
|
21210
21251
|
switch (agent) {
|
|
21211
21252
|
case "gemini":
|
|
21212
21253
|
return ["-y", "-p", prompt];
|
|
@@ -21219,7 +21260,7 @@ function agentArgs(agent, prompt, workspaceRoot) {
|
|
|
21219
21260
|
"--sandbox",
|
|
21220
21261
|
"workspace-write",
|
|
21221
21262
|
"--cd",
|
|
21222
|
-
|
|
21263
|
+
workspaceRoot2,
|
|
21223
21264
|
"exec",
|
|
21224
21265
|
prompt
|
|
21225
21266
|
];
|
|
@@ -21228,21 +21269,21 @@ function agentArgs(agent, prompt, workspaceRoot) {
|
|
|
21228
21269
|
function taskSlug(task) {
|
|
21229
21270
|
return task.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 6).join("-").slice(0, 48);
|
|
21230
21271
|
}
|
|
21231
|
-
function initAgentLog(
|
|
21232
|
-
const logsDir =
|
|
21233
|
-
|
|
21272
|
+
function initAgentLog(workspaceRoot2, slug, agent) {
|
|
21273
|
+
const logsDir = path5.join(workspaceRoot2, ".agent-kit", "logs", "agents");
|
|
21274
|
+
fs3.mkdirSync(logsDir, { recursive: true });
|
|
21234
21275
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
21235
|
-
const logFile =
|
|
21276
|
+
const logFile = path5.join(logsDir, `delegate-${now}-${slug}.log`);
|
|
21236
21277
|
const header = `Agent: ${agent}
|
|
21237
21278
|
Date: ${now}
|
|
21238
21279
|
${"\u2500".repeat(60)}
|
|
21239
21280
|
|
|
21240
21281
|
`;
|
|
21241
|
-
|
|
21242
|
-
const logStream =
|
|
21282
|
+
fs3.writeFileSync(logFile, header, "utf-8");
|
|
21283
|
+
const logStream = fs3.createWriteStream(logFile, { flags: "a" });
|
|
21243
21284
|
logStream.on("error", () => {
|
|
21244
21285
|
});
|
|
21245
|
-
return { logPath:
|
|
21286
|
+
return { logPath: path5.relative(workspaceRoot2, logFile), logStream };
|
|
21246
21287
|
}
|
|
21247
21288
|
function registerAgentTools(server2) {
|
|
21248
21289
|
server2.tool(
|
|
@@ -21255,17 +21296,17 @@ function registerAgentTools(server2) {
|
|
|
21255
21296
|
)
|
|
21256
21297
|
},
|
|
21257
21298
|
async ({ agent, task }, extra) => {
|
|
21258
|
-
const
|
|
21299
|
+
const workspaceRoot2 = getWorkspaceRoot();
|
|
21259
21300
|
let job;
|
|
21260
21301
|
let logPath;
|
|
21261
21302
|
let killChild;
|
|
21262
21303
|
try {
|
|
21263
21304
|
let prompt = task;
|
|
21264
|
-
const resolvedTask =
|
|
21265
|
-
if (
|
|
21305
|
+
const resolvedTask = path5.resolve(workspaceRoot2, task);
|
|
21306
|
+
if (fs3.existsSync(resolvedTask) && fs3.statSync(resolvedTask).isFile()) {
|
|
21266
21307
|
try {
|
|
21267
|
-
validatePath(task,
|
|
21268
|
-
prompt =
|
|
21308
|
+
validatePath(task, workspaceRoot2);
|
|
21309
|
+
prompt = fs3.readFileSync(resolvedTask, "utf-8");
|
|
21269
21310
|
} catch (err) {
|
|
21270
21311
|
return mcpText(`Error reading task file: ${err}`);
|
|
21271
21312
|
}
|
|
@@ -21278,17 +21319,17 @@ function registerAgentTools(server2) {
|
|
|
21278
21319
|
error: `${agent} CLI is not installed. ${INSTALL_HINTS[agent]}`
|
|
21279
21320
|
});
|
|
21280
21321
|
}
|
|
21281
|
-
const args = agentArgs(agent, prompt,
|
|
21282
|
-
const { logPath: lp, logStream } = initAgentLog(
|
|
21322
|
+
const args = agentArgs(agent, prompt, workspaceRoot2);
|
|
21323
|
+
const { logPath: lp, logStream } = initAgentLog(workspaceRoot2, taskSlug(task), agent);
|
|
21283
21324
|
logPath = lp;
|
|
21284
21325
|
const jobId = randomUUID();
|
|
21285
21326
|
const child = spawn(agent, args, {
|
|
21286
|
-
cwd:
|
|
21327
|
+
cwd: workspaceRoot2,
|
|
21287
21328
|
env: {
|
|
21288
21329
|
...process.env,
|
|
21289
|
-
CODEX_PROJECT_DIR:
|
|
21290
|
-
CODEX_WORKSPACE:
|
|
21291
|
-
GEMINI_WORKSPACE:
|
|
21330
|
+
CODEX_PROJECT_DIR: workspaceRoot2,
|
|
21331
|
+
CODEX_WORKSPACE: workspaceRoot2,
|
|
21332
|
+
GEMINI_WORKSPACE: workspaceRoot2
|
|
21292
21333
|
},
|
|
21293
21334
|
detached: process.platform !== "win32"
|
|
21294
21335
|
});
|
|
@@ -21366,10 +21407,10 @@ function registerAgentTools(server2) {
|
|
|
21366
21407
|
import { writeFileSync as writeFileSync3 } from "fs";
|
|
21367
21408
|
|
|
21368
21409
|
// src/utils/credentials.ts
|
|
21369
|
-
import * as
|
|
21410
|
+
import * as fs4 from "fs";
|
|
21370
21411
|
import * as os from "os";
|
|
21371
|
-
import * as
|
|
21372
|
-
var CREDENTIALS_FILE =
|
|
21412
|
+
import * as path6 from "path";
|
|
21413
|
+
var CREDENTIALS_FILE = path6.join(os.homedir(), ".claude", "credentials");
|
|
21373
21414
|
var cachedCredentials = null;
|
|
21374
21415
|
function parseIni(content) {
|
|
21375
21416
|
const result = {};
|
|
@@ -21394,12 +21435,12 @@ function parseIni(content) {
|
|
|
21394
21435
|
}
|
|
21395
21436
|
function loadCredentials() {
|
|
21396
21437
|
if (cachedCredentials !== null) return cachedCredentials;
|
|
21397
|
-
if (!
|
|
21438
|
+
if (!fs4.existsSync(CREDENTIALS_FILE)) {
|
|
21398
21439
|
cachedCredentials = {};
|
|
21399
21440
|
return cachedCredentials;
|
|
21400
21441
|
}
|
|
21401
21442
|
try {
|
|
21402
|
-
const stat =
|
|
21443
|
+
const stat = fs4.statSync(CREDENTIALS_FILE);
|
|
21403
21444
|
const mode = stat.mode & 511;
|
|
21404
21445
|
if (mode !== 384) {
|
|
21405
21446
|
process.stderr.write(
|
|
@@ -21409,7 +21450,7 @@ function loadCredentials() {
|
|
|
21409
21450
|
}
|
|
21410
21451
|
} catch {
|
|
21411
21452
|
}
|
|
21412
|
-
const content =
|
|
21453
|
+
const content = fs4.readFileSync(CREDENTIALS_FILE, "utf-8");
|
|
21413
21454
|
const ini = parseIni(content);
|
|
21414
21455
|
const profile = process.env.KIT_PROFILE ?? "default";
|
|
21415
21456
|
cachedCredentials = ini[profile] ?? {};
|
|
@@ -21742,6 +21783,714 @@ ${ticket.fields.labels?.join(", ") || "None"}`;
|
|
|
21742
21783
|
);
|
|
21743
21784
|
}
|
|
21744
21785
|
|
|
21786
|
+
// src/tools/memory.ts
|
|
21787
|
+
import * as path10 from "path";
|
|
21788
|
+
|
|
21789
|
+
// src/memory/embedder.ts
|
|
21790
|
+
import * as os2 from "os";
|
|
21791
|
+
import * as path7 from "path";
|
|
21792
|
+
var DEFAULT_CACHE_DIR = path7.join(os2.homedir(), ".cache", "fastembed");
|
|
21793
|
+
var EmbedderError = class extends Error {
|
|
21794
|
+
constructor(message, cause) {
|
|
21795
|
+
super(message);
|
|
21796
|
+
this.name = "EmbedderError";
|
|
21797
|
+
if (cause instanceof Error) this.cause = cause;
|
|
21798
|
+
}
|
|
21799
|
+
};
|
|
21800
|
+
var Embedder = class {
|
|
21801
|
+
modelName;
|
|
21802
|
+
pipeline = null;
|
|
21803
|
+
_dimension;
|
|
21804
|
+
constructor(modelName) {
|
|
21805
|
+
this.modelName = modelName;
|
|
21806
|
+
}
|
|
21807
|
+
async embed(texts) {
|
|
21808
|
+
if (texts.length === 0) return [];
|
|
21809
|
+
if (!this.pipeline) {
|
|
21810
|
+
await this.loadModel();
|
|
21811
|
+
}
|
|
21812
|
+
try {
|
|
21813
|
+
const results = await this.pipeline(texts);
|
|
21814
|
+
return results.map((vec) => {
|
|
21815
|
+
const arr = new Float32Array(vec);
|
|
21816
|
+
if (!this._dimension) this._dimension = arr.length;
|
|
21817
|
+
return arr;
|
|
21818
|
+
});
|
|
21819
|
+
} catch (cause) {
|
|
21820
|
+
throw new EmbedderError(`Embedding failed for batch of ${texts.length} texts`, cause);
|
|
21821
|
+
}
|
|
21822
|
+
}
|
|
21823
|
+
get dimension() {
|
|
21824
|
+
return this._dimension;
|
|
21825
|
+
}
|
|
21826
|
+
isReady() {
|
|
21827
|
+
return this.pipeline !== null;
|
|
21828
|
+
}
|
|
21829
|
+
async loadModel() {
|
|
21830
|
+
try {
|
|
21831
|
+
const { EmbeddingModel, FlagEmbedding } = await import("fastembed");
|
|
21832
|
+
const modelMap = {
|
|
21833
|
+
"Xenova/bge-small-en-v1.5": EmbeddingModel.BGESmallENV15,
|
|
21834
|
+
"Xenova/bge-base-en-v1.5": EmbeddingModel.BGEBaseENV15,
|
|
21835
|
+
"Xenova/all-MiniLM-L6-v2": EmbeddingModel.AllMiniLML6V2
|
|
21836
|
+
};
|
|
21837
|
+
const model = modelMap[this.modelName] ?? EmbeddingModel.BGESmallENV15;
|
|
21838
|
+
const embedder = await FlagEmbedding.init({ model, cacheDir: DEFAULT_CACHE_DIR, showDownloadProgress: false });
|
|
21839
|
+
this.pipeline = async (texts) => {
|
|
21840
|
+
const result = [];
|
|
21841
|
+
for await (const batch of embedder.embed(texts, texts.length)) {
|
|
21842
|
+
for (const vec of batch) {
|
|
21843
|
+
result.push(Array.from(vec));
|
|
21844
|
+
}
|
|
21845
|
+
}
|
|
21846
|
+
return result;
|
|
21847
|
+
};
|
|
21848
|
+
} catch (cause) {
|
|
21849
|
+
throw new EmbedderError(`Failed to load embedding model: ${this.modelName}`, cause);
|
|
21850
|
+
}
|
|
21851
|
+
}
|
|
21852
|
+
};
|
|
21853
|
+
|
|
21854
|
+
// src/memory/indexer.ts
|
|
21855
|
+
import * as fs5 from "fs";
|
|
21856
|
+
import * as path8 from "path";
|
|
21857
|
+
|
|
21858
|
+
// src/memory/chunker.ts
|
|
21859
|
+
import { createHash } from "crypto";
|
|
21860
|
+
function computeChunkId(content) {
|
|
21861
|
+
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
21862
|
+
}
|
|
21863
|
+
function cleanContentForEmbedding(text) {
|
|
21864
|
+
return text.replace(/<!--[\s\S]*?-->/g, "");
|
|
21865
|
+
}
|
|
21866
|
+
var HEADING_RE = /^(#{1,6}) (.+)$/;
|
|
21867
|
+
var SENTENCE_END_RE = /([.!?。!?])\s+(?![0-9])/g;
|
|
21868
|
+
var URL_RE = /https?:\/\/\S+/g;
|
|
21869
|
+
function splitAtParagraph(text, maxLen) {
|
|
21870
|
+
const paragraphs = text.split(/\n\n+/);
|
|
21871
|
+
const parts = [];
|
|
21872
|
+
let current = "";
|
|
21873
|
+
for (const para of paragraphs) {
|
|
21874
|
+
const candidate = current ? `${current}
|
|
21875
|
+
|
|
21876
|
+
${para}` : para;
|
|
21877
|
+
if (candidate.length <= maxLen) {
|
|
21878
|
+
current = candidate;
|
|
21879
|
+
} else {
|
|
21880
|
+
if (current) parts.push(current);
|
|
21881
|
+
current = para.length <= maxLen ? para : splitAtLine(para, maxLen).join("\n\n");
|
|
21882
|
+
}
|
|
21883
|
+
}
|
|
21884
|
+
if (current) parts.push(current);
|
|
21885
|
+
return parts.filter(Boolean);
|
|
21886
|
+
}
|
|
21887
|
+
function splitAtLine(text, maxLen) {
|
|
21888
|
+
const lines = text.split("\n");
|
|
21889
|
+
const parts = [];
|
|
21890
|
+
let current = "";
|
|
21891
|
+
for (const line of lines) {
|
|
21892
|
+
const candidate = current ? `${current}
|
|
21893
|
+
${line}` : line;
|
|
21894
|
+
if (candidate.length <= maxLen) {
|
|
21895
|
+
current = candidate;
|
|
21896
|
+
} else {
|
|
21897
|
+
if (current) parts.push(current);
|
|
21898
|
+
current = line.length <= maxLen ? line : splitAtSentence(line, maxLen).join(" ");
|
|
21899
|
+
}
|
|
21900
|
+
}
|
|
21901
|
+
if (current) parts.push(current);
|
|
21902
|
+
return parts.filter(Boolean);
|
|
21903
|
+
}
|
|
21904
|
+
function splitAtSentence(text, maxLen) {
|
|
21905
|
+
const sanitized = text.replace(URL_RE, "[URL]");
|
|
21906
|
+
const sentences = [];
|
|
21907
|
+
let lastIndex = 0;
|
|
21908
|
+
for (const match of sanitized.matchAll(SENTENCE_END_RE)) {
|
|
21909
|
+
const end = (match.index ?? 0) + match[0].length;
|
|
21910
|
+
sentences.push(text.slice(lastIndex, end).trim());
|
|
21911
|
+
lastIndex = end;
|
|
21912
|
+
}
|
|
21913
|
+
if (lastIndex < text.length) {
|
|
21914
|
+
sentences.push(text.slice(lastIndex).trim());
|
|
21915
|
+
}
|
|
21916
|
+
if (sentences.length === 0) return [text.slice(0, maxLen)];
|
|
21917
|
+
const parts = [];
|
|
21918
|
+
let current = "";
|
|
21919
|
+
for (const sentence of sentences) {
|
|
21920
|
+
const candidate = current ? `${current} ${sentence}` : sentence;
|
|
21921
|
+
if (candidate.length <= maxLen) {
|
|
21922
|
+
current = candidate;
|
|
21923
|
+
} else {
|
|
21924
|
+
if (current) parts.push(current);
|
|
21925
|
+
current = sentence.slice(0, maxLen);
|
|
21926
|
+
}
|
|
21927
|
+
}
|
|
21928
|
+
if (current) parts.push(current);
|
|
21929
|
+
return parts.filter(Boolean);
|
|
21930
|
+
}
|
|
21931
|
+
function splitSection(text, maxLen) {
|
|
21932
|
+
if (text.length <= maxLen) return [text];
|
|
21933
|
+
const byPara = splitAtParagraph(text, maxLen);
|
|
21934
|
+
if (byPara.every((p) => p.length <= maxLen)) return byPara;
|
|
21935
|
+
return byPara.flatMap((p) => p.length <= maxLen ? [p] : splitAtLine(p, maxLen));
|
|
21936
|
+
}
|
|
21937
|
+
function chunkMarkdown(text, source, config2) {
|
|
21938
|
+
if (!text || !text.trim()) return [];
|
|
21939
|
+
const { chunkSize, overlapLines } = config2;
|
|
21940
|
+
const lines = text.split("\n");
|
|
21941
|
+
const sections = [];
|
|
21942
|
+
let current = { heading: "", headingLevel: 0, lines: [] };
|
|
21943
|
+
for (let i = 0; i < lines.length; i++) {
|
|
21944
|
+
const line = lines[i];
|
|
21945
|
+
const headingMatch = line.match(HEADING_RE);
|
|
21946
|
+
if (headingMatch) {
|
|
21947
|
+
if (current.lines.length > 0) sections.push(current);
|
|
21948
|
+
current = {
|
|
21949
|
+
heading: headingMatch[2].trim(),
|
|
21950
|
+
headingLevel: headingMatch[1].length,
|
|
21951
|
+
lines: [{ text: line, lineNum: i + 1 }]
|
|
21952
|
+
};
|
|
21953
|
+
} else {
|
|
21954
|
+
current.lines.push({ text: line, lineNum: i + 1 });
|
|
21955
|
+
}
|
|
21956
|
+
}
|
|
21957
|
+
if (current.lines.length > 0) sections.push(current);
|
|
21958
|
+
const chunks = [];
|
|
21959
|
+
let prevOverlapLines = [];
|
|
21960
|
+
for (const section of sections) {
|
|
21961
|
+
const sectionText = section.lines.map((l) => l.text).join("\n");
|
|
21962
|
+
if (!sectionText.trim()) continue;
|
|
21963
|
+
const fullText = prevOverlapLines.length > 0 ? `${prevOverlapLines.join("\n")}
|
|
21964
|
+
${sectionText}` : sectionText;
|
|
21965
|
+
const parts = splitSection(fullText, chunkSize);
|
|
21966
|
+
for (let pi = 0; pi < parts.length; pi++) {
|
|
21967
|
+
const part = parts[pi];
|
|
21968
|
+
if (!part.trim()) continue;
|
|
21969
|
+
const partLines = part.split("\n");
|
|
21970
|
+
const firstLine = section.lines[0]?.lineNum ?? 1;
|
|
21971
|
+
const lastLine = section.lines[section.lines.length - 1]?.lineNum ?? firstLine;
|
|
21972
|
+
const content = cleanContentForEmbedding(part);
|
|
21973
|
+
chunks.push({
|
|
21974
|
+
id: computeChunkId(content),
|
|
21975
|
+
source,
|
|
21976
|
+
heading: section.heading,
|
|
21977
|
+
headingLevel: section.headingLevel,
|
|
21978
|
+
content,
|
|
21979
|
+
lineStart: firstLine,
|
|
21980
|
+
lineEnd: lastLine
|
|
21981
|
+
});
|
|
21982
|
+
if (pi === parts.length - 1) {
|
|
21983
|
+
prevOverlapLines = partLines.slice(-overlapLines);
|
|
21984
|
+
}
|
|
21985
|
+
}
|
|
21986
|
+
}
|
|
21987
|
+
return chunks;
|
|
21988
|
+
}
|
|
21989
|
+
|
|
21990
|
+
// src/memory/indexer.ts
|
|
21991
|
+
var LOCK_RETRY_MS = 50;
|
|
21992
|
+
var LOCK_TIMEOUT_MS = 500;
|
|
21993
|
+
async function acquireLock(lockPath) {
|
|
21994
|
+
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
21995
|
+
while (Date.now() < deadline) {
|
|
21996
|
+
try {
|
|
21997
|
+
fs5.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
|
|
21998
|
+
return true;
|
|
21999
|
+
} catch {
|
|
22000
|
+
await new Promise((r) => setTimeout(r, LOCK_RETRY_MS));
|
|
22001
|
+
}
|
|
22002
|
+
}
|
|
22003
|
+
return false;
|
|
22004
|
+
}
|
|
22005
|
+
function releaseLock(lockPath) {
|
|
22006
|
+
try {
|
|
22007
|
+
fs5.unlinkSync(lockPath);
|
|
22008
|
+
} catch {
|
|
22009
|
+
}
|
|
22010
|
+
}
|
|
22011
|
+
var MemoryIndexer = class {
|
|
22012
|
+
constructor(store, embedder, config2) {
|
|
22013
|
+
this.store = store;
|
|
22014
|
+
this.embedder = embedder;
|
|
22015
|
+
this.config = config2;
|
|
22016
|
+
}
|
|
22017
|
+
_ready = false;
|
|
22018
|
+
async indexFile(absolutePath) {
|
|
22019
|
+
return this.indexFileRelativeTo(absolutePath, this.config.wikiDir);
|
|
22020
|
+
}
|
|
22021
|
+
async indexFileRelativeTo(absolutePath, sourceRoot) {
|
|
22022
|
+
const source = path8.relative(sourceRoot, absolutePath);
|
|
22023
|
+
const existingHashes = this.store.hashesBySource(source);
|
|
22024
|
+
let text;
|
|
22025
|
+
try {
|
|
22026
|
+
text = fs5.readFileSync(absolutePath, "utf8");
|
|
22027
|
+
} catch (err) {
|
|
22028
|
+
console.warn(`[memory-indexer] Cannot read file ${absolutePath}:`, err);
|
|
22029
|
+
return { indexed: 0, deleted: 0, skipped: existingHashes.size };
|
|
22030
|
+
}
|
|
22031
|
+
const allChunks = chunkMarkdown(text, source, this.config);
|
|
22032
|
+
const newHashes = new Set(allChunks.map((c) => c.id));
|
|
22033
|
+
const toIndex = allChunks.filter((c) => !existingHashes.has(c.id));
|
|
22034
|
+
const toDelete = [...existingHashes].filter((h) => !newHashes.has(h));
|
|
22035
|
+
let embeddings = [];
|
|
22036
|
+
if (toIndex.length > 0) {
|
|
22037
|
+
try {
|
|
22038
|
+
embeddings = await this.embedder.embed(toIndex.map((c) => c.content));
|
|
22039
|
+
} catch (err) {
|
|
22040
|
+
console.warn(`[memory-indexer] Embedding failed for ${absolutePath}:`, err);
|
|
22041
|
+
return { indexed: 0, deleted: 0, skipped: existingHashes.size };
|
|
22042
|
+
}
|
|
22043
|
+
}
|
|
22044
|
+
if (toIndex.length > 0) {
|
|
22045
|
+
this.store.upsert(toIndex, embeddings);
|
|
22046
|
+
}
|
|
22047
|
+
if (toDelete.length > 0) {
|
|
22048
|
+
this.store.deleteByIds(toDelete);
|
|
22049
|
+
}
|
|
22050
|
+
return {
|
|
22051
|
+
indexed: toIndex.length,
|
|
22052
|
+
deleted: toDelete.length,
|
|
22053
|
+
skipped: allChunks.length - toIndex.length
|
|
22054
|
+
};
|
|
22055
|
+
}
|
|
22056
|
+
async indexDirectory(rootDir, opts = {}) {
|
|
22057
|
+
const totals = { indexed: 0, deleted: 0, skipped: 0 };
|
|
22058
|
+
if (!fs5.existsSync(rootDir)) return totals;
|
|
22059
|
+
const relativeBase = opts.relativeBase ?? this.config.wikiDir;
|
|
22060
|
+
const excludeFiles = new Set(opts.excludeFiles ?? []);
|
|
22061
|
+
const files = [];
|
|
22062
|
+
const walk = (dirPath) => {
|
|
22063
|
+
let entries;
|
|
22064
|
+
try {
|
|
22065
|
+
entries = fs5.readdirSync(dirPath, { withFileTypes: true });
|
|
22066
|
+
} catch (err) {
|
|
22067
|
+
console.warn(`[memory-indexer] Cannot scan directory ${dirPath}:`, err);
|
|
22068
|
+
return;
|
|
22069
|
+
}
|
|
22070
|
+
for (const entry of entries) {
|
|
22071
|
+
const entryPath = path8.join(dirPath, entry.name);
|
|
22072
|
+
if (entry.isDirectory()) {
|
|
22073
|
+
walk(entryPath);
|
|
22074
|
+
continue;
|
|
22075
|
+
}
|
|
22076
|
+
if (entry.isFile() && /\.md$/i.test(entry.name) && !excludeFiles.has(entry.name)) {
|
|
22077
|
+
files.push(entryPath);
|
|
22078
|
+
}
|
|
22079
|
+
}
|
|
22080
|
+
};
|
|
22081
|
+
walk(rootDir);
|
|
22082
|
+
const currentSources = new Set(
|
|
22083
|
+
files.map((file) => path8.relative(relativeBase, file))
|
|
22084
|
+
);
|
|
22085
|
+
for (const stale of this.store.indexedSources()) {
|
|
22086
|
+
if (!currentSources.has(stale)) {
|
|
22087
|
+
this.store.deleteBySource(stale);
|
|
22088
|
+
totals.deleted += 1;
|
|
22089
|
+
}
|
|
22090
|
+
}
|
|
22091
|
+
for (const file of files) {
|
|
22092
|
+
const stats = await this.indexFileRelativeTo(file, relativeBase);
|
|
22093
|
+
totals.indexed += stats.indexed;
|
|
22094
|
+
totals.deleted += stats.deleted;
|
|
22095
|
+
totals.skipped += stats.skipped;
|
|
22096
|
+
}
|
|
22097
|
+
return totals;
|
|
22098
|
+
}
|
|
22099
|
+
async search(query, topK) {
|
|
22100
|
+
const fetchLimit = topK * 2;
|
|
22101
|
+
let denseResults = [];
|
|
22102
|
+
if (this.store.vecAvailable) {
|
|
22103
|
+
try {
|
|
22104
|
+
const queryEmbedding = await this.embedder.embed([query]);
|
|
22105
|
+
denseResults = this.store.searchDense(queryEmbedding[0], fetchLimit);
|
|
22106
|
+
} catch (err) {
|
|
22107
|
+
console.warn("[memory-indexer] Dense search embedding failed:", err);
|
|
22108
|
+
}
|
|
22109
|
+
}
|
|
22110
|
+
const bm25Results = this.store.searchBm25(query, fetchLimit);
|
|
22111
|
+
const k = 60;
|
|
22112
|
+
const scoreMap = /* @__PURE__ */ new Map();
|
|
22113
|
+
denseResults.forEach((r, rank) => {
|
|
22114
|
+
scoreMap.set(r.id, { dense: 1 / (k + rank + 1), bm25: 0 });
|
|
22115
|
+
});
|
|
22116
|
+
bm25Results.forEach((r, rank) => {
|
|
22117
|
+
const existing = scoreMap.get(r.id);
|
|
22118
|
+
if (existing) {
|
|
22119
|
+
existing.bm25 = 1 / (k + rank + 1);
|
|
22120
|
+
} else {
|
|
22121
|
+
scoreMap.set(r.id, { dense: 0, bm25: 1 / (k + rank + 1) });
|
|
22122
|
+
}
|
|
22123
|
+
});
|
|
22124
|
+
const hasDense = this.store.vecAvailable && denseResults.length > 0;
|
|
22125
|
+
const numRetrievers = hasDense ? 2 : 1;
|
|
22126
|
+
const maxScore = numRetrievers / 61;
|
|
22127
|
+
const ranked = [...scoreMap.entries()].map(([id, scores]) => ({
|
|
22128
|
+
id,
|
|
22129
|
+
totalScore: scores.dense + scores.bm25,
|
|
22130
|
+
hasDense: scores.dense > 0,
|
|
22131
|
+
hasBm25: scores.bm25 > 0
|
|
22132
|
+
})).sort((a, b) => b.totalScore - a.totalScore);
|
|
22133
|
+
const chunks = this.store.getChunksByIds(ranked.map((r) => r.id));
|
|
22134
|
+
const chunkMap = new Map(chunks.map((c) => [c.id, c]));
|
|
22135
|
+
const results = [];
|
|
22136
|
+
const seenSources = /* @__PURE__ */ new Set();
|
|
22137
|
+
for (const r of ranked) {
|
|
22138
|
+
if (results.length >= topK) break;
|
|
22139
|
+
const chunk = chunkMap.get(r.id);
|
|
22140
|
+
if (!chunk) continue;
|
|
22141
|
+
if (seenSources.has(chunk.source)) continue;
|
|
22142
|
+
seenSources.add(chunk.source);
|
|
22143
|
+
const normalizedScore = maxScore > 0 ? r.totalScore / maxScore : 0;
|
|
22144
|
+
const retriever = r.hasDense && r.hasBm25 ? "both" : r.hasDense ? "dense" : "bm25";
|
|
22145
|
+
let contentSource = "file";
|
|
22146
|
+
try {
|
|
22147
|
+
chunk.content = fs5.readFileSync(path8.join(this.config.wikiDir, chunk.source), "utf8");
|
|
22148
|
+
} catch {
|
|
22149
|
+
contentSource = "fallback";
|
|
22150
|
+
}
|
|
22151
|
+
results.push({ chunk, score: normalizedScore, retriever, contentSource });
|
|
22152
|
+
}
|
|
22153
|
+
return results;
|
|
22154
|
+
}
|
|
22155
|
+
async save(content) {
|
|
22156
|
+
const datePart = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
22157
|
+
const rawDir = path8.join(this.config.wikiDir, "raw");
|
|
22158
|
+
const savePath = path8.join(rawDir, `conv_save_${datePart}.md`);
|
|
22159
|
+
const lockPath = `${savePath}.lock`;
|
|
22160
|
+
fs5.mkdirSync(rawDir, { recursive: true });
|
|
22161
|
+
const acquired = await acquireLock(lockPath);
|
|
22162
|
+
if (!acquired) {
|
|
22163
|
+
console.warn("[memory-indexer] Could not acquire write lock, writing anyway (fail-open)");
|
|
22164
|
+
}
|
|
22165
|
+
try {
|
|
22166
|
+
fs5.appendFileSync(savePath, `
|
|
22167
|
+
${content}
|
|
22168
|
+
`, "utf8");
|
|
22169
|
+
} finally {
|
|
22170
|
+
if (acquired) releaseLock(lockPath);
|
|
22171
|
+
}
|
|
22172
|
+
return { indexed: 0, deleted: 0, skipped: 0 };
|
|
22173
|
+
}
|
|
22174
|
+
async startupIndex() {
|
|
22175
|
+
try {
|
|
22176
|
+
await this.indexDirectory(path8.join(this.config.wikiDir, "compiled"), {
|
|
22177
|
+
relativeBase: this.config.wikiDir,
|
|
22178
|
+
excludeFiles: ["index.md", "log.md"]
|
|
22179
|
+
});
|
|
22180
|
+
} catch (err) {
|
|
22181
|
+
console.warn("[memory-indexer] Startup indexing failed:", err);
|
|
22182
|
+
} finally {
|
|
22183
|
+
this._ready = true;
|
|
22184
|
+
}
|
|
22185
|
+
}
|
|
22186
|
+
get ready() {
|
|
22187
|
+
return this._ready;
|
|
22188
|
+
}
|
|
22189
|
+
};
|
|
22190
|
+
|
|
22191
|
+
// src/memory/store.ts
|
|
22192
|
+
import Database from "better-sqlite3";
|
|
22193
|
+
import * as fs6 from "fs";
|
|
22194
|
+
import * as path9 from "path";
|
|
22195
|
+
var StoreError = class extends Error {
|
|
22196
|
+
constructor(message, cause) {
|
|
22197
|
+
super(message);
|
|
22198
|
+
this.name = "StoreError";
|
|
22199
|
+
if (cause instanceof Error) this.cause = cause;
|
|
22200
|
+
}
|
|
22201
|
+
};
|
|
22202
|
+
var MemoryStore = class {
|
|
22203
|
+
constructor(dbPath, config2) {
|
|
22204
|
+
this.config = config2;
|
|
22205
|
+
fs6.mkdirSync(path9.dirname(dbPath), { recursive: true });
|
|
22206
|
+
this.db = this.openDatabase(dbPath);
|
|
22207
|
+
}
|
|
22208
|
+
db;
|
|
22209
|
+
_vecAvailable = false;
|
|
22210
|
+
openDatabase(dbPath) {
|
|
22211
|
+
try {
|
|
22212
|
+
const db = new Database(dbPath);
|
|
22213
|
+
db.pragma("journal_mode = WAL");
|
|
22214
|
+
db.pragma("busy_timeout = 5000");
|
|
22215
|
+
this.loadVecExtension(db);
|
|
22216
|
+
this.createSchema(db);
|
|
22217
|
+
return db;
|
|
22218
|
+
} catch (err) {
|
|
22219
|
+
console.error("[memory-store] DB open failed, recreating:", err);
|
|
22220
|
+
try {
|
|
22221
|
+
fs6.unlinkSync(dbPath);
|
|
22222
|
+
} catch {
|
|
22223
|
+
}
|
|
22224
|
+
const db = new Database(dbPath);
|
|
22225
|
+
db.pragma("journal_mode = WAL");
|
|
22226
|
+
db.pragma("busy_timeout = 5000");
|
|
22227
|
+
this.loadVecExtension(db);
|
|
22228
|
+
this.createSchema(db);
|
|
22229
|
+
return db;
|
|
22230
|
+
}
|
|
22231
|
+
}
|
|
22232
|
+
loadVecExtension(db) {
|
|
22233
|
+
try {
|
|
22234
|
+
const sqliteVec = __require("sqlite-vec");
|
|
22235
|
+
sqliteVec.load(db);
|
|
22236
|
+
this._vecAvailable = true;
|
|
22237
|
+
} catch (err) {
|
|
22238
|
+
console.warn("[memory-store] sqlite-vec unavailable, degrading to FTS5-only:", err);
|
|
22239
|
+
this._vecAvailable = false;
|
|
22240
|
+
}
|
|
22241
|
+
}
|
|
22242
|
+
createSchema(db) {
|
|
22243
|
+
db.exec(`
|
|
22244
|
+
CREATE TABLE IF NOT EXISTS memory_chunks (
|
|
22245
|
+
rowid INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
22246
|
+
id TEXT NOT NULL UNIQUE,
|
|
22247
|
+
source TEXT NOT NULL,
|
|
22248
|
+
heading TEXT NOT NULL DEFAULT '',
|
|
22249
|
+
heading_level INTEGER NOT NULL DEFAULT 0,
|
|
22250
|
+
content TEXT NOT NULL,
|
|
22251
|
+
line_start INTEGER NOT NULL,
|
|
22252
|
+
line_end INTEGER NOT NULL,
|
|
22253
|
+
indexed_at INTEGER NOT NULL
|
|
22254
|
+
);
|
|
22255
|
+
|
|
22256
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(
|
|
22257
|
+
content,
|
|
22258
|
+
content='memory_chunks',
|
|
22259
|
+
content_rowid='rowid'
|
|
22260
|
+
);
|
|
22261
|
+
|
|
22262
|
+
CREATE TRIGGER IF NOT EXISTS memory_chunks_ai
|
|
22263
|
+
AFTER INSERT ON memory_chunks BEGIN
|
|
22264
|
+
INSERT INTO memory_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
22265
|
+
END;
|
|
22266
|
+
|
|
22267
|
+
CREATE TRIGGER IF NOT EXISTS memory_chunks_ad
|
|
22268
|
+
AFTER DELETE ON memory_chunks BEGIN
|
|
22269
|
+
INSERT INTO memory_fts(memory_fts, rowid, content)
|
|
22270
|
+
VALUES ('delete', old.rowid, old.content);
|
|
22271
|
+
END;
|
|
22272
|
+
`);
|
|
22273
|
+
if (this._vecAvailable) {
|
|
22274
|
+
try {
|
|
22275
|
+
db.exec(`
|
|
22276
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memory_vec USING vec0(
|
|
22277
|
+
embedding FLOAT[${this.config.vectorDimension}]
|
|
22278
|
+
);
|
|
22279
|
+
`);
|
|
22280
|
+
} catch (err) {
|
|
22281
|
+
console.warn("[memory-store] Failed to create memory_vec, disabling vector search:", err);
|
|
22282
|
+
this._vecAvailable = false;
|
|
22283
|
+
}
|
|
22284
|
+
}
|
|
22285
|
+
}
|
|
22286
|
+
upsert(chunks, embeddings) {
|
|
22287
|
+
const insertChunk = this.db.prepare(`
|
|
22288
|
+
INSERT OR REPLACE INTO memory_chunks
|
|
22289
|
+
(id, source, heading, heading_level, content, line_start, line_end, indexed_at)
|
|
22290
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
22291
|
+
`);
|
|
22292
|
+
let insertVec = null;
|
|
22293
|
+
if (this._vecAvailable) {
|
|
22294
|
+
insertVec = this.db.prepare(`INSERT OR REPLACE INTO memory_vec (rowid, embedding) VALUES (?, ?)`);
|
|
22295
|
+
}
|
|
22296
|
+
const upsertAll = this.db.transaction(() => {
|
|
22297
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
22298
|
+
const chunk = chunks[i];
|
|
22299
|
+
try {
|
|
22300
|
+
this.db.prepare(`DELETE FROM memory_chunks WHERE id = ?`).run(chunk.id);
|
|
22301
|
+
const info = insertChunk.run(
|
|
22302
|
+
chunk.id,
|
|
22303
|
+
chunk.source,
|
|
22304
|
+
chunk.heading,
|
|
22305
|
+
chunk.headingLevel,
|
|
22306
|
+
chunk.content,
|
|
22307
|
+
chunk.lineStart,
|
|
22308
|
+
chunk.lineEnd,
|
|
22309
|
+
Date.now()
|
|
22310
|
+
);
|
|
22311
|
+
if (insertVec && embeddings[i] && info.lastInsertRowid) {
|
|
22312
|
+
const rowid = Number(info.lastInsertRowid);
|
|
22313
|
+
const emb = embeddings[i];
|
|
22314
|
+
const buf = Buffer.from(emb.buffer, emb.byteOffset, emb.byteLength);
|
|
22315
|
+
insertVec.run(rowid, buf);
|
|
22316
|
+
}
|
|
22317
|
+
} catch (err) {
|
|
22318
|
+
console.warn(`[memory-store] Failed to upsert chunk ${chunk.id}:`, err);
|
|
22319
|
+
}
|
|
22320
|
+
}
|
|
22321
|
+
});
|
|
22322
|
+
try {
|
|
22323
|
+
upsertAll();
|
|
22324
|
+
} catch (err) {
|
|
22325
|
+
throw new StoreError("Full batch upsert failed", err);
|
|
22326
|
+
}
|
|
22327
|
+
}
|
|
22328
|
+
hashesBySource(source) {
|
|
22329
|
+
const rows = this.db.prepare(`SELECT id FROM memory_chunks WHERE source = ?`).all(source);
|
|
22330
|
+
return new Set(rows.map((r) => r.id));
|
|
22331
|
+
}
|
|
22332
|
+
indexedSources() {
|
|
22333
|
+
const rows = this.db.prepare(`SELECT DISTINCT source FROM memory_chunks`).all();
|
|
22334
|
+
return rows.map((r) => r.source);
|
|
22335
|
+
}
|
|
22336
|
+
deleteBySource(source) {
|
|
22337
|
+
const rows = this.db.prepare(`SELECT rowid FROM memory_chunks WHERE source = ?`).all(source);
|
|
22338
|
+
const deleteAll = this.db.transaction(() => {
|
|
22339
|
+
if (this._vecAvailable && rows.length > 0) {
|
|
22340
|
+
const placeholders = rows.map(() => "?").join(", ");
|
|
22341
|
+
this.db.prepare(`DELETE FROM memory_vec WHERE rowid IN (${placeholders})`).run(...rows.map((r) => r.rowid));
|
|
22342
|
+
}
|
|
22343
|
+
this.db.prepare(`DELETE FROM memory_chunks WHERE source = ?`).run(source);
|
|
22344
|
+
});
|
|
22345
|
+
deleteAll();
|
|
22346
|
+
}
|
|
22347
|
+
deleteByIds(ids) {
|
|
22348
|
+
if (ids.length === 0) return;
|
|
22349
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
22350
|
+
const rows = this.db.prepare(`SELECT rowid FROM memory_chunks WHERE id IN (${placeholders})`).all(...ids);
|
|
22351
|
+
const deleteAll = this.db.transaction(() => {
|
|
22352
|
+
if (this._vecAvailable && rows.length > 0) {
|
|
22353
|
+
const rPlaceholders = rows.map(() => "?").join(", ");
|
|
22354
|
+
this.db.prepare(`DELETE FROM memory_vec WHERE rowid IN (${rPlaceholders})`).run(...rows.map((r) => r.rowid));
|
|
22355
|
+
}
|
|
22356
|
+
this.db.prepare(`DELETE FROM memory_chunks WHERE id IN (${placeholders})`).run(...ids);
|
|
22357
|
+
});
|
|
22358
|
+
deleteAll();
|
|
22359
|
+
}
|
|
22360
|
+
searchDense(embedding, limit) {
|
|
22361
|
+
if (!this._vecAvailable) return [];
|
|
22362
|
+
try {
|
|
22363
|
+
const buf = Buffer.from(embedding.buffer);
|
|
22364
|
+
const rows = this.db.prepare(
|
|
22365
|
+
`SELECT mc.id, mv.distance
|
|
22366
|
+
FROM memory_vec mv
|
|
22367
|
+
JOIN memory_chunks mc ON mc.rowid = mv.rowid
|
|
22368
|
+
WHERE mv.embedding MATCH ? AND k = ?
|
|
22369
|
+
ORDER BY mv.distance`
|
|
22370
|
+
).all(buf, limit);
|
|
22371
|
+
return rows.map((r) => ({
|
|
22372
|
+
id: r.id,
|
|
22373
|
+
score: Math.max(0, 1 - r.distance)
|
|
22374
|
+
}));
|
|
22375
|
+
} catch (err) {
|
|
22376
|
+
console.warn("[memory-store] Dense search failed:", err);
|
|
22377
|
+
return [];
|
|
22378
|
+
}
|
|
22379
|
+
}
|
|
22380
|
+
searchBm25(query, limit) {
|
|
22381
|
+
if (!query.trim()) return [];
|
|
22382
|
+
const ftsQuery = query.trim().split(/\s+/).map((t) => t.replace(/['"*^():\-]/g, " ").trim()).filter(Boolean).join(" OR ");
|
|
22383
|
+
if (!ftsQuery) return [];
|
|
22384
|
+
try {
|
|
22385
|
+
const rows = this.db.prepare(
|
|
22386
|
+
`SELECT mc.id, rank
|
|
22387
|
+
FROM memory_fts
|
|
22388
|
+
JOIN memory_chunks mc ON mc.rowid = memory_fts.rowid
|
|
22389
|
+
WHERE memory_fts MATCH ?
|
|
22390
|
+
ORDER BY rank
|
|
22391
|
+
LIMIT ?`
|
|
22392
|
+
).all(ftsQuery, limit);
|
|
22393
|
+
if (rows.length === 0) return [];
|
|
22394
|
+
const ranks = rows.map((r) => r.rank);
|
|
22395
|
+
const minRank = Math.min(...ranks);
|
|
22396
|
+
const maxRank = Math.max(...ranks);
|
|
22397
|
+
const range = maxRank - minRank;
|
|
22398
|
+
return rows.map((r) => ({
|
|
22399
|
+
id: r.id,
|
|
22400
|
+
score: range > 0 ? (maxRank - r.rank) / range : 1
|
|
22401
|
+
}));
|
|
22402
|
+
} catch (err) {
|
|
22403
|
+
console.warn("[memory-store] BM25 search failed:", err);
|
|
22404
|
+
return [];
|
|
22405
|
+
}
|
|
22406
|
+
}
|
|
22407
|
+
getChunksByIds(ids) {
|
|
22408
|
+
if (ids.length === 0) return [];
|
|
22409
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
22410
|
+
const rows = this.db.prepare(
|
|
22411
|
+
`SELECT id, source, heading, heading_level, content, line_start, line_end
|
|
22412
|
+
FROM memory_chunks WHERE id IN (${placeholders})`
|
|
22413
|
+
).all(...ids);
|
|
22414
|
+
return rows.map((r) => ({
|
|
22415
|
+
id: r.id,
|
|
22416
|
+
source: r.source,
|
|
22417
|
+
heading: r.heading,
|
|
22418
|
+
headingLevel: r.heading_level,
|
|
22419
|
+
content: r.content,
|
|
22420
|
+
lineStart: r.line_start,
|
|
22421
|
+
lineEnd: r.line_end
|
|
22422
|
+
}));
|
|
22423
|
+
}
|
|
22424
|
+
get vecAvailable() {
|
|
22425
|
+
return this._vecAvailable;
|
|
22426
|
+
}
|
|
22427
|
+
close() {
|
|
22428
|
+
this.db.close();
|
|
22429
|
+
}
|
|
22430
|
+
};
|
|
22431
|
+
|
|
22432
|
+
// src/tools/memory.ts
|
|
22433
|
+
function registerMemoryToolHandlers(server2, indexer, store, config2) {
|
|
22434
|
+
server2.tool(
|
|
22435
|
+
"kit_memory_search",
|
|
22436
|
+
"Search persistent memory for context relevant to the current question. Returns semantically relevant results using hybrid dense+BM25+RRF search.",
|
|
22437
|
+
{
|
|
22438
|
+
query: external_exports.string().min(1).describe("Search query"),
|
|
22439
|
+
top_k: external_exports.number().int().positive().optional().describe("Number of results to return")
|
|
22440
|
+
},
|
|
22441
|
+
async ({ query, top_k }) => {
|
|
22442
|
+
try {
|
|
22443
|
+
const results = await indexer.search(query, top_k ?? config2.topK);
|
|
22444
|
+
if (results.length === 0) {
|
|
22445
|
+
const degradedNote2 = !store.vecAvailable ? "\u26A0\uFE0F Vector search unavailable \u2014 showing keyword-only results.\n\n" : "";
|
|
22446
|
+
return mcpText(`${degradedNote2}No memories found for query: "${query}"`);
|
|
22447
|
+
}
|
|
22448
|
+
const degradedNote = !store.vecAvailable ? "\u26A0\uFE0F Vector search unavailable \u2014 showing keyword-only results.\n\n" : "";
|
|
22449
|
+
const formatted = results.map((r) => {
|
|
22450
|
+
const displaySource = r.chunk.source.replace(/^compiled\//, "");
|
|
22451
|
+
const content = r.contentSource === "fallback" ? `\u26A0\uFE0F Source file unavailable \u2014 showing matched chunk only:
|
|
22452
|
+
${r.chunk.content}` : r.chunk.content;
|
|
22453
|
+
return `### ${displaySource} (score: ${r.score.toFixed(3)})
|
|
22454
|
+
${content}`;
|
|
22455
|
+
}).join("\n\n---\n\n");
|
|
22456
|
+
return mcpText(`${degradedNote}${formatted}`);
|
|
22457
|
+
} catch (err) {
|
|
22458
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
22459
|
+
return mcpText(`kit_memory_search failed: ${message}`);
|
|
22460
|
+
}
|
|
22461
|
+
}
|
|
22462
|
+
);
|
|
22463
|
+
server2.tool(
|
|
22464
|
+
"kit_memory_save",
|
|
22465
|
+
"Save content to wiki/raw for inclusion after the next /wiki compile.",
|
|
22466
|
+
{
|
|
22467
|
+
content: external_exports.string().min(1).describe("Content to save to memory")
|
|
22468
|
+
},
|
|
22469
|
+
async ({ content }) => {
|
|
22470
|
+
try {
|
|
22471
|
+
await indexer.save(content);
|
|
22472
|
+
return mcpJson({
|
|
22473
|
+
saved: true,
|
|
22474
|
+
queued_for_compile: true,
|
|
22475
|
+
message: "Saved to wiki/raw \u2014 will be indexed after next /wiki compile"
|
|
22476
|
+
});
|
|
22477
|
+
} catch (err) {
|
|
22478
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
22479
|
+
return mcpJson({ saved: false, error: message });
|
|
22480
|
+
}
|
|
22481
|
+
}
|
|
22482
|
+
);
|
|
22483
|
+
}
|
|
22484
|
+
function registerMemoryTools(server2, settings2, workspaceRoot2) {
|
|
22485
|
+
if (settings2.memory?.enabled !== true) return null;
|
|
22486
|
+
const config2 = resolveMemoryConfig(settings2, workspaceRoot2);
|
|
22487
|
+
const store = new MemoryStore(path10.join(config2.wikiDir, "index.db"), config2);
|
|
22488
|
+
const embedder = new Embedder(config2.embeddingModel);
|
|
22489
|
+
const indexer = new MemoryIndexer(store, embedder, config2);
|
|
22490
|
+
registerMemoryToolHandlers(server2, indexer, store, config2);
|
|
22491
|
+
return indexer;
|
|
22492
|
+
}
|
|
22493
|
+
|
|
21745
22494
|
// src/kit-server.ts
|
|
21746
22495
|
var server = new McpServer({
|
|
21747
22496
|
name: "kit-agents",
|
|
@@ -21750,5 +22499,11 @@ var server = new McpServer({
|
|
|
21750
22499
|
registerIntegrationTools(server);
|
|
21751
22500
|
registerCoreTools(server);
|
|
21752
22501
|
registerAgentTools(server);
|
|
22502
|
+
var workspaceRoot = getWorkspaceRoot();
|
|
22503
|
+
var settings = loadProjectSettings(workspaceRoot);
|
|
22504
|
+
var memoryIndexer = registerMemoryTools(server, settings, workspaceRoot);
|
|
21753
22505
|
var transport = new StdioServerTransport();
|
|
21754
22506
|
await server.connect(transport);
|
|
22507
|
+
if (memoryIndexer) {
|
|
22508
|
+
void memoryIndexer.startupIndex();
|
|
22509
|
+
}
|