claude-flow 3.47.1 → 3.48.0
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/.claude/helpers/auto-memory-hook.mjs +6 -2
- package/.claude/helpers/learning-service.mjs +4 -2
- package/.claude/helpers/metrics-db.mjs +4 -2
- package/node_modules/@claude-flow/codex/package.json +1 -1
- package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js +11 -3
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js.map +1 -1
- package/node_modules/@claude-flow/mcp/package.json +6 -3
- package/node_modules/@claude-flow/plugin-agent-federation/package.json +3 -3
- package/node_modules/@claude-flow/security/package.json +2 -3
- package/package.json +7 -7
- package/v3/@claude-flow/cli/README.md +0 -2
- package/v3/@claude-flow/cli/bin/cli.js +4 -1
- package/v3/@claude-flow/cli/catalog-manifest.json +2 -2
- package/v3/@claude-flow/cli/dist/src/commands/analyze.js +2 -2
- package/v3/@claude-flow/cli/dist/src/commands/config.js +5 -7
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +15 -5
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +89 -27
- package/v3/@claude-flow/cli/dist/src/commands/metaharness.js +1 -0
- package/v3/@claude-flow/cli/dist/src/commands/security.js +113 -55
- package/v3/@claude-flow/cli/dist/src/commands/session.js +6 -6
- package/v3/@claude-flow/cli/dist/src/commands/task.js +5 -4
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +4 -2
- package/v3/@claude-flow/cli/dist/src/mcp-server.js +10 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.js +29 -62
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.d.ts +0 -4
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +139 -37
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +89 -35
- package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.d.ts +8 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.js +80 -8
- package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.js +34 -8
- package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.d.ts +13 -0
- package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.js +14 -0
- package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.d.ts +3 -0
- package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.js +5 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +11 -20
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +348 -134
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +9 -1
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +41 -31
- package/v3/@claude-flow/cli/dist/src/ruvector/graph-analyzer.js +4 -1
- package/v3/@claude-flow/cli/dist/src/services/config-file-manager.js +30 -8
- package/v3/@claude-flow/cli/dist/src/services/memory-backup.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/services/memory-backup.js +67 -27
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +2 -2
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +23 -14
- package/v3/@claude-flow/cli/package.json +9 -9
- package/v3/@claude-flow/guidance/package.json +5 -6
- package/v3/@claude-flow/shared/package.json +6 -3
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @module v3/cli/mcp-tools/memory-tools
|
|
11
11
|
*/
|
|
12
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
|
|
12
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from 'fs';
|
|
13
13
|
import { homedir } from 'os';
|
|
14
14
|
import { join, resolve } from 'path';
|
|
15
15
|
import { createHash } from 'crypto';
|
|
@@ -273,11 +273,11 @@ const MEMORY_STATS_MAX_PAGES = 200;
|
|
|
273
273
|
* breakdown and embedding coverage computed from a prefix, and a listing
|
|
274
274
|
* that reported `success: false` was read as an empty one.
|
|
275
275
|
*/
|
|
276
|
-
async function collectAllEntries(listEntries) {
|
|
276
|
+
async function collectAllEntries(listEntries, dbPath) {
|
|
277
277
|
const entries = [];
|
|
278
278
|
let total = 0;
|
|
279
279
|
for (let page = 0; page < MEMORY_STATS_MAX_PAGES; page++) {
|
|
280
|
-
const result = await listEntries({ limit: MEMORY_STATS_PAGE, offset: entries.length });
|
|
280
|
+
const result = await listEntries({ limit: MEMORY_STATS_PAGE, offset: entries.length, dbPath });
|
|
281
281
|
if (!result.success) {
|
|
282
282
|
return { success: false, entries, total: result.total ?? total, error: result.error };
|
|
283
283
|
}
|
|
@@ -300,10 +300,10 @@ function memoryStatsUnavailable(error) {
|
|
|
300
300
|
* WAL frames, so it is allowed to fail without that meaning the store is
|
|
301
301
|
* missing — which is exactly the conflation #3311 reports.
|
|
302
302
|
*/
|
|
303
|
-
async function readMemoryStatusLabels() {
|
|
303
|
+
async function readMemoryStatusLabels(dbPath) {
|
|
304
304
|
try {
|
|
305
305
|
const { checkMemoryInitialization } = await getMemoryFunctions();
|
|
306
|
-
const status = await checkMemoryInitialization();
|
|
306
|
+
const status = await checkMemoryInitialization(dbPath);
|
|
307
307
|
return { version: status.version, features: status.features };
|
|
308
308
|
}
|
|
309
309
|
catch {
|
|
@@ -315,37 +315,43 @@ async function readMemoryStatusLabels() {
|
|
|
315
315
|
* #1606: Wrapped in try/catch to prevent process-level crashes that kill
|
|
316
316
|
* the stdio MCP transport on Windows/Codex.
|
|
317
317
|
*/
|
|
318
|
-
async function ensureInitialized() {
|
|
318
|
+
async function ensureInitialized(dbPath) {
|
|
319
319
|
try {
|
|
320
320
|
const { initializeMemoryDatabase, checkMemoryInitialization, storeEntry } = await getMemoryFunctions();
|
|
321
321
|
// Check if already initialized
|
|
322
|
-
const status = await checkMemoryInitialization();
|
|
322
|
+
const status = await checkMemoryInitialization(dbPath);
|
|
323
323
|
if (!status.initialized) {
|
|
324
|
-
await initializeMemoryDatabase({ force: false, verbose: false });
|
|
324
|
+
await initializeMemoryDatabase({ force: false, verbose: false, dbPath });
|
|
325
325
|
}
|
|
326
326
|
// Migrate legacy JSON data if exists (from old .claude-flow/memory/ location)
|
|
327
|
+
// A custom import target must not consume the one-time migration marker
|
|
328
|
+
// for the project's normal memory store.
|
|
327
329
|
if (hasLegacyStore()) {
|
|
328
|
-
const
|
|
329
|
-
if (
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
330
|
+
const { resolveDbPath } = await import('../memory/memory-initializer.js');
|
|
331
|
+
if (!dbPath || dbPath === resolveDbPath()) {
|
|
332
|
+
const legacyStore = loadLegacyStore();
|
|
333
|
+
if (legacyStore && Object.keys(legacyStore.entries).length > 0) {
|
|
334
|
+
console.error('[MCP Memory] Migrating legacy JSON store to sql.js...');
|
|
335
|
+
let migrated = 0;
|
|
336
|
+
for (const [key, entry] of Object.entries(legacyStore.entries)) {
|
|
337
|
+
try {
|
|
338
|
+
const value = typeof entry.value === 'string' ? entry.value : JSON.stringify(entry.value);
|
|
339
|
+
await storeEntry({
|
|
340
|
+
key,
|
|
341
|
+
value,
|
|
342
|
+
namespace: 'default',
|
|
343
|
+
generateEmbeddingFlag: true,
|
|
344
|
+
dbPath,
|
|
345
|
+
});
|
|
346
|
+
migrated++;
|
|
347
|
+
}
|
|
348
|
+
catch (e) {
|
|
349
|
+
console.error(`[MCP Memory] Failed to migrate key "${key}":`, e);
|
|
350
|
+
}
|
|
345
351
|
}
|
|
352
|
+
console.error(`[MCP Memory] Migrated ${migrated}/${Object.keys(legacyStore.entries).length} entries`);
|
|
353
|
+
markMigrationComplete();
|
|
346
354
|
}
|
|
347
|
-
console.error(`[MCP Memory] Migrated ${migrated}/${Object.keys(legacyStore.entries).length} entries`);
|
|
348
|
-
markMigrationComplete();
|
|
349
355
|
}
|
|
350
356
|
}
|
|
351
357
|
}
|
|
@@ -792,10 +798,18 @@ export const memoryTools = [
|
|
|
792
798
|
category: 'memory',
|
|
793
799
|
inputSchema: {
|
|
794
800
|
type: 'object',
|
|
795
|
-
properties: {
|
|
801
|
+
properties: {
|
|
802
|
+
dbPath: { type: 'string', description: 'Database file to inspect; omitted uses the MCP store default' },
|
|
803
|
+
},
|
|
796
804
|
},
|
|
797
|
-
handler: async () => {
|
|
798
|
-
|
|
805
|
+
handler: async (input) => {
|
|
806
|
+
if (input.dbPath !== undefined && (typeof input.dbPath !== 'string' || !input.dbPath.trim())) {
|
|
807
|
+
return memoryStatsUnavailable('dbPath must be a non-empty string');
|
|
808
|
+
}
|
|
809
|
+
const dbPath = typeof input.dbPath === 'string' ? resolve(input.dbPath) : undefined;
|
|
810
|
+
// An explicit read must not initialize or migrate the unrelated default store.
|
|
811
|
+
if (!dbPath)
|
|
812
|
+
await ensureInitialized();
|
|
799
813
|
const { listEntries } = await getMemoryFunctions();
|
|
800
814
|
// #3311: the store's own listing decides whether memory is there.
|
|
801
815
|
// `checkMemoryInitialization` opens a whole-image sql.js snapshot of
|
|
@@ -806,7 +820,7 @@ export const memoryTools = [
|
|
|
806
820
|
// but it no longer gets to overrule a working store.
|
|
807
821
|
let listing;
|
|
808
822
|
try {
|
|
809
|
-
listing = await collectAllEntries(listEntries);
|
|
823
|
+
listing = await collectAllEntries(listEntries, dbPath);
|
|
810
824
|
}
|
|
811
825
|
catch (error) {
|
|
812
826
|
return memoryStatsUnavailable(error instanceof Error ? error.message : 'Unknown error');
|
|
@@ -822,16 +836,49 @@ export const memoryTools = [
|
|
|
822
836
|
// from the breakdown while still being counted in the total.
|
|
823
837
|
const namespaces = Object.create(null);
|
|
824
838
|
let withEmbeddings = 0;
|
|
839
|
+
let oldest = Infinity;
|
|
840
|
+
let newest = -Infinity;
|
|
825
841
|
for (const entry of listing.entries) {
|
|
826
842
|
namespaces[entry.namespace] = (namespaces[entry.namespace] || 0) + 1;
|
|
827
843
|
if (entry.hasEmbedding)
|
|
828
844
|
withEmbeddings++;
|
|
845
|
+
// AgentDB returns INTEGER epoch milliseconds; legacy sql.js rows
|
|
846
|
+
// may expose ISO strings or numeric strings from SQLite TEXT affinity.
|
|
847
|
+
const rawCreated = entry.createdAt;
|
|
848
|
+
const millis = typeof rawCreated === 'number'
|
|
849
|
+
? rawCreated
|
|
850
|
+
: typeof rawCreated === 'string' && /^-?\d+$/.test(rawCreated)
|
|
851
|
+
? Number(rawCreated)
|
|
852
|
+
: Date.parse(rawCreated ?? '');
|
|
853
|
+
const created = new Date(millis).getTime();
|
|
854
|
+
if (Number.isFinite(created)) {
|
|
855
|
+
oldest = Math.min(oldest, created);
|
|
856
|
+
newest = Math.max(newest, created);
|
|
857
|
+
}
|
|
829
858
|
}
|
|
830
859
|
const counted = listing.entries.length;
|
|
831
|
-
const status = await readMemoryStatusLabels();
|
|
860
|
+
const status = await readMemoryStatusLabels(dbPath);
|
|
861
|
+
let totalSize = null;
|
|
862
|
+
if (dbPath) {
|
|
863
|
+
try {
|
|
864
|
+
let bytes = statSync(dbPath).size;
|
|
865
|
+
try {
|
|
866
|
+
bytes += statSync(dbPath + '-wal').size;
|
|
867
|
+
}
|
|
868
|
+
catch (error) {
|
|
869
|
+
if (error.code !== 'ENOENT')
|
|
870
|
+
throw error;
|
|
871
|
+
}
|
|
872
|
+
totalSize = `${bytes} B`;
|
|
873
|
+
}
|
|
874
|
+
catch { /* unavailable file metadata is unknown, not zero */ }
|
|
875
|
+
}
|
|
832
876
|
return {
|
|
833
877
|
initialized: true,
|
|
834
878
|
totalEntries: listing.total,
|
|
879
|
+
...(dbPath ? { location: dbPath, totalSize } : {}),
|
|
880
|
+
oldestEntry: counted >= listing.total && Number.isFinite(oldest) ? new Date(oldest).toISOString() : null,
|
|
881
|
+
newestEntry: counted >= listing.total && Number.isFinite(newest) ? new Date(newest).toISOString() : null,
|
|
835
882
|
entriesCounted: counted,
|
|
836
883
|
// The breakdown below covers `entriesCounted` rows, which is every
|
|
837
884
|
// row unless the listing was truncated; say so rather than letting
|
|
@@ -1484,11 +1531,15 @@ export const memoryTools = [
|
|
|
1484
1531
|
inputPath: { type: 'string', description: 'Path to the JSON export file' },
|
|
1485
1532
|
merge: { type: 'boolean', description: 'Merge into existing entries (upsert) vs. fail on conflict (default true)' },
|
|
1486
1533
|
namespace: { type: 'string', description: 'Override the namespace for all imported entries' },
|
|
1534
|
+
dbPath: { type: 'string', description: 'Database file to import into (defaults to the MCP memory store)' },
|
|
1487
1535
|
},
|
|
1488
1536
|
required: ['inputPath'],
|
|
1489
1537
|
},
|
|
1490
1538
|
handler: async (input) => {
|
|
1491
|
-
|
|
1539
|
+
const dbPath = typeof input.dbPath === 'string' && input.dbPath.trim()
|
|
1540
|
+
? resolve(input.dbPath)
|
|
1541
|
+
: undefined;
|
|
1542
|
+
await ensureInitialized(dbPath);
|
|
1492
1543
|
const { storeEntry } = await getMemoryFunctions();
|
|
1493
1544
|
const t0 = Date.now();
|
|
1494
1545
|
const inputPath = String(input.inputPath ?? '');
|
|
@@ -1517,8 +1568,11 @@ export const memoryTools = [
|
|
|
1517
1568
|
}
|
|
1518
1569
|
const value = typeof e.value === 'string' ? e.value : JSON.stringify(e.value ?? null);
|
|
1519
1570
|
try {
|
|
1520
|
-
await storeEntry({ key: e.key, value, namespace: nsOverride ?? e.namespace ?? 'default', upsert: input.merge !== false });
|
|
1521
|
-
|
|
1571
|
+
const result = await storeEntry({ key: e.key, value, namespace: nsOverride ?? e.namespace ?? 'default', upsert: input.merge !== false, dbPath });
|
|
1572
|
+
if (result.success)
|
|
1573
|
+
imported++;
|
|
1574
|
+
else
|
|
1575
|
+
skipped++;
|
|
1522
1576
|
}
|
|
1523
1577
|
catch {
|
|
1524
1578
|
skipped++;
|
|
@@ -62,6 +62,12 @@ export interface McpPolicy {
|
|
|
62
62
|
harnessId?: string;
|
|
63
63
|
defaultDeny?: boolean;
|
|
64
64
|
auditLog?: boolean;
|
|
65
|
+
/** Explicit destination; takes precedence over the environment and project default. */
|
|
66
|
+
auditLogPath?: string;
|
|
67
|
+
/** Maximum bytes per audit segment. Default 10 MiB; oversized records fail closed. */
|
|
68
|
+
auditLogMaxBytes?: number;
|
|
69
|
+
/** Rotated segments retained in addition to the active log. Default 5, maximum 100. */
|
|
70
|
+
auditLogMaxFiles?: number;
|
|
65
71
|
requireApprovalForDangerous?: boolean;
|
|
66
72
|
toolTimeoutMs?: number;
|
|
67
73
|
/**
|
|
@@ -103,10 +109,11 @@ export interface AuditLogEntry {
|
|
|
103
109
|
toolName: string;
|
|
104
110
|
allowed: boolean;
|
|
105
111
|
reason?: string;
|
|
112
|
+
projectPath?: string;
|
|
106
113
|
}
|
|
107
114
|
/** Test-only: redirect the audit log to a temp file instead of the default path. */
|
|
108
115
|
export declare function setAuditLogPathForTesting(p: string | null): void;
|
|
109
|
-
export declare function getAuditLogPath(): string;
|
|
116
|
+
export declare function getAuditLogPath(options?: Pick<McpPolicy, 'auditLogPath'>): string;
|
|
110
117
|
/**
|
|
111
118
|
* Appends one JSONL audit record. Returns `true` if `policy.auditLog` is not
|
|
112
119
|
* set (nothing was required) or the write succeeded; `false` only when
|
|
@@ -58,7 +58,6 @@
|
|
|
58
58
|
*/
|
|
59
59
|
import * as fs from 'fs';
|
|
60
60
|
import * as path from 'path';
|
|
61
|
-
import * as os from 'os';
|
|
62
61
|
export function isPolicyEnforcementEnabled(env = process.env) {
|
|
63
62
|
const v = env.RUFLO_MCP_ENFORCE_POLICY;
|
|
64
63
|
return v === '1' || (v ?? '').toLowerCase() === 'true';
|
|
@@ -118,11 +117,18 @@ let auditLogPathOverride = null;
|
|
|
118
117
|
export function setAuditLogPathForTesting(p) {
|
|
119
118
|
auditLogPathOverride = p;
|
|
120
119
|
}
|
|
121
|
-
function
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
120
|
+
export function getAuditLogPath(options = {}) {
|
|
121
|
+
if (auditLogPathOverride !== null)
|
|
122
|
+
return auditLogPathOverride;
|
|
123
|
+
if (options.auditLogPath !== undefined &&
|
|
124
|
+
(typeof options.auditLogPath !== 'string' || options.auditLogPath.trim().length === 0)) {
|
|
125
|
+
throw new Error('auditLogPath must be a non-empty string');
|
|
126
|
+
}
|
|
127
|
+
// Precedence: explicit caller/policy destination, environment, project default.
|
|
128
|
+
const configured = options.auditLogPath ?? process.env.RUFLO_MCP_AUDIT_LOG_PATH;
|
|
129
|
+
return configured
|
|
130
|
+
? path.resolve(process.cwd(), configured)
|
|
131
|
+
: path.join(process.cwd(), '.claude-flow', 'logs', 'mcp-audit.jsonl');
|
|
126
132
|
}
|
|
127
133
|
/**
|
|
128
134
|
* Appends one JSONL audit record. Returns `true` if `policy.auditLog` is not
|
|
@@ -135,8 +141,74 @@ export function appendAuditLog(policy, entry) {
|
|
|
135
141
|
if (!policy.auditLog)
|
|
136
142
|
return true;
|
|
137
143
|
try {
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
const maxBytes = policy.auditLogMaxBytes ?? 10 * 1024 * 1024;
|
|
145
|
+
const maxFiles = policy.auditLogMaxFiles ?? 5;
|
|
146
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 ||
|
|
147
|
+
!Number.isSafeInteger(maxFiles) || maxFiles < 1 || maxFiles > 100)
|
|
148
|
+
return false;
|
|
149
|
+
const line = `${JSON.stringify({ ...entry, projectPath: path.resolve(process.cwd()) })}\n`;
|
|
150
|
+
const bytes = Buffer.byteLength(line, 'utf8');
|
|
151
|
+
if (bytes > maxBytes)
|
|
152
|
+
return false;
|
|
153
|
+
const logPath = getAuditLogPath(policy);
|
|
154
|
+
fs.mkdirSync(path.dirname(logPath), { recursive: true, mode: 0o700 });
|
|
155
|
+
// All writers of a configured path serialize rotation AND append. On
|
|
156
|
+
// contention (or a stale lock after a crash), deny rather than lose audit
|
|
157
|
+
// evidence by racing a rename. Only the lock owner removes its lock.
|
|
158
|
+
const lockPath = `${logPath}.lock`;
|
|
159
|
+
const lock = fs.openSync(lockPath, 'wx', 0o600);
|
|
160
|
+
try {
|
|
161
|
+
let size = 0;
|
|
162
|
+
try {
|
|
163
|
+
const stat = fs.lstatSync(logPath);
|
|
164
|
+
if (!stat.isFile())
|
|
165
|
+
throw new Error('Audit log is not a regular file');
|
|
166
|
+
size = stat.size;
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (error.code !== 'ENOENT')
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
if (size + bytes > maxBytes) {
|
|
173
|
+
// Validate the entire chain before moving or expiring any evidence.
|
|
174
|
+
for (let index = 1; index <= maxFiles; index++) {
|
|
175
|
+
try {
|
|
176
|
+
if (!fs.lstatSync(`${logPath}.${index}`).isFile())
|
|
177
|
+
throw new Error('Audit segment is not a regular file');
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
if (error.code !== 'ENOENT')
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
for (let index = maxFiles; index >= 1; index--) {
|
|
185
|
+
const source = index === 1 ? logPath : `${logPath}.${index - 1}`;
|
|
186
|
+
const target = `${logPath}.${index}`;
|
|
187
|
+
if (index === maxFiles) {
|
|
188
|
+
try {
|
|
189
|
+
fs.unlinkSync(target);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
if (error.code !== 'ENOENT')
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
fs.renameSync(source, target);
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
if (error.code !== 'ENOENT')
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
fs.appendFileSync(logPath, line, { encoding: 'utf8', mode: 0o600 });
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
fs.closeSync(lock);
|
|
210
|
+
fs.unlinkSync(lockPath);
|
|
211
|
+
}
|
|
140
212
|
}
|
|
141
213
|
catch {
|
|
142
214
|
return false;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Tool definitions for session management with file persistence.
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync, readFileSync, readdirSync, unlinkSync, statSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { randomUUID } from 'node:crypto';
|
|
7
8
|
import { join } from 'node:path';
|
|
8
9
|
import { getProjectCwd } from './types.js';
|
|
9
10
|
import { mkdirRestricted, readFileMaybeEncrypted, writeFileRestricted, } from '../fs-secure.js';
|
|
@@ -126,7 +127,7 @@ export const sessionTools = [
|
|
|
126
127
|
if (!v.valid)
|
|
127
128
|
return { success: false, error: v.error };
|
|
128
129
|
}
|
|
129
|
-
const sessionId = `session-${Date.now()}-${
|
|
130
|
+
const sessionId = `session-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
130
131
|
// Load related data based on options
|
|
131
132
|
const data = loadRelatedStores({
|
|
132
133
|
includeMemory: input.includeMemory,
|
|
@@ -170,6 +171,9 @@ export const sessionTools = [
|
|
|
170
171
|
properties: {
|
|
171
172
|
sessionId: { type: 'string', description: 'Session ID to restore' },
|
|
172
173
|
name: { type: 'string', description: 'Session name to restore' },
|
|
174
|
+
restoreMemory: { type: 'boolean', description: 'Restore memory (default true)' },
|
|
175
|
+
restoreTasks: { type: 'boolean', description: 'Restore tasks (default true)' },
|
|
176
|
+
restoreAgents: { type: 'boolean', description: 'Restore agents (default true)' },
|
|
173
177
|
},
|
|
174
178
|
},
|
|
175
179
|
handler: async (input) => {
|
|
@@ -205,7 +209,7 @@ export const sessionTools = [
|
|
|
205
209
|
if (session) {
|
|
206
210
|
// Restore data to respective stores (legacy JSON for backward compat).
|
|
207
211
|
// audit_1776853149979: tighten perms on the restored stores too.
|
|
208
|
-
if (session.data?.memory) {
|
|
212
|
+
if (input.restoreMemory !== false && session.data?.memory) {
|
|
209
213
|
const memoryDir = join(getProjectCwd(), STORAGE_DIR, 'memory');
|
|
210
214
|
if (!existsSync(memoryDir))
|
|
211
215
|
mkdirRestricted(memoryDir);
|
|
@@ -233,13 +237,13 @@ export const sessionTools = [
|
|
|
233
237
|
// Legacy JSON restore is the fallback -- sql.js import may not be available
|
|
234
238
|
}
|
|
235
239
|
}
|
|
236
|
-
if (session.data?.tasks) {
|
|
240
|
+
if (input.restoreTasks !== false && session.data?.tasks) {
|
|
237
241
|
const taskDir = join(getProjectCwd(), STORAGE_DIR, 'tasks');
|
|
238
242
|
if (!existsSync(taskDir))
|
|
239
243
|
mkdirRestricted(taskDir);
|
|
240
244
|
writeFileRestricted(join(taskDir, 'store.json'), JSON.stringify(session.data.tasks, null, 2));
|
|
241
245
|
}
|
|
242
|
-
if (session.data?.agents) {
|
|
246
|
+
if (input.restoreAgents !== false && session.data?.agents) {
|
|
243
247
|
const agentDir = join(getProjectCwd(), STORAGE_DIR, 'agents');
|
|
244
248
|
if (!existsSync(agentDir))
|
|
245
249
|
mkdirRestricted(agentDir);
|
|
@@ -249,6 +253,11 @@ export const sessionTools = [
|
|
|
249
253
|
sessionId: session.sessionId,
|
|
250
254
|
name: session.name,
|
|
251
255
|
restored: true,
|
|
256
|
+
restoredComponents: {
|
|
257
|
+
memory: input.restoreMemory !== false && !!session.data?.memory,
|
|
258
|
+
tasks: input.restoreTasks !== false && !!session.data?.tasks,
|
|
259
|
+
agents: input.restoreAgents !== false && !!session.data?.agents,
|
|
260
|
+
},
|
|
252
261
|
restoredAt: new Date().toISOString(),
|
|
253
262
|
stats: session.stats,
|
|
254
263
|
};
|
|
@@ -437,7 +446,7 @@ export const sessionTools = [
|
|
|
437
446
|
properties: {
|
|
438
447
|
sessionId: { type: 'string', description: 'Session ID to export' },
|
|
439
448
|
outputPath: { type: 'string', description: 'File path to write the export to (optional)' },
|
|
440
|
-
includeMemory: { type: 'boolean', description: 'Include the memory snapshot (
|
|
449
|
+
includeMemory: { type: 'boolean', description: 'Include the memory snapshot (default true)' },
|
|
441
450
|
},
|
|
442
451
|
required: ['sessionId'],
|
|
443
452
|
},
|
|
@@ -449,6 +458,14 @@ export const sessionTools = [
|
|
|
449
458
|
const session = loadSession(sessionId);
|
|
450
459
|
if (!session)
|
|
451
460
|
return { sessionId, error: 'Session not found' };
|
|
461
|
+
// Apply the explicit exclusion before either returning or writing the
|
|
462
|
+
// snapshot. The saved source remains intact for a later full restore.
|
|
463
|
+
if (input.includeMemory === false) {
|
|
464
|
+
if (session.data)
|
|
465
|
+
delete session.data.memory;
|
|
466
|
+
session.stats = { ...session.stats, memoryEntries: 0, totalSize: 0 };
|
|
467
|
+
session.stats.totalSize = Buffer.byteLength(JSON.stringify(session), 'utf-8');
|
|
468
|
+
}
|
|
452
469
|
let path = null;
|
|
453
470
|
const outputPath = input.outputPath ? String(input.outputPath) : null;
|
|
454
471
|
if (outputPath) {
|
|
@@ -474,7 +491,7 @@ export const sessionTools = [
|
|
|
474
491
|
properties: {
|
|
475
492
|
inputPath: { type: 'string', description: 'Path to the session JSON file to import' },
|
|
476
493
|
name: { type: 'string', description: 'Override the imported session name' },
|
|
477
|
-
activate: { type: 'boolean', description: '
|
|
494
|
+
activate: { type: 'boolean', description: 'Restore the imported session into the active stores' },
|
|
478
495
|
},
|
|
479
496
|
required: ['inputPath'],
|
|
480
497
|
},
|
|
@@ -489,7 +506,7 @@ export const sessionTools = [
|
|
|
489
506
|
catch (e) {
|
|
490
507
|
return { error: `Invalid session JSON: ${e.message}` };
|
|
491
508
|
}
|
|
492
|
-
const newId = `session-${Date.now()}-${
|
|
509
|
+
const newId = `session-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
493
510
|
const stats = parsed.stats || { tasks: 0, agents: 0, memoryEntries: 0, totalSize: 0 };
|
|
494
511
|
const session = {
|
|
495
512
|
sessionId: newId,
|
|
@@ -500,6 +517,15 @@ export const sessionTools = [
|
|
|
500
517
|
data: parsed.data,
|
|
501
518
|
};
|
|
502
519
|
saveSession(session);
|
|
520
|
+
let activated = false;
|
|
521
|
+
if (input.activate === true) {
|
|
522
|
+
const restore = sessionTools.find(tool => tool.name === 'session_restore');
|
|
523
|
+
const result = await restore.handler({ sessionId: newId });
|
|
524
|
+
if (result.restored !== true) {
|
|
525
|
+
return { sessionId: newId, activated: false, error: result.error || 'Imported session could not be restored' };
|
|
526
|
+
}
|
|
527
|
+
activated = true;
|
|
528
|
+
}
|
|
503
529
|
return {
|
|
504
530
|
sessionId: newId,
|
|
505
531
|
name: session.name,
|
|
@@ -509,7 +535,7 @@ export const sessionTools = [
|
|
|
509
535
|
tasksImported: stats.tasks,
|
|
510
536
|
memoryEntriesImported: stats.memoryEntries,
|
|
511
537
|
},
|
|
512
|
-
activated
|
|
538
|
+
activated,
|
|
513
539
|
};
|
|
514
540
|
},
|
|
515
541
|
},
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Shared input contract for learned patterns supplied by feedback callers. */
|
|
2
|
+
export declare const feedbackPatternsSchema: {
|
|
3
|
+
type: string;
|
|
4
|
+
items: {
|
|
5
|
+
type: string;
|
|
6
|
+
minLength: number;
|
|
7
|
+
maxLength: number;
|
|
8
|
+
};
|
|
9
|
+
maxItems: number;
|
|
10
|
+
description: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function validateFeedbackPatterns(value: unknown): string[] | undefined;
|
|
13
|
+
//# sourceMappingURL=feedback-patterns.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Shared input contract for learned patterns supplied by feedback callers. */
|
|
2
|
+
export const feedbackPatternsSchema = {
|
|
3
|
+
type: 'array', items: { type: 'string', minLength: 1, maxLength: 10_000 }, maxItems: 100,
|
|
4
|
+
description: 'Learned patterns to retain; successful tasks with quality >= 0.9 may create reusable skills',
|
|
5
|
+
};
|
|
6
|
+
export function validateFeedbackPatterns(value) {
|
|
7
|
+
if (value === undefined)
|
|
8
|
+
return undefined;
|
|
9
|
+
if (!Array.isArray(value) || value.length > 100 || value.some(item => typeof item !== 'string' || !item.trim() || item.length > 10_000)) {
|
|
10
|
+
throw new Error('patterns must be an array of at most 100 non-empty strings (maximum 10000 characters each)');
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=feedback-patterns.js.map
|
|
@@ -36,6 +36,8 @@ export declare function shouldDisableNativeBridge(platform?: NodeJS.Platform, en
|
|
|
36
36
|
export declare function shouldSuppressInitLog(msg: string): boolean;
|
|
37
37
|
/** Test seam: forget cached registries so a test can exercise a fresh open. */
|
|
38
38
|
export declare function _resetRegistryCacheForTest(): void;
|
|
39
|
+
/** Inject registry construction without requiring the optional native package in tests. */
|
|
40
|
+
export declare function __setMemoryBridgeRegistryFactoryForTests(factory: (() => any) | null): void;
|
|
39
41
|
/** #3196: the sibling store AgentDB owns next to a given sql.js database. */
|
|
40
42
|
export declare function siblingAgentDbPath(dbPath: string): string | null;
|
|
41
43
|
/**
|
|
@@ -271,6 +273,7 @@ export declare function bridgeAddEmbedding(id: string, embedding: number[], entr
|
|
|
271
273
|
}, dbPath?: string): Promise<boolean | null>;
|
|
272
274
|
/**
|
|
273
275
|
* Get a named controller from AgentDB v3 via ControllerRegistry.
|
|
276
|
+
* This borrowed handle must not be retained across shutdownBridge().
|
|
274
277
|
* Returns null if unavailable.
|
|
275
278
|
*/
|
|
276
279
|
export declare function bridgeGetController(name: string, dbPath?: string): Promise<any | null>;
|
|
@@ -292,13 +295,15 @@ export declare function bridgeListControllers(dbPath?: string): Promise<Array<{
|
|
|
292
295
|
export declare function isBridgeAvailable(dbPath?: string): Promise<boolean>;
|
|
293
296
|
/**
|
|
294
297
|
* Get the ControllerRegistry instance (for advanced consumers).
|
|
298
|
+
* This is a borrowed handle, valid only until shutdownBridge(). Callers must
|
|
299
|
+
* finish using it before requesting shutdown; bridge operations own their own
|
|
300
|
+
* leases. Prefer bridgeListControllers() for status inspection.
|
|
295
301
|
*/
|
|
296
302
|
export declare function getControllerRegistry(dbPath?: string): Promise<any | null>;
|
|
297
303
|
/**
|
|
298
304
|
* Why the bridge last declined a write, or null when it has not.
|
|
299
305
|
*
|
|
300
|
-
*
|
|
301
|
-
* initialised fine can still fail every write — a schema mismatch throws
|
|
306
|
+
* A bridge that initialised fine can still fail every write — a schema mismatch throws
|
|
302
307
|
* per-operation while the registry stays healthy — and that case is exactly
|
|
303
308
|
* the one worth reporting, since the caller then demotes to a fallback whose
|
|
304
309
|
* error message describes something else entirely.
|
|
@@ -306,7 +311,7 @@ export declare function getControllerRegistry(dbPath?: string): Promise<any | nu
|
|
|
306
311
|
* Callers that surface a degraded-path error should include this so the
|
|
307
312
|
* operator learns the cause instead of only the symptom.
|
|
308
313
|
*/
|
|
309
|
-
export declare function getBridgeFailureReason(): string | null;
|
|
314
|
+
export declare function getBridgeFailureReason(dbPath?: string): string | null;
|
|
310
315
|
/**
|
|
311
316
|
* Install a pre-initialized registry for deterministic bridge tests.
|
|
312
317
|
*
|
|
@@ -321,10 +326,8 @@ export declare function __setMemoryBridgeRegistryForTests(registry: any | null):
|
|
|
321
326
|
* Shutdown the bridge and release resources.
|
|
322
327
|
*
|
|
323
328
|
* The cached state is cleared unconditionally. Previously the reset lived
|
|
324
|
-
* inside `if (registryInstance)`, so it could not clear a FAILED init
|
|
325
|
-
*
|
|
326
|
-
* precisely when init failed. A process that latched `bridgeAvailable = false`
|
|
327
|
-
* therefore had no recovery path short of a restart.
|
|
329
|
+
* inside `if (registryInstance)`, so it could not clear a FAILED init. This
|
|
330
|
+
* also clears per-path failed promises, allowing a later retry.
|
|
328
331
|
*/
|
|
329
332
|
export declare function shutdownBridge(): Promise<void>;
|
|
330
333
|
/**
|
|
@@ -589,15 +592,7 @@ export declare function bridgeHierarchicalRecall(params: {
|
|
|
589
592
|
topK?: number;
|
|
590
593
|
includeExpired?: boolean;
|
|
591
594
|
}): Promise<any>;
|
|
592
|
-
/**
|
|
593
|
-
* Run memory consolidation.
|
|
594
|
-
*
|
|
595
|
-
* Real MemoryConsolidation API (agentdb alpha.10+):
|
|
596
|
-
* consolidate() → Promise<ConsolidationReport>
|
|
597
|
-
* ConsolidationReport = { episodicProcessed, semanticCreated, memoriesForgotten, ... }
|
|
598
|
-
* Stub API (fallback):
|
|
599
|
-
* consolidate() → { promoted, pruned, timestamp }
|
|
600
|
-
*/
|
|
595
|
+
/** Run consolidation when a real controller is available. */
|
|
601
596
|
export declare function bridgeConsolidate(params: {
|
|
602
597
|
minAge?: number;
|
|
603
598
|
maxEntries?: number;
|
|
@@ -612,10 +607,6 @@ export declare function bridgeBatchOperation(params: {
|
|
|
612
607
|
operation: string;
|
|
613
608
|
entries: any[];
|
|
614
609
|
}): Promise<any>;
|
|
615
|
-
/**
|
|
616
|
-
* Synthesize context from memories.
|
|
617
|
-
* ContextSynthesizer.synthesize is a static method that takes MemoryPattern[] (not a string).
|
|
618
|
-
*/
|
|
619
610
|
export declare function bridgeContextSynthesize(params: {
|
|
620
611
|
query: string;
|
|
621
612
|
maxEntries?: number;
|