claude-flow 3.47.1 → 3.49.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 (84) hide show
  1. package/.claude/.proven-config-version +1 -0
  2. package/.claude/helpers/auto-memory-hook.mjs +6 -2
  3. package/.claude/helpers/hook-handler.cjs +7 -4
  4. package/.claude/helpers/learning-service.mjs +4 -2
  5. package/.claude/helpers/memory.cjs +1 -1
  6. package/.claude/helpers/metrics-db.mjs +4 -2
  7. package/.claude/helpers/router.cjs +1 -1
  8. package/.claude/helpers/session.cjs +1 -1
  9. package/.claude/proven-config.json +42 -0
  10. package/.claude-plugin/marketplace.json +16 -1
  11. package/README.md +1 -53
  12. package/README.zh-CN.md +1 -53
  13. package/node_modules/@claude-flow/codex/package.json +1 -1
  14. package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts.map +1 -1
  15. package/node_modules/@claude-flow/mcp/dist/tool-registry.js +11 -3
  16. package/node_modules/@claude-flow/mcp/dist/tool-registry.js.map +1 -1
  17. package/node_modules/@claude-flow/mcp/package.json +6 -3
  18. package/node_modules/@claude-flow/plugin-agent-federation/package.json +3 -3
  19. package/node_modules/@claude-flow/security/dist/policy/engine.d.ts +2 -6
  20. package/node_modules/@claude-flow/security/dist/policy/engine.d.ts.map +1 -1
  21. package/node_modules/@claude-flow/security/dist/policy/engine.js +35 -1
  22. package/node_modules/@claude-flow/security/dist/policy/engine.js.map +1 -1
  23. package/node_modules/@claude-flow/security/dist/policy/types.d.ts +18 -0
  24. package/node_modules/@claude-flow/security/dist/policy/types.d.ts.map +1 -1
  25. package/node_modules/@claude-flow/security/package.json +2 -3
  26. package/package.json +7 -7
  27. package/v3/@claude-flow/cli/README.md +1 -53
  28. package/v3/@claude-flow/cli/bin/cli.js +4 -1
  29. package/v3/@claude-flow/cli/catalog-manifest.json +4 -4
  30. package/v3/@claude-flow/cli/dist/src/commands/analyze.js +2 -2
  31. package/v3/@claude-flow/cli/dist/src/commands/config.js +5 -7
  32. package/v3/@claude-flow/cli/dist/src/commands/doctor.d.ts +19 -1
  33. package/v3/@claude-flow/cli/dist/src/commands/doctor.js +69 -8
  34. package/v3/@claude-flow/cli/dist/src/commands/hooks.js +22 -9
  35. package/v3/@claude-flow/cli/dist/src/commands/memory.js +119 -34
  36. package/v3/@claude-flow/cli/dist/src/commands/metaharness.js +1 -0
  37. package/v3/@claude-flow/cli/dist/src/commands/plugins.js +44 -7
  38. package/v3/@claude-flow/cli/dist/src/commands/policy.js +5 -2
  39. package/v3/@claude-flow/cli/dist/src/commands/security.js +113 -55
  40. package/v3/@claude-flow/cli/dist/src/commands/session.js +132 -25
  41. package/v3/@claude-flow/cli/dist/src/commands/swarm.js +11 -11
  42. package/v3/@claude-flow/cli/dist/src/commands/task.js +5 -4
  43. package/v3/@claude-flow/cli/dist/src/index.js +10 -1
  44. package/v3/@claude-flow/cli/dist/src/init/executor.js +11 -5
  45. package/v3/@claude-flow/cli/dist/src/init/helper-companions.d.ts +3 -0
  46. package/v3/@claude-flow/cli/dist/src/init/helper-companions.js +34 -0
  47. package/v3/@claude-flow/cli/dist/src/init/helper-integrity.d.ts +21 -0
  48. package/v3/@claude-flow/cli/dist/src/init/helper-integrity.js +62 -0
  49. package/v3/@claude-flow/cli/dist/src/init/helper-refresh.d.ts +18 -11
  50. package/v3/@claude-flow/cli/dist/src/init/helper-refresh.js +56 -13
  51. package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +14 -12
  52. package/v3/@claude-flow/cli/dist/src/mcp-server.js +10 -3
  53. package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.js +29 -62
  54. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.d.ts +16 -7
  55. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +226 -51
  56. package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.d.ts +7 -0
  57. package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +131 -40
  58. package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.d.ts +8 -1
  59. package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.js +80 -8
  60. package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.d.ts +15 -0
  61. package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.js +270 -58
  62. package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.d.ts +13 -0
  63. package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.js +14 -0
  64. package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.d.ts +3 -0
  65. package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.js +5 -0
  66. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +11 -20
  67. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +348 -134
  68. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +9 -1
  69. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +47 -34
  70. package/v3/@claude-flow/cli/dist/src/plugins/manager.d.ts +37 -10
  71. package/v3/@claude-flow/cli/dist/src/plugins/manager.js +106 -20
  72. package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.d.ts +61 -0
  73. package/v3/@claude-flow/cli/dist/src/plugins/trust-policy.js +84 -0
  74. package/v3/@claude-flow/cli/dist/src/ruvector/graph-analyzer.js +4 -1
  75. package/v3/@claude-flow/cli/dist/src/services/config-file-manager.js +30 -8
  76. package/v3/@claude-flow/cli/dist/src/services/memory-backup.d.ts +1 -1
  77. package/v3/@claude-flow/cli/dist/src/services/memory-backup.js +67 -27
  78. package/v3/@claude-flow/cli/dist/src/services/policy-runtime.d.ts +6 -0
  79. package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +27 -2
  80. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +2 -2
  81. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +23 -14
  82. package/v3/@claude-flow/cli/package.json +9 -9
  83. package/v3/@claude-flow/guidance/package.json +5 -6
  84. package/v3/@claude-flow/shared/package.json +6 -3
@@ -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';
@@ -68,17 +69,126 @@ function listSessions() {
68
69
  }
69
70
  return sessions;
70
71
  }
71
- // Load related stores for session data
72
- function loadRelatedStores(options) {
73
- const data = {};
74
- if (options.includeMemory) {
72
+ /** Snapshot format written by session_save since #3573. */
73
+ const MEMORY_SNAPSHOT_FORMAT = 'ruflo-session-memory/2';
74
+ const MEMORY_PAGE_SIZE = 500;
75
+ async function readLiveEntries(listEntries, dbPath, encryptWrites) {
76
+ const rows = [];
77
+ // Bounded: stop on a short page, once `total` rows are in hand, or after a
78
+ // hard cap, so a backend that ignores `offset` cannot loop forever.
79
+ for (let page = 0, offset = 0; page < 10_000; page++, offset += MEMORY_PAGE_SIZE) {
80
+ const result = await listEntries({
81
+ dbPath, includeContent: true, limit: MEMORY_PAGE_SIZE, offset,
82
+ ...(encryptWrites === undefined ? {} : { encryptWrites }),
83
+ });
84
+ if (!result.success)
85
+ throw new Error(result.error || `could not list ${dbPath}`);
86
+ rows.push(...result.entries);
87
+ if (result.entries.length < MEMORY_PAGE_SIZE || rows.length >= result.total)
88
+ break;
89
+ }
90
+ return rows;
91
+ }
92
+ /**
93
+ * #3573: capture the memory the user actually has.
94
+ *
95
+ * Before this, session_save read only the pre-SQLite `.claude-flow/memory/store.json`,
96
+ * which a current install never writes, so `--include-memory` saved nothing and
97
+ * reported "Memory Entries: 0". The snapshot now covers the same store `memory
98
+ * list` reads (resolveDbPath), plus rows that exist only in the sibling AgentDB
99
+ * store written by the MCP path, de-duplicated by namespace+key (default CLI
100
+ * writes are mirrored into AgentDB, so most rows are in both). A legacy
101
+ * store.json is still captured for backward compatibility.
102
+ */
103
+ async function captureMemorySnapshot() {
104
+ const report = {
105
+ requested: true, status: 'captured', entries: 0,
106
+ sources: { memoryDb: 0, agentdb: 0, legacyJson: 0 },
107
+ };
108
+ const entries = {};
109
+ let legacy;
110
+ const legacyPath = join(getProjectCwd(), STORAGE_DIR, 'memory', 'store.json');
111
+ if (existsSync(legacyPath)) {
75
112
  try {
76
- const memoryPath = join(getProjectCwd(), STORAGE_DIR, 'memory', 'store.json');
77
- if (existsSync(memoryPath)) {
78
- data.memory = JSON.parse(readFileSync(memoryPath, 'utf-8'));
113
+ legacy = JSON.parse(readFileSync(legacyPath, 'utf-8'));
114
+ const legacyEntries = legacy.entries || {};
115
+ for (const [id, entry] of Object.entries(legacyEntries)) {
116
+ entries[id] = entry;
117
+ report.sources.legacyJson++;
79
118
  }
80
119
  }
81
- catch { /* ignore */ }
120
+ catch { /* unreadable legacy file is not memory we can restore */ }
121
+ }
122
+ let sawStore = legacy !== undefined;
123
+ try {
124
+ const { listEntries, resolveDbPath } = await import('../memory/memory-initializer.js');
125
+ const primary = resolveDbPath();
126
+ const seen = new Set();
127
+ if (existsSync(primary)) {
128
+ sawStore = true;
129
+ for (const row of await readLiveEntries(listEntries, primary)) {
130
+ const id = `${row.namespace}::${row.key}`;
131
+ seen.add(id);
132
+ entries[id] = {
133
+ key: row.key, value: row.content ?? '', namespace: row.namespace,
134
+ provenanceType: row.provenanceType, source: 'memory-db',
135
+ };
136
+ report.sources.memoryDb++;
137
+ }
138
+ }
139
+ let sibling = null;
140
+ try {
141
+ const { siblingAgentDbPath } = await import('../memory/memory-bridge.js');
142
+ sibling = siblingAgentDbPath(primary);
143
+ }
144
+ catch { /* no bridge, no sibling store */ }
145
+ if (sibling && existsSync(sibling)) {
146
+ sawStore = true;
147
+ // The AgentDB store is read by native SQLite and must stay plaintext.
148
+ for (const row of await readLiveEntries(listEntries, sibling, false)) {
149
+ const id = `${row.namespace}::${row.key}`;
150
+ if (seen.has(id))
151
+ continue;
152
+ seen.add(id);
153
+ entries[id] = {
154
+ key: row.key, value: row.content ?? '', namespace: row.namespace,
155
+ provenanceType: row.provenanceType, source: 'agentdb',
156
+ };
157
+ report.sources.agentdb++;
158
+ }
159
+ }
160
+ }
161
+ catch (e) {
162
+ report.status = 'error';
163
+ report.error = e.message;
164
+ }
165
+ report.entries = Object.keys(entries).length;
166
+ if (report.status !== 'error' && !sawStore)
167
+ report.status = 'no-store';
168
+ if (report.entries === 0 && legacy === undefined) {
169
+ return { report };
170
+ }
171
+ return {
172
+ memory: {
173
+ format: MEMORY_SNAPSHOT_FORMAT,
174
+ entries,
175
+ ...(legacy !== undefined ? { legacy } : {}),
176
+ },
177
+ report,
178
+ };
179
+ }
180
+ // Load related stores for session data
181
+ async function loadRelatedStores(options) {
182
+ const data = {};
183
+ let memoryCapture = {
184
+ requested: false, status: 'not-requested', entries: 0,
185
+ sources: { memoryDb: 0, agentdb: 0, legacyJson: 0 },
186
+ };
187
+ if (options.includeMemory) {
188
+ const captured = await captureMemorySnapshot();
189
+ memoryCapture = captured.report;
190
+ if (captured.memory)
191
+ data.memory = captured.memory;
82
192
  }
83
193
  if (options.includeTasks) {
84
194
  try {
@@ -98,7 +208,92 @@ function loadRelatedStores(options) {
98
208
  }
99
209
  catch { /* ignore */ }
100
210
  }
101
- return data;
211
+ return { data, memoryCapture };
212
+ }
213
+ /** Count entries in a memory snapshot of either format. */
214
+ function countMemoryEntries(memory) {
215
+ if (!memory || typeof memory !== 'object')
216
+ return 0;
217
+ return Object.keys(memory.entries || {}).length;
218
+ }
219
+ /**
220
+ * Restore a memory snapshot into the live store and report what was actually
221
+ * written. #3573: the previous restore swallowed every write failure and then
222
+ * echoed the count recorded at save time.
223
+ */
224
+ async function restoreMemorySnapshot(memory) {
225
+ const outcome = { restored: 0, failed: 0, errors: [] };
226
+ const isV2 = memory.format === MEMORY_SNAPSHOT_FORMAT;
227
+ // Legacy snapshots are the old store.json itself; v2 snapshots carry it
228
+ // under `legacy` only when one existed at save time.
229
+ const legacyJson = isV2 ? memory.legacy : memory;
230
+ if (legacyJson !== undefined) {
231
+ const memoryDir = join(getProjectCwd(), STORAGE_DIR, 'memory');
232
+ if (!existsSync(memoryDir))
233
+ mkdirRestricted(memoryDir);
234
+ writeFileRestricted(join(memoryDir, 'store.json'), JSON.stringify(legacyJson, null, 2));
235
+ }
236
+ const entries = memory.entries;
237
+ if (!entries)
238
+ return outcome;
239
+ let storeEntry;
240
+ let isValidProvenanceType = () => false;
241
+ try {
242
+ const mod = await import('../memory/memory-initializer.js');
243
+ storeEntry = mod.storeEntry;
244
+ try {
245
+ if (typeof mod.isValidProvenanceType === 'function')
246
+ isValidProvenanceType = mod.isValidProvenanceType;
247
+ }
248
+ catch { /* optional: without it, provenance is simply not carried over */ }
249
+ }
250
+ catch (e) {
251
+ outcome.failed = Object.keys(entries).length;
252
+ outcome.errors.push(`memory store unavailable: ${e.message}`);
253
+ return outcome;
254
+ }
255
+ for (const entry of Object.values(entries)) {
256
+ const key = entry.key || entry.id || '';
257
+ const value = entry.value || entry.content || '';
258
+ if (!key || !value) {
259
+ outcome.failed++;
260
+ continue;
261
+ }
262
+ try {
263
+ // A snapshot from another version may carry a provenance value this one
264
+ // rejects; drop it rather than fail an otherwise-good row.
265
+ const provenance = entry.provenanceType && entry.provenanceType !== 'unknown'
266
+ && isValidProvenanceType(entry.provenanceType) ? entry.provenanceType : undefined;
267
+ const result = await storeEntry({
268
+ key,
269
+ value,
270
+ namespace: entry.namespace || 'restored',
271
+ upsert: true,
272
+ ...(provenance ? { provenanceType: provenance } : {}),
273
+ });
274
+ if (result && result.success === false) {
275
+ outcome.failed++;
276
+ if (result.error && outcome.errors.length < 5)
277
+ outcome.errors.push(`${key}: ${result.error}`);
278
+ }
279
+ else {
280
+ outcome.restored++;
281
+ }
282
+ }
283
+ catch (e) {
284
+ outcome.failed++;
285
+ if (outcome.errors.length < 5)
286
+ outcome.errors.push(`${key}: ${e.message}`);
287
+ }
288
+ }
289
+ return outcome;
290
+ }
291
+ /** A session record is an object that carries at least one of its defining fields. */
292
+ function isSessionRecordLike(value) {
293
+ if (!value || typeof value !== 'object' || Array.isArray(value))
294
+ return false;
295
+ const v = value;
296
+ return 'data' in v || 'stats' in v || 'sessionId' in v || 'savedAt' in v;
102
297
  }
103
298
  export const sessionTools = [
104
299
  {
@@ -126,9 +321,9 @@ export const sessionTools = [
126
321
  if (!v.valid)
127
322
  return { success: false, error: v.error };
128
323
  }
129
- const sessionId = `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
324
+ const sessionId = `session-${Date.now()}-${randomUUID().slice(0, 8)}`;
130
325
  // Load related data based on options
131
- const data = loadRelatedStores({
326
+ const { data, memoryCapture } = await loadRelatedStores({
132
327
  includeMemory: input.includeMemory,
133
328
  includeTasks: input.includeTasks,
134
329
  includeAgents: input.includeAgents,
@@ -137,7 +332,7 @@ export const sessionTools = [
137
332
  const stats = {
138
333
  tasks: data.tasks ? Object.keys(data.tasks.tasks || {}).length : 0,
139
334
  agents: data.agents ? Object.keys(data.agents.agents || {}).length : 0,
140
- memoryEntries: data.memory ? Object.keys(data.memory.entries || {}).length : 0,
335
+ memoryEntries: countMemoryEntries(data.memory),
141
336
  totalSize: 0,
142
337
  };
143
338
  const session = {
@@ -157,6 +352,7 @@ export const sessionTools = [
157
352
  name: session.name,
158
353
  savedAt: session.savedAt,
159
354
  stats: session.stats,
355
+ memoryCapture,
160
356
  path: getSessionPath(sessionId),
161
357
  };
162
358
  },
@@ -170,6 +366,9 @@ export const sessionTools = [
170
366
  properties: {
171
367
  sessionId: { type: 'string', description: 'Session ID to restore' },
172
368
  name: { type: 'string', description: 'Session name to restore' },
369
+ restoreMemory: { type: 'boolean', description: 'Restore memory (default true)' },
370
+ restoreTasks: { type: 'boolean', description: 'Restore tasks (default true)' },
371
+ restoreAgents: { type: 'boolean', description: 'Restore agents (default true)' },
173
372
  },
174
373
  },
175
374
  handler: async (input) => {
@@ -203,43 +402,19 @@ export const sessionTools = [
203
402
  }
204
403
  }
205
404
  if (session) {
206
- // Restore data to respective stores (legacy JSON for backward compat).
207
- // audit_1776853149979: tighten perms on the restored stores too.
208
- if (session.data?.memory) {
209
- const memoryDir = join(getProjectCwd(), STORAGE_DIR, 'memory');
210
- if (!existsSync(memoryDir))
211
- mkdirRestricted(memoryDir);
212
- writeFileRestricted(join(memoryDir, 'store.json'), JSON.stringify(session.data.memory, null, 2));
213
- // Also populate active sql.js SQLite database so memory-tools can find entries
214
- try {
215
- const { storeEntry } = await import('../memory/memory-initializer.js');
216
- const memoryData = session.data.memory;
217
- if (memoryData.entries) {
218
- for (const entry of Object.values(memoryData.entries)) {
219
- const key = entry.key || entry.id || '';
220
- const value = entry.value || entry.content || '';
221
- if (key && value) {
222
- await storeEntry({
223
- key,
224
- value,
225
- namespace: entry.namespace || 'restored',
226
- upsert: true,
227
- });
228
- }
229
- }
230
- }
231
- }
232
- catch {
233
- // Legacy JSON restore is the fallback -- sql.js import may not be available
234
- }
405
+ // Restore data to respective stores. audit_1776853149979: tighten
406
+ // perms on the restored stores too.
407
+ let memoryRestore;
408
+ if (input.restoreMemory !== false && session.data?.memory) {
409
+ memoryRestore = await restoreMemorySnapshot(session.data.memory);
235
410
  }
236
- if (session.data?.tasks) {
411
+ if (input.restoreTasks !== false && session.data?.tasks) {
237
412
  const taskDir = join(getProjectCwd(), STORAGE_DIR, 'tasks');
238
413
  if (!existsSync(taskDir))
239
414
  mkdirRestricted(taskDir);
240
415
  writeFileRestricted(join(taskDir, 'store.json'), JSON.stringify(session.data.tasks, null, 2));
241
416
  }
242
- if (session.data?.agents) {
417
+ if (input.restoreAgents !== false && session.data?.agents) {
243
418
  const agentDir = join(getProjectCwd(), STORAGE_DIR, 'agents');
244
419
  if (!existsSync(agentDir))
245
420
  mkdirRestricted(agentDir);
@@ -249,8 +424,18 @@ export const sessionTools = [
249
424
  sessionId: session.sessionId,
250
425
  name: session.name,
251
426
  restored: true,
427
+ restoredComponents: {
428
+ memory: input.restoreMemory !== false && !!session.data?.memory,
429
+ tasks: input.restoreTasks !== false && !!session.data?.tasks,
430
+ agents: input.restoreAgents !== false && !!session.data?.agents,
431
+ },
252
432
  restoredAt: new Date().toISOString(),
253
433
  stats: session.stats,
434
+ // #3573: counts actually written, not the count recorded at save time.
435
+ ...(memoryRestore ? {
436
+ memoryRestore,
437
+ stats: { ...session.stats, memoryEntriesRestored: memoryRestore.restored },
438
+ } : {}),
254
439
  };
255
440
  }
256
441
  return {
@@ -437,7 +622,7 @@ export const sessionTools = [
437
622
  properties: {
438
623
  sessionId: { type: 'string', description: 'Session ID to export' },
439
624
  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)' },
625
+ includeMemory: { type: 'boolean', description: 'Include the memory snapshot (default true)' },
441
626
  },
442
627
  required: ['sessionId'],
443
628
  },
@@ -449,6 +634,14 @@ export const sessionTools = [
449
634
  const session = loadSession(sessionId);
450
635
  if (!session)
451
636
  return { sessionId, error: 'Session not found' };
637
+ // Apply the explicit exclusion before either returning or writing the
638
+ // snapshot. The saved source remains intact for a later full restore.
639
+ if (input.includeMemory === false) {
640
+ if (session.data)
641
+ delete session.data.memory;
642
+ session.stats = { ...session.stats, memoryEntries: 0, totalSize: 0 };
643
+ session.stats.totalSize = Buffer.byteLength(JSON.stringify(session), 'utf-8');
644
+ }
452
645
  let path = null;
453
646
  const outputPath = input.outputPath ? String(input.outputPath) : null;
454
647
  if (outputPath) {
@@ -467,30 +660,40 @@ export const sessionTools = [
467
660
  // #1916: `ruflo session import <file>` referenced an unregistered
468
661
  // `session_import` tool. Reads a session JSON and re-saves it locally.
469
662
  name: 'session_import',
470
- description: 'Import a session JSON file (produced by session_export) into the local session store and optionally activate it. Use when native Read is wrong because the file is a structured session record that must be re-registered (new id, stats recomputed) rather than just read. For reading the file, native Read is fine. Pair with session_export on the source.',
663
+ description: 'Import a session (produced by session_export) into the local session store and optionally activate it. Pass either inputPath (a session JSON file) or data (the session record itself). Use when native Read is wrong because the file is a structured session record that must be re-registered (new id, stats recomputed) rather than just read. For reading the file, native Read is fine. Pair with session_export on the source.',
471
664
  category: 'session',
472
665
  inputSchema: {
473
666
  type: 'object',
474
667
  properties: {
475
- inputPath: { type: 'string', description: 'Path to the session JSON file to import' },
668
+ inputPath: { type: 'string', description: 'Path to the session JSON file to import (or pass data)' },
669
+ data: { type: 'object', description: 'The session record itself, as written by session_export (or pass inputPath)' },
476
670
  name: { type: 'string', description: 'Override the imported session name' },
477
- activate: { type: 'boolean', description: 'Make the imported session the current one (advisory)' },
671
+ activate: { type: 'boolean', description: 'Restore the imported session into the active stores' },
478
672
  },
479
- required: ['inputPath'],
480
673
  },
481
674
  handler: async (input) => {
482
- const inputPath = String(input.inputPath ?? '');
483
- if (!inputPath || !existsSync(inputPath))
484
- return { error: `File not found: ${inputPath || '(empty)'}` };
485
675
  let parsed;
486
- try {
487
- parsed = JSON.parse(readFileSync(inputPath, 'utf-8'));
676
+ if (input.data !== undefined && input.data !== null) {
677
+ parsed = input.data;
488
678
  }
489
- catch (e) {
490
- return { error: `Invalid session JSON: ${e.message}` };
679
+ else {
680
+ const inputPath = String(input.inputPath ?? '');
681
+ if (!inputPath)
682
+ return { error: 'Provide inputPath (a session JSON file) or data (a session record)' };
683
+ if (!existsSync(inputPath))
684
+ return { error: `File not found: ${inputPath}` };
685
+ try {
686
+ parsed = JSON.parse(readFileSync(inputPath, 'utf-8'));
687
+ }
688
+ catch (e) {
689
+ return { error: `Invalid session JSON: ${e.message}` };
690
+ }
491
691
  }
492
- const newId = `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
493
- const stats = parsed.stats || { tasks: 0, agents: 0, memoryEntries: 0, totalSize: 0 };
692
+ if (!isSessionRecordLike(parsed)) {
693
+ return { error: 'Not a session record: expected an object produced by session export' };
694
+ }
695
+ const newId = `session-${Date.now()}-${randomUUID().slice(0, 8)}`;
696
+ const stats = { tasks: 0, agents: 0, memoryEntries: 0, totalSize: 0, ...(parsed.stats || {}) };
494
697
  const session = {
495
698
  sessionId: newId,
496
699
  name: input.name ? String(input.name) : (parsed.name || 'imported-session'),
@@ -500,6 +703,15 @@ export const sessionTools = [
500
703
  data: parsed.data,
501
704
  };
502
705
  saveSession(session);
706
+ let activated = false;
707
+ if (input.activate === true) {
708
+ const restore = sessionTools.find(tool => tool.name === 'session_restore');
709
+ const result = await restore.handler({ sessionId: newId });
710
+ if (result.restored !== true) {
711
+ return { sessionId: newId, activated: false, error: result.error || 'Imported session could not be restored' };
712
+ }
713
+ activated = true;
714
+ }
503
715
  return {
504
716
  sessionId: newId,
505
717
  name: session.name,
@@ -509,7 +721,7 @@ export const sessionTools = [
509
721
  tasksImported: stats.tasks,
510
722
  memoryEntriesImported: stats.memoryEntries,
511
723
  },
512
- activated: input.activate === true,
724
+ activated,
513
725
  };
514
726
  },
515
727
  },
@@ -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;