@hanhnd/agent-kit 1.0.32 → 1.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/cli-digest-pending.test.js +111 -0
- package/dist/cli/commands/cli-memory.test.js +34 -0
- package/dist/cli/{memory.js → commands/memory.js} +24 -11
- package/dist/cli/index.js +2 -2
- package/dist/{utils → cli/utils}/args.d.ts +2 -1
- package/dist/{kit-cli.js → cli.js} +342 -58
- package/dist/{tools/config.d.ts → core/config/index.d.ts} +2 -7
- package/dist/{tools/config.js → core/config/index.js} +2 -13
- package/dist/core/handoffs/index.d.ts +16 -0
- package/dist/{tools/handoffs.js → core/handoffs/index.js} +3 -3
- package/dist/{tools/security.d.ts → core/security/index.d.ts} +0 -19
- package/dist/{tools/security.js → core/security/index.js} +3 -76
- package/dist/entrypoints/cli.js +11 -0
- package/dist/entrypoints/server.js +37 -0
- package/dist/{tools → mcp}/agent.js +1 -1
- package/dist/{tools → mcp}/core.d.ts +1 -1
- package/dist/{tools → mcp}/core.js +2 -2
- package/dist/{tools → mcp}/integration.js +3 -3
- package/dist/mcp/memory.d.ts +15 -0
- package/dist/{tools → mcp}/memory.js +15 -13
- package/dist/{kit-server.js → server.js} +80 -70
- package/dist/services/digest/background.d.ts +11 -0
- package/dist/services/digest/background.js +258 -0
- package/dist/{memory → services}/digest/constants.d.ts +4 -1
- package/dist/{memory → services}/digest/constants.js +6 -2
- package/dist/services/digest/digest-background.test.js +206 -0
- package/dist/{tools/__tests__ → services/digest}/digest-init.test.js +4 -3
- package/dist/{memory/__tests__ → services/digest}/digest-llama-local.test.js +1 -13
- package/dist/{memory/__tests__ → services/digest}/digest-pending.test.js +39 -11
- package/dist/{memory/__tests__ → services/digest}/digest-processor.test.js +5 -7
- package/dist/{memory → services}/digest/files.d.ts +0 -1
- package/dist/{memory → services}/digest/files.js +2 -2
- package/dist/services/digest/model-registry.d.ts +2 -0
- package/dist/{memory → services}/digest/model-registry.js +9 -8
- package/dist/services/digest/processor.d.ts +13 -0
- package/dist/{memory → services}/digest/processor.js +48 -31
- package/dist/services/digest/providers/llama-local.d.ts +2 -0
- package/dist/{memory → services}/digest/providers/llama-local.js +3 -4
- package/dist/{memory → services}/digest/types.d.ts +34 -2
- package/dist/services/digest/types.js +6 -0
- package/dist/{memory → services}/digest/worker.d.ts +0 -1
- package/dist/{memory → services}/digest/worker.js +2 -2
- package/dist/{utils → services/integration}/credentials.js +1 -1
- package/dist/{utils/parser.js → services/integration/parser/adf.js} +1 -0
- package/dist/{memory → services/memory}/chunker.d.ts +0 -2
- package/dist/{memory → services/memory}/chunker.js +3 -3
- package/dist/{memory/__tests__ → services/memory}/chunker.test.js +1 -34
- package/dist/{memory → services/memory}/constants.d.ts +1 -1
- package/dist/{memory → services/memory}/constants.js +2 -1
- package/dist/{memory → services/memory}/embedder.d.ts +2 -2
- package/dist/{memory → services/memory}/embedder.js +5 -5
- package/dist/services/memory/embedder.test.js +13 -0
- package/dist/{memory → services/memory}/indexer.d.ts +2 -1
- package/dist/{memory → services/memory}/indexer.js +1 -1
- package/dist/{memory/__tests__ → services/memory}/indexer.test.js +4 -3
- package/dist/{tools/__tests__ → services/memory}/memory.test.js +54 -13
- package/dist/{memory/__tests__ → services/memory}/store.test.js +3 -2
- package/dist/{memory → services/memory}/types.d.ts +7 -2
- package/dist/services/memory/types.js +6 -0
- package/dist/utils/files.d.ts +0 -1
- package/dist/utils/files.js +1 -1
- package/dist/{memory/__tests__/lockfile.test.js → utils/files.test.js} +14 -14
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/json.d.ts +0 -1
- package/dist/utils/json.js +0 -10
- package/dist/utils/paths.d.ts +0 -1
- package/dist/utils/paths.js +1 -1
- package/package.json +6 -6
- package/dist/memory/__tests__/digest-worker.test.js +0 -46
- package/dist/memory/__tests__/embedder.test.js +0 -14
- package/dist/memory/digest/model-registry.d.ts +0 -6
- package/dist/memory/digest/processor.d.ts +0 -7
- package/dist/memory/digest/providers/llama-local.d.ts +0 -7
- package/dist/memory/digest/types.js +0 -1
- package/dist/memory/types.js +0 -1
- package/dist/tools/__tests__/cli-digest-pending.test.js +0 -51
- package/dist/tools/__tests__/cli-memory.test.js +0 -22
- package/dist/tools/handoffs.d.ts +0 -22
- package/dist/tools/memory.d.ts +0 -16
- /package/dist/{tools/__tests__ → cli/commands}/cli-digest-pending.test.d.ts +0 -0
- /package/dist/{tools/__tests__ → cli/commands}/cli-memory.test.d.ts +0 -0
- /package/dist/cli/{memory.d.ts → commands/memory.d.ts} +0 -0
- /package/dist/{utils → cli/utils}/args.js +0 -0
- /package/dist/{kit-cli.d.ts → entrypoints/cli.d.ts} +0 -0
- /package/dist/{kit-server.d.ts → entrypoints/server.d.ts} +0 -0
- /package/dist/{tools → mcp}/agent.d.ts +0 -0
- /package/dist/{tools → mcp}/integration.d.ts +0 -0
- /package/dist/{memory/__tests__/chunker.test.d.ts → services/digest/digest-background.test.d.ts} +0 -0
- /package/dist/{tools/__tests__ → services/digest}/digest-init.test.d.ts +0 -0
- /package/dist/{memory/__tests__ → services/digest}/digest-llama-local.test.d.ts +0 -0
- /package/dist/{memory/__tests__ → services/digest}/digest-pending.test.d.ts +0 -0
- /package/dist/{memory/__tests__ → services/digest}/digest-processor.test.d.ts +0 -0
- /package/dist/{utils → services/integration}/credentials.d.ts +0 -0
- /package/dist/{utils/parser.d.ts → services/integration/parser/adf.d.ts} +0 -0
- /package/dist/{memory/__tests__/digest-worker.test.d.ts → services/memory/chunker.test.d.ts} +0 -0
- /package/dist/{memory/__tests__ → services/memory}/embedder.test.d.ts +0 -0
- /package/dist/{memory → services/memory}/index.d.ts +0 -0
- /package/dist/{memory → services/memory}/index.js +0 -0
- /package/dist/{memory/__tests__ → services/memory}/indexer.test.d.ts +0 -0
- /package/dist/{tools/__tests__ → services/memory}/memory.test.d.ts +0 -0
- /package/dist/{memory → services/memory}/store.d.ts +0 -0
- /package/dist/{memory → services/memory}/store.js +0 -0
- /package/dist/{memory/__tests__ → services/memory}/store.test.d.ts +0 -0
- /package/dist/{memory/__tests__/lockfile.test.d.ts → utils/files.test.d.ts} +0 -0
- /package/dist/{__tests__/temp-dir.d.ts → utils/temp-dir.test.d.ts} +0 -0
- /package/dist/{__tests__/temp-dir.js → utils/temp-dir.test.js} +0 -0
|
@@ -5,7 +5,7 @@ import { LLAMA_CONTEXT_SIZE, LLAMA_MAX_GENERATED_TOKENS, LLAMA_TEMPERATURE } fro
|
|
|
5
5
|
import { getDigestModelSpec } from '../model-registry.js';
|
|
6
6
|
import { withTimeout } from '../../../utils/async.js';
|
|
7
7
|
import { MODEL_CACHE_DIR } from '../../../utils/paths.js';
|
|
8
|
-
|
|
8
|
+
class LlamaLocalDigestProviderError extends Error {
|
|
9
9
|
constructor(message) {
|
|
10
10
|
super(message);
|
|
11
11
|
this.name = 'LlamaLocalDigestProviderError';
|
|
@@ -17,7 +17,7 @@ function titleFromSource(sourcePath) {
|
|
|
17
17
|
.replace(/\.[^.]+$/, '')
|
|
18
18
|
.replace(/[_-]+/g, ' ');
|
|
19
19
|
}
|
|
20
|
-
|
|
20
|
+
function trimConversationExport(content, maxInputChars) {
|
|
21
21
|
const trimmed = content.trim();
|
|
22
22
|
if (trimmed.length <= maxInputChars)
|
|
23
23
|
return trimmed;
|
|
@@ -28,7 +28,6 @@ export function trimConversationExport(content, maxInputChars) {
|
|
|
28
28
|
}
|
|
29
29
|
function buildPrompt(input, maxInputChars) {
|
|
30
30
|
const conversationExport = trimConversationExport(input.content, maxInputChars);
|
|
31
|
-
const sourceName = path.basename(input.sourcePath);
|
|
32
31
|
return [
|
|
33
32
|
{
|
|
34
33
|
role: 'system',
|
|
@@ -75,7 +74,7 @@ function stripCodeFence(text) {
|
|
|
75
74
|
const match = trimmed.match(/^```(?:markdown|md)?\s*\n([\s\S]*?)\n```$/i);
|
|
76
75
|
return match ? match[1].trim() : trimmed;
|
|
77
76
|
}
|
|
78
|
-
|
|
77
|
+
function sanitizeConversationDigestMarkdown(generatedText) {
|
|
79
78
|
return stripCodeFence(generatedText).trim() + '\n';
|
|
80
79
|
}
|
|
81
80
|
export async function createLlamaLocalDigestProvider(modelId) {
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
export declare enum DigestModelId {
|
|
2
|
+
TINY = "tiny",
|
|
3
|
+
DEFAULT = "default",
|
|
4
|
+
LARGE = "large"
|
|
5
|
+
}
|
|
1
6
|
export interface ConversationDigestSettings {
|
|
2
7
|
enabled?: boolean;
|
|
3
8
|
initialized: boolean;
|
|
@@ -5,7 +10,7 @@ export interface ConversationDigestSettings {
|
|
|
5
10
|
initializedAt: string;
|
|
6
11
|
}
|
|
7
12
|
export interface DigestModelSpec {
|
|
8
|
-
id:
|
|
13
|
+
id: DigestModelId;
|
|
9
14
|
ggufUri: string;
|
|
10
15
|
approxSizeBytes: number;
|
|
11
16
|
license: string;
|
|
@@ -17,7 +22,7 @@ export interface ConversationDigestInput {
|
|
|
17
22
|
content: string;
|
|
18
23
|
contentHash: string;
|
|
19
24
|
}
|
|
20
|
-
|
|
25
|
+
interface ConversationDigestOptions {
|
|
21
26
|
modelId: string;
|
|
22
27
|
maxInputChars: number;
|
|
23
28
|
timeoutMs: number;
|
|
@@ -83,3 +88,30 @@ export type DigestPendingResult = {
|
|
|
83
88
|
action: 'error';
|
|
84
89
|
error: string;
|
|
85
90
|
};
|
|
91
|
+
export type DigestWorkerState = 'running' | 'complete' | 'failed' | 'not-initialized' | 'no-pending' | 'locked' | 'stale';
|
|
92
|
+
export interface DigestWorkerStatus {
|
|
93
|
+
state: DigestWorkerState;
|
|
94
|
+
pid: number | null;
|
|
95
|
+
startedAt: string;
|
|
96
|
+
updatedAt: string;
|
|
97
|
+
pendingAtStart: number;
|
|
98
|
+
processed: number;
|
|
99
|
+
skipped: number;
|
|
100
|
+
errors: number;
|
|
101
|
+
lastError?: string;
|
|
102
|
+
}
|
|
103
|
+
export interface DigestPendingCandidateSummary {
|
|
104
|
+
initialized: boolean;
|
|
105
|
+
pending: number;
|
|
106
|
+
reason?: 'not-initialized' | 'no-pending';
|
|
107
|
+
}
|
|
108
|
+
export interface DigestPendingLauncherResult {
|
|
109
|
+
ok: boolean;
|
|
110
|
+
mode: 'background-launcher';
|
|
111
|
+
status: DigestWorkerStatus;
|
|
112
|
+
spawned: boolean;
|
|
113
|
+
pid?: number;
|
|
114
|
+
reason?: string;
|
|
115
|
+
error?: string;
|
|
116
|
+
}
|
|
117
|
+
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { parseArgs, numberFlag } from '../../utils/args.js';
|
|
2
|
+
import { parseArgs, numberFlag } from '../../cli/utils/args.js';
|
|
3
3
|
import { DEFAULT_DIGEST_TIMEOUT_MS, DIGEST_TIMEOUT_GRACE_MS, DIGEST_WORKER_FLAG, DIGEST_WORKER_RESULT_PREFIX, } from './constants.js';
|
|
4
4
|
function isDigestWorkerResult(value) {
|
|
5
5
|
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
@@ -7,7 +7,7 @@ function isDigestWorkerResult(value) {
|
|
|
7
7
|
const record = value;
|
|
8
8
|
return typeof record.markdown === 'string' && record.status === 'provisional';
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
function parseDigestWorkerSuccess(stdout) {
|
|
11
11
|
const line = stdout
|
|
12
12
|
.split(/\r?\n/)
|
|
13
13
|
.reverse()
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* Active profile: KIT_PROFILE env var (defaults to "default")
|
|
15
15
|
*/
|
|
16
16
|
import * as fs from 'fs';
|
|
17
|
-
import { CREDENTIALS_FILE } from '
|
|
17
|
+
import { CREDENTIALS_FILE } from '../../utils/paths.js';
|
|
18
18
|
let cachedCredentials = null;
|
|
19
19
|
/**
|
|
20
20
|
* Parse a simple INI file into a map of profiles.
|
|
@@ -1,4 +1,2 @@
|
|
|
1
1
|
import type { MemoryChunk, MemoryConfig } from './types.js';
|
|
2
|
-
export declare function computeChunkId(content: string): string;
|
|
3
|
-
export declare function cleanContentForEmbedding(text: string): string;
|
|
4
2
|
export declare function chunkMarkdown(text: string, source: string, config: Pick<MemoryConfig, 'chunkSize' | 'overlapLines'>): MemoryChunk[];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { sha256Hex } from '
|
|
2
|
-
|
|
1
|
+
import { sha256Hex } from '../../utils/hash.js';
|
|
2
|
+
function computeChunkId(content) {
|
|
3
3
|
return sha256Hex(content).slice(0, 16);
|
|
4
4
|
}
|
|
5
|
-
|
|
5
|
+
function cleanContentForEmbedding(text) {
|
|
6
6
|
return text.replace(/<!--[\s\S]*?-->/g, '');
|
|
7
7
|
}
|
|
8
8
|
const HEADING_RE = /^(#{1,6}) (.+)$/;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as assert from 'node:assert/strict';
|
|
2
2
|
import { describe, test } from 'node:test';
|
|
3
|
-
import { chunkMarkdown
|
|
3
|
+
import { chunkMarkdown } from './chunker.js';
|
|
4
4
|
const CFG = { chunkSize: 200, overlapLines: 2 };
|
|
5
5
|
const SRC = 'test.md';
|
|
6
6
|
describe('chunkMarkdown', () => {
|
|
@@ -70,36 +70,3 @@ describe('chunkMarkdown', () => {
|
|
|
70
70
|
assert.ok(chunk.content.includes('Before.'), 'Text before comment must remain');
|
|
71
71
|
});
|
|
72
72
|
});
|
|
73
|
-
describe('computeChunkId', () => {
|
|
74
|
-
test('returns 16-character hex string', () => {
|
|
75
|
-
const id = computeChunkId('hello world');
|
|
76
|
-
assert.equal(id.length, 16);
|
|
77
|
-
assert.match(id, /^[0-9a-f]{16}$/);
|
|
78
|
-
});
|
|
79
|
-
test('same content always produces same id', () => {
|
|
80
|
-
assert.equal(computeChunkId('abc'), computeChunkId('abc'));
|
|
81
|
-
});
|
|
82
|
-
test('different content produces different id', () => {
|
|
83
|
-
assert.notEqual(computeChunkId('abc'), computeChunkId('xyz'));
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
describe('cleanContentForEmbedding', () => {
|
|
87
|
-
test('strips HTML comment', () => {
|
|
88
|
-
assert.equal(cleanContentForEmbedding('hello <!-- comment --> world'), 'hello world');
|
|
89
|
-
});
|
|
90
|
-
test('strips multiline HTML comment', () => {
|
|
91
|
-
const input = 'before <!-- line1\nline2 --> after';
|
|
92
|
-
const result = cleanContentForEmbedding(input);
|
|
93
|
-
assert.ok(!result.includes('line1'));
|
|
94
|
-
assert.ok(result.includes('before'));
|
|
95
|
-
assert.ok(result.includes('after'));
|
|
96
|
-
});
|
|
97
|
-
test('returns original string when no comments present', () => {
|
|
98
|
-
const text = 'plain text without comments';
|
|
99
|
-
assert.equal(cleanContentForEmbedding(text), text);
|
|
100
|
-
});
|
|
101
|
-
test('preserves fenced code block content', () => {
|
|
102
|
-
const text = '```js\nconst x = 1;\n```';
|
|
103
|
-
assert.equal(cleanContentForEmbedding(text), text);
|
|
104
|
-
});
|
|
105
|
-
});
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { EmbeddingModelName } from './types.js';
|
|
1
2
|
export const DEFAULT_MEMORY_CONFIG = {
|
|
2
3
|
enabled: false,
|
|
3
4
|
wikiDir: '',
|
|
4
5
|
topK: 5,
|
|
5
6
|
chunkSize: 1500,
|
|
6
7
|
overlapLines: 2,
|
|
7
|
-
embeddingModel:
|
|
8
|
+
embeddingModel: EmbeddingModelName.SMALL,
|
|
8
9
|
vectorDimension: 384,
|
|
9
10
|
};
|
|
10
11
|
export const LOCK_RETRY_MS = 50;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { EmbeddingModelName } from './types.js';
|
|
2
2
|
export declare class EmbedderError extends Error {
|
|
3
3
|
constructor(message: string, cause?: unknown);
|
|
4
4
|
}
|
|
@@ -6,7 +6,7 @@ export declare class Embedder {
|
|
|
6
6
|
private readonly modelName;
|
|
7
7
|
private pipeline;
|
|
8
8
|
private _dimension;
|
|
9
|
-
constructor(modelName:
|
|
9
|
+
constructor(modelName: EmbeddingModelName);
|
|
10
10
|
embed(texts: string[]): Promise<Float32Array[]>;
|
|
11
11
|
get dimension(): number | undefined;
|
|
12
12
|
isReady(): boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { FASTEMBED_CACHE_DIR } from '
|
|
2
|
-
|
|
1
|
+
import { FASTEMBED_CACHE_DIR } from '../../utils/paths.js';
|
|
2
|
+
import { EmbeddingModelName } from './types.js';
|
|
3
3
|
export class EmbedderError extends Error {
|
|
4
4
|
constructor(message, cause) {
|
|
5
5
|
super(message);
|
|
@@ -49,9 +49,9 @@ export class Embedder {
|
|
|
49
49
|
try {
|
|
50
50
|
const { EmbeddingModel, FlagEmbedding } = await import('fastembed');
|
|
51
51
|
const modelMap = {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
[EmbeddingModelName.TINY]: EmbeddingModel.AllMiniLML6V2,
|
|
53
|
+
[EmbeddingModelName.SMALL]: EmbeddingModel.BGESmallENV15,
|
|
54
|
+
[EmbeddingModelName.LARGE]: EmbeddingModel.BGEBaseENV15,
|
|
55
55
|
};
|
|
56
56
|
const model = modelMap[this.modelName] ?? EmbeddingModel.BGESmallENV15;
|
|
57
57
|
const embedder = await FlagEmbedding.init({
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as assert from 'node:assert/strict';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { describe, test } from 'node:test';
|
|
5
|
+
import { CREDENTIALS_FILE, MODEL_CACHE_DIR, FASTEMBED_CACHE_DIR } from '../../utils/paths.js';
|
|
6
|
+
describe('Embedder cache paths', () => {
|
|
7
|
+
test('uses global Agent Kit home for user-level files', () => {
|
|
8
|
+
const home = path.join(os.homedir(), '.agent-kit');
|
|
9
|
+
assert.equal(CREDENTIALS_FILE, path.join(home, 'credentials'));
|
|
10
|
+
assert.equal(MODEL_CACHE_DIR, path.join(home, 'cache', 'models'));
|
|
11
|
+
assert.equal(FASTEMBED_CACHE_DIR, path.join(MODEL_CACHE_DIR, 'fastembed'));
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { MemoryStore } from './store.js';
|
|
2
2
|
import type { IndexStats, MemoryConfig, SearchResult } from './types.js';
|
|
3
|
-
|
|
3
|
+
interface TextEmbedder {
|
|
4
4
|
embed(texts: string[]): Promise<Float32Array[]>;
|
|
5
5
|
initialize(): Promise<void>;
|
|
6
6
|
}
|
|
@@ -21,3 +21,4 @@ export declare class MemoryIndexer {
|
|
|
21
21
|
startupIndex(): Promise<void>;
|
|
22
22
|
get ready(): boolean;
|
|
23
23
|
}
|
|
24
|
+
export {};
|
|
@@ -2,7 +2,7 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { chunkMarkdown } from './chunker.js';
|
|
4
4
|
import { LOCK_RETRY_MS, LOCK_TIMEOUT_MS, RRF_K } from './constants.js';
|
|
5
|
-
import { acquireLock, releaseLock } from '
|
|
5
|
+
import { acquireLock, releaseLock } from '../../utils/files.js';
|
|
6
6
|
export class MemoryIndexer {
|
|
7
7
|
store;
|
|
8
8
|
embedder;
|
|
@@ -5,8 +5,9 @@ import { syncBuiltinESMExports } from 'node:module';
|
|
|
5
5
|
import * as os from 'node:os';
|
|
6
6
|
import * as path from 'node:path';
|
|
7
7
|
import { after, before, describe, test } from 'node:test';
|
|
8
|
-
import { MemoryIndexer } from '
|
|
9
|
-
import { MemoryStore } from '
|
|
8
|
+
import { MemoryIndexer } from './indexer.js';
|
|
9
|
+
import { MemoryStore } from './store.js';
|
|
10
|
+
import { EmbeddingModelName } from './types.js';
|
|
10
11
|
class StubEmbedder {
|
|
11
12
|
async embed(texts) {
|
|
12
13
|
return texts.map(() => new Float32Array(384).fill(0.05));
|
|
@@ -22,7 +23,7 @@ function makeConfig(wikiDir) {
|
|
|
22
23
|
topK: 5,
|
|
23
24
|
chunkSize: 1500,
|
|
24
25
|
overlapLines: 2,
|
|
25
|
-
embeddingModel:
|
|
26
|
+
embeddingModel: EmbeddingModelName.SMALL,
|
|
26
27
|
vectorDimension: 384,
|
|
27
28
|
};
|
|
28
29
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as assert from 'node:assert/strict';
|
|
2
2
|
import { describe, test } from 'node:test';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { EmbeddingModelName } from './types.js';
|
|
4
|
+
import { resolveMemoryConfig } from '../../core/config/index.js';
|
|
5
|
+
import { registerMemoryTools } from '../../mcp/memory.js';
|
|
5
6
|
// Minimal McpServer stub that captures tool registrations
|
|
6
7
|
function makeMockServer() {
|
|
7
8
|
const tools = new Map();
|
|
@@ -30,7 +31,7 @@ const BASE_CONFIG = {
|
|
|
30
31
|
topK: 5,
|
|
31
32
|
chunkSize: 1500,
|
|
32
33
|
overlapLines: 2,
|
|
33
|
-
embeddingModel:
|
|
34
|
+
embeddingModel: EmbeddingModelName.SMALL,
|
|
34
35
|
vectorDimension: 384,
|
|
35
36
|
};
|
|
36
37
|
function makeIndexerStub(overrides = {}) {
|
|
@@ -61,7 +62,11 @@ describe('kit_memory_search', () => {
|
|
|
61
62
|
const indexer = makeIndexerStub({ search: async () => [searchResult] });
|
|
62
63
|
const store = makeStoreStub(true);
|
|
63
64
|
const { server, tools } = makeMockServer();
|
|
64
|
-
|
|
65
|
+
registerMemoryTools(server, { memory: { enabled: true } }, '/tmp', {
|
|
66
|
+
indexer,
|
|
67
|
+
store,
|
|
68
|
+
config: BASE_CONFIG,
|
|
69
|
+
});
|
|
65
70
|
const result = await tools.get('kit_memory_search')({ query: 'test query' });
|
|
66
71
|
const text = extractText(result);
|
|
67
72
|
assert.ok(text.includes('Important memory content'), 'Result must include chunk content');
|
|
@@ -73,7 +78,11 @@ describe('kit_memory_search', () => {
|
|
|
73
78
|
const indexer = makeIndexerStub({ search: async () => [] });
|
|
74
79
|
const store = makeStoreStub(true);
|
|
75
80
|
const { server, tools } = makeMockServer();
|
|
76
|
-
|
|
81
|
+
registerMemoryTools(server, { memory: { enabled: true } }, '/tmp', {
|
|
82
|
+
indexer,
|
|
83
|
+
store,
|
|
84
|
+
config: BASE_CONFIG,
|
|
85
|
+
});
|
|
77
86
|
const result = await tools.get('kit_memory_search')({ query: 'nothing here' });
|
|
78
87
|
const text = extractText(result);
|
|
79
88
|
assert.ok(text.toLowerCase().includes('no memories') || text.includes('No memories'), `Expected "no memories" message, got: ${text.slice(0, 100)}`);
|
|
@@ -82,7 +91,11 @@ describe('kit_memory_search', () => {
|
|
|
82
91
|
const indexer = makeIndexerStub({ search: async () => [] });
|
|
83
92
|
const store = makeStoreStub(false);
|
|
84
93
|
const { server, tools } = makeMockServer();
|
|
85
|
-
|
|
94
|
+
registerMemoryTools(server, { memory: { enabled: true } }, '/tmp', {
|
|
95
|
+
indexer,
|
|
96
|
+
store,
|
|
97
|
+
config: BASE_CONFIG,
|
|
98
|
+
});
|
|
86
99
|
const result = await tools.get('kit_memory_search')({ query: 'test' });
|
|
87
100
|
const text = extractText(result);
|
|
88
101
|
assert.ok(text.includes('Vector search unavailable') || text.includes('keyword-only'), `Expected degraded warning in: ${text.slice(0, 200)}`);
|
|
@@ -98,7 +111,11 @@ describe('kit_memory_search', () => {
|
|
|
98
111
|
const indexer = makeIndexerStub({ search: async () => [searchResult] });
|
|
99
112
|
const store = makeStoreStub(false);
|
|
100
113
|
const { server, tools } = makeMockServer();
|
|
101
|
-
|
|
114
|
+
registerMemoryTools(server, { memory: { enabled: true } }, '/tmp', {
|
|
115
|
+
indexer,
|
|
116
|
+
store,
|
|
117
|
+
config: BASE_CONFIG,
|
|
118
|
+
});
|
|
102
119
|
const result = await tools.get('kit_memory_search')({ query: 'test' });
|
|
103
120
|
const text = extractText(result);
|
|
104
121
|
assert.ok(text.includes('Vector search unavailable') || text.includes('keyword-only'), `Expected degraded warning with results in: ${text.slice(0, 200)}`);
|
|
@@ -115,7 +132,11 @@ describe('kit_memory_search', () => {
|
|
|
115
132
|
const indexer = makeIndexerStub({ search: async () => [searchResult] });
|
|
116
133
|
const store = makeStoreStub(true);
|
|
117
134
|
const { server, tools } = makeMockServer();
|
|
118
|
-
|
|
135
|
+
registerMemoryTools(server, { memory: { enabled: true } }, '/tmp', {
|
|
136
|
+
indexer,
|
|
137
|
+
store,
|
|
138
|
+
config: BASE_CONFIG,
|
|
139
|
+
});
|
|
119
140
|
const result = await tools.get('kit_memory_search')({ query: 'test' });
|
|
120
141
|
const text = extractText(result);
|
|
121
142
|
assert.ok(text.includes('Source file unavailable'), `Expected fallback warning in: ${text}`);
|
|
@@ -131,7 +152,11 @@ describe('kit_memory_search', () => {
|
|
|
131
152
|
});
|
|
132
153
|
const store = makeStoreStub(true);
|
|
133
154
|
const { server, tools } = makeMockServer();
|
|
134
|
-
|
|
155
|
+
registerMemoryTools(server, { memory: { enabled: true } }, '/tmp', {
|
|
156
|
+
indexer,
|
|
157
|
+
store,
|
|
158
|
+
config: BASE_CONFIG,
|
|
159
|
+
});
|
|
135
160
|
await tools.get('kit_memory_search')({ query: 'test', top_k: 3 });
|
|
136
161
|
assert.equal(capturedTopK, 3);
|
|
137
162
|
});
|
|
@@ -145,7 +170,11 @@ describe('kit_memory_search', () => {
|
|
|
145
170
|
});
|
|
146
171
|
const store = makeStoreStub(true);
|
|
147
172
|
const { server, tools } = makeMockServer();
|
|
148
|
-
|
|
173
|
+
registerMemoryTools(server, { memory: { enabled: true } }, '/tmp', {
|
|
174
|
+
indexer,
|
|
175
|
+
store,
|
|
176
|
+
config: BASE_CONFIG,
|
|
177
|
+
});
|
|
149
178
|
await tools.get('kit_memory_search')({ query: 'test' });
|
|
150
179
|
assert.equal(capturedTopK, BASE_CONFIG.topK);
|
|
151
180
|
});
|
|
@@ -157,7 +186,11 @@ describe('kit_memory_search', () => {
|
|
|
157
186
|
});
|
|
158
187
|
const store = makeStoreStub(true);
|
|
159
188
|
const { server, tools } = makeMockServer();
|
|
160
|
-
|
|
189
|
+
registerMemoryTools(server, { memory: { enabled: true } }, '/tmp', {
|
|
190
|
+
indexer,
|
|
191
|
+
store,
|
|
192
|
+
config: BASE_CONFIG,
|
|
193
|
+
});
|
|
161
194
|
const result = await tools.get('kit_memory_search')({ query: 'test' });
|
|
162
195
|
const text = extractText(result);
|
|
163
196
|
assert.ok(text.includes('kit_memory_search failed'), `Expected error prefix, got: ${text}`);
|
|
@@ -171,7 +204,11 @@ describe('kit_memory_save', () => {
|
|
|
171
204
|
});
|
|
172
205
|
const store = makeStoreStub(true);
|
|
173
206
|
const { server, tools } = makeMockServer();
|
|
174
|
-
|
|
207
|
+
registerMemoryTools(server, { memory: { enabled: true } }, '/tmp', {
|
|
208
|
+
indexer,
|
|
209
|
+
store,
|
|
210
|
+
config: BASE_CONFIG,
|
|
211
|
+
});
|
|
175
212
|
const result = await tools.get('kit_memory_save')({ content: 'Some important note' });
|
|
176
213
|
const text = extractText(result);
|
|
177
214
|
const parsed = JSON.parse(text);
|
|
@@ -187,7 +224,11 @@ describe('kit_memory_save', () => {
|
|
|
187
224
|
});
|
|
188
225
|
const store = makeStoreStub(true);
|
|
189
226
|
const { server, tools } = makeMockServer();
|
|
190
|
-
|
|
227
|
+
registerMemoryTools(server, { memory: { enabled: true } }, '/tmp', {
|
|
228
|
+
indexer,
|
|
229
|
+
store,
|
|
230
|
+
config: BASE_CONFIG,
|
|
231
|
+
});
|
|
191
232
|
const result = await tools.get('kit_memory_save')({ content: 'note' });
|
|
192
233
|
const text = extractText(result);
|
|
193
234
|
const parsed = JSON.parse(text);
|
|
@@ -3,14 +3,15 @@ import * as fs from 'node:fs';
|
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { after, before, describe, test } from 'node:test';
|
|
6
|
-
import { MemoryStore } from '
|
|
6
|
+
import { MemoryStore } from './store.js';
|
|
7
|
+
import { EmbeddingModelName } from './types.js';
|
|
7
8
|
const TEST_CONFIG = {
|
|
8
9
|
enabled: true,
|
|
9
10
|
wikiDir: '',
|
|
10
11
|
topK: 5,
|
|
11
12
|
chunkSize: 1500,
|
|
12
13
|
overlapLines: 2,
|
|
13
|
-
embeddingModel:
|
|
14
|
+
embeddingModel: EmbeddingModelName.SMALL,
|
|
14
15
|
vectorDimension: 384,
|
|
15
16
|
};
|
|
16
17
|
function makeChunk(overrides = {}) {
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type { ConversationDigestSettings } from '
|
|
1
|
+
import type { ConversationDigestSettings } from '../digest/types.js';
|
|
2
|
+
export declare enum EmbeddingModelName {
|
|
3
|
+
TINY = "tiny",
|
|
4
|
+
SMALL = "small",
|
|
5
|
+
LARGE = "large"
|
|
6
|
+
}
|
|
2
7
|
export interface MemoryChunk {
|
|
3
8
|
id: string;
|
|
4
9
|
source: string;
|
|
@@ -20,7 +25,7 @@ export interface MemoryConfig {
|
|
|
20
25
|
topK: number;
|
|
21
26
|
chunkSize: number;
|
|
22
27
|
overlapLines: number;
|
|
23
|
-
embeddingModel:
|
|
28
|
+
embeddingModel: EmbeddingModelName;
|
|
24
29
|
vectorDimension: number;
|
|
25
30
|
conversationDigest?: ConversationDigestSettings;
|
|
26
31
|
}
|
package/dist/utils/files.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export declare function atomicWriteTextFile(filePath: string, content: string): void;
|
|
2
2
|
export declare function atomicWriteJsonFile(filePath: string, value: unknown): void;
|
|
3
|
-
export declare function tryWriteFileExclusive(filePath: string, content: string): boolean;
|
|
4
3
|
export declare function acquireLock(lockPath: string, timeoutMs: number, retryMs: number): Promise<boolean>;
|
|
5
4
|
export declare function releaseLock(lockPath: string, opts?: {
|
|
6
5
|
ignoreErrors?: boolean;
|
package/dist/utils/files.js
CHANGED
|
@@ -9,7 +9,7 @@ export function atomicWriteTextFile(filePath, content) {
|
|
|
9
9
|
export function atomicWriteJsonFile(filePath, value) {
|
|
10
10
|
atomicWriteTextFile(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
function tryWriteFileExclusive(filePath, content) {
|
|
13
13
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
14
14
|
try {
|
|
15
15
|
fs.writeFileSync(filePath, content, { flag: 'wx' });
|
|
@@ -2,9 +2,9 @@ import * as assert from 'node:assert/strict';
|
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import { afterEach, describe, test } from 'node:test';
|
|
5
|
-
import { createTempDirTracker } from '
|
|
6
|
-
import { DIGEST_LOCKFILE_REL_PATH } from '../digest/constants.js';
|
|
7
|
-
import { releaseLock, tryAcquireProcessLock } from '
|
|
5
|
+
import { createTempDirTracker } from './temp-dir.test.js';
|
|
6
|
+
import { DIGEST_LOCKFILE_REL_PATH } from '../services/digest/constants.js';
|
|
7
|
+
import { releaseLock, tryAcquireProcessLock } from './files.js';
|
|
8
8
|
const tempDirs = createTempDirTracker();
|
|
9
9
|
afterEach(() => {
|
|
10
10
|
tempDirs.cleanup();
|
|
@@ -12,20 +12,22 @@ afterEach(() => {
|
|
|
12
12
|
function lockPath(workspace) {
|
|
13
13
|
return path.join(workspace, DIGEST_LOCKFILE_REL_PATH);
|
|
14
14
|
}
|
|
15
|
+
function lockDir(workspace) {
|
|
16
|
+
return path.dirname(lockPath(workspace));
|
|
17
|
+
}
|
|
15
18
|
describe('tryAcquireProcessLock', () => {
|
|
16
19
|
test('C1: no lockfile → creates it with current PID and returns true', () => {
|
|
17
20
|
const workspace = tempDirs.makeTempDir('lockfile-c1-');
|
|
18
21
|
const acquired = tryAcquireProcessLock(lockPath(workspace));
|
|
19
22
|
assert.equal(acquired, true);
|
|
20
|
-
const lockFile =
|
|
23
|
+
const lockFile = lockPath(workspace);
|
|
21
24
|
const content = JSON.parse(fs.readFileSync(lockFile, 'utf8'));
|
|
22
25
|
assert.equal(content.pid, process.pid);
|
|
23
26
|
});
|
|
24
27
|
test('C2: lockfile with live foreign PID → returns false without modifying file', () => {
|
|
25
28
|
const workspace = tempDirs.makeTempDir('lockfile-c2-');
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
fs.mkdirSync(lockDir, { recursive: true });
|
|
29
|
+
const lockFile = lockPath(workspace);
|
|
30
|
+
fs.mkdirSync(lockDir(workspace), { recursive: true });
|
|
29
31
|
// Use process.pid as "live" pid (current process is definitely alive)
|
|
30
32
|
const livePid = process.pid;
|
|
31
33
|
fs.writeFileSync(lockFile, JSON.stringify({ pid: livePid }), 'utf8');
|
|
@@ -37,9 +39,8 @@ describe('tryAcquireProcessLock', () => {
|
|
|
37
39
|
});
|
|
38
40
|
test('C3: lockfile with dead PID (ESRCH) → deletes stale lock, creates new one, returns true', () => {
|
|
39
41
|
const workspace = tempDirs.makeTempDir('lockfile-c3-');
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
fs.mkdirSync(lockDir, { recursive: true });
|
|
42
|
+
const lockFile = lockPath(workspace);
|
|
43
|
+
fs.mkdirSync(lockDir(workspace), { recursive: true });
|
|
43
44
|
// PID 999999999 is virtually guaranteed to be dead
|
|
44
45
|
fs.writeFileSync(lockFile, JSON.stringify({ pid: 999999999 }), 'utf8');
|
|
45
46
|
const acquired = tryAcquireProcessLock(lockPath(workspace));
|
|
@@ -57,9 +58,8 @@ describe('tryAcquireProcessLock', () => {
|
|
|
57
58
|
});
|
|
58
59
|
test('stale unparseable lockfile → treated as stale, returns true', () => {
|
|
59
60
|
const workspace = tempDirs.makeTempDir('lockfile-stale-');
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
fs.mkdirSync(lockDir, { recursive: true });
|
|
61
|
+
const lockFile = lockPath(workspace);
|
|
62
|
+
fs.mkdirSync(lockDir(workspace), { recursive: true });
|
|
63
63
|
fs.writeFileSync(lockFile, 'not-json', 'utf8');
|
|
64
64
|
const acquired = tryAcquireProcessLock(lockPath(workspace));
|
|
65
65
|
assert.equal(acquired, true);
|
|
@@ -69,7 +69,7 @@ describe('releaseLock', () => {
|
|
|
69
69
|
test('C5: lockfile exists → deletes it', () => {
|
|
70
70
|
const workspace = tempDirs.makeTempDir('lockfile-c5-');
|
|
71
71
|
assert.equal(tryAcquireProcessLock(lockPath(workspace)), true);
|
|
72
|
-
const lockFile =
|
|
72
|
+
const lockFile = lockPath(workspace);
|
|
73
73
|
assert.equal(fs.existsSync(lockFile), true);
|
|
74
74
|
releaseLock(lockPath(workspace));
|
|
75
75
|
assert.equal(fs.existsSync(lockFile), false);
|
package/dist/utils/json.d.ts
CHANGED
package/dist/utils/json.js
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
import * as fs from 'node:fs';
|
|
2
1
|
export function isRecord(value) {
|
|
3
2
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
4
3
|
}
|
|
5
|
-
export function readJsonObjectFile(filePath) {
|
|
6
|
-
if (!fs.existsSync(filePath))
|
|
7
|
-
return {};
|
|
8
|
-
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
9
|
-
if (!isRecord(parsed)) {
|
|
10
|
-
throw new Error(`${filePath} must contain a JSON object`);
|
|
11
|
-
}
|
|
12
|
-
return parsed;
|
|
13
|
-
}
|
package/dist/utils/paths.d.ts
CHANGED