@claude-flow/cli 3.55.0 → 3.56.1
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/helpers.manifest.json +2 -2
- package/.claude/settings.json +9 -9
- package/catalog-manifest.json +2 -2
- package/dist/src/auth/client.js +40 -24
- package/dist/src/commands/agent.js +12 -0
- package/dist/src/commands/analyze.js +18 -14
- package/dist/src/commands/config.js +1 -1
- package/dist/src/commands/daemon.js +11 -19
- package/dist/src/commands/doctor.js +65 -58
- package/dist/src/commands/memory-backup.d.ts +1 -1
- package/dist/src/commands/memory-backup.js +23 -9
- package/dist/src/commands/memory.d.ts +0 -4
- package/dist/src/commands/memory.js +10 -2
- package/dist/src/commands/plugins.js +13 -1
- package/dist/src/commands/task.js +12 -0
- package/dist/src/init/settings-generator.js +3 -3
- package/dist/src/mcp-tools/agent-execute-core.js +244 -188
- package/dist/src/mcp-tools/agent-tools.js +15 -16
- package/dist/src/mcp-tools/agentdb-tools.js +22 -3
- package/dist/src/mcp-tools/claims-tools.js +3 -11
- package/dist/src/mcp-tools/config-tools.js +118 -13
- package/dist/src/mcp-tools/hooks-tools.js +47 -2
- package/dist/src/mcp-tools/memory-tools.js +29 -10
- package/dist/src/mcp-tools/record-store.d.ts +3 -0
- package/dist/src/mcp-tools/record-store.js +21 -0
- package/dist/src/mcp-tools/session-tools.js +47 -13
- package/dist/src/mcp-tools/task-tools.js +5 -11
- package/dist/src/mcp-tools/workflow-tools.js +78 -29
- package/dist/src/memory/append-conditions.d.ts +28 -0
- package/dist/src/memory/append-conditions.js +56 -0
- package/dist/src/memory/graph-edge-writer.d.ts +9 -0
- package/dist/src/memory/graph-edge-writer.js +90 -0
- package/dist/src/memory/memory-bridge.d.ts +4 -0
- package/dist/src/memory/memory-bridge.js +50 -9
- package/dist/src/memory/memory-initializer.d.ts +4 -0
- package/dist/src/memory/memory-initializer.js +100 -62
- package/dist/src/memory/memory-root.d.ts +2 -0
- package/dist/src/memory/memory-root.js +28 -0
- package/dist/src/memory/sona-optimizer.d.ts +2 -0
- package/dist/src/memory/sona-optimizer.js +4 -0
- package/dist/src/parser.js +7 -0
- package/dist/src/plugins/store/discovery.d.ts +6 -0
- package/dist/src/plugins/store/discovery.js +7 -3
- package/dist/src/ruvector/neural-router.js +7 -2
- package/dist/src/services/claim-service.js +39 -18
- package/dist/src/services/claims-store.d.ts +3 -0
- package/dist/src/services/claims-store.js +40 -0
- package/dist/src/services/config-file-manager.d.ts +2 -2
- package/dist/src/services/config-file-manager.js +22 -3
- package/dist/src/services/distill-tuning.js +17 -10
- package/dist/src/services/memory-backup.d.ts +7 -0
- package/dist/src/services/memory-backup.js +29 -2
- package/dist/src/services/memory-distillation.js +2 -2
- package/dist/src/services/policy-ledger-anchor.d.ts +8 -0
- package/dist/src/services/policy-ledger-anchor.js +18 -3
- package/dist/src/services/policy-runtime.js +2 -2
- package/dist/src/services/worker-daemon.d.ts +4 -2
- package/dist/src/services/worker-daemon.js +14 -6
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.d.ts +2 -1
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.js +48 -16
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/path-validator.d.ts +2 -2
- package/node_modules/@claude-flow/security/dist/path-validator.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/path-validator.js +15 -4
- package/node_modules/@claude-flow/security/dist/path-validator.js.map +1 -1
- package/node_modules/@claude-flow/security/package.json +1 -1
- package/package.json +4 -4
- package/plugins/ruflo-metaharness/CHANGELOG.md +48 -0
- package/.claude/agents/tmp.json +0 -0
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
|
+
import { readRecordStore } from './record-store.js';
|
|
9
10
|
import { getProjectCwd } from './types.js';
|
|
10
11
|
import { validateIdentifier, validateText, validateAgentSpawn } from './validate-input.js';
|
|
11
12
|
import { executeAgentTask } from './agent-execute-core.js';
|
|
@@ -32,17 +33,7 @@ function ensureAgentDir() {
|
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
function loadAgentStore() {
|
|
35
|
-
|
|
36
|
-
const path = getAgentPath();
|
|
37
|
-
if (existsSync(path)) {
|
|
38
|
-
const data = readFileSync(path, 'utf-8');
|
|
39
|
-
return JSON.parse(data);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
// Return empty store on error
|
|
44
|
-
}
|
|
45
|
-
return { agents: {}, version: '3.0.0' };
|
|
36
|
+
return readRecordStore(getAgentPath(), 'agents', () => ({ agents: {}, version: '3.0.0' }));
|
|
46
37
|
}
|
|
47
38
|
function saveAgentStore(store) {
|
|
48
39
|
ensureAgentDir();
|
|
@@ -247,7 +238,6 @@ export const agentTools = [
|
|
|
247
238
|
if (!validation.valid) {
|
|
248
239
|
return { success: false, error: `Input validation failed: ${validation.errors.join('; ')}` };
|
|
249
240
|
}
|
|
250
|
-
const store = loadAgentStore();
|
|
251
241
|
const agentId = input.agentId || `agent-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
252
242
|
const agentType = input.agentType;
|
|
253
243
|
const config = input.config || {};
|
|
@@ -285,6 +275,7 @@ export const agentTools = [
|
|
|
285
275
|
: routingResult.provider ? { provider: routingResult.provider } : {}),
|
|
286
276
|
...(routingResult.openrouterModel ? { openrouterModel: routingResult.openrouterModel } : {}),
|
|
287
277
|
};
|
|
278
|
+
const store = loadAgentStore();
|
|
288
279
|
store.agents[agentId] = agent;
|
|
289
280
|
saveAgentStore(store);
|
|
290
281
|
// #2085 — also push to the swarm store's agents array so that
|
|
@@ -337,8 +328,13 @@ export const agentTools = [
|
|
|
337
328
|
memoryBranch = br.branchPath;
|
|
338
329
|
agent.memoryBranch = br.branchPath;
|
|
339
330
|
agent.memoryBase = br.basePath;
|
|
340
|
-
|
|
341
|
-
|
|
331
|
+
const latest = loadAgentStore();
|
|
332
|
+
const current = latest.agents[agentId];
|
|
333
|
+
if (current && current.status !== 'terminated') {
|
|
334
|
+
current.memoryBranch = br.branchPath;
|
|
335
|
+
current.memoryBase = br.basePath;
|
|
336
|
+
saveAgentStore(latest);
|
|
337
|
+
}
|
|
342
338
|
}
|
|
343
339
|
// else: degraded (agenticow missing / kill-switched) — agent stands
|
|
344
340
|
// without an isolated branch; callers see no memoryBranch field.
|
|
@@ -629,7 +625,10 @@ export const agentTools = [
|
|
|
629
625
|
};
|
|
630
626
|
}
|
|
631
627
|
if (action === 'scale') {
|
|
632
|
-
const targetSize = input.targetSize
|
|
628
|
+
const targetSize = input.targetSize === undefined ? 5 : input.targetSize;
|
|
629
|
+
if (typeof targetSize !== 'number' || !Number.isSafeInteger(targetSize) || targetSize < 0) {
|
|
630
|
+
return { action, error: 'targetSize must be a non-negative safe integer' };
|
|
631
|
+
}
|
|
633
632
|
const agentType = input.agentType || 'worker';
|
|
634
633
|
const currentSize = agents.filter(a => a.agentType === agentType).length;
|
|
635
634
|
const delta = targetSize - currentSize;
|
|
@@ -663,7 +662,7 @@ export const agentTools = [
|
|
|
663
662
|
agentType,
|
|
664
663
|
previousSize: currentSize,
|
|
665
664
|
targetSize,
|
|
666
|
-
newSize: currentSize +
|
|
665
|
+
newSize: currentSize + added.length - removed.length,
|
|
667
666
|
added,
|
|
668
667
|
removed,
|
|
669
668
|
};
|
|
@@ -61,6 +61,25 @@ async function getGraphEdgeWriter() {
|
|
|
61
61
|
graphEdgeWriterMod = await import('../memory/graph-edge-writer.js');
|
|
62
62
|
return graphEdgeWriterMod;
|
|
63
63
|
}
|
|
64
|
+
// #2889 — a null graph_edges DB handle can mean several unrelated things
|
|
65
|
+
// (missing native module, permissions, genuine corruption, or an RFE1
|
|
66
|
+
// encrypted-at-rest store). Recommending `memory init` for the encrypted
|
|
67
|
+
// case is actively harmful — it creates a fresh EMPTY store instead of
|
|
68
|
+
// using the existing encrypted data — so distinguish it before choosing
|
|
69
|
+
// a hint.
|
|
70
|
+
async function bridgeUnavailableResult() {
|
|
71
|
+
const { isBridgeDbEncryptedAtRest } = await getGraphEdgeWriter();
|
|
72
|
+
if (isBridgeDbEncryptedAtRest()) {
|
|
73
|
+
return {
|
|
74
|
+
error: 'graph_edges DB is encrypted at rest (RFE1) — the native bridge cannot open it directly.',
|
|
75
|
+
hint: 'Your existing data is intact; this is a known limitation, not corruption. Do NOT run `memory init` — see https://github.com/ruvnet/ruflo/issues/2889.',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
error: 'graph_edges DB unavailable (sql.js could not load)',
|
|
80
|
+
hint: 'Check Node version + try `ruflo memory init` to initialize manually.',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
64
83
|
let memInitMod = null;
|
|
65
84
|
async function getMemInit() {
|
|
66
85
|
if (!memInitMod)
|
|
@@ -1152,7 +1171,7 @@ export const agentdbGraphQuery = {
|
|
|
1152
1171
|
// fresh environments work without a pre-existing memory init.
|
|
1153
1172
|
const db = await getBridgeDb(undefined, { createIfMissing: true });
|
|
1154
1173
|
if (!db)
|
|
1155
|
-
return { success: false,
|
|
1174
|
+
return { success: false, ...(await bridgeUnavailableResult()), mode, nodeId };
|
|
1156
1175
|
// Load all rows with embedding_ref and score by cosine.
|
|
1157
1176
|
// better-sqlite3 API — `db.exec(sql, params)` (sql.js) silently
|
|
1158
1177
|
// throws "datatype mismatch" because exec ignores params, so `?`
|
|
@@ -1194,7 +1213,7 @@ export const agentdbGraphQuery = {
|
|
|
1194
1213
|
// fresh environments work without a pre-existing memory init.
|
|
1195
1214
|
const db = await getBridgeDb(undefined, { createIfMissing: true });
|
|
1196
1215
|
if (!db)
|
|
1197
|
-
return { success: false,
|
|
1216
|
+
return { success: false, ...(await bridgeUnavailableResult()), mode, nodeId };
|
|
1198
1217
|
// better-sqlite3 API — see semantic-mode comment above.
|
|
1199
1218
|
const edges = db.prepare(`SELECT source_id, target_id, weight FROM graph_edges LIMIT ?`).raw().all(budget.maxNodesVisited);
|
|
1200
1219
|
if (edges.length === 0) {
|
|
@@ -1380,7 +1399,7 @@ export const agentdbGraphPathfinder = {
|
|
|
1380
1399
|
// #2246 fix: lazy-create memory.db on first pathfinder call.
|
|
1381
1400
|
const db = await getBridgeDb(undefined, { createIfMissing: true });
|
|
1382
1401
|
if (!db)
|
|
1383
|
-
return { success: false,
|
|
1402
|
+
return { success: false, ...(await bridgeUnavailableResult()), seedNodeId };
|
|
1384
1403
|
const colsSql = algorithm === 'witness-chain-divergence'
|
|
1385
1404
|
? 'source_id, target_id, weight, last_reinforced, witness_id'
|
|
1386
1405
|
: algorithm === 'temporal-centrality'
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module @claude-flow/cli/mcp-tools/claims
|
|
8
8
|
*/
|
|
9
|
+
import { readClaimsStore } from '../services/claims-store.js';
|
|
9
10
|
import { validateIdentifier, validateText } from './validate-input.js';
|
|
10
11
|
// File-based persistence
|
|
11
|
-
import { existsSync,
|
|
12
|
+
import { existsSync, writeFileSync, mkdirSync } from 'fs';
|
|
12
13
|
import { join, resolve } from 'path';
|
|
13
14
|
const CLAIMS_DIR = '.claude-flow/claims';
|
|
14
15
|
const CLAIMS_FILE = 'claims.json';
|
|
@@ -22,16 +23,7 @@ function ensureClaimsDir() {
|
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
function loadClaims() {
|
|
25
|
-
|
|
26
|
-
const path = getClaimsPath();
|
|
27
|
-
if (existsSync(path)) {
|
|
28
|
-
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
// Return empty store on error
|
|
33
|
-
}
|
|
34
|
-
return { claims: {}, stealable: {}, contests: {} };
|
|
26
|
+
return readClaimsStore(getClaimsPath());
|
|
35
27
|
}
|
|
36
28
|
function saveClaims(store) {
|
|
37
29
|
ensureClaimsDir();
|
|
@@ -35,12 +35,110 @@ function ensureConfigDir() {
|
|
|
35
35
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
// Plain CLI files and legacy MCP envelopes share the same path. Keep the
|
|
39
|
+
// original plain document so MCP writes remain readable by the CLI.
|
|
40
|
+
const plainDocuments = new WeakMap();
|
|
41
|
+
const STORE_METADATA = new Set(['scopes', 'version', 'updatedAt']);
|
|
42
|
+
function isRecord(value) {
|
|
43
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
44
|
+
}
|
|
45
|
+
function flattenValues(document) {
|
|
46
|
+
const values = {};
|
|
47
|
+
function visit(value, key) {
|
|
48
|
+
if (isRecord(value) && Object.keys(value).length > 0) {
|
|
49
|
+
for (const [child, entry] of Object.entries(value))
|
|
50
|
+
visit(entry, `${key}.${child}`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
Object.defineProperty(values, key, { value, enumerable: true, configurable: true, writable: true });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
for (const [key, value] of Object.entries(document)) {
|
|
57
|
+
if (!STORE_METADATA.has(key))
|
|
58
|
+
visit(value, key);
|
|
59
|
+
}
|
|
60
|
+
// CLI reads literal dotted keys before traversing nested objects.
|
|
61
|
+
for (const [key, value] of Object.entries(document)) {
|
|
62
|
+
if (key.includes('.'))
|
|
63
|
+
Object.defineProperty(values, key, { value, enumerable: true, configurable: true, writable: true });
|
|
64
|
+
}
|
|
65
|
+
return values;
|
|
66
|
+
}
|
|
67
|
+
function readDefaultValue(store, key) {
|
|
68
|
+
const document = plainDocuments.get(store);
|
|
69
|
+
if (!document)
|
|
70
|
+
return store.values[key];
|
|
71
|
+
return Object.hasOwn(document, key) ? document[key] : getNestedValue(document, key);
|
|
72
|
+
}
|
|
73
|
+
function writeDefaultValue(store, key, value) {
|
|
74
|
+
const document = plainDocuments.get(store);
|
|
75
|
+
if (!document) {
|
|
76
|
+
store.values[key] = value;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Keep existing literal dotted keys; otherwise use the CLI's nested shape.
|
|
80
|
+
if (Object.hasOwn(document, key)) {
|
|
81
|
+
// Reuse the same segment validation as nested writes.
|
|
82
|
+
setNestedValue({}, key, value);
|
|
83
|
+
document[key] = value;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
setNestedValue(document, key, value);
|
|
87
|
+
}
|
|
88
|
+
store.values = flattenValues(document);
|
|
89
|
+
}
|
|
90
|
+
function deleteDefaultValue(store, key) {
|
|
91
|
+
const document = plainDocuments.get(store);
|
|
92
|
+
if (!document)
|
|
93
|
+
return Object.hasOwn(store.values, key) && delete store.values[key];
|
|
94
|
+
let target = document;
|
|
95
|
+
let leaf = key;
|
|
96
|
+
if (!Object.hasOwn(document, key)) {
|
|
97
|
+
const parts = key.split('.');
|
|
98
|
+
leaf = parts.pop();
|
|
99
|
+
for (const part of parts) {
|
|
100
|
+
if (!Object.hasOwn(target, part) || !isRecord(target[part]))
|
|
101
|
+
return false;
|
|
102
|
+
target = target[part];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const deleted = Object.hasOwn(target, leaf) && delete target[leaf];
|
|
106
|
+
store.values = flattenValues(document);
|
|
107
|
+
return deleted;
|
|
108
|
+
}
|
|
109
|
+
function replaceDefaultValues(store, values) {
|
|
110
|
+
const document = plainDocuments.get(store);
|
|
111
|
+
if (!document) {
|
|
112
|
+
store.values = values;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
for (const key of Object.keys(document)) {
|
|
116
|
+
if (!STORE_METADATA.has(key))
|
|
117
|
+
delete document[key];
|
|
118
|
+
}
|
|
119
|
+
for (const [key, value] of Object.entries(values))
|
|
120
|
+
writeDefaultValue(store, key, value);
|
|
121
|
+
}
|
|
38
122
|
function loadConfigStore() {
|
|
39
123
|
try {
|
|
40
124
|
const path = getConfigPath();
|
|
41
125
|
if (existsSync(path)) {
|
|
42
126
|
const data = readFileSync(path, 'utf-8');
|
|
43
|
-
|
|
127
|
+
const document = JSON.parse(data);
|
|
128
|
+
const isEnvelope = isRecord(document) && isRecord(document.values)
|
|
129
|
+
&& isRecord(document.scopes) && typeof document.version === 'string'
|
|
130
|
+
&& typeof document.updatedAt === 'string';
|
|
131
|
+
if (isRecord(document) && !isEnvelope) {
|
|
132
|
+
const store = {
|
|
133
|
+
values: flattenValues(document),
|
|
134
|
+
scopes: isRecord(document.scopes) ? document.scopes : {},
|
|
135
|
+
version: typeof document.version === 'string' ? document.version : '3.0.0',
|
|
136
|
+
updatedAt: typeof document.updatedAt === 'string' ? document.updatedAt : '',
|
|
137
|
+
};
|
|
138
|
+
plainDocuments.set(store, document);
|
|
139
|
+
return store;
|
|
140
|
+
}
|
|
141
|
+
return document;
|
|
44
142
|
}
|
|
45
143
|
}
|
|
46
144
|
catch {
|
|
@@ -56,13 +154,20 @@ function loadConfigStore() {
|
|
|
56
154
|
function saveConfigStore(store) {
|
|
57
155
|
ensureConfigDir();
|
|
58
156
|
store.updatedAt = new Date().toISOString();
|
|
59
|
-
|
|
157
|
+
const document = plainDocuments.get(store);
|
|
158
|
+
if (document) {
|
|
159
|
+
document.updatedAt = store.updatedAt;
|
|
160
|
+
if (Object.keys(store.scopes).length > 0 || Object.hasOwn(document, 'scopes')) {
|
|
161
|
+
document.scopes = store.scopes;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
writeFileSync(getConfigPath(), JSON.stringify(document ?? store, null, 2), 'utf-8');
|
|
60
165
|
}
|
|
61
166
|
function getNestedValue(obj, key) {
|
|
62
167
|
const parts = key.split('.');
|
|
63
168
|
let current = obj;
|
|
64
169
|
for (const part of parts) {
|
|
65
|
-
if (current && typeof current === 'object' && part
|
|
170
|
+
if (current && typeof current === 'object' && Object.hasOwn(current, part)) {
|
|
66
171
|
current = current[part];
|
|
67
172
|
}
|
|
68
173
|
else {
|
|
@@ -95,7 +200,7 @@ function setNestedValue(obj, key, value) {
|
|
|
95
200
|
let current = obj;
|
|
96
201
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
97
202
|
const part = parts[i];
|
|
98
|
-
if (!(part
|
|
203
|
+
if (!Object.hasOwn(current, part) || current[part] === null || typeof current[part] !== 'object') {
|
|
99
204
|
current[part] = {};
|
|
100
205
|
}
|
|
101
206
|
current = current[part];
|
|
@@ -134,7 +239,7 @@ export const configTools = [
|
|
|
134
239
|
value = store.scopes[scope][key];
|
|
135
240
|
}
|
|
136
241
|
if (value === undefined) {
|
|
137
|
-
value = store
|
|
242
|
+
value = readDefaultValue(store, key);
|
|
138
243
|
}
|
|
139
244
|
if (value === undefined) {
|
|
140
245
|
value = DEFAULT_CONFIG[key];
|
|
@@ -144,7 +249,7 @@ export const configTools = [
|
|
|
144
249
|
value,
|
|
145
250
|
scope,
|
|
146
251
|
exists: value !== undefined,
|
|
147
|
-
source: value !== undefined ? (store
|
|
252
|
+
source: value !== undefined ? (readDefaultValue(store, key) !== undefined ? 'stored' : 'default') : 'none',
|
|
148
253
|
};
|
|
149
254
|
},
|
|
150
255
|
},
|
|
@@ -175,9 +280,9 @@ export const configTools = [
|
|
|
175
280
|
const key = input.key;
|
|
176
281
|
const value = input.value;
|
|
177
282
|
const scope = input.scope || 'default';
|
|
178
|
-
const previousValue = store
|
|
283
|
+
const previousValue = readDefaultValue(store, key);
|
|
179
284
|
if (scope === 'default') {
|
|
180
|
-
store
|
|
285
|
+
writeDefaultValue(store, key, value);
|
|
181
286
|
}
|
|
182
287
|
else {
|
|
183
288
|
if (!store.scopes[scope]) {
|
|
@@ -289,8 +394,7 @@ export const configTools = [
|
|
|
289
394
|
if (key) {
|
|
290
395
|
// Reset specific key
|
|
291
396
|
if (scope === 'default') {
|
|
292
|
-
if (key
|
|
293
|
-
delete store.values[key];
|
|
397
|
+
if (deleteDefaultValue(store, key)) {
|
|
294
398
|
resetKeys.push(key);
|
|
295
399
|
}
|
|
296
400
|
}
|
|
@@ -303,7 +407,7 @@ export const configTools = [
|
|
|
303
407
|
// Reset all keys in scope
|
|
304
408
|
if (scope === 'default') {
|
|
305
409
|
resetKeys = Object.keys(store.values);
|
|
306
|
-
store
|
|
410
|
+
replaceDefaultValues(store, { ...DEFAULT_CONFIG });
|
|
307
411
|
}
|
|
308
412
|
else if (store.scopes[scope]) {
|
|
309
413
|
resetKeys = Object.keys(store.scopes[scope]);
|
|
@@ -385,10 +489,11 @@ export const configTools = [
|
|
|
385
489
|
const importedKeys = Object.keys(config);
|
|
386
490
|
if (scope === 'default') {
|
|
387
491
|
if (merge) {
|
|
388
|
-
Object.
|
|
492
|
+
for (const [key, value] of Object.entries(config))
|
|
493
|
+
writeDefaultValue(store, key, value);
|
|
389
494
|
}
|
|
390
495
|
else {
|
|
391
|
-
store
|
|
496
|
+
replaceDefaultValues(store, { ...DEFAULT_CONFIG, ...config });
|
|
392
497
|
}
|
|
393
498
|
}
|
|
394
499
|
else {
|
|
@@ -1154,14 +1154,59 @@ export const hooksRoute = {
|
|
|
1154
1154
|
catch {
|
|
1155
1155
|
// AgentDB router not available — fall through to local routing
|
|
1156
1156
|
}
|
|
1157
|
+
// Trajectory learning persists SONA outcomes separately from the local
|
|
1158
|
+
// semantic index. Only consume an eligible learned pattern here; native,
|
|
1159
|
+
// Q-learning and keyword suggestions retain their existing routing paths.
|
|
1160
|
+
try {
|
|
1161
|
+
const sonaStart = performance.now();
|
|
1162
|
+
const sona = await getSONAOptimizer();
|
|
1163
|
+
const suggestion = await sona?.getRoutingSuggestion(task);
|
|
1164
|
+
const { isSupportedSonaAgent } = await import('../memory/sona-optimizer.js');
|
|
1165
|
+
if (suggestion?.source === 'sona-pattern'
|
|
1166
|
+
&& Number.isFinite(suggestion.confidence)
|
|
1167
|
+
&& suggestion.confidence >= 0.6 && suggestion.confidence <= 1
|
|
1168
|
+
&& isSupportedSonaAgent(suggestion.agent)) {
|
|
1169
|
+
const confidence = Math.round(suggestion.confidence * 100) / 100;
|
|
1170
|
+
const complexity = task.length > 200 ? 'high' : task.length < 50 ? 'low' : 'medium';
|
|
1171
|
+
return {
|
|
1172
|
+
task,
|
|
1173
|
+
routing: { method: 'sona-pattern', backend: 'SONA persisted keyword patterns', latencyMs: performance.now() - sonaStart, throughput: 'N/A' },
|
|
1174
|
+
matched: true,
|
|
1175
|
+
matchedPattern: 'sona-pattern',
|
|
1176
|
+
semanticMatches: [],
|
|
1177
|
+
primaryAgent: {
|
|
1178
|
+
type: suggestion.agent,
|
|
1179
|
+
confidence,
|
|
1180
|
+
reason: `Persisted SONA outcome pattern (${Math.round(suggestion.confidence * 100)}% confidence)`,
|
|
1181
|
+
},
|
|
1182
|
+
alternativeAgents: suggestion.alternatives
|
|
1183
|
+
.filter(alternative => isSupportedSonaAgent(alternative.agent) && Number.isFinite(alternative.score))
|
|
1184
|
+
.map(alternative => ({
|
|
1185
|
+
type: alternative.agent,
|
|
1186
|
+
confidence: Math.round(alternative.score * 100) / 100,
|
|
1187
|
+
reason: 'Alternative from persisted SONA patterns',
|
|
1188
|
+
})),
|
|
1189
|
+
estimatedMetrics: {
|
|
1190
|
+
successProbability: confidence,
|
|
1191
|
+
estimatedDuration: complexity === 'high' ? '2-4 hours' : complexity === 'medium' ? '30-60 min' : '10-30 min',
|
|
1192
|
+
complexity,
|
|
1193
|
+
},
|
|
1194
|
+
swarmRecommendation: null,
|
|
1195
|
+
learning: { source: suggestion.source, matchedKeywords: suggestion.matchedKeywords },
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
catch {
|
|
1200
|
+
// Optional SONA state unavailable — preserve the existing local route.
|
|
1201
|
+
}
|
|
1157
1202
|
}
|
|
1158
1203
|
return routeTaskLocal(task, context, useSemanticRouter);
|
|
1159
1204
|
}),
|
|
1160
1205
|
};
|
|
1161
1206
|
/**
|
|
1162
1207
|
* hooks_route's local routing (semantic index + keyword fallback), run after the
|
|
1163
|
-
* AgentDB pre-
|
|
1164
|
-
* measures
|
|
1208
|
+
* AgentDB and eligible SONA pre-routes. Shared with {@link routeTaskForBench}
|
|
1209
|
+
* so the benchmark measures this local path. (Body kept at its original
|
|
1165
1210
|
* indentation to keep this refactor's diff small.)
|
|
1166
1211
|
*/
|
|
1167
1212
|
async function routeTaskLocal(task, context, useSemanticRouter, embedderOverride) {
|
|
@@ -1500,7 +1500,21 @@ export const memoryTools = [
|
|
|
1500
1500
|
// entry body. Without this, `value` is always null because listEntries
|
|
1501
1501
|
// strips content by default (callers pay for the JSON parse only when
|
|
1502
1502
|
// they need it).
|
|
1503
|
-
|
|
1503
|
+
// A successful export must cover the whole reported store. Read all
|
|
1504
|
+
// pages before touching an existing snapshot, including on read failure.
|
|
1505
|
+
const entries = [];
|
|
1506
|
+
let total;
|
|
1507
|
+
do {
|
|
1508
|
+
const page = await listEntries({ limit: 10000, offset: entries.length, namespace, includeContent: true });
|
|
1509
|
+
if (!page.success)
|
|
1510
|
+
return { error: page.error || 'Could not read memory for export' };
|
|
1511
|
+
total ??= page.total;
|
|
1512
|
+
if (page.entries.length === 0 && entries.length < total) {
|
|
1513
|
+
return { error: `Incomplete memory export: read ${entries.length} of ${total} entries` };
|
|
1514
|
+
}
|
|
1515
|
+
entries.push(...page.entries);
|
|
1516
|
+
} while (entries.length < total);
|
|
1517
|
+
const all = { entries };
|
|
1504
1518
|
const payload = {
|
|
1505
1519
|
schema: 'ruflo-memory-export/v1',
|
|
1506
1520
|
exportedAt: new Date().toISOString(),
|
|
@@ -1595,6 +1609,8 @@ export const memoryTools = [
|
|
|
1595
1609
|
}
|
|
1596
1610
|
let imported = 0;
|
|
1597
1611
|
let skipped = 0;
|
|
1612
|
+
let failed = 0;
|
|
1613
|
+
const errors = [];
|
|
1598
1614
|
for (const e of entries) {
|
|
1599
1615
|
if (!e || typeof e.key !== 'string') {
|
|
1600
1616
|
skipped++;
|
|
@@ -1603,19 +1619,22 @@ export const memoryTools = [
|
|
|
1603
1619
|
const value = typeof e.value === 'string' ? e.value : JSON.stringify(e.value ?? null);
|
|
1604
1620
|
try {
|
|
1605
1621
|
const result = await storeEntry({ key: e.key, value, namespace: nsOverride ?? e.namespace ?? 'default', upsert: input.merge !== false, dbPath });
|
|
1606
|
-
if (result.success)
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
else
|
|
1612
|
-
skipped++;
|
|
1622
|
+
if (!result.success)
|
|
1623
|
+
throw new Error(result.error || 'Memory write was rejected');
|
|
1624
|
+
imported++;
|
|
1625
|
+
if (result.embedding)
|
|
1626
|
+
vectors++;
|
|
1613
1627
|
}
|
|
1614
|
-
catch {
|
|
1615
|
-
|
|
1628
|
+
catch (error) {
|
|
1629
|
+
failed++;
|
|
1630
|
+
if (errors.length < 10)
|
|
1631
|
+
errors.push(`${e.key}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1616
1632
|
}
|
|
1617
1633
|
}
|
|
1618
1634
|
return {
|
|
1635
|
+
success: failed === 0,
|
|
1636
|
+
error: failed > 0 ? `${failed} memory write(s) failed: ${errors.join('; ')}` : undefined,
|
|
1637
|
+
failed,
|
|
1619
1638
|
inputPath,
|
|
1620
1639
|
imported: { entries: imported, vectors, patterns: 0 },
|
|
1621
1640
|
skipped,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
/** Only an absent store is empty. Failed reads must never authorize replacement. */
|
|
3
|
+
export function readRecordStore(path, collection, empty) {
|
|
4
|
+
let text;
|
|
5
|
+
try {
|
|
6
|
+
text = readFileSync(path, 'utf-8');
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
if (error.code === 'ENOENT')
|
|
10
|
+
return empty();
|
|
11
|
+
throw error;
|
|
12
|
+
}
|
|
13
|
+
const store = JSON.parse(text);
|
|
14
|
+
const records = store?.[collection];
|
|
15
|
+
if (!store || typeof store !== 'object' || Array.isArray(store)
|
|
16
|
+
|| !records || typeof records !== 'object' || Array.isArray(records)) {
|
|
17
|
+
throw new Error(`Invalid ${collection} store at ${path}; existing data was preserved`);
|
|
18
|
+
}
|
|
19
|
+
return store;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=record-store.js.map
|
|
@@ -10,6 +10,33 @@ import { join } from 'node:path';
|
|
|
10
10
|
import { getProjectCwd } from './types.js';
|
|
11
11
|
import { mkdirRestricted, readFileMaybeEncrypted, writeFileRestricted, } from '../fs-secure.js';
|
|
12
12
|
import { validateIdentifier, validateText } from './validate-input.js';
|
|
13
|
+
function isRecord(value) {
|
|
14
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
// Imported snapshots become live stores on restore. Validate their containers
|
|
17
|
+
// before registering them, while allowing older metadata-only snapshots.
|
|
18
|
+
function validateImportedSession(value) {
|
|
19
|
+
if (!isRecord(value) || (typeof value.name !== 'string' && !isSessionRecordLike(value))) {
|
|
20
|
+
return 'Invalid session: Not a session record; expected a named snapshot or snapshot data';
|
|
21
|
+
}
|
|
22
|
+
if (value.name !== undefined && typeof value.name !== 'string') {
|
|
23
|
+
return 'Invalid session: name must be a string';
|
|
24
|
+
}
|
|
25
|
+
if (value.data === undefined)
|
|
26
|
+
return undefined;
|
|
27
|
+
if (!isRecord(value.data))
|
|
28
|
+
return 'Invalid session: data must be an object';
|
|
29
|
+
for (const [component, collection] of [['tasks', 'tasks'], ['agents', 'agents'], ['memory', 'entries']]) {
|
|
30
|
+
const store = value.data[component];
|
|
31
|
+
if (store === undefined)
|
|
32
|
+
continue;
|
|
33
|
+
if (!isRecord(store) || !isRecord(store[collection]) ||
|
|
34
|
+
Object.values(store[collection]).some(record => !isRecord(record))) {
|
|
35
|
+
return `Invalid session: data.${component}.${collection} must be a record map`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
13
40
|
// Storage paths
|
|
14
41
|
const STORAGE_DIR = '.claude-flow';
|
|
15
42
|
const SESSION_DIR = 'sessions';
|
|
@@ -306,9 +333,9 @@ export const sessionTools = [
|
|
|
306
333
|
properties: {
|
|
307
334
|
name: { type: 'string', description: 'Session name' },
|
|
308
335
|
description: { type: 'string', description: 'Session description' },
|
|
309
|
-
includeMemory: { type: 'boolean', description: 'Include memory in session' },
|
|
310
|
-
includeTasks: { type: 'boolean', description: 'Include tasks in session' },
|
|
311
|
-
includeAgents: { type: 'boolean', description: 'Include agents in session' },
|
|
336
|
+
includeMemory: { type: 'boolean', default: true, description: 'Include memory in session' },
|
|
337
|
+
includeTasks: { type: 'boolean', default: true, description: 'Include tasks in session' },
|
|
338
|
+
includeAgents: { type: 'boolean', default: true, description: 'Include agents in session' },
|
|
312
339
|
},
|
|
313
340
|
required: ['name'],
|
|
314
341
|
},
|
|
@@ -325,9 +352,9 @@ export const sessionTools = [
|
|
|
325
352
|
const sessionId = `session-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
326
353
|
// Load related data based on options
|
|
327
354
|
const { data, memoryCapture } = await loadRelatedStores({
|
|
328
|
-
includeMemory: input.includeMemory,
|
|
329
|
-
includeTasks: input.includeTasks,
|
|
330
|
-
includeAgents: input.includeAgents,
|
|
355
|
+
includeMemory: input.includeMemory !== false,
|
|
356
|
+
includeTasks: input.includeTasks !== false,
|
|
357
|
+
includeAgents: input.includeAgents !== false,
|
|
331
358
|
});
|
|
332
359
|
// Calculate stats
|
|
333
360
|
const stats = {
|
|
@@ -692,19 +719,26 @@ export const sessionTools = [
|
|
|
692
719
|
return { error: `Invalid session JSON: ${e.message}` };
|
|
693
720
|
}
|
|
694
721
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
722
|
+
const validationError = validateImportedSession(parsed);
|
|
723
|
+
if (validationError)
|
|
724
|
+
return { error: validationError };
|
|
725
|
+
const record = parsed;
|
|
698
726
|
const newId = `session-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
699
|
-
const stats = {
|
|
727
|
+
const stats = {
|
|
728
|
+
tasks: Object.keys(record.data?.tasks?.tasks || {}).length,
|
|
729
|
+
agents: Object.keys(record.data?.agents?.agents || {}).length,
|
|
730
|
+
memoryEntries: countMemoryEntries(record.data?.memory),
|
|
731
|
+
totalSize: 0,
|
|
732
|
+
};
|
|
700
733
|
const session = {
|
|
701
734
|
sessionId: newId,
|
|
702
|
-
name: input.name ? String(input.name) : (
|
|
703
|
-
description:
|
|
735
|
+
name: input.name ? String(input.name) : (record.name || 'imported-session'),
|
|
736
|
+
description: record.description,
|
|
704
737
|
savedAt: new Date().toISOString(),
|
|
705
738
|
stats,
|
|
706
|
-
data:
|
|
739
|
+
data: record.data,
|
|
707
740
|
};
|
|
741
|
+
session.stats.totalSize = Buffer.byteLength(JSON.stringify(session), 'utf-8');
|
|
708
742
|
saveSession(session);
|
|
709
743
|
let activated = false;
|
|
710
744
|
if (input.activate === true) {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
|
+
import { readRecordStore } from './record-store.js';
|
|
8
9
|
import { getProjectCwd } from './types.js';
|
|
9
10
|
import { validateIdentifier, validateText } from './validate-input.js';
|
|
10
11
|
// Storage paths
|
|
@@ -24,17 +25,7 @@ function ensureTaskDir() {
|
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
function loadTaskStore() {
|
|
27
|
-
|
|
28
|
-
const path = getTaskPath();
|
|
29
|
-
if (existsSync(path)) {
|
|
30
|
-
const data = readFileSync(path, 'utf-8');
|
|
31
|
-
return JSON.parse(data);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
// Return empty store on error
|
|
36
|
-
}
|
|
37
|
-
return { tasks: {}, version: '3.0.0' };
|
|
28
|
+
return readRecordStore(getTaskPath(), 'tasks', () => ({ tasks: {}, version: '3.0.0' }));
|
|
38
29
|
}
|
|
39
30
|
function saveTaskStore(store) {
|
|
40
31
|
ensureTaskDir();
|
|
@@ -378,6 +369,9 @@ export const taskTools = [
|
|
|
378
369
|
return { taskId, error: 'Task not found' };
|
|
379
370
|
}
|
|
380
371
|
const previouslyAssigned = [...task.assignedTo];
|
|
372
|
+
// Load agent store to sync worker state
|
|
373
|
+
const agentStorePath = join(getProjectCwd(), STORAGE_DIR, 'agents', 'store.json');
|
|
374
|
+
readRecordStore(agentStorePath, 'agents', () => ({ agents: {} }));
|
|
381
375
|
if (input.unassign) {
|
|
382
376
|
// Revert previously assigned agents to idle
|
|
383
377
|
releaseAgents(previouslyAssigned, taskId);
|