@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.
Files changed (83) hide show
  1. package/.claude/helpers/auto-memory-hook.mjs +17 -0
  2. package/.claude/helpers/helpers.manifest.json +6 -6
  3. package/.claude/helpers/hook-handler.cjs +23 -10
  4. package/.claude/helpers/intelligence.cjs +23 -1
  5. package/.claude/helpers/statusline.cjs +29 -14
  6. package/README.md +18 -3
  7. package/bin/cli.js +13 -18
  8. package/bin/mcp-server.js +13 -18
  9. package/catalog-manifest.json +2 -2
  10. package/dist/src/appliance/rvfa-distribution.js +2 -2
  11. package/dist/src/benchmarks/gaia-critic.d.ts +1 -7
  12. package/dist/src/benchmarks/gaia-critic.js +1 -7
  13. package/dist/src/benchmarks/gaia-decomposer.d.ts +1 -8
  14. package/dist/src/benchmarks/gaia-decomposer.js +1 -8
  15. package/dist/src/commands/agent-wasm.js +2 -2
  16. package/dist/src/commands/analyze.js +1 -1
  17. package/dist/src/commands/daemon.js +1 -1
  18. package/dist/src/commands/doctor.js +3 -2
  19. package/dist/src/commands/guidance.js +9 -9
  20. package/dist/src/commands/hooks.js +13 -8
  21. package/dist/src/commands/init.js +14 -16
  22. package/dist/src/commands/memory.js +4 -3
  23. package/dist/src/commands/neural.js +8 -6
  24. package/dist/src/commands/route.js +1 -1
  25. package/dist/src/commands/ruvector/backup.js +7 -5
  26. package/dist/src/commands/ruvector/benchmark.js +6 -1
  27. package/dist/src/commands/ruvector/import.js +19 -5
  28. package/dist/src/commands/ruvector/init.js +1 -1
  29. package/dist/src/commands/start.js +1 -1
  30. package/dist/src/commands/status.js +1 -1
  31. package/dist/src/commands/task.js +18 -1
  32. package/dist/src/commands/update.js +1 -1
  33. package/dist/src/funnel/local-signals.js +7 -2
  34. package/dist/src/init/helpers-generator.js +16 -1
  35. package/dist/src/mcp-protocol.d.ts +90 -0
  36. package/dist/src/mcp-protocol.js +179 -0
  37. package/dist/src/mcp-server.d.ts +1 -0
  38. package/dist/src/mcp-server.js +20 -22
  39. package/dist/src/mcp-tools/agent-execute-core.js +9 -1
  40. package/dist/src/mcp-tools/agent-tools.d.ts +13 -0
  41. package/dist/src/mcp-tools/agent-tools.js +63 -12
  42. package/dist/src/mcp-tools/agentbbs-federation.d.ts +18 -1
  43. package/dist/src/mcp-tools/agentbbs-federation.js +23 -7
  44. package/dist/src/mcp-tools/browser-argv.d.ts +27 -0
  45. package/dist/src/mcp-tools/browser-argv.js +92 -0
  46. package/dist/src/mcp-tools/browser-session-tools.js +4 -0
  47. package/dist/src/mcp-tools/browser-tools.d.ts +1 -1
  48. package/dist/src/mcp-tools/browser-tools.js +25 -8
  49. package/dist/src/mcp-tools/coordination-tools.js +5 -4
  50. package/dist/src/mcp-tools/embeddings-tools.js +12 -4
  51. package/dist/src/mcp-tools/glob-exclusions.d.ts +6 -0
  52. package/dist/src/mcp-tools/glob-exclusions.js +81 -0
  53. package/dist/src/mcp-tools/hive-mind-tools.js +104 -35
  54. package/dist/src/mcp-tools/hooks-tools.js +12 -5
  55. package/dist/src/mcp-tools/import-limits.d.ts +4 -0
  56. package/dist/src/mcp-tools/import-limits.js +28 -0
  57. package/dist/src/mcp-tools/memory-tools.js +68 -72
  58. package/dist/src/mcp-tools/neural-tools.js +8 -2
  59. package/dist/src/mcp-tools/path-containment.d.ts +34 -0
  60. package/dist/src/mcp-tools/path-containment.js +133 -0
  61. package/dist/src/mcp-tools/performance-tools.js +7 -2
  62. package/dist/src/mcp-tools/record-store.d.ts +15 -0
  63. package/dist/src/mcp-tools/record-store.js +117 -3
  64. package/dist/src/mcp-tools/security-tools.d.ts +9 -0
  65. package/dist/src/mcp-tools/security-tools.js +39 -3
  66. package/dist/src/mcp-tools/session-tools.js +9 -41
  67. package/dist/src/mcp-tools/swarm-tools.js +36 -14
  68. package/dist/src/mcp-tools/task-tools.js +68 -5
  69. package/dist/src/mcp-tools/terminal-tools.js +16 -2
  70. package/dist/src/mcp-tools/validate-input.d.ts +2 -0
  71. package/dist/src/mcp-tools/validate-input.js +14 -0
  72. package/dist/src/mcp-tools/workflow-tools.js +13 -12
  73. package/dist/src/mcp-tools/x-federation-join.js +55 -14
  74. package/dist/src/memory/intelligence.d.ts +11 -0
  75. package/dist/src/memory/intelligence.js +84 -6
  76. package/dist/src/memory/memory-initializer.d.ts +6 -2
  77. package/dist/src/memory/memory-initializer.js +15 -6
  78. package/dist/src/ruvector/model-router.d.ts +6 -1
  79. package/dist/src/ruvector/model-router.js +16 -4
  80. package/dist/src/services/worker-daemon.js +30 -12
  81. package/dist/src/transfer/storage/gcs.js +3 -2
  82. package/package.json +1 -1
  83. 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 args (e.g., ["--no-sandbox", "--disable-dev-shm-usage"])',
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 launchArgs = input.args || [];
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
- args.push(path);
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
- store.nodes[nodeId].lastHeartbeat = new Date().toISOString();
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
- const result = await realFn(text);
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
- try {
203
- const path = getHivePath();
204
- if (existsSync(path)) {
205
- const data = readFileSync(path, 'utf-8');
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
- // Import agent store helpers for spawn functionality
247
- import { existsSync as agentStoreExists, readFileSync as readAgentStore, writeFileSync as writeAgentStore, mkdirSync as mkdirAgentStore } from 'node:fs';
248
- function loadAgentStore() {
249
- const storePath = join(getProjectCwd(), '.claude-flow', 'agents.json');
240
+ function getHiveAgentPath() {
241
+ return join(getProjectCwd(), STORAGE_DIR, 'agents.json');
242
+ }
243
+ function loadAgentStore(storePath = getHiveAgentPath()) {
244
+ let text;
250
245
  try {
251
- if (agentStoreExists(storePath)) {
252
- return JSON.parse(readAgentStore(storePath, 'utf-8'));
253
- }
246
+ text = readFileSync(storePath, 'utf-8');
254
247
  }
255
- catch { /* ignore */ }
256
- return { agents: {} };
257
- }
258
- function saveAgentStore(store) {
259
- const storeDir = join(getProjectCwd(), '.claude-flow');
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
- writeAgentStore(join(storeDir, 'agents.json'), JSON.stringify(store, null, 2), 'utf-8');
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 count = Math.min(Math.max(1, input.count || 1), 20); // Cap at 20
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
- agentStore.agents[agentId] = {
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.agents[w];
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
- if (!state.workers.includes(agentId)) {
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: input.role || 'worker',
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
- for (const workerId of state.workers) {
1077
- if (agentStore.agents[workerId]) {
1078
- delete agentStore.agents[workerId];
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
- const minConfidence = params.minConfidence || 0.7;
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
- const confidence = params.confidence || 0.8;
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
- const minConfidence = params.minConfidence || 0.3;
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
- const minConfidence = params.minConfidence || 0.5;
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,
@@ -0,0 +1,4 @@
1
+ export declare const MAX_IMPORT_BYTES: number;
2
+ /** Bound allocation and reads even if an import file grows after its size check. */
3
+ export declare function readImportFile(file: string): Buffer;
4
+ //# sourceMappingURL=import-limits.d.ts.map