@hanhnd/agent-kit 1.0.31 → 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.
Files changed (105) hide show
  1. package/dist/cli/commands/cli-digest-pending.test.js +111 -0
  2. package/dist/cli/commands/cli-memory.test.js +34 -0
  3. package/dist/cli/commands/memory.d.ts +1 -0
  4. package/dist/cli/commands/memory.js +90 -0
  5. package/dist/cli/index.d.ts +1 -0
  6. package/dist/cli/index.js +9 -0
  7. package/dist/cli/utils/args.d.ts +8 -0
  8. package/dist/cli/utils/args.js +31 -0
  9. package/dist/cli.js +16489 -0
  10. package/dist/{tools/config.d.ts → core/config/index.d.ts} +4 -9
  11. package/dist/{tools/config.js → core/config/index.js} +20 -23
  12. package/dist/core/handoffs/index.d.ts +16 -0
  13. package/dist/{tools/handoffs.js → core/handoffs/index.js} +4 -5
  14. package/dist/{tools/security.d.ts → core/security/index.d.ts} +0 -19
  15. package/dist/{tools/security.js → core/security/index.js} +3 -80
  16. package/dist/entrypoints/cli.d.ts +2 -0
  17. package/dist/entrypoints/cli.js +11 -0
  18. package/dist/entrypoints/server.js +37 -0
  19. package/dist/{tools → mcp}/agent.js +3 -14
  20. package/dist/{tools → mcp}/core.d.ts +1 -1
  21. package/dist/{tools → mcp}/core.js +3 -5
  22. package/dist/{tools → mcp}/integration.js +5 -11
  23. package/dist/mcp/memory.d.ts +15 -0
  24. package/dist/{tools → mcp}/memory.js +40 -12
  25. package/dist/{kit-server.js → server.js} +720 -224
  26. package/dist/services/digest/background.d.ts +11 -0
  27. package/dist/services/digest/background.js +258 -0
  28. package/dist/services/digest/constants.d.ts +17 -0
  29. package/dist/services/digest/constants.js +19 -0
  30. package/dist/services/digest/digest-background.test.js +206 -0
  31. package/dist/services/digest/digest-init.test.js +33 -0
  32. package/dist/services/digest/digest-llama-local.test.js +21 -0
  33. package/dist/services/digest/digest-pending.test.d.ts +1 -0
  34. package/dist/services/digest/digest-pending.test.js +192 -0
  35. package/dist/services/digest/digest-processor.test.d.ts +1 -0
  36. package/dist/services/digest/digest-processor.test.js +32 -0
  37. package/dist/services/digest/files.d.ts +6 -0
  38. package/dist/services/digest/files.js +45 -0
  39. package/dist/services/digest/model-registry.d.ts +2 -0
  40. package/dist/services/digest/model-registry.js +49 -0
  41. package/dist/services/digest/processor.d.ts +13 -0
  42. package/dist/services/digest/processor.js +188 -0
  43. package/dist/services/digest/providers/llama-local.d.ts +2 -0
  44. package/dist/services/digest/providers/llama-local.js +135 -0
  45. package/dist/services/digest/types.d.ts +117 -0
  46. package/dist/services/digest/types.js +6 -0
  47. package/dist/services/digest/worker.d.ts +2 -0
  48. package/dist/services/digest/worker.js +51 -0
  49. package/dist/{utils → services/integration}/credentials.js +1 -1
  50. package/dist/{utils/parser.js → services/integration/parser/adf.js} +3 -6
  51. package/dist/{memory → services/memory}/chunker.d.ts +0 -2
  52. package/dist/{memory → services/memory}/chunker.js +5 -7
  53. package/dist/services/memory/chunker.test.d.ts +1 -0
  54. package/dist/{memory/__tests__ → services/memory}/chunker.test.js +1 -34
  55. package/dist/services/memory/constants.d.ts +5 -0
  56. package/dist/services/memory/constants.js +13 -0
  57. package/dist/{memory → services/memory}/embedder.d.ts +3 -2
  58. package/dist/{memory → services/memory}/embedder.js +15 -6
  59. package/dist/services/memory/embedder.test.d.ts +1 -0
  60. package/dist/services/memory/embedder.test.js +13 -0
  61. package/dist/{memory → services/memory}/index.d.ts +1 -1
  62. package/dist/{memory → services/memory}/index.js +1 -1
  63. package/dist/{memory → services/memory}/indexer.d.ts +6 -2
  64. package/dist/{memory → services/memory}/indexer.js +15 -37
  65. package/dist/services/memory/indexer.test.d.ts +1 -0
  66. package/dist/{memory/__tests__ → services/memory}/indexer.test.js +11 -15
  67. package/dist/services/memory/memory.test.d.ts +1 -0
  68. package/dist/{tools/__tests__ → services/memory}/memory.test.js +82 -19
  69. package/dist/{memory → services/memory}/store.js +6 -18
  70. package/dist/services/memory/store.test.d.ts +1 -0
  71. package/dist/{memory/__tests__ → services/memory}/store.test.js +18 -5
  72. package/dist/{memory → services/memory}/types.d.ts +8 -2
  73. package/dist/services/memory/types.js +6 -0
  74. package/dist/utils/async.d.ts +1 -0
  75. package/dist/utils/async.js +15 -0
  76. package/dist/utils/files.d.ts +7 -0
  77. package/dist/utils/files.js +77 -0
  78. package/dist/utils/files.test.d.ts +1 -0
  79. package/dist/utils/files.test.js +81 -0
  80. package/dist/utils/hash.d.ts +1 -0
  81. package/dist/utils/hash.js +4 -0
  82. package/dist/utils/index.d.ts +6 -0
  83. package/dist/utils/index.js +6 -0
  84. package/dist/utils/json.d.ts +1 -0
  85. package/dist/utils/json.js +3 -0
  86. package/dist/utils/paths.d.ts +0 -1
  87. package/dist/utils/paths.js +1 -1
  88. package/dist/utils/temp-dir.test.d.ts +4 -0
  89. package/dist/utils/temp-dir.test.js +20 -0
  90. package/package.json +8 -6
  91. package/dist/memory/__tests__/embedder.test.js +0 -14
  92. package/dist/memory/types.js +0 -9
  93. package/dist/tools/handoffs.d.ts +0 -22
  94. package/dist/tools/memory.d.ts +0 -16
  95. /package/dist/{memory/__tests__/chunker.test.d.ts → cli/commands/cli-digest-pending.test.d.ts} +0 -0
  96. /package/dist/{memory/__tests__/embedder.test.d.ts → cli/commands/cli-memory.test.d.ts} +0 -0
  97. /package/dist/{kit-server.d.ts → entrypoints/server.d.ts} +0 -0
  98. /package/dist/{tools → mcp}/agent.d.ts +0 -0
  99. /package/dist/{tools → mcp}/integration.d.ts +0 -0
  100. /package/dist/{memory/__tests__/indexer.test.d.ts → services/digest/digest-background.test.d.ts} +0 -0
  101. /package/dist/{memory/__tests__/store.test.d.ts → services/digest/digest-init.test.d.ts} +0 -0
  102. /package/dist/{tools/__tests__/memory.test.d.ts → services/digest/digest-llama-local.test.d.ts} +0 -0
  103. /package/dist/{utils → services/integration}/credentials.d.ts +0 -0
  104. /package/dist/{utils/parser.d.ts → services/integration/parser/adf.d.ts} +0 -0
  105. /package/dist/{memory → services/memory}/store.d.ts +0 -0
@@ -0,0 +1,135 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { getLlama, LlamaChatSession, createModelDownloader } from 'node-llama-cpp';
4
+ import { LLAMA_CONTEXT_SIZE, LLAMA_MAX_GENERATED_TOKENS, LLAMA_TEMPERATURE } from '../constants.js';
5
+ import { getDigestModelSpec } from '../model-registry.js';
6
+ import { withTimeout } from '../../../utils/async.js';
7
+ import { MODEL_CACHE_DIR } from '../../../utils/paths.js';
8
+ class LlamaLocalDigestProviderError extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = 'LlamaLocalDigestProviderError';
12
+ }
13
+ }
14
+ function titleFromSource(sourcePath) {
15
+ return path
16
+ .basename(sourcePath)
17
+ .replace(/\.[^.]+$/, '')
18
+ .replace(/[_-]+/g, ' ');
19
+ }
20
+ function trimConversationExport(content, maxInputChars) {
21
+ const trimmed = content.trim();
22
+ if (trimmed.length <= maxInputChars)
23
+ return trimmed;
24
+ return [
25
+ '[Earlier conversation content omitted to fit the local digest context window.]',
26
+ trimmed.slice(-maxInputChars),
27
+ ].join('\n\n');
28
+ }
29
+ function buildPrompt(input, maxInputChars) {
30
+ const conversationExport = trimConversationExport(input.content, maxInputChars);
31
+ return [
32
+ {
33
+ role: 'system',
34
+ content: [
35
+ '# ROLE',
36
+ 'You write short project-memory wiki pages from developer transcripts.',
37
+ 'Your output is temporary recall to prevent blind steps.',
38
+ '',
39
+ '# RULES',
40
+ '1. Read the end of the export FIRST to discover the ultimate resolution.',
41
+ '2. What the user says LAST ALWAYS overrides earlier agreements.',
42
+ '3. Capture absolute engineering conclusions, never conversational pleasantries.',
43
+ '4. Return Markdown only. Do not wrap output in code blocks (```markdown ... ```).',
44
+ ].join('\n'),
45
+ },
46
+ {
47
+ role: 'user',
48
+ content: [
49
+ 'Generate a high-level project memory page. STRICTLY adhere to this architectural layout:',
50
+ '',
51
+ '<layout>',
52
+ '# Digest: ' + titleFromSource(input.sourcePath),
53
+ '- **Ultimate Core Resolution**: [Describe the final working state of the feature and what problem it solves in 1-2 sentences. No conversational filler.]',
54
+ '- **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.]',
55
+ '- **Component State**: [Briefly state how the Plugin side and the MCP/CLI side now interact based on the final decision.]',
56
+ '- **Considered & Rejected**: [List concepts proposed but discarded (e.g., initial hooks location, mocking strategies). Omit if empty.]',
57
+ '</layout>',
58
+ '',
59
+ '# CRITICAL RULES:',
60
+ '- DO NOT list individual file paths, line numbers, or specific test cases (EXCLUDE lists of files).',
61
+ '- Focus entirely on the SYSTEM DESIGN and ARCHITECTURE that the next engineer needs to know.',
62
+ '- Keep output under 300 tokens. Stop immediately after the last valid section.',
63
+ '',
64
+ 'Source path: ' + input.sourcePath,
65
+ '<conversation_export format="memory-kit">',
66
+ conversationExport,
67
+ '</conversation_export>',
68
+ ].join('\n'),
69
+ },
70
+ ];
71
+ }
72
+ function stripCodeFence(text) {
73
+ const trimmed = text.trim();
74
+ const match = trimmed.match(/^```(?:markdown|md)?\s*\n([\s\S]*?)\n```$/i);
75
+ return match ? match[1].trim() : trimmed;
76
+ }
77
+ function sanitizeConversationDigestMarkdown(generatedText) {
78
+ return stripCodeFence(generatedText).trim() + '\n';
79
+ }
80
+ export async function createLlamaLocalDigestProvider(modelId) {
81
+ const spec = getDigestModelSpec(modelId);
82
+ const modelCacheDir = path.join(MODEL_CACHE_DIR, 'llama');
83
+ fs.mkdirSync(modelCacheDir, { recursive: true });
84
+ let modelFilePath;
85
+ try {
86
+ const downloader = await createModelDownloader({
87
+ modelUri: spec.ggufUri,
88
+ dirPath: modelCacheDir,
89
+ });
90
+ modelFilePath = downloader.entrypointFilePath;
91
+ if (!fs.existsSync(modelFilePath)) {
92
+ await downloader.download();
93
+ }
94
+ }
95
+ catch (err) {
96
+ const message = err instanceof Error ? err.message : String(err);
97
+ throw new LlamaLocalDigestProviderError(`Failed to download model: ${message}`);
98
+ }
99
+ let llama;
100
+ let model;
101
+ try {
102
+ llama = await getLlama();
103
+ model = await llama.loadModel({ modelPath: modelFilePath });
104
+ }
105
+ catch (err) {
106
+ const message = err instanceof Error ? err.message : String(err);
107
+ throw new LlamaLocalDigestProviderError(`Failed to load model: ${message}`);
108
+ }
109
+ return {
110
+ id: 'llama-local',
111
+ async dispose() {
112
+ await model.dispose();
113
+ },
114
+ async generateDigestMarkdown(input, options) {
115
+ const messages = buildPrompt(input, options.maxInputChars);
116
+ const systemContent = messages[0].content;
117
+ const userContent = messages[1].content;
118
+ const context = await model.createContext({ contextSize: LLAMA_CONTEXT_SIZE });
119
+ try {
120
+ const session = new LlamaChatSession({
121
+ contextSequence: context.getSequence(),
122
+ systemPrompt: systemContent,
123
+ });
124
+ const response = await withTimeout(session.prompt(userContent, {
125
+ maxTokens: LLAMA_MAX_GENERATED_TOKENS,
126
+ temperature: LLAMA_TEMPERATURE,
127
+ }), options.timeoutMs, () => new LlamaLocalDigestProviderError(`Llama provider timed out after ${options.timeoutMs}ms`));
128
+ return sanitizeConversationDigestMarkdown(response);
129
+ }
130
+ finally {
131
+ await context.dispose();
132
+ }
133
+ },
134
+ };
135
+ }
@@ -0,0 +1,117 @@
1
+ export declare enum DigestModelId {
2
+ TINY = "tiny",
3
+ DEFAULT = "default",
4
+ LARGE = "large"
5
+ }
6
+ export interface ConversationDigestSettings {
7
+ enabled?: boolean;
8
+ initialized: boolean;
9
+ modelId: string;
10
+ initializedAt: string;
11
+ }
12
+ export interface DigestModelSpec {
13
+ id: DigestModelId;
14
+ ggufUri: string;
15
+ approxSizeBytes: number;
16
+ license: string;
17
+ sourceUrl: string;
18
+ enabled: boolean;
19
+ }
20
+ export interface ConversationDigestInput {
21
+ sourcePath: string;
22
+ content: string;
23
+ contentHash: string;
24
+ }
25
+ interface ConversationDigestOptions {
26
+ modelId: string;
27
+ maxInputChars: number;
28
+ timeoutMs: number;
29
+ }
30
+ export interface ConversationDigestInitResult {
31
+ initialized: boolean;
32
+ modelId: string;
33
+ initializedAt?: string;
34
+ error?: string;
35
+ }
36
+ export interface ConversationDigestProvider {
37
+ readonly id: string;
38
+ dispose?(): Promise<void>;
39
+ generateDigestMarkdown(input: ConversationDigestInput, options: ConversationDigestOptions): Promise<string>;
40
+ }
41
+ export interface DigestFileOptions {
42
+ workspaceRoot: string;
43
+ inputPath: string;
44
+ modelId: string;
45
+ outDir?: string;
46
+ maxInputChars?: number;
47
+ timeoutMs?: number;
48
+ }
49
+ export interface ProvisionalDigestResult {
50
+ markdown: string;
51
+ status: 'provisional';
52
+ contentHash: string;
53
+ indexed: boolean;
54
+ skipped: boolean;
55
+ error?: string;
56
+ }
57
+ export interface InitializeConversationDigestInput {
58
+ workspaceRoot: string;
59
+ modelId: string;
60
+ allowDownload: boolean;
61
+ enabled?: boolean;
62
+ }
63
+ export type DigestPendingResult = {
64
+ ok: true;
65
+ initialized: false;
66
+ action: 'noop';
67
+ reason: 'not-initialized';
68
+ } | {
69
+ ok: true;
70
+ initialized: true;
71
+ action: 'noop';
72
+ reason: 'locked';
73
+ } | {
74
+ ok: true;
75
+ initialized: true;
76
+ action: 'noop';
77
+ reason: 'no-pending';
78
+ } | {
79
+ ok: true;
80
+ initialized: true;
81
+ action: 'digested';
82
+ count: number;
83
+ skipped: number;
84
+ errors: number;
85
+ } | {
86
+ ok: false;
87
+ initialized: true;
88
+ action: 'error';
89
+ error: string;
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 {};
@@ -0,0 +1,6 @@
1
+ export var DigestModelId;
2
+ (function (DigestModelId) {
3
+ DigestModelId["TINY"] = "tiny";
4
+ DigestModelId["DEFAULT"] = "default";
5
+ DigestModelId["LARGE"] = "large";
6
+ })(DigestModelId || (DigestModelId = {}));
@@ -0,0 +1,2 @@
1
+ export declare function isDigestWorkerInvocation(args: string[]): boolean;
2
+ export declare function runDigestFileInWorker(args: string[]): number | undefined;
@@ -0,0 +1,51 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { parseArgs, numberFlag } from '../../cli/utils/args.js';
3
+ import { DEFAULT_DIGEST_TIMEOUT_MS, DIGEST_TIMEOUT_GRACE_MS, DIGEST_WORKER_FLAG, DIGEST_WORKER_RESULT_PREFIX, } from './constants.js';
4
+ function isDigestWorkerResult(value) {
5
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
6
+ return false;
7
+ const record = value;
8
+ return typeof record.markdown === 'string' && record.status === 'provisional';
9
+ }
10
+ function parseDigestWorkerSuccess(stdout) {
11
+ const line = stdout
12
+ .split(/\r?\n/)
13
+ .reverse()
14
+ .find((candidate) => candidate.startsWith(DIGEST_WORKER_RESULT_PREFIX));
15
+ if (!line)
16
+ return false;
17
+ try {
18
+ return isDigestWorkerResult(JSON.parse(line.slice(DIGEST_WORKER_RESULT_PREFIX.length)));
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
24
+ export function isDigestWorkerInvocation(args) {
25
+ return parseArgs(args).flags.get(DIGEST_WORKER_FLAG) === true;
26
+ }
27
+ export function runDigestFileInWorker(args) {
28
+ const parsed = parseArgs(args);
29
+ if (parsed.flags.get(DIGEST_WORKER_FLAG) === true)
30
+ return undefined;
31
+ const entrypoint = process.argv[1];
32
+ if (!entrypoint)
33
+ return undefined;
34
+ const timeoutMs = numberFlag(parsed.flags, 'timeout-ms', DEFAULT_DIGEST_TIMEOUT_MS);
35
+ const result = spawnSync(process.execPath, [entrypoint, 'memory', 'digest-file', ...args, `--${DIGEST_WORKER_FLAG}`], {
36
+ cwd: process.cwd(),
37
+ encoding: 'utf8',
38
+ maxBuffer: 1024 * 1024 * 20,
39
+ timeout: timeoutMs + DIGEST_TIMEOUT_GRACE_MS,
40
+ });
41
+ if (result.stdout)
42
+ process.stdout.write(result.stdout);
43
+ const workerSucceeded = parseDigestWorkerSuccess(result.stdout);
44
+ if (result.stderr && !workerSucceeded)
45
+ process.stderr.write(result.stderr);
46
+ if (result.status === 0 || workerSucceeded)
47
+ return 0;
48
+ if (result.status !== null)
49
+ return result.status;
50
+ return 1;
51
+ }
@@ -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 './paths.js';
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,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1
2
  export function adfToMarkdown(adf) {
2
3
  if (!adf || typeof adf !== 'object')
3
4
  return 'No description';
@@ -17,8 +18,7 @@ export function adfToMarkdown(adf) {
17
18
  }
18
19
  case 'heading': {
19
20
  const level = node.attrs?.level || 1;
20
- result +=
21
- '#'.repeat(level) + ' ' + renderNodes(node.content, listDepth, node.type) + '\n\n';
21
+ result += '#'.repeat(level) + ' ' + renderNodes(node.content, listDepth, node.type) + '\n\n';
22
22
  break;
23
23
  }
24
24
  case 'text': {
@@ -61,10 +61,7 @@ export function adfToMarkdown(adf) {
61
61
  break;
62
62
  }
63
63
  case 'blockquote':
64
- result +=
65
- '> ' +
66
- renderNodes(node.content, listDepth, node.type).trim().replace(/\n/g, '\n> ') +
67
- '\n\n';
64
+ result += '> ' + renderNodes(node.content, listDepth, node.type).trim().replace(/\n/g, '\n> ') + '\n\n';
68
65
  break;
69
66
  case 'codeBlock':
70
67
  result += `\`\`\`${node.attrs?.language || ''}\n${renderNodes(node.content, listDepth, node.type).trim()}\n\`\`\`\n\n`;
@@ -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 { createHash } from 'crypto';
2
- export function computeChunkId(content) {
3
- return createHash('sha256').update(content).digest('hex').slice(0, 16);
1
+ import { sha256Hex } from '../../utils/hash.js';
2
+ function computeChunkId(content) {
3
+ return sha256Hex(content).slice(0, 16);
4
4
  }
5
- export function cleanContentForEmbedding(text) {
5
+ function cleanContentForEmbedding(text) {
6
6
  return text.replace(/<!--[\s\S]*?-->/g, '');
7
7
  }
8
8
  const HEADING_RE = /^(#{1,6}) (.+)$/;
@@ -116,9 +116,7 @@ export function chunkMarkdown(text, source, config) {
116
116
  const sectionText = section.lines.map((l) => l.text).join('\n');
117
117
  if (!sectionText.trim())
118
118
  continue;
119
- const fullText = prevOverlapLines.length > 0
120
- ? `${prevOverlapLines.join('\n')}\n${sectionText}`
121
- : sectionText;
119
+ const fullText = prevOverlapLines.length > 0 ? `${prevOverlapLines.join('\n')}\n${sectionText}` : sectionText;
122
120
  const parts = splitSection(fullText, chunkSize);
123
121
  for (let pi = 0; pi < parts.length; pi++) {
124
122
  const part = parts[pi];
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
1
  import * as assert from 'node:assert/strict';
2
2
  import { describe, test } from 'node:test';
3
- import { chunkMarkdown, cleanContentForEmbedding, computeChunkId } from '../chunker.js';
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
- });
@@ -0,0 +1,5 @@
1
+ import { type MemoryConfig } from './types.js';
2
+ export declare const DEFAULT_MEMORY_CONFIG: MemoryConfig;
3
+ export declare const LOCK_RETRY_MS = 50;
4
+ export declare const LOCK_TIMEOUT_MS = 500;
5
+ export declare const RRF_K = 60;
@@ -0,0 +1,13 @@
1
+ import { EmbeddingModelName } from './types.js';
2
+ export const DEFAULT_MEMORY_CONFIG = {
3
+ enabled: false,
4
+ wikiDir: '',
5
+ topK: 5,
6
+ chunkSize: 1500,
7
+ overlapLines: 2,
8
+ embeddingModel: EmbeddingModelName.SMALL,
9
+ vectorDimension: 384,
10
+ };
11
+ export const LOCK_RETRY_MS = 50;
12
+ export const LOCK_TIMEOUT_MS = 500;
13
+ export const RRF_K = 60;
@@ -1,4 +1,4 @@
1
- export { FASTEMBED_CACHE_DIR, MODEL_CACHE_DIR as DEFAULT_MODEL_CACHE_DIR } from '../utils/paths.js';
1
+ import { EmbeddingModelName } from './types.js';
2
2
  export declare class EmbedderError extends Error {
3
3
  constructor(message: string, cause?: unknown);
4
4
  }
@@ -6,9 +6,10 @@ export declare class Embedder {
6
6
  private readonly modelName;
7
7
  private pipeline;
8
8
  private _dimension;
9
- constructor(modelName: string);
9
+ constructor(modelName: EmbeddingModelName);
10
10
  embed(texts: string[]): Promise<Float32Array[]>;
11
11
  get dimension(): number | undefined;
12
12
  isReady(): boolean;
13
+ initialize(): Promise<void>;
13
14
  private loadModel;
14
15
  }
@@ -1,5 +1,5 @@
1
- import { FASTEMBED_CACHE_DIR } from '../utils/paths.js';
2
- export { FASTEMBED_CACHE_DIR, MODEL_CACHE_DIR as DEFAULT_MODEL_CACHE_DIR } from '../utils/paths.js';
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);
@@ -40,16 +40,25 @@ export class Embedder {
40
40
  isReady() {
41
41
  return this.pipeline !== null;
42
42
  }
43
+ async initialize() {
44
+ if (!this.pipeline) {
45
+ await this.loadModel();
46
+ }
47
+ }
43
48
  async loadModel() {
44
49
  try {
45
50
  const { EmbeddingModel, FlagEmbedding } = await import('fastembed');
46
51
  const modelMap = {
47
- 'Xenova/bge-small-en-v1.5': EmbeddingModel.BGESmallENV15,
48
- 'Xenova/bge-base-en-v1.5': EmbeddingModel.BGEBaseENV15,
49
- 'Xenova/all-MiniLM-L6-v2': EmbeddingModel.AllMiniLML6V2,
52
+ [EmbeddingModelName.TINY]: EmbeddingModel.AllMiniLML6V2,
53
+ [EmbeddingModelName.SMALL]: EmbeddingModel.BGESmallENV15,
54
+ [EmbeddingModelName.LARGE]: EmbeddingModel.BGEBaseENV15,
50
55
  };
51
56
  const model = modelMap[this.modelName] ?? EmbeddingModel.BGESmallENV15;
52
- const embedder = await FlagEmbedding.init({ model: model, cacheDir: FASTEMBED_CACHE_DIR, showDownloadProgress: false });
57
+ const embedder = await FlagEmbedding.init({
58
+ model: model,
59
+ cacheDir: FASTEMBED_CACHE_DIR,
60
+ showDownloadProgress: false,
61
+ });
53
62
  this.pipeline = async (texts) => {
54
63
  const result = [];
55
64
  for await (const batch of embedder.embed(texts, texts.length)) {
@@ -0,0 +1 @@
1
+ export {};
@@ -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
+ });
@@ -2,4 +2,4 @@ export { MemoryStore, StoreError } from './store.js';
2
2
  export { Embedder, EmbedderError } from './embedder.js';
3
3
  export { MemoryIndexer } from './indexer.js';
4
4
  export type { MemoryChunk, SearchResult, MemoryConfig, IndexStats } from './types.js';
5
- export { DEFAULT_MEMORY_CONFIG } from './types.js';
5
+ export { DEFAULT_MEMORY_CONFIG } from './constants.js';
@@ -1,4 +1,4 @@
1
1
  export { MemoryStore, StoreError } from './store.js';
2
2
  export { Embedder, EmbedderError } from './embedder.js';
3
3
  export { MemoryIndexer } from './indexer.js';
4
- export { DEFAULT_MEMORY_CONFIG } from './types.js';
4
+ export { DEFAULT_MEMORY_CONFIG } from './constants.js';
@@ -1,12 +1,15 @@
1
- import type { Embedder } from './embedder.js';
2
1
  import type { MemoryStore } from './store.js';
3
2
  import type { IndexStats, MemoryConfig, SearchResult } from './types.js';
3
+ interface TextEmbedder {
4
+ embed(texts: string[]): Promise<Float32Array[]>;
5
+ initialize(): Promise<void>;
6
+ }
4
7
  export declare class MemoryIndexer {
5
8
  private readonly store;
6
9
  private readonly embedder;
7
10
  private readonly config;
8
11
  private _ready;
9
- constructor(store: MemoryStore, embedder: Embedder, config: MemoryConfig);
12
+ constructor(store: MemoryStore, embedder: TextEmbedder, config: MemoryConfig);
10
13
  indexFile(absolutePath: string): Promise<IndexStats>;
11
14
  private indexFileRelativeTo;
12
15
  indexDirectory(rootDir: string, opts?: {
@@ -18,3 +21,4 @@ export declare class MemoryIndexer {
18
21
  startupIndex(): Promise<void>;
19
22
  get ready(): boolean;
20
23
  }
24
+ export {};