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.
- package/.claude/helpers/auto-memory-hook.mjs +6 -2
- package/.claude/helpers/learning-service.mjs +4 -2
- package/.claude/helpers/metrics-db.mjs +4 -2
- package/node_modules/@claude-flow/codex/package.json +1 -1
- package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js +11 -3
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js.map +1 -1
- package/node_modules/@claude-flow/mcp/package.json +6 -3
- package/node_modules/@claude-flow/plugin-agent-federation/package.json +3 -3
- package/node_modules/@claude-flow/security/package.json +2 -3
- package/package.json +7 -7
- package/v3/@claude-flow/cli/README.md +0 -2
- package/v3/@claude-flow/cli/bin/cli.js +4 -1
- package/v3/@claude-flow/cli/catalog-manifest.json +2 -2
- package/v3/@claude-flow/cli/dist/src/commands/analyze.js +2 -2
- package/v3/@claude-flow/cli/dist/src/commands/config.js +5 -7
- package/v3/@claude-flow/cli/dist/src/commands/hive-mind-worker.d.ts +88 -0
- package/v3/@claude-flow/cli/dist/src/commands/hive-mind-worker.js +242 -0
- package/v3/@claude-flow/cli/dist/src/commands/hive-mind.js +24 -41
- package/v3/@claude-flow/cli/dist/src/commands/hooks.js +15 -5
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +89 -27
- package/v3/@claude-flow/cli/dist/src/commands/metaharness.js +1 -0
- package/v3/@claude-flow/cli/dist/src/commands/security.js +113 -55
- package/v3/@claude-flow/cli/dist/src/commands/session.js +6 -6
- package/v3/@claude-flow/cli/dist/src/commands/task.js +5 -4
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +4 -2
- package/v3/@claude-flow/cli/dist/src/mcp-server.js +10 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.js +29 -62
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.d.ts +0 -4
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +139 -37
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +89 -35
- package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.d.ts +8 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/policy-enforcer.js +80 -8
- package/v3/@claude-flow/cli/dist/src/mcp-tools/session-tools.js +34 -8
- package/v3/@claude-flow/cli/dist/src/mcp-tools/task-tools.js +73 -53
- package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.d.ts +13 -0
- package/v3/@claude-flow/cli/dist/src/memory/feedback-patterns.js +14 -0
- package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.d.ts +3 -0
- package/v3/@claude-flow/cli/dist/src/memory/live-memory-row.js +5 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +11 -20
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +348 -134
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +9 -1
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +41 -31
- package/v3/@claude-flow/cli/dist/src/ruvector/graph-analyzer.js +4 -1
- package/v3/@claude-flow/cli/dist/src/services/config-file-manager.js +30 -8
- package/v3/@claude-flow/cli/dist/src/services/memory-backup.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/services/memory-backup.js +67 -27
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +2 -2
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +23 -14
- package/v3/@claude-flow/cli/package.json +9 -9
- package/v3/@claude-flow/guidance/package.json +5 -6
- package/v3/@claude-flow/shared/package.json +6 -3
|
@@ -58,7 +58,6 @@
|
|
|
58
58
|
*/
|
|
59
59
|
import * as fs from 'fs';
|
|
60
60
|
import * as path from 'path';
|
|
61
|
-
import * as os from 'os';
|
|
62
61
|
export function isPolicyEnforcementEnabled(env = process.env) {
|
|
63
62
|
const v = env.RUFLO_MCP_ENFORCE_POLICY;
|
|
64
63
|
return v === '1' || (v ?? '').toLowerCase() === 'true';
|
|
@@ -118,11 +117,18 @@ let auditLogPathOverride = null;
|
|
|
118
117
|
export function setAuditLogPathForTesting(p) {
|
|
119
118
|
auditLogPathOverride = p;
|
|
120
119
|
}
|
|
121
|
-
function
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
120
|
+
export function getAuditLogPath(options = {}) {
|
|
121
|
+
if (auditLogPathOverride !== null)
|
|
122
|
+
return auditLogPathOverride;
|
|
123
|
+
if (options.auditLogPath !== undefined &&
|
|
124
|
+
(typeof options.auditLogPath !== 'string' || options.auditLogPath.trim().length === 0)) {
|
|
125
|
+
throw new Error('auditLogPath must be a non-empty string');
|
|
126
|
+
}
|
|
127
|
+
// Precedence: explicit caller/policy destination, environment, project default.
|
|
128
|
+
const configured = options.auditLogPath ?? process.env.RUFLO_MCP_AUDIT_LOG_PATH;
|
|
129
|
+
return configured
|
|
130
|
+
? path.resolve(process.cwd(), configured)
|
|
131
|
+
: path.join(process.cwd(), '.claude-flow', 'logs', 'mcp-audit.jsonl');
|
|
126
132
|
}
|
|
127
133
|
/**
|
|
128
134
|
* Appends one JSONL audit record. Returns `true` if `policy.auditLog` is not
|
|
@@ -135,8 +141,74 @@ export function appendAuditLog(policy, entry) {
|
|
|
135
141
|
if (!policy.auditLog)
|
|
136
142
|
return true;
|
|
137
143
|
try {
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
const maxBytes = policy.auditLogMaxBytes ?? 10 * 1024 * 1024;
|
|
145
|
+
const maxFiles = policy.auditLogMaxFiles ?? 5;
|
|
146
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 ||
|
|
147
|
+
!Number.isSafeInteger(maxFiles) || maxFiles < 1 || maxFiles > 100)
|
|
148
|
+
return false;
|
|
149
|
+
const line = `${JSON.stringify({ ...entry, projectPath: path.resolve(process.cwd()) })}\n`;
|
|
150
|
+
const bytes = Buffer.byteLength(line, 'utf8');
|
|
151
|
+
if (bytes > maxBytes)
|
|
152
|
+
return false;
|
|
153
|
+
const logPath = getAuditLogPath(policy);
|
|
154
|
+
fs.mkdirSync(path.dirname(logPath), { recursive: true, mode: 0o700 });
|
|
155
|
+
// All writers of a configured path serialize rotation AND append. On
|
|
156
|
+
// contention (or a stale lock after a crash), deny rather than lose audit
|
|
157
|
+
// evidence by racing a rename. Only the lock owner removes its lock.
|
|
158
|
+
const lockPath = `${logPath}.lock`;
|
|
159
|
+
const lock = fs.openSync(lockPath, 'wx', 0o600);
|
|
160
|
+
try {
|
|
161
|
+
let size = 0;
|
|
162
|
+
try {
|
|
163
|
+
const stat = fs.lstatSync(logPath);
|
|
164
|
+
if (!stat.isFile())
|
|
165
|
+
throw new Error('Audit log is not a regular file');
|
|
166
|
+
size = stat.size;
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (error.code !== 'ENOENT')
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
if (size + bytes > maxBytes) {
|
|
173
|
+
// Validate the entire chain before moving or expiring any evidence.
|
|
174
|
+
for (let index = 1; index <= maxFiles; index++) {
|
|
175
|
+
try {
|
|
176
|
+
if (!fs.lstatSync(`${logPath}.${index}`).isFile())
|
|
177
|
+
throw new Error('Audit segment is not a regular file');
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
if (error.code !== 'ENOENT')
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
for (let index = maxFiles; index >= 1; index--) {
|
|
185
|
+
const source = index === 1 ? logPath : `${logPath}.${index - 1}`;
|
|
186
|
+
const target = `${logPath}.${index}`;
|
|
187
|
+
if (index === maxFiles) {
|
|
188
|
+
try {
|
|
189
|
+
fs.unlinkSync(target);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
if (error.code !== 'ENOENT')
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
fs.renameSync(source, target);
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
if (error.code !== 'ENOENT')
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
fs.appendFileSync(logPath, line, { encoding: 'utf8', mode: 0o600 });
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
fs.closeSync(lock);
|
|
210
|
+
fs.unlinkSync(lockPath);
|
|
211
|
+
}
|
|
140
212
|
}
|
|
141
213
|
catch {
|
|
142
214
|
return false;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Tool definitions for session management with file persistence.
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync, readFileSync, readdirSync, unlinkSync, statSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { randomUUID } from 'node:crypto';
|
|
7
8
|
import { join } from 'node:path';
|
|
8
9
|
import { getProjectCwd } from './types.js';
|
|
9
10
|
import { mkdirRestricted, readFileMaybeEncrypted, writeFileRestricted, } from '../fs-secure.js';
|
|
@@ -126,7 +127,7 @@ export const sessionTools = [
|
|
|
126
127
|
if (!v.valid)
|
|
127
128
|
return { success: false, error: v.error };
|
|
128
129
|
}
|
|
129
|
-
const sessionId = `session-${Date.now()}-${
|
|
130
|
+
const sessionId = `session-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
130
131
|
// Load related data based on options
|
|
131
132
|
const data = loadRelatedStores({
|
|
132
133
|
includeMemory: input.includeMemory,
|
|
@@ -170,6 +171,9 @@ export const sessionTools = [
|
|
|
170
171
|
properties: {
|
|
171
172
|
sessionId: { type: 'string', description: 'Session ID to restore' },
|
|
172
173
|
name: { type: 'string', description: 'Session name to restore' },
|
|
174
|
+
restoreMemory: { type: 'boolean', description: 'Restore memory (default true)' },
|
|
175
|
+
restoreTasks: { type: 'boolean', description: 'Restore tasks (default true)' },
|
|
176
|
+
restoreAgents: { type: 'boolean', description: 'Restore agents (default true)' },
|
|
173
177
|
},
|
|
174
178
|
},
|
|
175
179
|
handler: async (input) => {
|
|
@@ -205,7 +209,7 @@ export const sessionTools = [
|
|
|
205
209
|
if (session) {
|
|
206
210
|
// Restore data to respective stores (legacy JSON for backward compat).
|
|
207
211
|
// audit_1776853149979: tighten perms on the restored stores too.
|
|
208
|
-
if (session.data?.memory) {
|
|
212
|
+
if (input.restoreMemory !== false && session.data?.memory) {
|
|
209
213
|
const memoryDir = join(getProjectCwd(), STORAGE_DIR, 'memory');
|
|
210
214
|
if (!existsSync(memoryDir))
|
|
211
215
|
mkdirRestricted(memoryDir);
|
|
@@ -233,13 +237,13 @@ export const sessionTools = [
|
|
|
233
237
|
// Legacy JSON restore is the fallback -- sql.js import may not be available
|
|
234
238
|
}
|
|
235
239
|
}
|
|
236
|
-
if (session.data?.tasks) {
|
|
240
|
+
if (input.restoreTasks !== false && session.data?.tasks) {
|
|
237
241
|
const taskDir = join(getProjectCwd(), STORAGE_DIR, 'tasks');
|
|
238
242
|
if (!existsSync(taskDir))
|
|
239
243
|
mkdirRestricted(taskDir);
|
|
240
244
|
writeFileRestricted(join(taskDir, 'store.json'), JSON.stringify(session.data.tasks, null, 2));
|
|
241
245
|
}
|
|
242
|
-
if (session.data?.agents) {
|
|
246
|
+
if (input.restoreAgents !== false && session.data?.agents) {
|
|
243
247
|
const agentDir = join(getProjectCwd(), STORAGE_DIR, 'agents');
|
|
244
248
|
if (!existsSync(agentDir))
|
|
245
249
|
mkdirRestricted(agentDir);
|
|
@@ -249,6 +253,11 @@ export const sessionTools = [
|
|
|
249
253
|
sessionId: session.sessionId,
|
|
250
254
|
name: session.name,
|
|
251
255
|
restored: true,
|
|
256
|
+
restoredComponents: {
|
|
257
|
+
memory: input.restoreMemory !== false && !!session.data?.memory,
|
|
258
|
+
tasks: input.restoreTasks !== false && !!session.data?.tasks,
|
|
259
|
+
agents: input.restoreAgents !== false && !!session.data?.agents,
|
|
260
|
+
},
|
|
252
261
|
restoredAt: new Date().toISOString(),
|
|
253
262
|
stats: session.stats,
|
|
254
263
|
};
|
|
@@ -437,7 +446,7 @@ export const sessionTools = [
|
|
|
437
446
|
properties: {
|
|
438
447
|
sessionId: { type: 'string', description: 'Session ID to export' },
|
|
439
448
|
outputPath: { type: 'string', description: 'File path to write the export to (optional)' },
|
|
440
|
-
includeMemory: { type: 'boolean', description: 'Include the memory snapshot (
|
|
449
|
+
includeMemory: { type: 'boolean', description: 'Include the memory snapshot (default true)' },
|
|
441
450
|
},
|
|
442
451
|
required: ['sessionId'],
|
|
443
452
|
},
|
|
@@ -449,6 +458,14 @@ export const sessionTools = [
|
|
|
449
458
|
const session = loadSession(sessionId);
|
|
450
459
|
if (!session)
|
|
451
460
|
return { sessionId, error: 'Session not found' };
|
|
461
|
+
// Apply the explicit exclusion before either returning or writing the
|
|
462
|
+
// snapshot. The saved source remains intact for a later full restore.
|
|
463
|
+
if (input.includeMemory === false) {
|
|
464
|
+
if (session.data)
|
|
465
|
+
delete session.data.memory;
|
|
466
|
+
session.stats = { ...session.stats, memoryEntries: 0, totalSize: 0 };
|
|
467
|
+
session.stats.totalSize = Buffer.byteLength(JSON.stringify(session), 'utf-8');
|
|
468
|
+
}
|
|
452
469
|
let path = null;
|
|
453
470
|
const outputPath = input.outputPath ? String(input.outputPath) : null;
|
|
454
471
|
if (outputPath) {
|
|
@@ -474,7 +491,7 @@ export const sessionTools = [
|
|
|
474
491
|
properties: {
|
|
475
492
|
inputPath: { type: 'string', description: 'Path to the session JSON file to import' },
|
|
476
493
|
name: { type: 'string', description: 'Override the imported session name' },
|
|
477
|
-
activate: { type: 'boolean', description: '
|
|
494
|
+
activate: { type: 'boolean', description: 'Restore the imported session into the active stores' },
|
|
478
495
|
},
|
|
479
496
|
required: ['inputPath'],
|
|
480
497
|
},
|
|
@@ -489,7 +506,7 @@ export const sessionTools = [
|
|
|
489
506
|
catch (e) {
|
|
490
507
|
return { error: `Invalid session JSON: ${e.message}` };
|
|
491
508
|
}
|
|
492
|
-
const newId = `session-${Date.now()}-${
|
|
509
|
+
const newId = `session-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
493
510
|
const stats = parsed.stats || { tasks: 0, agents: 0, memoryEntries: 0, totalSize: 0 };
|
|
494
511
|
const session = {
|
|
495
512
|
sessionId: newId,
|
|
@@ -500,6 +517,15 @@ export const sessionTools = [
|
|
|
500
517
|
data: parsed.data,
|
|
501
518
|
};
|
|
502
519
|
saveSession(session);
|
|
520
|
+
let activated = false;
|
|
521
|
+
if (input.activate === true) {
|
|
522
|
+
const restore = sessionTools.find(tool => tool.name === 'session_restore');
|
|
523
|
+
const result = await restore.handler({ sessionId: newId });
|
|
524
|
+
if (result.restored !== true) {
|
|
525
|
+
return { sessionId: newId, activated: false, error: result.error || 'Imported session could not be restored' };
|
|
526
|
+
}
|
|
527
|
+
activated = true;
|
|
528
|
+
}
|
|
503
529
|
return {
|
|
504
530
|
sessionId: newId,
|
|
505
531
|
name: session.name,
|
|
@@ -509,7 +535,7 @@ export const sessionTools = [
|
|
|
509
535
|
tasksImported: stats.tasks,
|
|
510
536
|
memoryEntriesImported: stats.memoryEntries,
|
|
511
537
|
},
|
|
512
|
-
activated
|
|
538
|
+
activated,
|
|
513
539
|
};
|
|
514
540
|
},
|
|
515
541
|
},
|
|
@@ -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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
|
@@ -36,6 +36,8 @@ export declare function shouldDisableNativeBridge(platform?: NodeJS.Platform, en
|
|
|
36
36
|
export declare function shouldSuppressInitLog(msg: string): boolean;
|
|
37
37
|
/** Test seam: forget cached registries so a test can exercise a fresh open. */
|
|
38
38
|
export declare function _resetRegistryCacheForTest(): void;
|
|
39
|
+
/** Inject registry construction without requiring the optional native package in tests. */
|
|
40
|
+
export declare function __setMemoryBridgeRegistryFactoryForTests(factory: (() => any) | null): void;
|
|
39
41
|
/** #3196: the sibling store AgentDB owns next to a given sql.js database. */
|
|
40
42
|
export declare function siblingAgentDbPath(dbPath: string): string | null;
|
|
41
43
|
/**
|
|
@@ -271,6 +273,7 @@ export declare function bridgeAddEmbedding(id: string, embedding: number[], entr
|
|
|
271
273
|
}, dbPath?: string): Promise<boolean | null>;
|
|
272
274
|
/**
|
|
273
275
|
* Get a named controller from AgentDB v3 via ControllerRegistry.
|
|
276
|
+
* This borrowed handle must not be retained across shutdownBridge().
|
|
274
277
|
* Returns null if unavailable.
|
|
275
278
|
*/
|
|
276
279
|
export declare function bridgeGetController(name: string, dbPath?: string): Promise<any | null>;
|
|
@@ -292,13 +295,15 @@ export declare function bridgeListControllers(dbPath?: string): Promise<Array<{
|
|
|
292
295
|
export declare function isBridgeAvailable(dbPath?: string): Promise<boolean>;
|
|
293
296
|
/**
|
|
294
297
|
* Get the ControllerRegistry instance (for advanced consumers).
|
|
298
|
+
* This is a borrowed handle, valid only until shutdownBridge(). Callers must
|
|
299
|
+
* finish using it before requesting shutdown; bridge operations own their own
|
|
300
|
+
* leases. Prefer bridgeListControllers() for status inspection.
|
|
295
301
|
*/
|
|
296
302
|
export declare function getControllerRegistry(dbPath?: string): Promise<any | null>;
|
|
297
303
|
/**
|
|
298
304
|
* Why the bridge last declined a write, or null when it has not.
|
|
299
305
|
*
|
|
300
|
-
*
|
|
301
|
-
* initialised fine can still fail every write — a schema mismatch throws
|
|
306
|
+
* A bridge that initialised fine can still fail every write — a schema mismatch throws
|
|
302
307
|
* per-operation while the registry stays healthy — and that case is exactly
|
|
303
308
|
* the one worth reporting, since the caller then demotes to a fallback whose
|
|
304
309
|
* error message describes something else entirely.
|
|
@@ -306,7 +311,7 @@ export declare function getControllerRegistry(dbPath?: string): Promise<any | nu
|
|
|
306
311
|
* Callers that surface a degraded-path error should include this so the
|
|
307
312
|
* operator learns the cause instead of only the symptom.
|
|
308
313
|
*/
|
|
309
|
-
export declare function getBridgeFailureReason(): string | null;
|
|
314
|
+
export declare function getBridgeFailureReason(dbPath?: string): string | null;
|
|
310
315
|
/**
|
|
311
316
|
* Install a pre-initialized registry for deterministic bridge tests.
|
|
312
317
|
*
|
|
@@ -321,10 +326,8 @@ export declare function __setMemoryBridgeRegistryForTests(registry: any | null):
|
|
|
321
326
|
* Shutdown the bridge and release resources.
|
|
322
327
|
*
|
|
323
328
|
* The cached state is cleared unconditionally. Previously the reset lived
|
|
324
|
-
* inside `if (registryInstance)`, so it could not clear a FAILED init
|
|
325
|
-
*
|
|
326
|
-
* precisely when init failed. A process that latched `bridgeAvailable = false`
|
|
327
|
-
* therefore had no recovery path short of a restart.
|
|
329
|
+
* inside `if (registryInstance)`, so it could not clear a FAILED init. This
|
|
330
|
+
* also clears per-path failed promises, allowing a later retry.
|
|
328
331
|
*/
|
|
329
332
|
export declare function shutdownBridge(): Promise<void>;
|
|
330
333
|
/**
|
|
@@ -589,15 +592,7 @@ export declare function bridgeHierarchicalRecall(params: {
|
|
|
589
592
|
topK?: number;
|
|
590
593
|
includeExpired?: boolean;
|
|
591
594
|
}): Promise<any>;
|
|
592
|
-
/**
|
|
593
|
-
* Run memory consolidation.
|
|
594
|
-
*
|
|
595
|
-
* Real MemoryConsolidation API (agentdb alpha.10+):
|
|
596
|
-
* consolidate() → Promise<ConsolidationReport>
|
|
597
|
-
* ConsolidationReport = { episodicProcessed, semanticCreated, memoriesForgotten, ... }
|
|
598
|
-
* Stub API (fallback):
|
|
599
|
-
* consolidate() → { promoted, pruned, timestamp }
|
|
600
|
-
*/
|
|
595
|
+
/** Run consolidation when a real controller is available. */
|
|
601
596
|
export declare function bridgeConsolidate(params: {
|
|
602
597
|
minAge?: number;
|
|
603
598
|
maxEntries?: number;
|
|
@@ -612,10 +607,6 @@ export declare function bridgeBatchOperation(params: {
|
|
|
612
607
|
operation: string;
|
|
613
608
|
entries: any[];
|
|
614
609
|
}): Promise<any>;
|
|
615
|
-
/**
|
|
616
|
-
* Synthesize context from memories.
|
|
617
|
-
* ContextSynthesizer.synthesize is a static method that takes MemoryPattern[] (not a string).
|
|
618
|
-
*/
|
|
619
610
|
export declare function bridgeContextSynthesize(params: {
|
|
620
611
|
query: string;
|
|
621
612
|
maxEntries?: number;
|