@claude-flow/cli 3.71.3 → 3.72.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 +17 -0
- package/.claude/helpers/helpers.manifest.json +6 -6
- package/.claude/helpers/hook-handler.cjs +23 -10
- package/.claude/helpers/intelligence.cjs +23 -1
- package/.claude/helpers/statusline.cjs +29 -14
- package/README.md +18 -3
- package/bin/cli.js +13 -18
- package/bin/mcp-server.js +13 -18
- package/catalog-manifest.json +2 -2
- package/dist/src/appliance/rvfa-distribution.js +2 -2
- package/dist/src/benchmarks/gaia-critic.d.ts +1 -7
- package/dist/src/benchmarks/gaia-critic.js +1 -7
- package/dist/src/benchmarks/gaia-decomposer.d.ts +1 -8
- package/dist/src/benchmarks/gaia-decomposer.js +1 -8
- package/dist/src/commands/agent-wasm.js +2 -2
- package/dist/src/commands/analyze.js +1 -1
- package/dist/src/commands/daemon.js +1 -1
- package/dist/src/commands/doctor.js +3 -2
- package/dist/src/commands/guidance.js +9 -9
- package/dist/src/commands/hooks.js +13 -8
- package/dist/src/commands/init.js +14 -16
- package/dist/src/commands/memory.js +4 -3
- package/dist/src/commands/neural.js +8 -6
- package/dist/src/commands/route.js +1 -1
- package/dist/src/commands/ruvector/backup.js +7 -5
- package/dist/src/commands/ruvector/benchmark.js +6 -1
- package/dist/src/commands/ruvector/import.js +19 -5
- package/dist/src/commands/ruvector/init.js +1 -1
- package/dist/src/commands/start.js +1 -1
- package/dist/src/commands/status.js +1 -1
- package/dist/src/commands/task.js +18 -1
- package/dist/src/commands/update.js +1 -1
- package/dist/src/funnel/local-signals.js +7 -2
- package/dist/src/init/helpers-generator.js +16 -1
- package/dist/src/mcp-protocol.d.ts +90 -0
- package/dist/src/mcp-protocol.js +179 -0
- package/dist/src/mcp-server.d.ts +1 -0
- package/dist/src/mcp-server.js +20 -22
- package/dist/src/mcp-tools/agent-execute-core.js +9 -1
- package/dist/src/mcp-tools/agent-tools.d.ts +13 -0
- package/dist/src/mcp-tools/agent-tools.js +63 -12
- package/dist/src/mcp-tools/agentbbs-federation.d.ts +18 -1
- package/dist/src/mcp-tools/agentbbs-federation.js +23 -7
- package/dist/src/mcp-tools/browser-argv.d.ts +27 -0
- package/dist/src/mcp-tools/browser-argv.js +92 -0
- package/dist/src/mcp-tools/browser-session-tools.js +4 -0
- package/dist/src/mcp-tools/browser-tools.d.ts +1 -1
- package/dist/src/mcp-tools/browser-tools.js +25 -8
- package/dist/src/mcp-tools/coordination-tools.js +5 -4
- package/dist/src/mcp-tools/embeddings-tools.js +12 -4
- package/dist/src/mcp-tools/glob-exclusions.d.ts +6 -0
- package/dist/src/mcp-tools/glob-exclusions.js +81 -0
- package/dist/src/mcp-tools/hive-mind-tools.js +104 -35
- package/dist/src/mcp-tools/hooks-tools.js +12 -5
- package/dist/src/mcp-tools/import-limits.d.ts +4 -0
- package/dist/src/mcp-tools/import-limits.js +28 -0
- package/dist/src/mcp-tools/memory-tools.js +68 -72
- package/dist/src/mcp-tools/neural-tools.js +8 -2
- package/dist/src/mcp-tools/path-containment.d.ts +34 -0
- package/dist/src/mcp-tools/path-containment.js +133 -0
- package/dist/src/mcp-tools/performance-tools.js +7 -2
- package/dist/src/mcp-tools/record-store.d.ts +15 -0
- package/dist/src/mcp-tools/record-store.js +117 -3
- package/dist/src/mcp-tools/security-tools.d.ts +9 -0
- package/dist/src/mcp-tools/security-tools.js +39 -3
- package/dist/src/mcp-tools/session-tools.js +9 -41
- package/dist/src/mcp-tools/swarm-tools.js +36 -14
- package/dist/src/mcp-tools/task-tools.js +68 -5
- package/dist/src/mcp-tools/terminal-tools.js +16 -2
- package/dist/src/mcp-tools/validate-input.d.ts +2 -0
- package/dist/src/mcp-tools/validate-input.js +14 -0
- package/dist/src/mcp-tools/workflow-tools.js +13 -12
- package/dist/src/mcp-tools/x-federation-join.js +55 -14
- package/dist/src/memory/intelligence.d.ts +11 -0
- package/dist/src/memory/intelligence.js +84 -6
- package/dist/src/memory/memory-initializer.d.ts +6 -2
- package/dist/src/memory/memory-initializer.js +15 -6
- package/dist/src/ruvector/model-router.d.ts +6 -1
- package/dist/src/ruvector/model-router.js +16 -4
- package/dist/src/services/worker-daemon.js +30 -12
- package/dist/src/transfer/storage/gcs.js +3 -2
- package/package.json +1 -1
- package/plugins/ruflo-metaharness/.claude-plugin/plugin.json +1 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Argument checks for the agent-browser bridge.
|
|
3
|
+
*
|
|
4
|
+
* agent-browser reads its own options anywhere in argv, and forwards `--args`
|
|
5
|
+
* to the Chromium it launches. Both are reachable from MCP tool input, so
|
|
6
|
+
* caller text must never be able to add an option, and launch flags are
|
|
7
|
+
* limited to a fixed list of plain switches.
|
|
8
|
+
*/
|
|
9
|
+
/** Chromium launch switches a caller may request. Exact matches only; none takes a value. */
|
|
10
|
+
export declare const ALLOWED_LAUNCH_ARGS: ReadonlySet<string>;
|
|
11
|
+
/** Returns an error when a launch-flag list contains anything outside the allowlist. */
|
|
12
|
+
export declare function launchArgsError(value: unknown): string | null;
|
|
13
|
+
/**
|
|
14
|
+
* Returns an error when argv contains an option the tools did not mean to
|
|
15
|
+
* pass — for example a selector or typed text of "--executable-path".
|
|
16
|
+
*/
|
|
17
|
+
export declare function commandArgvError(args: readonly string[]): string | null;
|
|
18
|
+
/**
|
|
19
|
+
* Returns an error when a navigation target is not a web page. The browser
|
|
20
|
+
* tools return page text to the caller, so a `file:` URL (or `view-source:`,
|
|
21
|
+
* `chrome:`, `data:` and the like) would read local files or browser state
|
|
22
|
+
* that `http_fetch` never serves. Only http(s) URLs, `about:blank`, and
|
|
23
|
+
* scheme-less targets such as `example.com/path` or `localhost:3000` (which
|
|
24
|
+
* agent-browser opens over http(s)) are accepted.
|
|
25
|
+
*/
|
|
26
|
+
export declare function browserUrlError(value: unknown): string | null;
|
|
27
|
+
//# sourceMappingURL=browser-argv.d.ts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Argument checks for the agent-browser bridge.
|
|
3
|
+
*
|
|
4
|
+
* agent-browser reads its own options anywhere in argv, and forwards `--args`
|
|
5
|
+
* to the Chromium it launches. Both are reachable from MCP tool input, so
|
|
6
|
+
* caller text must never be able to add an option, and launch flags are
|
|
7
|
+
* limited to a fixed list of plain switches.
|
|
8
|
+
*/
|
|
9
|
+
/** Chromium launch switches a caller may request. Exact matches only; none takes a value. */
|
|
10
|
+
export const ALLOWED_LAUNCH_ARGS = new Set([
|
|
11
|
+
'--no-sandbox',
|
|
12
|
+
'--disable-setuid-sandbox',
|
|
13
|
+
'--disable-gpu',
|
|
14
|
+
'--disable-dev-shm-usage',
|
|
15
|
+
'--disable-extensions',
|
|
16
|
+
'--mute-audio',
|
|
17
|
+
'--hide-scrollbars',
|
|
18
|
+
'--incognito',
|
|
19
|
+
]);
|
|
20
|
+
/** The agent-browser options the browser tools themselves emit after a subcommand. */
|
|
21
|
+
const COMMAND_FLAGS = new Set([
|
|
22
|
+
'--wait-until', '--full', '--button', '--count', '--delay', '--text', '--url',
|
|
23
|
+
'-i', '-c', '-d', '-s',
|
|
24
|
+
]);
|
|
25
|
+
/** Returns an error when a launch-flag list contains anything outside the allowlist. */
|
|
26
|
+
export function launchArgsError(value) {
|
|
27
|
+
if (value === undefined || value === null)
|
|
28
|
+
return null;
|
|
29
|
+
if (!Array.isArray(value))
|
|
30
|
+
return 'args must be an array of strings';
|
|
31
|
+
for (const arg of value) {
|
|
32
|
+
if (typeof arg !== 'string')
|
|
33
|
+
return 'args must be an array of strings';
|
|
34
|
+
if (!ALLOWED_LAUNCH_ARGS.has(arg)) {
|
|
35
|
+
return `launch arg not allowed: ${JSON.stringify(arg.slice(0, 80))} (allowed: ${[...ALLOWED_LAUNCH_ARGS].join(', ')})`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Returns an error when argv contains an option the tools did not mean to
|
|
42
|
+
* pass — for example a selector or typed text of "--executable-path".
|
|
43
|
+
*/
|
|
44
|
+
export function commandArgvError(args) {
|
|
45
|
+
if (args[0] === 'open') {
|
|
46
|
+
const urlError = browserUrlError(args[1]);
|
|
47
|
+
if (urlError)
|
|
48
|
+
return urlError;
|
|
49
|
+
}
|
|
50
|
+
for (const arg of args) {
|
|
51
|
+
if (typeof arg !== 'string')
|
|
52
|
+
return 'browser command arguments must be strings';
|
|
53
|
+
if (arg.startsWith('--')) {
|
|
54
|
+
const name = arg.split('=', 1)[0];
|
|
55
|
+
if (!COMMAND_FLAGS.has(name))
|
|
56
|
+
return `value must not start with "--": ${JSON.stringify(arg.slice(0, 80))}`;
|
|
57
|
+
}
|
|
58
|
+
else if (/^-[A-Za-z]$/.test(arg) && !COMMAND_FLAGS.has(arg)) {
|
|
59
|
+
return `value must not be an option: ${JSON.stringify(arg)}`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Returns an error when a navigation target is not a web page. The browser
|
|
66
|
+
* tools return page text to the caller, so a `file:` URL (or `view-source:`,
|
|
67
|
+
* `chrome:`, `data:` and the like) would read local files or browser state
|
|
68
|
+
* that `http_fetch` never serves. Only http(s) URLs, `about:blank`, and
|
|
69
|
+
* scheme-less targets such as `example.com/path` or `localhost:3000` (which
|
|
70
|
+
* agent-browser opens over http(s)) are accepted.
|
|
71
|
+
*/
|
|
72
|
+
export function browserUrlError(value) {
|
|
73
|
+
if (typeof value !== 'string' || !value.trim())
|
|
74
|
+
return 'url must be a non-empty string';
|
|
75
|
+
// Chromium drops tabs and newlines anywhere in a URL and trims leading
|
|
76
|
+
// control characters and spaces, so check what it would actually parse.
|
|
77
|
+
const url = value.replace(/[\t\n\r]/g, '').replace(/^[\u0000- ]+/, '');
|
|
78
|
+
const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(url);
|
|
79
|
+
if (!scheme)
|
|
80
|
+
return null;
|
|
81
|
+
const name = scheme[1].toLowerCase();
|
|
82
|
+
if (name === 'http' || name === 'https')
|
|
83
|
+
return null;
|
|
84
|
+
if (url.toLowerCase() === 'about:blank')
|
|
85
|
+
return null;
|
|
86
|
+
// `host:port` with no scheme, e.g. `localhost:3000/app`. The host must be
|
|
87
|
+
// `localhost` or contain a dot, so `file:1/...` is not mistaken for one.
|
|
88
|
+
if ((name === 'localhost' || name.includes('.')) && /^[a-z0-9.-]+:\d{1,5}(?:[/?#]|$)/i.test(url))
|
|
89
|
+
return null;
|
|
90
|
+
return `url scheme not allowed: ${JSON.stringify(name.slice(0, 40))} (use http or https)`;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=browser-argv.js.map
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* rather than a process crash.
|
|
23
23
|
*/
|
|
24
24
|
import { validateIdentifier, validateText } from './validate-input.js';
|
|
25
|
+
import { browserUrlError } from './browser-argv.js';
|
|
25
26
|
// Pin matches the version in this package's own dependency tree so the npx
|
|
26
27
|
// fallback never downloads a second ruvector. Subcommand surface verified
|
|
27
28
|
// against ruvector@0.2.27 --help: `rvf create/compact/status/derive/segments`
|
|
@@ -144,6 +145,9 @@ export const browserSessionTools = [
|
|
|
144
145
|
const vUrl = validateText(input.url, 'url');
|
|
145
146
|
if (!vUrl.valid)
|
|
146
147
|
return fail(vUrl.error || 'invalid url');
|
|
148
|
+
const urlError = browserUrlError(input.url);
|
|
149
|
+
if (urlError)
|
|
150
|
+
return fail(urlError);
|
|
147
151
|
const vTask = validateText(input.task, 'task');
|
|
148
152
|
if (!vTask.valid)
|
|
149
153
|
return fail(vTask.error || 'invalid task');
|
|
@@ -12,7 +12,7 @@ import type { MCPTool, MCPToolResult } from './types.js';
|
|
|
12
12
|
* Exported so browser-intent-tools.ts (ADR-175 `browser_act`) can reuse the same
|
|
13
13
|
* exec + fallback + session-plumbing instead of duplicating it.
|
|
14
14
|
*/
|
|
15
|
-
export declare function execBrowserCommand(args: string[], session?: string): Promise<MCPToolResult>;
|
|
15
|
+
export declare function execBrowserCommand(args: string[], session?: string, launchArgs?: readonly string[]): Promise<MCPToolResult>;
|
|
16
16
|
/**
|
|
17
17
|
* Browser MCP Tools
|
|
18
18
|
*/
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
import { readFileSync, existsSync } from 'node:fs';
|
|
8
8
|
import { validateIdentifier, validateText } from './validate-input.js';
|
|
9
9
|
import { resolveAgentBrowserLaunch, resolveNpxLaunch } from '../runtime/browser-command.js';
|
|
10
|
+
import { commandArgvError, launchArgsError } from './browser-argv.js';
|
|
11
|
+
import { containedWritePath } from './path-containment.js';
|
|
12
|
+
const SCREENSHOT_EXTENSIONS = ['.png', '.jpg', '.jpeg'];
|
|
10
13
|
// Session registry for multi-session support
|
|
11
14
|
const browserSessions = new Map();
|
|
12
15
|
/** Preserve agent-browser's JSON error on non-zero exit without echoing other process output. */
|
|
@@ -44,9 +47,14 @@ function browserCommandFailure(error, missingExecutable) {
|
|
|
44
47
|
* Exported so browser-intent-tools.ts (ADR-175 `browser_act`) can reuse the same
|
|
45
48
|
* exec + fallback + session-plumbing instead of duplicating it.
|
|
46
49
|
*/
|
|
47
|
-
export async function execBrowserCommand(args, session = 'default') {
|
|
50
|
+
export async function execBrowserCommand(args, session = 'default', launchArgs = []) {
|
|
51
|
+
const argvError = commandArgvError(args) ?? launchArgsError(launchArgs);
|
|
52
|
+
if (argvError)
|
|
53
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: argvError }) }], isError: true };
|
|
48
54
|
const { execFileSync } = await import('child_process');
|
|
49
55
|
const fullArgs = ['--session', session, '--json', ...args];
|
|
56
|
+
if (launchArgs.length > 0)
|
|
57
|
+
fullArgs.push('--args', launchArgs.join(','));
|
|
50
58
|
// ADR-401: fullArgs carries tool-supplied text (fill values, eval scripts,
|
|
51
59
|
// selectors), so no spawn here may use a shell. On Windows the npm shims are
|
|
52
60
|
// resolved to the process behind them instead of enabling `shell: true`,
|
|
@@ -147,7 +155,7 @@ export const browserTools = [
|
|
|
147
155
|
args: {
|
|
148
156
|
type: 'array',
|
|
149
157
|
items: { type: 'string' },
|
|
150
|
-
description: 'Additional Chrome launch
|
|
158
|
+
description: 'Additional Chrome launch switches. Only these are accepted: --no-sandbox, --disable-setuid-sandbox, --disable-gpu, --disable-dev-shm-usage, --disable-extensions, --mute-audio, --hide-scrollbars, --incognito',
|
|
151
159
|
},
|
|
152
160
|
},
|
|
153
161
|
required: ['url'],
|
|
@@ -162,15 +170,16 @@ export const browserTools = [
|
|
|
162
170
|
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vS.error }) }], isError: true };
|
|
163
171
|
}
|
|
164
172
|
const { url, session, waitUntil } = input;
|
|
165
|
-
const
|
|
173
|
+
const vArgs = launchArgsError(input.args);
|
|
174
|
+
if (vArgs)
|
|
175
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: vArgs }) }], isError: true };
|
|
176
|
+
const launchArgs = [...(input.args || [])];
|
|
166
177
|
if (needsNoSandbox() && !launchArgs.includes('--no-sandbox')) {
|
|
167
178
|
launchArgs.push('--no-sandbox');
|
|
168
179
|
}
|
|
169
180
|
const args = ['open', url];
|
|
170
181
|
if (waitUntil)
|
|
171
182
|
args.push('--wait-until', waitUntil);
|
|
172
|
-
if (launchArgs.length > 0)
|
|
173
|
-
args.push('--args', launchArgs.join(','));
|
|
174
183
|
// Create session if new
|
|
175
184
|
const sessionId = session || 'default';
|
|
176
185
|
if (!browserSessions.has(sessionId)) {
|
|
@@ -180,7 +189,7 @@ export const browserTools = [
|
|
|
180
189
|
lastActivity: new Date().toISOString(),
|
|
181
190
|
});
|
|
182
191
|
}
|
|
183
|
-
return execBrowserCommand(args, sessionId);
|
|
192
|
+
return execBrowserCommand(args, sessionId, launchArgs);
|
|
184
193
|
},
|
|
185
194
|
},
|
|
186
195
|
{
|
|
@@ -317,8 +326,16 @@ export const browserTools = [
|
|
|
317
326
|
}
|
|
318
327
|
const { session, path, fullPage } = input;
|
|
319
328
|
const args = ['screenshot'];
|
|
320
|
-
if (path)
|
|
321
|
-
|
|
329
|
+
if (path) {
|
|
330
|
+
// The browser writes the image wherever it is told and replaces what
|
|
331
|
+
// is there, so a caller-named path must stay inside the project.
|
|
332
|
+
const target = containedWritePath(path, SCREENSHOT_EXTENSIONS);
|
|
333
|
+
if (!target) {
|
|
334
|
+
const error = `path must be a new or regular file inside the project ending in ${SCREENSHOT_EXTENSIONS.join(', ')}`;
|
|
335
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: false, error }) }], isError: true };
|
|
336
|
+
}
|
|
337
|
+
args.push(target);
|
|
338
|
+
}
|
|
322
339
|
if (fullPage)
|
|
323
340
|
args.push('--full');
|
|
324
341
|
return execBrowserCommand(args, session);
|
|
@@ -346,11 +346,12 @@ export const coordinationTools = [
|
|
|
346
346
|
}
|
|
347
347
|
if (action === 'heartbeat') {
|
|
348
348
|
const nodeId = input.nodeId;
|
|
349
|
-
if (store.nodes[nodeId]) {
|
|
350
|
-
|
|
351
|
-
store.nodes[nodeId].status = 'active';
|
|
352
|
-
saveCoordStore(store);
|
|
349
|
+
if (!store.nodes[nodeId]) {
|
|
350
|
+
return { success: false, error: 'Node not found' };
|
|
353
351
|
}
|
|
352
|
+
store.nodes[nodeId].lastHeartbeat = new Date().toISOString();
|
|
353
|
+
store.nodes[nodeId].status = 'active';
|
|
354
|
+
saveCoordStore(store);
|
|
354
355
|
return {
|
|
355
356
|
success: true,
|
|
356
357
|
action: 'heartbeat',
|
|
@@ -50,11 +50,14 @@ async function getRealEmbeddingFunction() {
|
|
|
50
50
|
}
|
|
51
51
|
return realEmbeddingFn;
|
|
52
52
|
}
|
|
53
|
-
async function generateRealEmbedding(text, dimension) {
|
|
53
|
+
async function generateRealEmbedding(text, dimension, normalize = true) {
|
|
54
54
|
const realFn = await getRealEmbeddingFunction();
|
|
55
55
|
if (realFn) {
|
|
56
56
|
try {
|
|
57
|
-
|
|
57
|
+
// Default callers stay a single-argument call so existing mocks and
|
|
58
|
+
// the unit-norm path are unchanged. Only an explicit opt-out asks the
|
|
59
|
+
// generator for the pre-L2 vector.
|
|
60
|
+
const result = normalize ? await realFn(text) : await realFn(text, { normalize: false });
|
|
58
61
|
return {
|
|
59
62
|
embedding: result.embedding,
|
|
60
63
|
backend: result.backend ?? 'onnx',
|
|
@@ -76,7 +79,10 @@ async function generateRealEmbedding(text, dimension) {
|
|
|
76
79
|
const seed = hash + i * 1337;
|
|
77
80
|
embedding.push(Math.sin(seed) * Math.cos(seed * 0.5));
|
|
78
81
|
}
|
|
79
|
-
// L2 normalize
|
|
82
|
+
// L2 normalize unless the caller opted out (#4724).
|
|
83
|
+
if (!normalize) {
|
|
84
|
+
return { embedding, backend: 'mock', model: 'hash-fallback' };
|
|
85
|
+
}
|
|
80
86
|
const norm = Math.sqrt(embedding.reduce((sum, x) => sum + x * x, 0));
|
|
81
87
|
return {
|
|
82
88
|
embedding: embedding.map(x => x / norm),
|
|
@@ -256,8 +262,10 @@ export const embeddingsTools = [
|
|
|
256
262
|
return { success: false, error: v.error };
|
|
257
263
|
}
|
|
258
264
|
const useHyperbolic = input.hyperbolic === true && config.hyperbolic.enabled;
|
|
265
|
+
// Default true: omitted and explicit true keep the historical unit vector.
|
|
266
|
+
const shouldNormalize = input.normalize !== false;
|
|
259
267
|
// Generate real ONNX embedding
|
|
260
|
-
const generated = await generateRealEmbedding(text, config.dimension);
|
|
268
|
+
const generated = await generateRealEmbedding(text, config.dimension, shouldNormalize);
|
|
261
269
|
const embedding = generated.embedding;
|
|
262
270
|
let result;
|
|
263
271
|
let geometry;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Bounded glob matching for caller-provided memory-import exclusions. */
|
|
2
|
+
export declare const MAX_EXCLUDE_PATTERNS = 64;
|
|
3
|
+
export declare const MAX_EXCLUDE_PATTERN_LENGTH = 1024;
|
|
4
|
+
export declare const MAX_EXCLUDE_WILDCARDS = 32;
|
|
5
|
+
export declare function compileExclusionPatterns(input: unknown): Array<(path: string) => boolean>;
|
|
6
|
+
//# sourceMappingURL=glob-exclusions.d.ts.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** Bounded glob matching for caller-provided memory-import exclusions. */
|
|
2
|
+
export const MAX_EXCLUDE_PATTERNS = 64;
|
|
3
|
+
export const MAX_EXCLUDE_PATTERN_LENGTH = 1024;
|
|
4
|
+
export const MAX_EXCLUDE_WILDCARDS = 32;
|
|
5
|
+
function tokenize(pattern) {
|
|
6
|
+
const tokens = [];
|
|
7
|
+
let wildcards = 0;
|
|
8
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
9
|
+
const char = pattern[i];
|
|
10
|
+
if (char === '*') {
|
|
11
|
+
let end = i + 1;
|
|
12
|
+
while (pattern[end] === '*')
|
|
13
|
+
end++;
|
|
14
|
+
tokens.push({ kind: end - i > 1 ? 'globstar' : 'star' });
|
|
15
|
+
// Preserve the trailing segment-star in odd runs, including its newline behavior.
|
|
16
|
+
if (end - i > 1 && (end - i) % 2 === 1) {
|
|
17
|
+
tokens.push({ kind: 'star' });
|
|
18
|
+
wildcards++;
|
|
19
|
+
}
|
|
20
|
+
i = end - 1;
|
|
21
|
+
wildcards++;
|
|
22
|
+
}
|
|
23
|
+
else if (char === '?') {
|
|
24
|
+
tokens.push({ kind: 'question' });
|
|
25
|
+
wildcards++;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
tokens.push({ kind: 'literal', value: char });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (wildcards > MAX_EXCLUDE_WILDCARDS) {
|
|
32
|
+
throw new Error(`excludeFilePatterns entries may contain at most ${MAX_EXCLUDE_WILDCARDS} wildcard operators`);
|
|
33
|
+
}
|
|
34
|
+
return tokens;
|
|
35
|
+
}
|
|
36
|
+
function matches(tokens, path) {
|
|
37
|
+
// Each path character visits each token once; wildcard placement cannot cause backtracking.
|
|
38
|
+
let previous = new Uint8Array(tokens.length + 1);
|
|
39
|
+
let current = new Uint8Array(tokens.length + 1);
|
|
40
|
+
previous[0] = 1;
|
|
41
|
+
for (let j = 1; j <= tokens.length; j++) {
|
|
42
|
+
const token = tokens[j - 1];
|
|
43
|
+
if (token.kind === 'star' || token.kind === 'globstar')
|
|
44
|
+
previous[j] = previous[j - 1];
|
|
45
|
+
}
|
|
46
|
+
for (let i = 0; i < path.length; i++) {
|
|
47
|
+
current[0] = 0;
|
|
48
|
+
const char = path[i];
|
|
49
|
+
const inSegment = char !== '/' && char !== '\\';
|
|
50
|
+
for (let j = 1; j <= tokens.length; j++) {
|
|
51
|
+
const token = tokens[j - 1];
|
|
52
|
+
if (token.kind === 'star' || token.kind === 'globstar') {
|
|
53
|
+
const canConsume = token.kind === 'globstar'
|
|
54
|
+
? !'\n\r\u2028\u2029'.includes(char)
|
|
55
|
+
: inSegment;
|
|
56
|
+
current[j] = current[j - 1] || (canConsume ? previous[j] : 0);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const canConsume = token.kind === 'literal' ? token.value === char : inSegment;
|
|
60
|
+
current[j] = canConsume ? previous[j - 1] : 0;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
[previous, current] = [current, previous];
|
|
64
|
+
}
|
|
65
|
+
return previous[tokens.length] === 1;
|
|
66
|
+
}
|
|
67
|
+
export function compileExclusionPatterns(input) {
|
|
68
|
+
if (input === undefined)
|
|
69
|
+
return [];
|
|
70
|
+
if (!Array.isArray(input) || input.length > MAX_EXCLUDE_PATTERNS) {
|
|
71
|
+
throw new Error(`excludeFilePatterns must be an array of at most ${MAX_EXCLUDE_PATTERNS} patterns`);
|
|
72
|
+
}
|
|
73
|
+
return Array.from(input, (pattern) => {
|
|
74
|
+
if (typeof pattern !== 'string' || pattern.length > MAX_EXCLUDE_PATTERN_LENGTH) {
|
|
75
|
+
throw new Error(`excludeFilePatterns entries must be strings of at most ${MAX_EXCLUDE_PATTERN_LENGTH} characters`);
|
|
76
|
+
}
|
|
77
|
+
const tokens = tokenize(pattern);
|
|
78
|
+
return (path) => matches(tokens, path);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=glob-exclusions.js.map
|
|
@@ -8,6 +8,7 @@ import { join } from 'node:path';
|
|
|
8
8
|
import { randomBytes, createHash, timingSafeEqual } from 'node:crypto';
|
|
9
9
|
import { getProjectCwd } from './types.js';
|
|
10
10
|
import { validateIdentifier, validateText } from './validate-input.js';
|
|
11
|
+
import { readPreservedJsonStore } from './record-store.js';
|
|
11
12
|
// Storage paths
|
|
12
13
|
const STORAGE_DIR = '.claude-flow';
|
|
13
14
|
const HIVE_DIR = 'hive-mind';
|
|
@@ -199,17 +200,10 @@ function ensureHiveDir() {
|
|
|
199
200
|
catch { /* not ours / unsupported */ }
|
|
200
201
|
}
|
|
201
202
|
function loadHiveState() {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
return JSON.parse(data);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
catch {
|
|
210
|
-
// Return default state on error
|
|
211
|
-
}
|
|
212
|
-
return {
|
|
203
|
+
// #4737: a parse error used to return a fresh hive, and the next save
|
|
204
|
+
// overwrote state.json (including its capability token). Only a missing
|
|
205
|
+
// file is empty; corrupt bytes are copied aside and the error is reported.
|
|
206
|
+
return readPreservedJsonStore(getHivePath(), 'hive-mind', () => ({
|
|
213
207
|
initialized: false,
|
|
214
208
|
topology: 'mesh',
|
|
215
209
|
workers: [],
|
|
@@ -217,7 +211,7 @@ function loadHiveState() {
|
|
|
217
211
|
sharedMemory: {},
|
|
218
212
|
createdAt: new Date().toISOString(),
|
|
219
213
|
updatedAt: new Date().toISOString(),
|
|
220
|
-
};
|
|
214
|
+
}));
|
|
221
215
|
}
|
|
222
216
|
function saveHiveState(state) {
|
|
223
217
|
ensureHiveDir();
|
|
@@ -243,24 +237,50 @@ function saveHiveState(state) {
|
|
|
243
237
|
throw err;
|
|
244
238
|
}
|
|
245
239
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
240
|
+
function getHiveAgentPath() {
|
|
241
|
+
return join(getProjectCwd(), STORAGE_DIR, 'agents.json');
|
|
242
|
+
}
|
|
243
|
+
function loadAgentStore(storePath = getHiveAgentPath()) {
|
|
244
|
+
let text;
|
|
250
245
|
try {
|
|
251
|
-
|
|
252
|
-
return JSON.parse(readAgentStore(storePath, 'utf-8'));
|
|
253
|
-
}
|
|
246
|
+
text = readFileSync(storePath, 'utf-8');
|
|
254
247
|
}
|
|
255
|
-
catch {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (!agentStoreExists(storeDir)) {
|
|
261
|
-
mkdirAgentStore(storeDir, { recursive: true });
|
|
248
|
+
catch (error) {
|
|
249
|
+
// Only an absent file is empty: never replace corrupt/unreadable data.
|
|
250
|
+
if (error.code === 'ENOENT')
|
|
251
|
+
return { agents: {} };
|
|
252
|
+
throw error;
|
|
262
253
|
}
|
|
263
|
-
|
|
254
|
+
const store = JSON.parse(text);
|
|
255
|
+
if (!store || typeof store !== 'object' || Array.isArray(store)
|
|
256
|
+
|| !store.agents || typeof store.agents !== 'object'
|
|
257
|
+
|| Object.values(store.agents).some(agent => !agent || typeof agent !== 'object' || Array.isArray(agent))) {
|
|
258
|
+
throw new Error(`Invalid agent store at ${storePath}; existing data was preserved`);
|
|
259
|
+
}
|
|
260
|
+
return store;
|
|
261
|
+
}
|
|
262
|
+
function saveAgentStore(store, storePath = getHiveAgentPath()) {
|
|
263
|
+
// A new hive store lives directly in STORAGE_DIR; join only writes stores
|
|
264
|
+
// that already contain its agent, so their parent directories already exist.
|
|
265
|
+
mkdirSync(join(getProjectCwd(), STORAGE_DIR), { recursive: true });
|
|
266
|
+
writeFileSync(storePath, JSON.stringify(store, null, 2), 'utf-8');
|
|
267
|
+
}
|
|
268
|
+
function findAgent(store, agentId) {
|
|
269
|
+
if (Array.isArray(store.agents))
|
|
270
|
+
return store.agents.find(agent => agent.agentId === agentId);
|
|
271
|
+
return Object.prototype.hasOwnProperty.call(store.agents, agentId) ? store.agents[agentId] : undefined;
|
|
272
|
+
}
|
|
273
|
+
const HIVE_ROLES = new Set(['worker', 'specialist', 'scout']);
|
|
274
|
+
function capacityError(state, additionalWorkers) {
|
|
275
|
+
const maxAgents = state.maxAgents ?? 15;
|
|
276
|
+
if (state.workers.length + additionalWorkers <= maxAgents)
|
|
277
|
+
return null;
|
|
278
|
+
return {
|
|
279
|
+
success: false,
|
|
280
|
+
error: `Hive-mind capacity exceeded: ${state.workers.length}/${maxAgents} workers; cannot add ${additionalWorkers}. Leave the hive or initialize with a larger maxAgents.`,
|
|
281
|
+
totalWorkers: state.workers.length,
|
|
282
|
+
maxAgents,
|
|
283
|
+
};
|
|
264
284
|
}
|
|
265
285
|
export const hiveMindTools = [
|
|
266
286
|
{
|
|
@@ -303,8 +323,19 @@ export const hiveMindTools = [
|
|
|
303
323
|
if (!v.valid)
|
|
304
324
|
return { success: false, error: v.error };
|
|
305
325
|
}
|
|
306
|
-
const
|
|
326
|
+
const requestedCount = input.count === undefined ? 1 : input.count;
|
|
327
|
+
if (typeof requestedCount !== 'number' || !Number.isInteger(requestedCount) || requestedCount < 1) {
|
|
328
|
+
return { success: false, error: 'count must be a positive integer' };
|
|
329
|
+
}
|
|
330
|
+
const count = Math.min(requestedCount, 20); // Preserve the per-call cap.
|
|
331
|
+
// Reject the entire batch before creating agents or mutating the roster.
|
|
332
|
+
const full = capacityError(state, count);
|
|
333
|
+
if (full)
|
|
334
|
+
return full;
|
|
307
335
|
const role = input.role || 'worker';
|
|
336
|
+
if (!HIVE_ROLES.has(role)) {
|
|
337
|
+
return { success: false, error: `role must be one of: ${[...HIVE_ROLES].join(', ')}` };
|
|
338
|
+
}
|
|
308
339
|
const agentType = input.agentType || 'worker';
|
|
309
340
|
const prefix = input.prefix || 'hive-worker';
|
|
310
341
|
const agentStore = loadAgentStore();
|
|
@@ -312,7 +343,7 @@ export const hiveMindTools = [
|
|
|
312
343
|
for (let i = 0; i < count; i++) {
|
|
313
344
|
const agentId = `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
314
345
|
// Create agent record (like agent/spawn)
|
|
315
|
-
|
|
346
|
+
const agent = {
|
|
316
347
|
agentId,
|
|
317
348
|
agentType,
|
|
318
349
|
status: 'idle',
|
|
@@ -322,6 +353,10 @@ export const hiveMindTools = [
|
|
|
322
353
|
createdAt: new Date().toISOString(),
|
|
323
354
|
domain: 'hive-mind',
|
|
324
355
|
};
|
|
356
|
+
if (Array.isArray(agentStore.agents))
|
|
357
|
+
agentStore.agents.push(agent);
|
|
358
|
+
else
|
|
359
|
+
agentStore.agents[agentId] = agent;
|
|
325
360
|
// Join to hive-mind (like hive-mind/join)
|
|
326
361
|
if (!state.workers.includes(agentId)) {
|
|
327
362
|
state.workers.push(agentId);
|
|
@@ -521,7 +556,7 @@ export const hiveMindTools = [
|
|
|
521
556
|
term: state.queen.term,
|
|
522
557
|
} : { id: 'N/A', status: 'offline', load: 0, tasksQueued: 0 },
|
|
523
558
|
workers: state.workers.map(w => {
|
|
524
|
-
const agent = agentStore
|
|
559
|
+
const agent = findAgent(agentStore, w);
|
|
525
560
|
return {
|
|
526
561
|
id: w,
|
|
527
562
|
type: agent?.agentType || 'worker',
|
|
@@ -601,14 +636,43 @@ export const hiveMindTools = [
|
|
|
601
636
|
if (tokenError) {
|
|
602
637
|
return { success: false, agentId, error: tokenError };
|
|
603
638
|
}
|
|
604
|
-
|
|
639
|
+
const alreadyJoined = state.workers.includes(agentId);
|
|
640
|
+
// Retrying a registered member does not consume another slot.
|
|
641
|
+
if (!alreadyJoined) {
|
|
642
|
+
const full = capacityError(state, 1);
|
|
643
|
+
if (full)
|
|
644
|
+
return { ...full, agentId };
|
|
645
|
+
}
|
|
646
|
+
// Both registration paths are legitimate (#1916). Update each existing
|
|
647
|
+
// record in place, preserving metadata and map/array representation.
|
|
648
|
+
const paths = [
|
|
649
|
+
join(getProjectCwd(), STORAGE_DIR, 'agents', 'store.json'),
|
|
650
|
+
getHiveAgentPath(),
|
|
651
|
+
];
|
|
652
|
+
const records = paths.flatMap(path => {
|
|
653
|
+
const store = loadAgentStore(path);
|
|
654
|
+
const agent = findAgent(store, agentId);
|
|
655
|
+
return agent ? [{ path, store, agent }] : [];
|
|
656
|
+
});
|
|
657
|
+
if (records.length === 0) {
|
|
658
|
+
return { success: false, agentId, error: 'Agent not found. Register it with agent_spawn or hive-mind_spawn first.' };
|
|
659
|
+
}
|
|
660
|
+
const role = input.role || 'worker';
|
|
661
|
+
if (!HIVE_ROLES.has(role)) {
|
|
662
|
+
return { success: false, agentId, error: `role must be one of: ${[...HIVE_ROLES].join(', ')}` };
|
|
663
|
+
}
|
|
664
|
+
for (const { path, store, agent } of records) {
|
|
665
|
+
agent.config = { ...agent.config, role, hiveRole: role };
|
|
666
|
+
saveAgentStore(store, path);
|
|
667
|
+
}
|
|
668
|
+
if (!alreadyJoined) {
|
|
605
669
|
state.workers.push(agentId);
|
|
606
670
|
saveHiveState(state);
|
|
607
671
|
}
|
|
608
672
|
return {
|
|
609
673
|
success: true,
|
|
610
674
|
agentId,
|
|
611
|
-
role
|
|
675
|
+
role,
|
|
612
676
|
totalWorkers: state.workers.length,
|
|
613
677
|
joinedAt: new Date().toISOString(),
|
|
614
678
|
};
|
|
@@ -1073,9 +1137,14 @@ export const hiveMindTools = [
|
|
|
1073
1137
|
}
|
|
1074
1138
|
// Clear workers from agent store
|
|
1075
1139
|
const agentStore = loadAgentStore();
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1140
|
+
if (Array.isArray(agentStore.agents)) {
|
|
1141
|
+
agentStore.agents = agentStore.agents.filter(agent => !state.workers.includes(agent.agentId));
|
|
1142
|
+
}
|
|
1143
|
+
else {
|
|
1144
|
+
for (const workerId of state.workers) {
|
|
1145
|
+
if (Object.prototype.hasOwnProperty.call(agentStore.agents, workerId)) {
|
|
1146
|
+
delete agentStore.agents[workerId];
|
|
1147
|
+
}
|
|
1079
1148
|
}
|
|
1080
1149
|
}
|
|
1081
1150
|
saveAgentStore(agentStore);
|
|
@@ -2288,7 +2288,8 @@ export const hooksTransfer = {
|
|
|
2288
2288
|
},
|
|
2289
2289
|
handler: async (params) => {
|
|
2290
2290
|
const sourcePath = params.sourcePath;
|
|
2291
|
-
|
|
2291
|
+
// #4728: an explicit 0 must not fall through to 0.7 (`0 || 0.7` is 0.7).
|
|
2292
|
+
const minConfidence = params.minConfidence ?? 0.7;
|
|
2292
2293
|
const filter = params.filter;
|
|
2293
2294
|
{
|
|
2294
2295
|
const v = validatePath(sourcePath, 'sourcePath');
|
|
@@ -3474,7 +3475,8 @@ export const hooksPatternStore = {
|
|
|
3474
3475
|
handler: async (params) => {
|
|
3475
3476
|
const pattern = params.pattern;
|
|
3476
3477
|
const type = params.type || 'general';
|
|
3477
|
-
|
|
3478
|
+
// #4728: an explicit 0 must not fall through to 0.8 (`0 || 0.8` is 0.8).
|
|
3479
|
+
const confidence = params.confidence ?? 0.8;
|
|
3478
3480
|
const metadata = params.metadata;
|
|
3479
3481
|
const timestamp = new Date().toISOString();
|
|
3480
3482
|
{
|
|
@@ -3559,7 +3561,8 @@ export const hooksPatternSearch = {
|
|
|
3559
3561
|
handler: async (params) => {
|
|
3560
3562
|
const query = params.query;
|
|
3561
3563
|
const topK = params.topK || 5;
|
|
3562
|
-
|
|
3564
|
+
// #4728: an explicit 0 must not fall through to 0.3 (`0 || 0.3` is 0.3).
|
|
3565
|
+
const minConfidence = params.minConfidence ?? 0.3;
|
|
3563
3566
|
const namespace = params.namespace || 'pattern';
|
|
3564
3567
|
const strategy = params.strategy || 'semantic';
|
|
3565
3568
|
{
|
|
@@ -4865,7 +4868,8 @@ export const hooksWorkerDetect = {
|
|
|
4865
4868
|
handler: async (params) => {
|
|
4866
4869
|
const prompt = params.prompt;
|
|
4867
4870
|
const autoDispatch = params.autoDispatch;
|
|
4868
|
-
|
|
4871
|
+
// #4728: an explicit 0 must not fall through to 0.5 (`0 || 0.5` is 0.5).
|
|
4872
|
+
const minConfidence = params.minConfidence ?? 0.5;
|
|
4869
4873
|
{
|
|
4870
4874
|
const v = validateText(prompt, 'prompt');
|
|
4871
4875
|
if (!v.valid)
|
|
@@ -4968,7 +4972,10 @@ export const hooksModelRoute = {
|
|
|
4968
4972
|
implementation: 'fallback',
|
|
4969
4973
|
};
|
|
4970
4974
|
}
|
|
4971
|
-
const result = await router.route(task
|
|
4975
|
+
const result = await router.route(task, undefined, {
|
|
4976
|
+
preferCost: typeof params.preferCost === 'boolean' ? params.preferCost : undefined,
|
|
4977
|
+
preferSpeed: typeof params.preferSpeed === 'boolean' ? params.preferSpeed : undefined,
|
|
4978
|
+
});
|
|
4972
4979
|
return {
|
|
4973
4980
|
model: result.model,
|
|
4974
4981
|
confidence: result.confidence,
|