claude-flow 3.47.0 → 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 (52) 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/hive-mind-worker.d.ts +88 -0
  18. package/v3/@claude-flow/cli/dist/src/commands/hive-mind-worker.js +242 -0
  19. package/v3/@claude-flow/cli/dist/src/commands/hive-mind.js +24 -41
  20. package/v3/@claude-flow/cli/dist/src/commands/hooks.js +15 -5
  21. package/v3/@claude-flow/cli/dist/src/commands/memory.js +89 -27
  22. package/v3/@claude-flow/cli/dist/src/commands/metaharness.js +1 -0
  23. package/v3/@claude-flow/cli/dist/src/commands/security.js +113 -55
  24. package/v3/@claude-flow/cli/dist/src/commands/session.js +6 -6
  25. package/v3/@claude-flow/cli/dist/src/commands/task.js +5 -4
  26. package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +4 -2
  27. package/v3/@claude-flow/cli/dist/src/mcp-server.js +10 -3
  28. package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.js +29 -62
  29. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.d.ts +0 -4
  30. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +139 -37
  31. package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +89 -35
  32. package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.d.ts +8 -1
  33. package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.js +80 -8
  34. package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.js +34 -8
  35. package/v3/@claude-flow/cli/dist/src/mcp-tools/task-tools.js +73 -53
  36. package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.d.ts +13 -0
  37. package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.js +14 -0
  38. package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.d.ts +3 -0
  39. package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.js +5 -0
  40. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +11 -20
  41. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +348 -134
  42. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +9 -1
  43. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +41 -31
  44. package/v3/@claude-flow/cli/dist/src/ruvector/graph-analyzer.js +4 -1
  45. package/v3/@claude-flow/cli/dist/src/services/config-file-manager.js +30 -8
  46. package/v3/@claude-flow/cli/dist/src/services/memory-backup.d.ts +1 -1
  47. package/v3/@claude-flow/cli/dist/src/services/memory-backup.js +67 -27
  48. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +2 -2
  49. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +23 -14
  50. package/v3/@claude-flow/cli/package.json +9 -9
  51. package/v3/@claude-flow/guidance/package.json +5 -6
  52. package/v3/@claude-flow/shared/package.json +6 -3
@@ -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
  },
@@ -40,6 +40,53 @@ function saveTaskStore(store) {
40
40
  ensureTaskDir();
41
41
  writeFileSync(getTaskPath(), JSON.stringify(store, null, 2), 'utf-8');
42
42
  }
43
+ /**
44
+ * Apply an agent-state change to every store that holds the agent.
45
+ *
46
+ * `hive-mind_spawn` keeps its workers in `.claude-flow/agents.json`, not the
47
+ * canonical `.claude-flow/agents/store.json` (#1916). Syncing only the
48
+ * canonical store meant assigning a task to a hive worker never marked it
49
+ * busy, and completing one never counted it: `hive-mind status` showed the
50
+ * worker idle with 0 completed while its task sat in the queue.
51
+ */
52
+ function updateAgents(agentIds, mutate) {
53
+ if (agentIds.length === 0)
54
+ return;
55
+ const paths = [
56
+ join(getProjectCwd(), STORAGE_DIR, 'agents', 'store.json'),
57
+ join(getProjectCwd(), STORAGE_DIR, 'agents.json'),
58
+ ];
59
+ for (const path of paths) {
60
+ try {
61
+ if (!existsSync(path))
62
+ continue;
63
+ const store = JSON.parse(readFileSync(path, 'utf-8'));
64
+ if (!store.agents)
65
+ continue;
66
+ let touched = false;
67
+ for (const id of agentIds) {
68
+ if (store.agents[id]) {
69
+ mutate(store.agents[id]);
70
+ touched = true;
71
+ }
72
+ }
73
+ if (touched)
74
+ writeFileSync(path, JSON.stringify(store, null, 2), 'utf-8');
75
+ }
76
+ catch {
77
+ // Best-effort agent sync: a corrupt agent store must not fail the task op.
78
+ }
79
+ }
80
+ }
81
+ /** Return agents still holding `taskId` to idle. */
82
+ function releaseAgents(agentIds, taskId) {
83
+ updateAgents(agentIds, (agent) => {
84
+ if (agent.currentTask === taskId) {
85
+ agent.status = 'idle';
86
+ agent.currentTask = null;
87
+ }
88
+ });
89
+ }
43
90
  export const taskTools = [
44
91
  {
45
92
  name: 'task_create',
@@ -214,33 +261,22 @@ export const taskTools = [
214
261
  const taskId = input.taskId;
215
262
  const task = store.tasks[taskId];
216
263
  if (task) {
264
+ if (task.status === 'completed') {
265
+ return { taskId: task.taskId, status: task.status, completedAt: task.completedAt, result: task.result };
266
+ }
217
267
  task.status = 'completed';
218
268
  task.progress = 100;
219
269
  task.completedAt = new Date().toISOString();
220
270
  task.result = input.result || {};
221
271
  saveTaskStore(store);
222
272
  // Sync assigned agents back to idle and increment taskCount
223
- if (task.assignedTo.length > 0) {
224
- const agentStorePath = join(getProjectCwd(), STORAGE_DIR, 'agents', 'store.json');
225
- try {
226
- let agentStore = { agents: {} };
227
- if (existsSync(agentStorePath)) {
228
- agentStore = JSON.parse(readFileSync(agentStorePath, 'utf-8'));
229
- }
230
- for (const agentId of task.assignedTo) {
231
- if (agentStore.agents[agentId]) {
232
- agentStore.agents[agentId].status = 'idle';
233
- agentStore.agents[agentId].currentTask = null;
234
- agentStore.agents[agentId].taskCount =
235
- (agentStore.agents[agentId].taskCount || 0) + 1;
236
- }
237
- }
238
- writeFileSync(agentStorePath, JSON.stringify(agentStore, null, 2), 'utf-8');
239
- }
240
- catch {
241
- // Best-effort agent sync
273
+ updateAgents(task.assignedTo, (agent) => {
274
+ if (agent.currentTask === taskId) {
275
+ agent.status = 'idle';
276
+ agent.currentTask = null;
242
277
  }
243
- }
278
+ agent.taskCount = (agent.taskCount || 0) + 1;
279
+ });
244
280
  return {
245
281
  taskId: task.taskId,
246
282
  status: task.status,
@@ -266,6 +302,7 @@ export const taskTools = [
266
302
  status: { type: 'string', description: 'New status' },
267
303
  progress: { type: 'number', description: 'Progress percentage (0-100)' },
268
304
  assignTo: { type: 'array', items: { type: 'string' }, description: 'Agent IDs to assign' },
305
+ result: { type: 'object', description: 'Result data (e.g. the failure of a failed task)' },
269
306
  },
270
307
  required: ['taskId'],
271
308
  },
@@ -284,6 +321,12 @@ export const taskTools = [
284
321
  if (newStatus === 'in_progress' && !task.startedAt) {
285
322
  task.startedAt = new Date().toISOString();
286
323
  }
324
+ // A failed task frees its workers, as task_complete and task_cancel do;
325
+ // otherwise a worker whose run failed stays `busy` forever.
326
+ if (newStatus === 'failed') {
327
+ task.completedAt = new Date().toISOString();
328
+ releaseAgents(task.assignedTo, taskId);
329
+ }
287
330
  }
288
331
  if (typeof input.progress === 'number') {
289
332
  task.progress = Math.min(100, Math.max(0, input.progress));
@@ -291,6 +334,9 @@ export const taskTools = [
291
334
  if (input.assignTo) {
292
335
  task.assignedTo = input.assignTo;
293
336
  }
337
+ if (input.result && typeof input.result === 'object') {
338
+ task.result = input.result;
339
+ }
294
340
  saveTaskStore(store);
295
341
  return {
296
342
  success: true,
@@ -332,41 +378,20 @@ export const taskTools = [
332
378
  return { taskId, error: 'Task not found' };
333
379
  }
334
380
  const previouslyAssigned = [...task.assignedTo];
335
- // Load agent store to sync worker state
336
- const agentStorePath = join(getProjectCwd(), STORAGE_DIR, 'agents', 'store.json');
337
- let agentStore = { agents: {} };
338
- try {
339
- if (existsSync(agentStorePath)) {
340
- agentStore = JSON.parse(readFileSync(agentStorePath, 'utf-8'));
341
- }
342
- }
343
- catch { /* ignore */ }
344
381
  if (input.unassign) {
345
382
  // Revert previously assigned agents to idle
346
- for (const agentId of previouslyAssigned) {
347
- if (agentStore.agents[agentId]) {
348
- agentStore.agents[agentId].status = 'idle';
349
- agentStore.agents[agentId].currentTask = null;
350
- }
351
- }
383
+ releaseAgents(previouslyAssigned, taskId);
352
384
  task.assignedTo = [];
353
385
  }
354
386
  else {
355
387
  const agentIds = input.agentIds || [];
356
388
  // Revert old agents to idle
357
- for (const agentId of previouslyAssigned) {
358
- if (!agentIds.includes(agentId) && agentStore.agents[agentId]) {
359
- agentStore.agents[agentId].status = 'idle';
360
- agentStore.agents[agentId].currentTask = null;
361
- }
362
- }
389
+ releaseAgents(previouslyAssigned.filter((id) => !agentIds.includes(id)), taskId);
363
390
  // Set new agents to active
364
- for (const agentId of agentIds) {
365
- if (agentStore.agents[agentId]) {
366
- agentStore.agents[agentId].status = 'active';
367
- agentStore.agents[agentId].currentTask = taskId;
368
- }
369
- }
391
+ updateAgents(agentIds, (agent) => {
392
+ agent.status = 'busy';
393
+ agent.currentTask = taskId;
394
+ });
370
395
  task.assignedTo = agentIds;
371
396
  // Auto-transition task to in_progress if pending
372
397
  if (task.status === 'pending' && agentIds.length > 0) {
@@ -377,12 +402,6 @@ export const taskTools = [
377
402
  }
378
403
  }
379
404
  saveTaskStore(store);
380
- // Save agent store
381
- const agentDir = join(getProjectCwd(), STORAGE_DIR, 'agents');
382
- if (!existsSync(agentDir)) {
383
- mkdirSync(agentDir, { recursive: true });
384
- }
385
- writeFileSync(agentStorePath, JSON.stringify(agentStore, null, 2), 'utf-8');
386
405
  return {
387
406
  taskId: task.taskId,
388
407
  assignedTo: task.assignedTo,
@@ -421,6 +440,7 @@ export const taskTools = [
421
440
  task.completedAt = new Date().toISOString();
422
441
  task.result = { cancelReason: input.reason || 'Cancelled by user' };
423
442
  saveTaskStore(store);
443
+ releaseAgents(task.assignedTo, taskId);
424
444
  return {
425
445
  success: true,
426
446
  taskId: task.taskId,
@@ -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;