@claude-flow/cli 3.71.1 → 3.71.2
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/catalog-manifest.json +2 -2
- package/dist/src/commands/embeddings.js +6 -6
- package/dist/src/commands/init.js +4 -0
- package/dist/src/commands/session.js +10 -7
- package/dist/src/init/executor.js +15 -5
- package/dist/src/init/types.d.ts +2 -0
- package/dist/src/init/types.js +4 -1
- package/dist/src/mcp-tools/claims-tools.js +7 -1
- package/dist/src/mcp-tools/daa-tools.js +22 -2
- package/dist/src/mcp-tools/embeddings-tools.js +2 -1
- package/dist/src/mcp-tools/session-tools.js +42 -4
- package/dist/src/mcp-tools/terminal-tools.js +5 -0
- package/dist/src/mcp-tools/workflow-tools.js +22 -3
- package/dist/src/memory/memory-bridge.js +29 -5
- package/dist/src/memory/memory-initializer.js +32 -10
- package/dist/src/services/claims-store.js +5 -2
- package/package.json +1 -1
- package/plugins/ruflo-console/dist/tui.mjs +51 -51
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest": {
|
|
3
|
-
"version": "3.71.
|
|
3
|
+
"version": "3.71.2",
|
|
4
4
|
"files": {
|
|
5
5
|
"auto-memory-hook.mjs": "631fac9d518835e2852a406888316bbbddddb49e7ed8e406d2e971f46833df9a",
|
|
6
6
|
"hook-handler.cjs": "0ce70981aa33f76d008b6f49626effaf4269918c4094f1a28c1ef6fceb097d6f",
|
|
@@ -10,6 +10,6 @@
|
|
|
10
10
|
"learning-loop.cjs": "d046cd6f8efb7dd40e1d2451516c181fc8b26477aefb1124c0df5f52a23f9190"
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
|
-
"signature": "
|
|
13
|
+
"signature": "awy0GR0jee+KKCI+7BgzocA29zGZTAZcNYRAwp7IDQ6xwzc6Rby9hqhTnk+68AHde2YvpNpuO8AyqU/P9JDFBw==",
|
|
14
14
|
"algorithm": "ed25519"
|
|
15
15
|
}
|
package/catalog-manifest.json
CHANGED
|
@@ -121,7 +121,7 @@ const searchCommand = {
|
|
|
121
121
|
const threshold = rawThreshold === undefined || rawThreshold === null
|
|
122
122
|
? 0.5
|
|
123
123
|
: parseFloat(String(rawThreshold));
|
|
124
|
-
const dbPath = ctx.flags
|
|
124
|
+
const dbPath = ctx.flags.dbPath || '.swarm/memory.db';
|
|
125
125
|
if (!query) {
|
|
126
126
|
output.printError('Query is required');
|
|
127
127
|
return { success: false, exitCode: 1 };
|
|
@@ -394,7 +394,7 @@ const collectionsCommand = {
|
|
|
394
394
|
],
|
|
395
395
|
action: async (ctx) => {
|
|
396
396
|
const action = ctx.flags.action || 'list';
|
|
397
|
-
const dbPath = ctx.flags
|
|
397
|
+
const dbPath = ctx.flags.dbPath || '.swarm/memory.db';
|
|
398
398
|
output.writeln();
|
|
399
399
|
output.writeln(output.bold('Embedding Collections (Namespaces)'));
|
|
400
400
|
output.writeln(output.dim('─'.repeat(60)));
|
|
@@ -500,7 +500,7 @@ const indexCommand = {
|
|
|
500
500
|
action: async (ctx) => {
|
|
501
501
|
const action = ctx.flags.action || 'status';
|
|
502
502
|
const collection = ctx.flags.collection;
|
|
503
|
-
const efConstruction = parseInt(ctx.flags
|
|
503
|
+
const efConstruction = parseInt(ctx.flags.efConstruction || '200', 10);
|
|
504
504
|
const m = parseInt(ctx.flags.m || '16', 10);
|
|
505
505
|
output.writeln();
|
|
506
506
|
output.writeln(output.bold(`HNSW Index: ${action}`));
|
|
@@ -850,7 +850,7 @@ const chunkCommand = {
|
|
|
850
850
|
action: async (ctx) => {
|
|
851
851
|
const embeddings = await getEmbeddings();
|
|
852
852
|
const text = ctx.flags.text || '';
|
|
853
|
-
const maxSize = parseInt(ctx.flags
|
|
853
|
+
const maxSize = parseInt(ctx.flags.maxSize || '512', 10);
|
|
854
854
|
const overlap = parseInt(ctx.flags.overlap || '50', 10);
|
|
855
855
|
const strategy = ctx.flags.strategy || 'sentence';
|
|
856
856
|
output.writeln();
|
|
@@ -1275,7 +1275,7 @@ const cacheCommand = {
|
|
|
1275
1275
|
],
|
|
1276
1276
|
action: async (ctx) => {
|
|
1277
1277
|
const action = ctx.flags.action || 'stats';
|
|
1278
|
-
const dbPath = ctx.flags
|
|
1278
|
+
const dbPath = ctx.flags.dbPath || '.cache/embeddings.db';
|
|
1279
1279
|
output.writeln();
|
|
1280
1280
|
output.writeln(output.bold('Embedding Cache'));
|
|
1281
1281
|
output.writeln(output.dim('─'.repeat(50)));
|
|
@@ -1479,7 +1479,7 @@ const benchmarkCommand = {
|
|
|
1479
1479
|
],
|
|
1480
1480
|
action: async (ctx) => {
|
|
1481
1481
|
const iterations = parseInt(ctx.flags.iterations || '10', 10);
|
|
1482
|
-
const batchSize = parseInt(ctx.flags
|
|
1482
|
+
const batchSize = parseInt(ctx.flags.batchSize || '5', 10);
|
|
1483
1483
|
const full = ctx.flags.full === true;
|
|
1484
1484
|
output.writeln();
|
|
1485
1485
|
output.writeln(output.bold('Embedding Performance Benchmark'));
|
|
@@ -1091,6 +1091,7 @@ const wizardCommand = {
|
|
|
1091
1091
|
{ value: 'github', label: 'GitHub', hint: 'GitHub integration skills', selected: true },
|
|
1092
1092
|
{ value: 'flowNexus', label: 'Flow Nexus', hint: 'Cloud platform skills', selected: false },
|
|
1093
1093
|
{ value: 'v3', label: 'V3', hint: 'V3 implementation skills', selected: true },
|
|
1094
|
+
{ value: 'legacy', label: 'Legacy', hint: 'Skills that document commands v3 does not ship', selected: false },
|
|
1094
1095
|
],
|
|
1095
1096
|
});
|
|
1096
1097
|
options.skills.core = skillSets.includes('core');
|
|
@@ -1098,6 +1099,7 @@ const wizardCommand = {
|
|
|
1098
1099
|
options.skills.github = skillSets.includes('github');
|
|
1099
1100
|
options.skills.flowNexus = skillSets.includes('flowNexus');
|
|
1100
1101
|
options.skills.v3 = skillSets.includes('v3');
|
|
1102
|
+
options.skills.legacy = skillSets.includes('legacy');
|
|
1101
1103
|
}
|
|
1102
1104
|
// Hooks selection
|
|
1103
1105
|
if (options.components.settings) {
|
|
@@ -1354,6 +1356,7 @@ const skillsCommand = {
|
|
|
1354
1356
|
{ name: 'agentdb', description: 'Install AgentDB skills', type: 'boolean', default: false },
|
|
1355
1357
|
{ name: 'github', description: 'Install GitHub skills', type: 'boolean', default: false },
|
|
1356
1358
|
{ name: 'v3', description: 'Install V3 skills', type: 'boolean', default: false },
|
|
1359
|
+
{ name: 'legacy', description: 'Install skills that document commands v3 does not ship (stream-chain, sparc)', type: 'boolean', default: false },
|
|
1357
1360
|
],
|
|
1358
1361
|
action: async (ctx) => {
|
|
1359
1362
|
const options = {
|
|
@@ -1380,6 +1383,7 @@ const skillsCommand = {
|
|
|
1380
1383
|
browser: false,
|
|
1381
1384
|
v3: ctx.flags.v3,
|
|
1382
1385
|
dualMode: false,
|
|
1386
|
+
legacy: ctx.flags.legacy,
|
|
1383
1387
|
},
|
|
1384
1388
|
};
|
|
1385
1389
|
const spinner = output.createSpinner({ text: 'Installing skills...' });
|
|
@@ -211,9 +211,9 @@ const saveCommand = {
|
|
|
211
211
|
const result = await callMCPTool('session_save', {
|
|
212
212
|
name: sessionName,
|
|
213
213
|
description,
|
|
214
|
-
includeMemory: ctx.flags['include-memory'] !== false,
|
|
215
|
-
includeAgents: ctx.flags['include-agents'] !== false,
|
|
216
|
-
includeTasks: ctx.flags['include-tasks'] !== false
|
|
214
|
+
includeMemory: ctx.flags.includeMemory !== false && ctx.flags['include-memory'] !== false,
|
|
215
|
+
includeAgents: ctx.flags.includeAgents !== false && ctx.flags['include-agents'] !== false,
|
|
216
|
+
includeTasks: ctx.flags.includeTasks !== false && ctx.flags['include-tasks'] !== false
|
|
217
217
|
});
|
|
218
218
|
const failure = toolError(result);
|
|
219
219
|
if (failure) {
|
|
@@ -342,9 +342,12 @@ const restoreCommand = {
|
|
|
342
342
|
spinner.start();
|
|
343
343
|
try {
|
|
344
344
|
// Determine what to restore
|
|
345
|
-
const
|
|
346
|
-
const
|
|
347
|
-
const
|
|
345
|
+
const memoryOnly = ctx.flags.memoryOnly === true || ctx.flags['memory-only'] === true;
|
|
346
|
+
const agentsOnly = ctx.flags.agentsOnly === true || ctx.flags['agents-only'] === true;
|
|
347
|
+
const tasksOnly = ctx.flags.tasksOnly === true || ctx.flags['tasks-only'] === true;
|
|
348
|
+
const restoreMemory = !agentsOnly && !tasksOnly;
|
|
349
|
+
const restoreAgents = !memoryOnly && !tasksOnly;
|
|
350
|
+
const restoreTasks = !memoryOnly && !agentsOnly;
|
|
348
351
|
const result = await callMCPTool('session_restore', {
|
|
349
352
|
sessionId,
|
|
350
353
|
restoreMemory,
|
|
@@ -552,7 +555,7 @@ const exportCommand = {
|
|
|
552
555
|
try {
|
|
553
556
|
const result = await callMCPTool('session_export', {
|
|
554
557
|
sessionId,
|
|
555
|
-
includeMemory: ctx.flags['include-memory'] !== false
|
|
558
|
+
includeMemory: ctx.flags.includeMemory !== false && ctx.flags['include-memory'] !== false
|
|
556
559
|
});
|
|
557
560
|
const failure = toolError(result);
|
|
558
561
|
if (failure || result.data === undefined) {
|
|
@@ -29,11 +29,9 @@ const SKILLS_MAP = {
|
|
|
29
29
|
core: [
|
|
30
30
|
'swarm-orchestration',
|
|
31
31
|
'swarm-advanced',
|
|
32
|
-
'sparc-methodology',
|
|
33
32
|
'hooks-automation',
|
|
34
33
|
'pair-programming',
|
|
35
34
|
'verification-quality',
|
|
36
|
-
'stream-chain',
|
|
37
35
|
'skill-builder',
|
|
38
36
|
],
|
|
39
37
|
browser: ['browser'], // agent-browser integration
|
|
@@ -47,12 +45,11 @@ const SKILLS_MAP = {
|
|
|
47
45
|
'reasoningbank-agentdb',
|
|
48
46
|
'reasoningbank-intelligence',
|
|
49
47
|
],
|
|
48
|
+
// github-code-review and github-project-management only use hooks, which v3
|
|
49
|
+
// ships; the other three drive `ruflo github`, so they live in skills.legacy.
|
|
50
50
|
github: [
|
|
51
51
|
'github-code-review',
|
|
52
|
-
'github-multi-repo',
|
|
53
52
|
'github-project-management',
|
|
54
|
-
'github-release-management',
|
|
55
|
-
'github-workflow-automation',
|
|
56
53
|
],
|
|
57
54
|
flowNexus: [
|
|
58
55
|
'flow-nexus-neural',
|
|
@@ -70,6 +67,17 @@ const SKILLS_MAP = {
|
|
|
70
67
|
'v3-security-overhaul',
|
|
71
68
|
'v3-swarm-coordination',
|
|
72
69
|
],
|
|
70
|
+
// ADR-128 / #4539: skills that document v2-era verbs v3 does not ship
|
|
71
|
+
// (`stream-chain`, `sparc`, `github`). Their examples fail with `Unknown
|
|
72
|
+
// command`, so they are opt-in like skills.v3 instead of being installed by
|
|
73
|
+
// default.
|
|
74
|
+
legacy: [
|
|
75
|
+
'github-multi-repo',
|
|
76
|
+
'github-release-management',
|
|
77
|
+
'github-workflow-automation',
|
|
78
|
+
'sparc-methodology',
|
|
79
|
+
'stream-chain',
|
|
80
|
+
],
|
|
73
81
|
};
|
|
74
82
|
/**
|
|
75
83
|
* Commands to copy based on configuration
|
|
@@ -1077,6 +1085,8 @@ async function copySkills(targetDir, options, result) {
|
|
|
1077
1085
|
skillsToCopy.push(...SKILLS_MAP.v3);
|
|
1078
1086
|
if (skillsConfig.dualMode)
|
|
1079
1087
|
skillsToCopy.push(...SKILLS_MAP.dualMode);
|
|
1088
|
+
if (skillsConfig.legacy)
|
|
1089
|
+
skillsToCopy.push(...SKILLS_MAP.legacy);
|
|
1080
1090
|
}
|
|
1081
1091
|
// Find source skills directory
|
|
1082
1092
|
const sourceSkillsDir = findSourceDir('skills', options.sourceBaseDir);
|
package/dist/src/init/types.d.ts
CHANGED
|
@@ -76,6 +76,8 @@ export interface SkillsConfig {
|
|
|
76
76
|
v3: boolean;
|
|
77
77
|
/** Include dual-mode skills (Claude Code + Codex hybrid) */
|
|
78
78
|
dualMode: boolean;
|
|
79
|
+
/** Include legacy skills that document commands v3 does not ship (opt-in) */
|
|
80
|
+
legacy: boolean;
|
|
79
81
|
/** Include all available skills */
|
|
80
82
|
all: boolean;
|
|
81
83
|
}
|
package/dist/src/init/types.js
CHANGED
|
@@ -73,11 +73,12 @@ export const DEFAULT_INIT_OPTIONS = {
|
|
|
73
73
|
skills: {
|
|
74
74
|
core: true,
|
|
75
75
|
agentdb: true,
|
|
76
|
-
github: true,
|
|
76
|
+
github: true, // #4539: only the hook-only GitHub skills remain; the rest are skills.legacy
|
|
77
77
|
flowNexus: false,
|
|
78
78
|
browser: true,
|
|
79
79
|
v3: false, // ADR-128: opt-in like agents.v3; use --full or `init skills --v3`
|
|
80
80
|
dualMode: false, // Optional: enable with --dual flag
|
|
81
|
+
legacy: false, // #4539: stream-chain/sparc document verbs v3 does not ship
|
|
81
82
|
all: false,
|
|
82
83
|
},
|
|
83
84
|
commands: {
|
|
@@ -186,6 +187,7 @@ export const MINIMAL_INIT_OPTIONS = {
|
|
|
186
187
|
browser: false,
|
|
187
188
|
v3: false,
|
|
188
189
|
dualMode: false,
|
|
190
|
+
legacy: false,
|
|
189
191
|
all: false,
|
|
190
192
|
},
|
|
191
193
|
agents: {
|
|
@@ -246,6 +248,7 @@ export const FULL_INIT_OPTIONS = {
|
|
|
246
248
|
browser: true,
|
|
247
249
|
v3: true,
|
|
248
250
|
dualMode: true, // Include in full init
|
|
251
|
+
legacy: true, // Include in full init
|
|
249
252
|
all: true,
|
|
250
253
|
},
|
|
251
254
|
commands: {
|
|
@@ -402,6 +402,10 @@ export const claimsTools = [
|
|
|
402
402
|
if (progress !== undefined) {
|
|
403
403
|
claim.progress = Math.min(100, Math.max(0, progress));
|
|
404
404
|
}
|
|
405
|
+
// claims_status never sets 'stealable', so a claim it updates is no
|
|
406
|
+
// longer up for grabs: drop it from the list claims_stealable and
|
|
407
|
+
// claims_steal read.
|
|
408
|
+
delete store.stealable[issueId];
|
|
405
409
|
store.claims[issueId] = claim;
|
|
406
410
|
saveClaims(store);
|
|
407
411
|
return {
|
|
@@ -592,7 +596,9 @@ export const claimsTools = [
|
|
|
592
596
|
claim.claimant = stealer;
|
|
593
597
|
claim.status = 'active';
|
|
594
598
|
claim.statusChangedAt = now;
|
|
595
|
-
|
|
599
|
+
// The context given to claims_mark-stealable is the handoff note for the
|
|
600
|
+
// stealer; without one, keep the context recorded with the claim.
|
|
601
|
+
claim.context = stealableInfo.context ?? claim.context;
|
|
596
602
|
delete store.stealable[issueId];
|
|
597
603
|
store.claims[issueId] = claim;
|
|
598
604
|
saveClaims(store);
|
|
@@ -28,17 +28,37 @@ function ensureDAADir() {
|
|
|
28
28
|
mkdirSync(dir, { recursive: true });
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* SECURITY: record maps are keyed by ids taken from MCP tool input, and
|
|
33
|
+
* validateIdentifier accepts names like `__proto__` and `constructor`. On a
|
|
34
|
+
* plain object those resolve to Object.prototype or a built-in, which a handler
|
|
35
|
+
* would then write into. Null-prototype maps only ever return own records.
|
|
36
|
+
*/
|
|
37
|
+
function withRecordMaps(store, keys) {
|
|
38
|
+
const maps = store;
|
|
39
|
+
for (const key of keys) {
|
|
40
|
+
const map = maps[key];
|
|
41
|
+
if (map && typeof map === 'object' && !Array.isArray(map))
|
|
42
|
+
Object.setPrototypeOf(map, null);
|
|
43
|
+
else
|
|
44
|
+
maps[key] = Object.create(null);
|
|
45
|
+
}
|
|
46
|
+
return store;
|
|
47
|
+
}
|
|
48
|
+
const DAA_MAPS = ['agents', 'workflows', 'knowledge'];
|
|
31
49
|
function loadDAAStore() {
|
|
32
50
|
try {
|
|
33
51
|
const path = getDAAPath();
|
|
34
52
|
if (existsSync(path)) {
|
|
35
|
-
|
|
53
|
+
const parsed = JSON.parse(readFileSync(path, 'utf-8'));
|
|
54
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
|
|
55
|
+
return withRecordMaps(parsed, DAA_MAPS);
|
|
36
56
|
}
|
|
37
57
|
}
|
|
38
58
|
catch {
|
|
39
59
|
// Return empty store
|
|
40
60
|
}
|
|
41
|
-
return { agents: {}, workflows: {}, knowledge: {}, version: '3.0.0' };
|
|
61
|
+
return withRecordMaps({ agents: {}, workflows: {}, knowledge: {}, version: '3.0.0' }, DAA_MAPS);
|
|
42
62
|
}
|
|
43
63
|
function saveDAAStore(store) {
|
|
44
64
|
ensureDAADir();
|
|
@@ -425,7 +425,8 @@ export const embeddingsTools = [
|
|
|
425
425
|
}
|
|
426
426
|
const query = input.query;
|
|
427
427
|
const topK = input.topK || 5;
|
|
428
|
-
|
|
428
|
+
// `??`, not `||`: threshold 0 is a valid request for every match, as in memory_search.
|
|
429
|
+
const threshold = input.threshold ?? 0.5;
|
|
429
430
|
const namespace = input.namespace;
|
|
430
431
|
{
|
|
431
432
|
const v = validateText(query, 'query');
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Tool definitions for session management with file persistence.
|
|
5
5
|
*/
|
|
6
|
-
import { existsSync, readFileSync, readdirSync, unlinkSync, statSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync, unlinkSync, statSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { randomUUID } from 'node:crypto';
|
|
8
8
|
import { gunzipSync } from 'node:zlib';
|
|
9
|
-
import { join } from 'node:path';
|
|
9
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } 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';
|
|
@@ -351,6 +351,41 @@ function isSessionRecordLike(value) {
|
|
|
351
351
|
const v = value;
|
|
352
352
|
return 'data' in v || 'stats' in v || 'sessionId' in v || 'savedAt' in v;
|
|
353
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* SECURITY: `outputPath` comes from MCP tool input. Resolve it against the
|
|
356
|
+
* project root and require the real (symlink-resolved) parent directory to stay
|
|
357
|
+
* inside the real project root, and refuse an existing symlink as the target,
|
|
358
|
+
* so session_export cannot write files elsewhere on the host. The CLI's
|
|
359
|
+
* `session export` writes its own file and does not pass outputPath.
|
|
360
|
+
*/
|
|
361
|
+
function containedExportPath(outputPath) {
|
|
362
|
+
if (!outputPath || outputPath.includes('\0'))
|
|
363
|
+
return null;
|
|
364
|
+
try {
|
|
365
|
+
const root = realpathSync(getProjectCwd());
|
|
366
|
+
const abs = resolve(root, outputPath);
|
|
367
|
+
const parent = realpathSync(dirname(abs));
|
|
368
|
+
const target = join(parent, basename(abs));
|
|
369
|
+
const rel = relative(root, target);
|
|
370
|
+
if (!rel || rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel))
|
|
371
|
+
return null;
|
|
372
|
+
const existing = lstatOrNull(target);
|
|
373
|
+
if (existing && (existing.isSymbolicLink() || !existing.isFile()))
|
|
374
|
+
return null;
|
|
375
|
+
return target;
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function lstatOrNull(p) {
|
|
382
|
+
try {
|
|
383
|
+
return lstatSync(p);
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
354
389
|
export const sessionTools = [
|
|
355
390
|
{
|
|
356
391
|
name: 'session_save',
|
|
@@ -705,9 +740,12 @@ export const sessionTools = [
|
|
|
705
740
|
let path = null;
|
|
706
741
|
const outputPath = input.outputPath ? String(input.outputPath) : null;
|
|
707
742
|
if (outputPath) {
|
|
743
|
+
const target = containedExportPath(outputPath);
|
|
744
|
+
if (!target)
|
|
745
|
+
return { sessionId, error: 'outputPath must be a file inside the project directory (no symlinks)' };
|
|
708
746
|
try {
|
|
709
|
-
writeFileSync(
|
|
710
|
-
path =
|
|
747
|
+
writeFileSync(target, JSON.stringify(session, null, 2), 'utf-8');
|
|
748
|
+
path = target;
|
|
711
749
|
}
|
|
712
750
|
catch (e) {
|
|
713
751
|
return { sessionId, error: `Could not write ${outputPath}: ${e.message}` };
|
|
@@ -132,6 +132,11 @@ export const terminalTools = [
|
|
|
132
132
|
const store = loadTerminalStore();
|
|
133
133
|
const sessionId = input.sessionId;
|
|
134
134
|
const command = input.command;
|
|
135
|
+
// An explicit sessionId must name a stored session; only fall back to
|
|
136
|
+
// (or create) the default session when no sessionId was given.
|
|
137
|
+
if (sessionId && !store.sessions[sessionId]) {
|
|
138
|
+
return { success: false, error: 'Session not found' };
|
|
139
|
+
}
|
|
135
140
|
// Find or create default session
|
|
136
141
|
let session = sessionId ? store.sessions[sessionId] : Object.values(store.sessions).find(s => s.status === 'active');
|
|
137
142
|
if (!session) {
|
|
@@ -55,18 +55,37 @@ function ensureWorkflowDir() {
|
|
|
55
55
|
mkdirSync(dir, { recursive: true });
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* SECURITY: record maps are keyed by ids taken from MCP tool input, and
|
|
60
|
+
* validateIdentifier accepts names like `__proto__` and `constructor`. On a
|
|
61
|
+
* plain object those resolve to Object.prototype or a built-in, which a handler
|
|
62
|
+
* would then write into. Null-prototype maps only ever return own records.
|
|
63
|
+
*/
|
|
64
|
+
function withRecordMaps(store, keys) {
|
|
65
|
+
const maps = store;
|
|
66
|
+
for (const key of keys) {
|
|
67
|
+
const map = maps[key];
|
|
68
|
+
if (map && typeof map === 'object' && !Array.isArray(map))
|
|
69
|
+
Object.setPrototypeOf(map, null);
|
|
70
|
+
else
|
|
71
|
+
maps[key] = Object.create(null);
|
|
72
|
+
}
|
|
73
|
+
return store;
|
|
74
|
+
}
|
|
75
|
+
const WORKFLOW_MAPS = ['workflows', 'templates'];
|
|
58
76
|
function loadWorkflowStore() {
|
|
59
77
|
try {
|
|
60
78
|
const path = getWorkflowPath();
|
|
61
79
|
if (existsSync(path)) {
|
|
62
|
-
const data = readFileSync(path, 'utf-8');
|
|
63
|
-
|
|
80
|
+
const data = JSON.parse(readFileSync(path, 'utf-8'));
|
|
81
|
+
if (data && typeof data === 'object' && !Array.isArray(data))
|
|
82
|
+
return withRecordMaps(data, WORKFLOW_MAPS);
|
|
64
83
|
}
|
|
65
84
|
}
|
|
66
85
|
catch {
|
|
67
86
|
// Return default store on error
|
|
68
87
|
}
|
|
69
|
-
return { workflows: {}, templates: {}, version: '3.0.0' };
|
|
88
|
+
return withRecordMaps({ workflows: {}, templates: {}, version: '3.0.0' }, WORKFLOW_MAPS);
|
|
70
89
|
}
|
|
71
90
|
function saveWorkflowStore(store) {
|
|
72
91
|
ensureWorkflowDir();
|
|
@@ -1540,15 +1540,39 @@ export async function bridgeListEntries(options) {
|
|
|
1540
1540
|
// List
|
|
1541
1541
|
const entries = [];
|
|
1542
1542
|
try {
|
|
1543
|
-
|
|
1544
|
-
const
|
|
1545
|
-
|
|
1543
|
+
const columns = 'id, key, namespace, content, embedding, access_count, created_at, updated_at, provenance_type, expires_at, tags';
|
|
1544
|
+
const listLimit = options.includeEmbedding ? Math.min(safeLimit, MAX_LIST_EMBEDDINGS) : safeLimit;
|
|
1545
|
+
// #4686: sort only row identities first, then fetch the payload for the
|
|
1546
|
+
// page. The old query sorted full `content`/`embedding` payloads for
|
|
1547
|
+
// every row and discarded all but the requested page.
|
|
1548
|
+
const pagedSql = `
|
|
1549
|
+
SELECT ${columns}
|
|
1550
|
+
FROM memory_entries
|
|
1551
|
+
WHERE _rowid_ IN (
|
|
1552
|
+
SELECT _rowid_
|
|
1553
|
+
FROM memory_entries
|
|
1554
|
+
WHERE ${statusFilter} ${extraFilter}
|
|
1555
|
+
ORDER BY updated_at DESC
|
|
1556
|
+
LIMIT ? OFFSET ?
|
|
1557
|
+
)
|
|
1558
|
+
ORDER BY updated_at DESC
|
|
1559
|
+
`;
|
|
1560
|
+
const originalSql = `
|
|
1561
|
+
SELECT ${columns}
|
|
1546
1562
|
FROM memory_entries
|
|
1547
1563
|
WHERE ${statusFilter} ${extraFilter}
|
|
1548
1564
|
ORDER BY updated_at DESC
|
|
1549
1565
|
LIMIT ? OFFSET ?
|
|
1550
|
-
|
|
1551
|
-
|
|
1566
|
+
`;
|
|
1567
|
+
let rows;
|
|
1568
|
+
try {
|
|
1569
|
+
rows = ctx.db.prepare(pagedSql).all(...filterParams, listLimit, safeOffset);
|
|
1570
|
+
}
|
|
1571
|
+
catch {
|
|
1572
|
+
// WITHOUT ROWID/legacy tables have no _rowid_: preserve the original
|
|
1573
|
+
// query rather than failing the page.
|
|
1574
|
+
rows = ctx.db.prepare(originalSql).all(...filterParams, listLimit, safeOffset);
|
|
1575
|
+
}
|
|
1552
1576
|
for (const row of rows) {
|
|
1553
1577
|
let tags = [];
|
|
1554
1578
|
if (row.tags) {
|
|
@@ -3115,16 +3115,38 @@ export async function listEntries(options) {
|
|
|
3115
3115
|
const total = countRows[0]?.[0] || 0;
|
|
3116
3116
|
// Get entries. ADR-472: embedding rows are capped at MAX_LIST_EMBEDDINGS.
|
|
3117
3117
|
const listLimit = options.includeEmbedding ? Math.min(safeLimit, MAX_LIST_EMBEDDINGS) : safeLimit;
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3118
|
+
const columns = 'id, key, namespace, content, embedding, access_count, created_at, updated_at, provenance_type, expires_at, tags';
|
|
3119
|
+
// #4686: page row identities first so the temporary sort B-tree does not
|
|
3120
|
+
// carry full content/embedding payloads for rows outside the page.
|
|
3121
|
+
const pagedSql = `SELECT ${columns} FROM memory_entries
|
|
3122
|
+
WHERE _rowid_ IN (
|
|
3123
|
+
SELECT _rowid_ FROM memory_entries WHERE ${whereSql}
|
|
3124
|
+
ORDER BY updated_at DESC LIMIT ? OFFSET ?
|
|
3125
|
+
)
|
|
3126
|
+
ORDER BY updated_at DESC`;
|
|
3127
|
+
const originalSql = `SELECT ${columns} FROM memory_entries WHERE ${whereSql}
|
|
3128
|
+
ORDER BY updated_at DESC LIMIT ? OFFSET ?`;
|
|
3129
|
+
let listRows = [];
|
|
3130
|
+
const runList = (sql) => {
|
|
3131
|
+
const stmt = db.prepare(sql);
|
|
3132
|
+
try {
|
|
3133
|
+
stmt.bind([...whereParams, listLimit, safeOffset]);
|
|
3134
|
+
const rows = [];
|
|
3135
|
+
while (stmt.step())
|
|
3136
|
+
rows.push(stmt.get());
|
|
3137
|
+
return rows;
|
|
3138
|
+
}
|
|
3139
|
+
finally {
|
|
3140
|
+
stmt.free();
|
|
3141
|
+
}
|
|
3142
|
+
};
|
|
3143
|
+
try {
|
|
3144
|
+
listRows = runList(pagedSql);
|
|
3145
|
+
}
|
|
3146
|
+
catch {
|
|
3147
|
+
// Legacy WITHOUT ROWID tables have no _rowid_: keep the original query.
|
|
3148
|
+
listRows = runList(originalSql);
|
|
3149
|
+
}
|
|
3128
3150
|
const entries = [];
|
|
3129
3151
|
for (const row of listRows) {
|
|
3130
3152
|
const [id, key, ns, content, embedding, accessCount, createdAt, updatedAt, provenanceTypeVal, expiresAtVal, tagsJson] = row;
|
|
@@ -11,7 +11,7 @@ export function readClaimsStore(file) {
|
|
|
11
11
|
}
|
|
12
12
|
catch (error) {
|
|
13
13
|
if (error.code === 'ENOENT')
|
|
14
|
-
return { claims:
|
|
14
|
+
return { claims: Object.create(null), stealable: Object.create(null), contests: Object.create(null) };
|
|
15
15
|
throw error;
|
|
16
16
|
}
|
|
17
17
|
const data = JSON.parse(text);
|
|
@@ -35,6 +35,9 @@ export function readClaimsStore(file) {
|
|
|
35
35
|
if (data[key] !== undefined && !record(data[key]))
|
|
36
36
|
throw new Error(`Invalid claims store: ${key} must be an object`);
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
// SECURITY: issue ids come from tool input; null-prototype maps keep a lookup
|
|
39
|
+
// by `constructor`/`__proto__` from resolving to an inherited built-in.
|
|
40
|
+
const nullProto = (map) => Object.assign(Object.create(null), map ?? {});
|
|
41
|
+
return { ...data, claims, stealable: nullProto(data.stealable), contests: nullProto(data.contests) };
|
|
39
42
|
}
|
|
40
43
|
//# sourceMappingURL=claims-store.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.71.
|
|
3
|
+
"version": "3.71.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|