@hanhnd/agent-kit 1.0.39 → 1.0.40
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.js +55 -15
- package/dist/entrypoints/server.js +3 -3
- package/dist/mcp/memory.d.ts +4 -9
- package/dist/mcp/memory.js +42 -18
- package/dist/server.js +212 -85
- package/dist/services/memory/index.d.ts +3 -1
- package/dist/services/memory/index.js +1 -0
- package/dist/services/memory/indexer.d.ts +5 -6
- package/dist/services/memory/indexer.js +36 -15
- package/dist/services/memory/indexer.test.js +132 -0
- package/dist/services/memory/memory.test.js +58 -0
- package/dist/services/memory/subsystem.d.ts +19 -0
- package/dist/services/memory/subsystem.js +67 -0
- package/dist/services/memory/types.d.ts +20 -0
- package/dist/utils/async.d.ts +1 -0
- package/dist/utils/async.js +17 -0
- package/dist/utils/async.test.d.ts +1 -0
- package/dist/utils/async.test.js +48 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -15195,6 +15195,22 @@ async function withTimeout(promise, timeoutMs, createError) {
|
|
|
15195
15195
|
if (timer) clearTimeout(timer);
|
|
15196
15196
|
}
|
|
15197
15197
|
}
|
|
15198
|
+
async function mapLimit(items, limit, worker) {
|
|
15199
|
+
if (items.length === 0) return [];
|
|
15200
|
+
const concurrency = Math.max(1, Math.floor(limit));
|
|
15201
|
+
const results = new Array(items.length);
|
|
15202
|
+
let nextIndex = 0;
|
|
15203
|
+
async function runWorker() {
|
|
15204
|
+
while (nextIndex < items.length) {
|
|
15205
|
+
const currentIndex = nextIndex;
|
|
15206
|
+
nextIndex++;
|
|
15207
|
+
results[currentIndex] = await worker(items[currentIndex], currentIndex);
|
|
15208
|
+
}
|
|
15209
|
+
}
|
|
15210
|
+
const runners = Array.from({ length: Math.min(concurrency, items.length) }, () => runWorker());
|
|
15211
|
+
await Promise.all(runners);
|
|
15212
|
+
return results;
|
|
15213
|
+
}
|
|
15198
15214
|
|
|
15199
15215
|
// src/utils/paths.ts
|
|
15200
15216
|
import * as os2 from "os";
|
|
@@ -15912,9 +15928,19 @@ var MemoryIndexer = class {
|
|
|
15912
15928
|
}
|
|
15913
15929
|
_ready = false;
|
|
15914
15930
|
async indexFile(absolutePath) {
|
|
15915
|
-
|
|
15931
|
+
const mutation = await this.prepareIndexFileRelativeTo(absolutePath, this.config.wikiDir);
|
|
15932
|
+
return this.commitPreparedMutation(mutation);
|
|
15916
15933
|
}
|
|
15917
|
-
|
|
15934
|
+
makeSkippedMutation(source, skipped) {
|
|
15935
|
+
return {
|
|
15936
|
+
source,
|
|
15937
|
+
chunksToUpsert: [],
|
|
15938
|
+
embeddings: [],
|
|
15939
|
+
idsToDelete: [],
|
|
15940
|
+
stats: { indexed: 0, deleted: 0, skipped }
|
|
15941
|
+
};
|
|
15942
|
+
}
|
|
15943
|
+
async prepareIndexFileRelativeTo(absolutePath, sourceRoot) {
|
|
15918
15944
|
const source = path8.relative(sourceRoot, absolutePath);
|
|
15919
15945
|
const existingChunkIds = this.store.hashesBySource(source);
|
|
15920
15946
|
let text;
|
|
@@ -15922,7 +15948,7 @@ var MemoryIndexer = class {
|
|
|
15922
15948
|
text = await fs6.promises.readFile(absolutePath, "utf8");
|
|
15923
15949
|
} catch (err) {
|
|
15924
15950
|
console.warn(`[memory-indexer] Cannot read file ${absolutePath}:`, err);
|
|
15925
|
-
return
|
|
15951
|
+
return this.makeSkippedMutation(source, existingChunkIds.size);
|
|
15926
15952
|
}
|
|
15927
15953
|
let fileMtimeAt;
|
|
15928
15954
|
try {
|
|
@@ -15942,21 +15968,30 @@ var MemoryIndexer = class {
|
|
|
15942
15968
|
embeddings = await this.embedder.embed(toIndex.map((c) => c.content));
|
|
15943
15969
|
} catch (err) {
|
|
15944
15970
|
console.warn(`[memory-indexer] Embedding failed for ${absolutePath}:`, err);
|
|
15945
|
-
return
|
|
15971
|
+
return this.makeSkippedMutation(source, existingChunkIds.size);
|
|
15946
15972
|
}
|
|
15947
15973
|
}
|
|
15948
|
-
if (toIndex.length > 0) {
|
|
15949
|
-
this.store.upsert(toIndex, embeddings);
|
|
15950
|
-
}
|
|
15951
|
-
if (toDelete.length > 0) {
|
|
15952
|
-
this.store.deleteByIds(toDelete);
|
|
15953
|
-
}
|
|
15954
15974
|
return {
|
|
15955
|
-
|
|
15956
|
-
|
|
15957
|
-
|
|
15975
|
+
source,
|
|
15976
|
+
chunksToUpsert: toIndex,
|
|
15977
|
+
embeddings,
|
|
15978
|
+
idsToDelete: toDelete,
|
|
15979
|
+
stats: {
|
|
15980
|
+
indexed: toIndex.length,
|
|
15981
|
+
deleted: toDelete.length,
|
|
15982
|
+
skipped: allChunks.length - toIndex.length
|
|
15983
|
+
}
|
|
15958
15984
|
};
|
|
15959
15985
|
}
|
|
15986
|
+
commitPreparedMutation(mutation) {
|
|
15987
|
+
if (mutation.chunksToUpsert.length > 0) {
|
|
15988
|
+
this.store.upsert(mutation.chunksToUpsert, mutation.embeddings);
|
|
15989
|
+
}
|
|
15990
|
+
if (mutation.idsToDelete.length > 0) {
|
|
15991
|
+
this.store.deleteByIds(mutation.idsToDelete);
|
|
15992
|
+
}
|
|
15993
|
+
return mutation.stats;
|
|
15994
|
+
}
|
|
15960
15995
|
async indexDirectory(rootDir, opts = {}) {
|
|
15961
15996
|
const totals = { indexed: 0, deleted: 0, skipped: 0 };
|
|
15962
15997
|
let rootExists;
|
|
@@ -15998,8 +16033,13 @@ var MemoryIndexer = class {
|
|
|
15998
16033
|
totals.deleted += 1;
|
|
15999
16034
|
}
|
|
16000
16035
|
}
|
|
16001
|
-
|
|
16002
|
-
|
|
16036
|
+
const preparedMutations = await mapLimit(
|
|
16037
|
+
files,
|
|
16038
|
+
opts.fileConcurrency ?? 2,
|
|
16039
|
+
(file) => this.prepareIndexFileRelativeTo(file, relativeBase)
|
|
16040
|
+
);
|
|
16041
|
+
for (const mutation of preparedMutations) {
|
|
16042
|
+
const stats = this.commitPreparedMutation(mutation);
|
|
16003
16043
|
totals.indexed += stats.indexed;
|
|
16004
16044
|
totals.deleted += stats.deleted;
|
|
16005
16045
|
totals.skipped += stats.skipped;
|
|
@@ -23,13 +23,13 @@ registerIntegrationTools(server);
|
|
|
23
23
|
registerCoreTools(server);
|
|
24
24
|
registerAgentTools(server);
|
|
25
25
|
const workspaceRoot = getWorkspaceRoot();
|
|
26
|
-
const
|
|
26
|
+
const memorySubsystem = registerMemoryTools(server, workspaceRoot);
|
|
27
27
|
// ═══════════════════════════════════════════════════════════════
|
|
28
28
|
// START SERVER
|
|
29
29
|
// ═══════════════════════════════════════════════════════════════
|
|
30
30
|
const transport = new StdioServerTransport();
|
|
31
31
|
await server.connect(transport);
|
|
32
32
|
// Fire-and-forget startup indexing — must run after server.connect
|
|
33
|
-
if (
|
|
34
|
-
void
|
|
33
|
+
if (memorySubsystem) {
|
|
34
|
+
void memorySubsystem.startWarmup();
|
|
35
35
|
}
|
package/dist/mcp/memory.d.ts
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import {
|
|
3
|
-
import { MemoryStore } from '../services/memory/store.js';
|
|
4
|
-
import type { MemoryConfig } from '../services/memory/types.js';
|
|
2
|
+
import { type MemorySubsystem, type MemorySubsystemOverrides } from '../services/memory/subsystem.js';
|
|
5
3
|
import { type ProjectSettings } from '../core/config/index.js';
|
|
6
4
|
/**
|
|
7
5
|
* Initializes the memory subsystem and registers all memory tools.
|
|
8
|
-
* Returns the
|
|
6
|
+
* Returns the MemorySubsystem so the caller can fire startWarmup() after server.connect().
|
|
9
7
|
* Returns null when memory is disabled (settings.memory.enabled !== true).
|
|
10
8
|
*/
|
|
11
|
-
export declare function registerMemoryTools(server: McpServer, workspaceRoot: string, overrides?: {
|
|
12
|
-
indexer?: MemoryIndexer;
|
|
13
|
-
store?: MemoryStore;
|
|
14
|
-
config?: MemoryConfig;
|
|
9
|
+
export declare function registerMemoryTools(server: McpServer, workspaceRoot: string, overrides?: MemorySubsystemOverrides & {
|
|
15
10
|
settings?: ProjectSettings;
|
|
16
|
-
}):
|
|
11
|
+
}): MemorySubsystem | null;
|
package/dist/mcp/memory.js
CHANGED
|
@@ -1,29 +1,33 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
import {
|
|
5
|
-
import { MemoryIndexer } from '../services/memory/indexer.js';
|
|
6
|
-
import { MemoryStore } from '../services/memory/store.js';
|
|
4
|
+
import { createMemorySubsystem, } from '../services/memory/subsystem.js';
|
|
7
5
|
import { SOURCE_TYPES } from '../services/memory/types.js';
|
|
8
6
|
import { initializeConversationDigestModel } from '../services/digest/processor.js';
|
|
9
7
|
import { DEFAULT_DIGEST_MODEL_ID } from '../services/digest/constants.js';
|
|
10
8
|
import { formatError, mcpJson, mcpText } from '../utils/utils.js';
|
|
11
|
-
import { loadGlobalSettings, resolveConversationDigestConfig
|
|
9
|
+
import { loadGlobalSettings, resolveConversationDigestConfig } from '../core/config/index.js';
|
|
12
10
|
/**
|
|
13
|
-
* Registers tool handlers
|
|
11
|
+
* Registers tool handlers against the current memory subsystem state.
|
|
14
12
|
*/
|
|
15
|
-
function registerMemoryToolHandlers(server,
|
|
13
|
+
function registerMemoryToolHandlers(server, subsystem, workspaceRoot = process.cwd()) {
|
|
14
|
+
const { config } = subsystem;
|
|
16
15
|
server.tool('kit_memory_search', 'Search persistent memory for factual or semantic matches (decisions, concepts, entities). Extract concise keywords only, removing stopwords and filler words from the user request before querying. Use kit_memory_recent for temporal or recency questions.', {
|
|
17
16
|
query: z.string().min(1).describe('Keyword-only search query with stopwords removed'),
|
|
18
17
|
top_k: z.number().int().positive().optional().describe('Number of results to return'),
|
|
19
18
|
}, async ({ query, top_k }) => {
|
|
20
19
|
try {
|
|
20
|
+
if (!subsystem.indexer || !subsystem.store) {
|
|
21
|
+
return mcpText(formatInitializationFailure(subsystem));
|
|
22
|
+
}
|
|
23
|
+
const { indexer, store } = subsystem;
|
|
21
24
|
const results = await indexer.search(query, top_k ?? config.topK);
|
|
25
|
+
const lifecycleNote = formatLifecycleNote(subsystem);
|
|
22
26
|
const degradedNote = !store.vecAvailable
|
|
23
27
|
? '⚠️ Vector search unavailable — showing keyword-only results.\n\n'
|
|
24
28
|
: '';
|
|
25
29
|
if (results.length === 0) {
|
|
26
|
-
return mcpText(`${degradedNote}No memories found for query: "${query}"`);
|
|
30
|
+
return mcpText(`${lifecycleNote}${degradedNote}No memories found for query: "${query}"`);
|
|
27
31
|
}
|
|
28
32
|
const formatted = results
|
|
29
33
|
.map((r) => {
|
|
@@ -34,7 +38,7 @@ function registerMemoryToolHandlers(server, indexer, store, config, workspaceRoo
|
|
|
34
38
|
return `### ${displaySource} (score: ${r.score.toFixed(3)})\n${content}`;
|
|
35
39
|
})
|
|
36
40
|
.join('\n\n---\n\n');
|
|
37
|
-
return mcpText(`${degradedNote}${formatted}`);
|
|
41
|
+
return mcpText(`${lifecycleNote}${degradedNote}${formatted}`);
|
|
38
42
|
}
|
|
39
43
|
catch (err) {
|
|
40
44
|
return mcpText(`kit_memory_search failed: ${formatError(err)}`);
|
|
@@ -45,6 +49,10 @@ function registerMemoryToolHandlers(server, indexer, store, config, workspaceRoo
|
|
|
45
49
|
source_type: z.enum(SOURCE_TYPES).optional().describe('Optional source type filter'),
|
|
46
50
|
}, async ({ n, source_type }) => {
|
|
47
51
|
try {
|
|
52
|
+
if (!subsystem.store) {
|
|
53
|
+
return mcpText(formatInitializationFailure(subsystem));
|
|
54
|
+
}
|
|
55
|
+
const store = subsystem.store;
|
|
48
56
|
const rows = store.getRecentSources({ limit: n ?? 5, sourceType: source_type });
|
|
49
57
|
if (rows.length === 0)
|
|
50
58
|
return mcpText('No recent sources found.');
|
|
@@ -71,6 +79,13 @@ function registerMemoryToolHandlers(server, indexer, store, config, workspaceRoo
|
|
|
71
79
|
content: z.string().min(1).describe('Content to save to memory'),
|
|
72
80
|
}, async ({ content }) => {
|
|
73
81
|
try {
|
|
82
|
+
if (!subsystem.indexer) {
|
|
83
|
+
return mcpJson({
|
|
84
|
+
saved: false,
|
|
85
|
+
error: formatInitializationFailure(subsystem),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const indexer = subsystem.indexer;
|
|
74
89
|
await indexer.save(content);
|
|
75
90
|
return mcpJson({
|
|
76
91
|
saved: true,
|
|
@@ -109,22 +124,31 @@ function registerMemoryToolHandlers(server, indexer, store, config, workspaceRoo
|
|
|
109
124
|
}
|
|
110
125
|
});
|
|
111
126
|
}
|
|
127
|
+
function formatLifecycleNote(subsystem) {
|
|
128
|
+
if (subsystem.status.state === 'warming' || subsystem.status.state === 'initializing') {
|
|
129
|
+
return '⚠️ Memory index is warming — results may be stale until startup indexing completes.\n\n';
|
|
130
|
+
}
|
|
131
|
+
if (subsystem.status.state === 'degraded') {
|
|
132
|
+
return `⚠️ Memory index is degraded${subsystem.status.error ? `: ${subsystem.status.error}` : ''}.\n\n`;
|
|
133
|
+
}
|
|
134
|
+
return '';
|
|
135
|
+
}
|
|
136
|
+
function formatInitializationFailure(subsystem) {
|
|
137
|
+
const detail = subsystem.status.error ? `: ${subsystem.status.error}` : '';
|
|
138
|
+
return `Memory initialization failed${detail}`;
|
|
139
|
+
}
|
|
112
140
|
/**
|
|
113
141
|
* Initializes the memory subsystem and registers all memory tools.
|
|
114
|
-
* Returns the
|
|
142
|
+
* Returns the MemorySubsystem so the caller can fire startWarmup() after server.connect().
|
|
115
143
|
* Returns null when memory is disabled (settings.memory.enabled !== true).
|
|
116
144
|
*/
|
|
117
145
|
export function registerMemoryTools(server, workspaceRoot, overrides) {
|
|
118
146
|
const settings = overrides?.settings ?? loadGlobalSettings();
|
|
119
147
|
if (settings.memory?.enabled !== true)
|
|
120
148
|
return null;
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return new MemoryIndexer(store, embedder, config);
|
|
127
|
-
})();
|
|
128
|
-
registerMemoryToolHandlers(server, indexer, store, config, workspaceRoot);
|
|
129
|
-
return indexer;
|
|
149
|
+
const subsystem = createMemorySubsystem(workspaceRoot, { ...overrides, settings });
|
|
150
|
+
if (!subsystem)
|
|
151
|
+
return null;
|
|
152
|
+
registerMemoryToolHandlers(server, subsystem, workspaceRoot);
|
|
153
|
+
return subsystem;
|
|
130
154
|
}
|