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.
Files changed (48) hide show
  1. package/.claude/helpers/auto-memory-hook.mjs +6 -2
  2. package/.claude/helpers/learning-service.mjs +4 -2
  3. package/.claude/helpers/metrics-db.mjs +4 -2
  4. package/node_modules/@claude-flow/codex/package.json +1 -1
  5. package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts.map +1 -1
  6. package/node_modules/@claude-flow/mcp/dist/tool-registry.js +11 -3
  7. package/node_modules/@claude-flow/mcp/dist/tool-registry.js.map +1 -1
  8. package/node_modules/@claude-flow/mcp/package.json +6 -3
  9. package/node_modules/@claude-flow/plugin-agent-federation/package.json +3 -3
  10. package/node_modules/@claude-flow/security/package.json +2 -3
  11. package/package.json +7 -7
  12. package/v3/@claude-flow/cli/README.md +0 -2
  13. package/v3/@claude-flow/cli/bin/cli.js +4 -1
  14. package/v3/@claude-flow/cli/catalog-manifest.json +2 -2
  15. package/v3/@claude-flow/cli/dist/src/commands/analyze.js +2 -2
  16. package/v3/@claude-flow/cli/dist/src/commands/config.js +5 -7
  17. package/v3/@claude-flow/cli/dist/src/commands/hooks.js +15 -5
  18. package/v3/@claude-flow/cli/dist/src/commands/memory.js +89 -27
  19. package/v3/@claude-flow/cli/dist/src/commands/metaharness.js +1 -0
  20. package/v3/@claude-flow/cli/dist/src/commands/security.js +113 -55
  21. package/v3/@claude-flow/cli/dist/src/commands/session.js +6 -6
  22. package/v3/@claude-flow/cli/dist/src/commands/task.js +5 -4
  23. package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +4 -2
  24. package/v3/@claude-flow/cli/dist/src/mcp-server.js +10 -3
  25. package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.js +29 -62
  26. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.d.ts +0 -4
  27. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +139 -37
  28. package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +89 -35
  29. package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.d.ts +8 -1
  30. package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.js +80 -8
  31. package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.js +34 -8
  32. package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.d.ts +13 -0
  33. package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.js +14 -0
  34. package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.d.ts +3 -0
  35. package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.js +5 -0
  36. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +11 -20
  37. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +348 -134
  38. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +9 -1
  39. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +41 -31
  40. package/v3/@claude-flow/cli/dist/src/ruvector/graph-analyzer.js +4 -1
  41. package/v3/@claude-flow/cli/dist/src/services/config-file-manager.js +30 -8
  42. package/v3/@claude-flow/cli/dist/src/services/memory-backup.d.ts +1 -1
  43. package/v3/@claude-flow/cli/dist/src/services/memory-backup.js +67 -27
  44. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +2 -2
  45. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +23 -14
  46. package/v3/@claude-flow/cli/package.json +9 -9
  47. package/v3/@claude-flow/guidance/package.json +5 -6
  48. 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 legacyStore = loadLegacyStore();
329
- if (legacyStore && Object.keys(legacyStore.entries).length > 0) {
330
- console.error('[MCP Memory] Migrating legacy JSON store to sql.js...');
331
- let migrated = 0;
332
- for (const [key, entry] of Object.entries(legacyStore.entries)) {
333
- try {
334
- const value = typeof entry.value === 'string' ? entry.value : JSON.stringify(entry.value);
335
- await storeEntry({
336
- key,
337
- value,
338
- namespace: 'default',
339
- generateEmbeddingFlag: true,
340
- });
341
- migrated++;
342
- }
343
- catch (e) {
344
- console.error(`[MCP Memory] Failed to migrate key "${key}":`, e);
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
- await ensureInitialized();
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
- await ensureInitialized();
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
- imported++;
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 defaultAuditLogPath() {
122
- return path.join(os.tmpdir(), 'ruflo-mcp-audit.jsonl');
123
- }
124
- export function getAuditLogPath() {
125
- return auditLogPathOverride ?? defaultAuditLogPath();
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
- fs.appendFileSync(getAuditLogPath(), `${JSON.stringify(entry)}\n`, 'utf-8');
139
- return true;
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()}-${Math.random().toString(36).slice(2, 8)}`;
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 (advisory — already in the saved record)' },
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: 'Make the imported session the current one (advisory)' },
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()}-${Math.random().toString(36).slice(2, 8)}`;
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: input.activate === true,
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
@@ -0,0 +1,3 @@
1
+ /** TTL is a read-time visibility rule, independent of background cleanup. */
2
+ export declare function liveMemoryRowSql(): string;
3
+ //# sourceMappingURL=live-memory-row.d.ts.map
@@ -0,0 +1,5 @@
1
+ /** TTL is a read-time visibility rule, independent of background cleanup. */
2
+ export function liveMemoryRowSql() {
3
+ return `(status = 'active' OR status IS NULL) AND (expires_at IS NULL OR expires_at > ${Date.now()})`;
4
+ }
5
+ //# sourceMappingURL=live-memory-row.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
- * Deliberately NOT gated on `bridgeAvailable === false`. A bridge that
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 — the
325
- * one state that actually needs clearing, since `registryInstance` is null
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;