claude-flow 3.57.2 → 3.58.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.claude/proven-config.json +1 -1
  2. package/.claude-plugin/marketplace.json +11 -0
  3. package/README.md +1 -0
  4. package/node_modules/@claude-flow/codex/README.md +78 -0
  5. package/node_modules/@claude-flow/codex/dist/activity/cli.d.ts +3 -0
  6. package/node_modules/@claude-flow/codex/dist/activity/cli.d.ts.map +1 -0
  7. package/node_modules/@claude-flow/codex/dist/activity/cli.js +115 -0
  8. package/node_modules/@claude-flow/codex/dist/activity/cli.js.map +1 -0
  9. package/node_modules/@claude-flow/codex/dist/activity/files.d.ts +14 -0
  10. package/node_modules/@claude-flow/codex/dist/activity/files.d.ts.map +1 -0
  11. package/node_modules/@claude-flow/codex/dist/activity/files.js +142 -0
  12. package/node_modules/@claude-flow/codex/dist/activity/files.js.map +1 -0
  13. package/node_modules/@claude-flow/codex/dist/activity/parser.d.ts +21 -0
  14. package/node_modules/@claude-flow/codex/dist/activity/parser.d.ts.map +1 -0
  15. package/node_modules/@claude-flow/codex/dist/activity/parser.js +179 -0
  16. package/node_modules/@claude-flow/codex/dist/activity/parser.js.map +1 -0
  17. package/node_modules/@claude-flow/codex/dist/activity/reader.d.ts +20 -0
  18. package/node_modules/@claude-flow/codex/dist/activity/reader.d.ts.map +1 -0
  19. package/node_modules/@claude-flow/codex/dist/activity/reader.js +106 -0
  20. package/node_modules/@claude-flow/codex/dist/activity/reader.js.map +1 -0
  21. package/node_modules/@claude-flow/codex/dist/activity/sanitize.d.ts +4 -0
  22. package/node_modules/@claude-flow/codex/dist/activity/sanitize.d.ts.map +1 -0
  23. package/node_modules/@claude-flow/codex/dist/activity/sanitize.js +27 -0
  24. package/node_modules/@claude-flow/codex/dist/activity/sanitize.js.map +1 -0
  25. package/node_modules/@claude-flow/codex/dist/activity/types.d.ts +31 -0
  26. package/node_modules/@claude-flow/codex/dist/activity/types.d.ts.map +1 -0
  27. package/node_modules/@claude-flow/codex/dist/activity/types.js +5 -0
  28. package/node_modules/@claude-flow/codex/dist/activity/types.js.map +1 -0
  29. package/node_modules/@claude-flow/codex/dist/activity/viewer.d.ts +21 -0
  30. package/node_modules/@claude-flow/codex/dist/activity/viewer.d.ts.map +1 -0
  31. package/node_modules/@claude-flow/codex/dist/activity/viewer.js +168 -0
  32. package/node_modules/@claude-flow/codex/dist/activity/viewer.js.map +1 -0
  33. package/node_modules/@claude-flow/codex/dist/cli.js +3 -0
  34. package/node_modules/@claude-flow/codex/dist/cli.js.map +1 -1
  35. package/node_modules/@claude-flow/mcp/dist/server.d.ts.map +1 -1
  36. package/node_modules/@claude-flow/mcp/dist/server.js +5 -3
  37. package/node_modules/@claude-flow/mcp/dist/server.js.map +1 -1
  38. package/node_modules/@claude-flow/mcp/dist/types.d.ts +11 -0
  39. package/node_modules/@claude-flow/mcp/dist/types.d.ts.map +1 -1
  40. package/node_modules/@claude-flow/mcp/dist/types.js.map +1 -1
  41. package/package.json +1 -1
  42. package/v3/@claude-flow/cli/README.md +1 -0
  43. package/v3/@claude-flow/cli/catalog-manifest.json +2 -2
  44. package/v3/@claude-flow/cli/dist/src/commands/agent.js +4 -1
  45. package/v3/@claude-flow/cli/dist/src/commands/mcp.js +66 -6
  46. package/v3/@claude-flow/cli/dist/src/commands/memory.js +53 -2
  47. package/v3/@claude-flow/cli/dist/src/mcp-server.d.ts +5 -0
  48. package/v3/@claude-flow/cli/dist/src/mcp-server.js +39 -0
  49. package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-tools.js +28 -0
  50. package/v3/@claude-flow/cli/dist/src/mcp-tools/claims-tools.js +25 -15
  51. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +13 -3
  52. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +43 -4
  53. package/v3/@claude-flow/cli/dist/src/memory/memory-peek.d.ts +28 -0
  54. package/v3/@claude-flow/cli/dist/src/memory/memory-peek.js +222 -0
  55. package/v3/@claude-flow/cli/dist/src/memory/model-cache-dir.d.ts +11 -0
  56. package/v3/@claude-flow/cli/dist/src/memory/model-cache-dir.js +17 -0
  57. package/v3/@claude-flow/cli/dist/src/parser.js +3 -2
  58. package/v3/@claude-flow/cli/dist/src/ruvector/task-embedder.js +38 -4
  59. package/v3/@claude-flow/cli/package.json +1 -1
@@ -216,6 +216,7 @@ export const agentTools = [
216
216
  properties: {
217
217
  agentType: { type: 'string', description: 'Type of agent to spawn' },
218
218
  agentId: { type: 'string', description: 'Optional custom agent ID' },
219
+ name: { type: 'string', description: 'Optional display name for the agent (shown by agent_list, agent_status and ruflo-console)' },
219
220
  // #2085 — accept swarmId so spawned agents register in the
220
221
  // swarm.agents array that swarm_status reports. Omit to register
221
222
  // with the most-recently-created swarm.
@@ -241,6 +242,18 @@ export const agentTools = [
241
242
  }
242
243
  const agentId = input.agentId || `agent-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
243
244
  const agentType = input.agentType;
245
+ // #4300 — older callers (including `agent spawn` before this fix) send
246
+ // the name only as metadata.name.
247
+ const metadata = input.metadata;
248
+ const rawName = input.name ?? metadata?.name;
249
+ let name;
250
+ if (rawName !== undefined && rawName !== '') {
251
+ const vName = validateText(rawName, 'name', 200);
252
+ if (!vName.valid) {
253
+ return { success: false, error: `Input validation failed: ${vName.error}` };
254
+ }
255
+ name = vName.sanitized;
256
+ }
244
257
  const config = input.config || {};
245
258
  // Add explicit model to config if provided
246
259
  if (input.model) {
@@ -262,6 +275,7 @@ export const agentTools = [
262
275
  const agent = {
263
276
  agentId,
264
277
  agentType,
278
+ ...(name ? { name } : {}),
265
279
  status: 'idle',
266
280
  health: 1.0,
267
281
  taskCount: 0,
@@ -347,6 +361,7 @@ export const agentTools = [
347
361
  success: true,
348
362
  agentId,
349
363
  agentType: agent.agentType,
364
+ ...(agent.name ? { name: agent.name } : {}),
350
365
  model: agent.model,
351
366
  modelRoutedBy: routingResult.routedBy,
352
367
  ...(routingResult.modelId ? { modelId: routingResult.modelId } : {}),
@@ -501,6 +516,7 @@ export const agentTools = [
501
516
  return {
502
517
  agentId: agent.agentId,
503
518
  agentType: agent.agentType,
519
+ ...(agent.name ? { name: agent.name } : {}),
504
520
  status: agent.status,
505
521
  health: agent.health,
506
522
  taskCount: agent.taskCount,
@@ -525,6 +541,7 @@ export const agentTools = [
525
541
  properties: {
526
542
  status: { type: 'string', description: 'Filter by status' },
527
543
  domain: { type: 'string', description: 'Filter by domain' },
544
+ agentType: { type: 'string', description: 'Filter by agent type' },
528
545
  includeTerminated: { type: 'boolean', description: 'Include terminated agents' },
529
546
  },
530
547
  },
@@ -539,6 +556,11 @@ export const agentTools = [
539
556
  if (!v.valid)
540
557
  return { agents: [], total: 0, error: `Input validation failed: ${v.error}` };
541
558
  }
559
+ if (input.agentType) {
560
+ const v = validateIdentifier(input.agentType, 'agentType');
561
+ if (!v.valid)
562
+ return { agents: [], total: 0, error: `Input validation failed: ${v.error}` };
563
+ }
542
564
  let agents = Object.values(loadAllAgents()); // #1916: includes hive-mind-spawned workers
543
565
  // Filter by status
544
566
  if (input.status) {
@@ -551,10 +573,15 @@ export const agentTools = [
551
573
  if (input.domain) {
552
574
  agents = agents.filter(a => a.domain === input.domain);
553
575
  }
576
+ // Filter by agent type
577
+ if (input.agentType) {
578
+ agents = agents.filter(a => a.agentType === input.agentType);
579
+ }
554
580
  return {
555
581
  agents: agents.map(a => ({
556
582
  agentId: a.agentId,
557
583
  agentType: a.agentType,
584
+ ...(a.name ? { name: a.name } : {}),
558
585
  status: a.status,
559
586
  health: a.health,
560
587
  taskCount: a.taskCount,
@@ -565,6 +592,7 @@ export const agentTools = [
565
592
  filters: {
566
593
  status: input.status,
567
594
  domain: input.domain,
595
+ agentType: input.agentType,
568
596
  includeTerminated: input.includeTerminated,
569
597
  },
570
598
  };
@@ -13,6 +13,7 @@ import { existsSync, writeFileSync, mkdirSync } from 'fs';
13
13
  import { join, resolve } from 'path';
14
14
  const CLAIMS_DIR = '.claude-flow/claims';
15
15
  const CLAIMS_FILE = 'claims.json';
16
+ const DEFAULT_MAX_CLAIMS = 5;
16
17
  function getClaimsPath() {
17
18
  return resolve(join(CLAIMS_DIR, CLAIMS_FILE));
18
19
  }
@@ -47,7 +48,7 @@ function parseClaimant(str) {
47
48
  export const claimsTools = [
48
49
  {
49
50
  name: 'claims_claim',
50
- description: 'Claim an issue for work (human or agent) Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
51
+ description: 'Claim an issue for work (human or agent). Use when multiple agents might pick up the same issue — claiming makes ownership visible via claims_list before work starts.',
51
52
  category: 'claims',
52
53
  inputSchema: {
53
54
  type: 'object',
@@ -64,6 +65,10 @@ export const claimsTools = [
64
65
  type: 'string',
65
66
  description: 'Optional context about the work approach',
66
67
  },
68
+ ttlMs: {
69
+ type: 'number',
70
+ description: 'Optional time-to-live in milliseconds; when set, the claim records an expiresAt timestamp. Informational only today — no handler currently enforces it (e.g. claims_steal still requires claims_mark-stealable rather than checking expiry).',
71
+ },
67
72
  },
68
73
  required: ['issueId', 'claimant'],
69
74
  },
@@ -71,6 +76,7 @@ export const claimsTools = [
71
76
  const issueId = input.issueId;
72
77
  const claimantStr = input.claimant;
73
78
  const context = input.context;
79
+ const ttlMs = input.ttlMs;
74
80
  {
75
81
  const v = validateIdentifier(issueId, 'issueId');
76
82
  if (!v.valid)
@@ -86,6 +92,9 @@ export const claimsTools = [
86
92
  if (!v.valid)
87
93
  return { success: false, error: v.error };
88
94
  }
95
+ if (ttlMs !== undefined && (typeof ttlMs !== 'number' || !Number.isFinite(ttlMs) || ttlMs <= 0)) {
96
+ return { success: false, error: 'ttlMs must be a positive number' };
97
+ }
89
98
  const claimant = parseClaimant(claimantStr);
90
99
  if (!claimant) {
91
100
  return { success: false, error: 'Invalid claimant format. Use "human:userId:name" or "agent:agentId:agentType"' };
@@ -109,6 +118,7 @@ export const claimsTools = [
109
118
  statusChangedAt: now,
110
119
  progress: 0,
111
120
  context,
121
+ expiresAt: ttlMs !== undefined ? new Date(Date.now() + ttlMs).toISOString() : undefined,
112
122
  };
113
123
  store.claims[issueId] = claim;
114
124
  saveClaims(store);
@@ -121,7 +131,7 @@ export const claimsTools = [
121
131
  },
122
132
  {
123
133
  name: 'claims_release',
124
- description: 'Release a claim on an issue Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
134
+ description: 'Release a claim on an issue. Use when the claimant stops working on it, freeing it for another claims_claim instead of leaving it claimed indefinitely.',
125
135
  category: 'claims',
126
136
  inputSchema: {
127
137
  type: 'object',
@@ -186,7 +196,7 @@ export const claimsTools = [
186
196
  },
187
197
  {
188
198
  name: 'claims_handoff',
189
- description: 'Request handoff of an issue to another claimant Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
199
+ description: 'Request handoff of an issue to another claimant. Use when transferring in-progress work to a different agent, instead of releasing and having them claim it fresh.',
190
200
  category: 'claims',
191
201
  inputSchema: {
192
202
  type: 'object',
@@ -270,7 +280,7 @@ export const claimsTools = [
270
280
  },
271
281
  {
272
282
  name: 'claims_accept-handoff',
273
- description: 'Accept a pending handoff Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
283
+ description: 'Accept a pending handoff. Use when the receiving claimant confirms a claims_handoff request, completing the ownership transfer.',
274
284
  category: 'claims',
275
285
  inputSchema: {
276
286
  type: 'object',
@@ -333,7 +343,7 @@ export const claimsTools = [
333
343
  },
334
344
  {
335
345
  name: 'claims_status',
336
- description: 'Update claim status Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
346
+ description: 'Update claim status. Use when progress changes (active, blocked, review-requested, etc.) so claims_list and claims_load reflect current state.',
337
347
  category: 'claims',
338
348
  inputSchema: {
339
349
  type: 'object',
@@ -398,7 +408,7 @@ export const claimsTools = [
398
408
  },
399
409
  {
400
410
  name: 'claims_list',
401
- description: 'List all claims or filter by criteria Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
411
+ description: 'List all claims or filter by criteria. Use when checking what is already claimed before starting new work, instead of guessing from issue comments.',
402
412
  category: 'claims',
403
413
  inputSchema: {
404
414
  type: 'object',
@@ -453,7 +463,7 @@ export const claimsTools = [
453
463
  },
454
464
  {
455
465
  name: 'claims_mark-stealable',
456
- description: 'Mark an issue as stealable by other agents Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
466
+ description: 'Mark an issue as stealable by other agents. Use when a claimant is overloaded or stalled and wants another agent to pick it up via claims_steal.',
457
467
  category: 'claims',
458
468
  inputSchema: {
459
469
  type: 'object',
@@ -521,7 +531,7 @@ export const claimsTools = [
521
531
  },
522
532
  {
523
533
  name: 'claims_steal',
524
- description: 'Steal a stealable issue Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
534
+ description: 'Steal a stealable issue. Use when picking up work already marked stealable via claims_mark-stealable, rather than claiming an issue someone else still owns.',
525
535
  category: 'claims',
526
536
  inputSchema: {
527
537
  type: 'object',
@@ -592,7 +602,7 @@ export const claimsTools = [
592
602
  },
593
603
  {
594
604
  name: 'claims_stealable',
595
- description: 'List all stealable issues Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
605
+ description: 'List all stealable issues. Use when looking for available work that has been freed up via claims_mark-stealable.',
596
606
  category: 'claims',
597
607
  inputSchema: {
598
608
  type: 'object',
@@ -628,7 +638,7 @@ export const claimsTools = [
628
638
  },
629
639
  {
630
640
  name: 'claims_load',
631
- description: 'Get agent load information Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
641
+ description: 'Get agent load information. Use when deciding which agent should take new work by comparing claim counts against claims_rebalance targets.',
632
642
  category: 'claims',
633
643
  inputSchema: {
634
644
  type: 'object',
@@ -689,8 +699,8 @@ export const claimsTools = [
689
699
  agentId: l.agentId,
690
700
  agentType: l.agentType,
691
701
  claimCount: l.claims.length,
692
- maxClaims: 5, // Default max
693
- utilization: l.claims.length / 5,
702
+ maxClaims: DEFAULT_MAX_CLAIMS,
703
+ utilization: l.claims.length / DEFAULT_MAX_CLAIMS,
694
704
  blockedCount: l.blockedCount,
695
705
  claims: l.claims.map(c => ({
696
706
  issueId: c.issueId,
@@ -711,7 +721,7 @@ export const claimsTools = [
711
721
  },
712
722
  {
713
723
  name: 'claims_board',
714
- description: 'Get a visual board view of all claims Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
724
+ description: 'Get a visual board view of all claims. Use when scanning overall claim status across issues and agents instead of composing the view from claims_list yourself.',
715
725
  category: 'claims',
716
726
  inputSchema: {
717
727
  type: 'object',
@@ -760,7 +770,7 @@ export const claimsTools = [
760
770
  },
761
771
  {
762
772
  name: 'claims_rebalance',
763
- description: 'Suggest or apply load rebalancing across agents Use when nothing native covers per-agent capability gating — Claude Code agents have file-system access by default. Pair claims_grant + claims_check before letting an agent run privileged ops. For trusted in-session work, no claims call is needed.',
773
+ description: 'Suggest or apply load rebalancing across agents. Use when some agents are overloaded and others idle, instead of manually reassigning claims one at a time.',
764
774
  category: 'claims',
765
775
  inputSchema: {
766
776
  type: 'object',
@@ -794,7 +804,7 @@ export const claimsTools = [
794
804
  agentLoads.get(key).claims.push(claim);
795
805
  }
796
806
  const loads = Array.from(agentLoads.values());
797
- const maxClaims = 5;
807
+ const maxClaims = DEFAULT_MAX_CLAIMS;
798
808
  const avgLoad = loads.length > 0
799
809
  ? loads.reduce((sum, l) => sum + l.claims.length, 0) / loads.length
800
810
  : 0;
@@ -5,7 +5,7 @@ import { feedbackPatternsSchema, validateFeedbackPatterns } from '../memory/feed
5
5
  */
6
6
  import { mkdirSync, writeFileSync, existsSync, readFileSync, statSync, unlinkSync, readdirSync } from 'fs';
7
7
  import * as nodeFs from 'fs';
8
- import { dirname, join, resolve } from 'path';
8
+ import { dirname, join, resolve, sep } from 'path';
9
9
  import { getProjectCwd } from './types.js';
10
10
  import { validateIdentifier, validateText, validatePath } from './validate-input.js';
11
11
  import { commandRecordingDisabled, redactCommandForStorage } from '../services/command-recording.js';
@@ -5136,7 +5136,17 @@ export const hooksCodemod = {
5136
5136
  if (!v.valid)
5137
5137
  return v.error;
5138
5138
  const abs = resolve(cwd, v.sanitized);
5139
- if (!abs.startsWith(cwd))
5139
+ // Anchor containment at a path-separator boundary — an unanchored
5140
+ // `abs.startsWith(cwd)` string-prefix check would let an absolute
5141
+ // path like `${cwd}-staging/x.ts` pass as "inside" `cwd`. Matches
5142
+ // the pattern already used correctly elsewhere in this codebase
5143
+ // (@claude-flow/security's `isWithinPrefix`, hooks/workers, plugins/security).
5144
+ // KNOWN LIMITATION (disclosed 2026-10-20, not fixed here): this
5145
+ // compares the resolved-but-not-realpath'd path, so a symlink
5146
+ // inside cwd pointing outside it still escapes containment
5147
+ // (CWE-59, a distinct/broader gap than the CWE-22 prefix bug this
5148
+ // anchors). Needs fs.realpathSync-based containment as a follow-up.
5149
+ if (abs !== cwd && !abs.startsWith(cwd + sep))
5140
5150
  return `path escapes project root: ${p}`;
5141
5151
  out.add(abs);
5142
5152
  return undefined;
@@ -5169,7 +5179,7 @@ export const hooksCodemod = {
5169
5179
  }
5170
5180
  for (const m of matches) {
5171
5181
  const abs = resolve(cwd, m);
5172
- if (abs.startsWith(cwd) && CODEMOD_EXTENSIONS.has(abs.slice(abs.lastIndexOf('.')).toLowerCase())) {
5182
+ if ((abs === cwd || abs.startsWith(cwd + sep)) && CODEMOD_EXTENSIONS.has(abs.slice(abs.lastIndexOf('.')).toLowerCase())) {
5173
5183
  out.add(abs);
5174
5184
  }
5175
5185
  }
@@ -10,6 +10,7 @@ import { createRequire } from 'node:module';
10
10
  import { readFileMaybeEncrypted, writeDbImagePreservingFormat, writeFileRestricted } from '../fs-secure.js';
11
11
  import { restoreMemoryDbFromBackup } from '../services/memory-backup.js';
12
12
  import { validateIdentifier } from '../mcp-tools/validate-input.js';
13
+ import { resolveModelCacheDir } from './model-cache-dir.js';
13
14
  /**
14
15
  * ADR-323 — typed memory provenance. Distinguishes WHO/WHAT wrote a memory
15
16
  * entry (a user's stated claim vs an agent's own output vs a tool result vs
@@ -2009,6 +2010,8 @@ export async function applyTemporalDecay(dbPath) {
2009
2010
  };
2010
2011
  }
2011
2012
  }
2013
+ /** ADR-454: minimum interval before a 'retryable' degraded state re-attempts real init. */
2014
+ const MODEL_INIT_RETRY_INTERVAL_MS = 60_000;
2012
2015
  /**
2013
2016
  * State of the LOCAL embedding chain only (transformers.js / agentic-flow /
2014
2017
  * ruvector ONNX / hash). #3375: the AgentDB bridge's result is cached
@@ -2058,8 +2061,15 @@ export async function loadEmbeddingModel(options) {
2058
2061
  */
2059
2062
  async function loadLocalEmbeddingChain(verbose = false, startTime = Date.now()) {
2060
2063
  if (embeddingModelState?.loaded) {
2061
- return { success: true, dimensions: embeddingModelState.dimensions, modelName: 'cached', loadTime: 0 };
2064
+ const isRetryableDue = embeddingModelState.degradedReason === 'retryable' &&
2065
+ Date.now() - (embeddingModelState.lastAttemptAt ?? 0) >= MODEL_INIT_RETRY_INTERVAL_MS;
2066
+ if (!isRetryableDue) {
2067
+ return { success: true, dimensions: embeddingModelState.dimensions, modelName: 'cached', loadTime: 0 };
2068
+ }
2069
+ // Fall through to re-attempt real initialization — the retryable
2070
+ // failure (e.g. EACCES on the model cache dir) may now be fixed.
2062
2071
  }
2072
+ let sawRetryableFailure = false;
2063
2073
  try {
2064
2074
  // ADR-094: prefer @huggingface/transformers (clears protobufjs <7.5.5
2065
2075
  // critical RCE chain), fall back to legacy @xenova/transformers.
@@ -2068,6 +2078,7 @@ async function loadLocalEmbeddingChain(verbose = false, startTime = Date.now())
2068
2078
  // @claude-flow/embeddings/src/transformers-loader.ts.
2069
2079
  let transformersSource = null;
2070
2080
  let pipelineFn = null;
2081
+ let transformersModule = null;
2071
2082
  {
2072
2083
  const tryLoad = async (specifier) => {
2073
2084
  try {
@@ -2081,12 +2092,14 @@ async function loadLocalEmbeddingChain(verbose = false, startTime = Date.now())
2081
2092
  if (hf && typeof hf.pipeline === 'function') {
2082
2093
  pipelineFn = hf.pipeline;
2083
2094
  transformersSource = '@huggingface/transformers';
2095
+ transformersModule = hf;
2084
2096
  }
2085
2097
  else {
2086
2098
  const xen = await tryLoad('@xenova/transformers');
2087
2099
  if (xen && typeof xen.pipeline === 'function') {
2088
2100
  pipelineFn = xen.pipeline;
2089
2101
  transformersSource = '@xenova/transformers';
2102
+ transformersModule = xen;
2090
2103
  }
2091
2104
  }
2092
2105
  }
@@ -2103,6 +2116,14 @@ async function loadLocalEmbeddingChain(verbose = false, startTime = Date.now())
2103
2116
  if (verbose) {
2104
2117
  console.log(`Loading ONNX embedding model via ${transformersSource} (all-MiniLM-L6-v2)...`);
2105
2118
  }
2119
+ // ADR-454: route model downloads to a user-writable cache dir —
2120
+ // the package-relative default is root-owned under a system-prefix
2121
+ // global install, which throws EACCES for the runtime user.
2122
+ const cacheDir = resolveModelCacheDir();
2123
+ fs.mkdirSync(cacheDir, { recursive: true });
2124
+ const env = transformersModule?.env;
2125
+ if (env)
2126
+ env.cacheDir = cacheDir;
2106
2127
  const embedder = await pipelineFn('feature-extraction', 'Xenova/all-MiniLM-L6-v2');
2107
2128
  embeddingModelState = {
2108
2129
  loaded: true,
@@ -2118,6 +2139,10 @@ async function loadLocalEmbeddingChain(verbose = false, startTime = Date.now())
2118
2139
  };
2119
2140
  }
2120
2141
  catch (err) {
2142
+ // ADR-454: a real backend was importable and callable — this failure
2143
+ // (EACCES, ENOSPC, transient network error, etc.) is environmental,
2144
+ // not a missing dependency, so it must not be memoized permanently.
2145
+ sawRetryableFailure = true;
2121
2146
  if (verbose) {
2122
2147
  console.warn(`${transformersSource} pipeline init failed (${err instanceof Error ? err.message : String(err)}); ` +
2123
2148
  'falling through to ruvector ONNX / agentic-flow / hash fallback.');
@@ -2186,12 +2211,18 @@ async function loadLocalEmbeddingChain(verbose = false, startTime = Date.now())
2186
2211
  }
2187
2212
  // No agentic-flow root-entry fallback: no published root exports
2188
2213
  // `embeddings`, and importing it runs its CLI main() in 3.0.0-alpha.x.
2189
- // No ONNX model available - use fallback
2214
+ // No ONNX model available - use fallback.
2215
+ // ADR-454: mark whether this is a permanent (no backend importable at
2216
+ // all) or retryable (a backend existed but init failed for an
2217
+ // environmental reason) degraded state, so a later call can re-attempt
2218
+ // real initialization once the condition is fixed.
2190
2219
  embeddingModelState = {
2191
2220
  loaded: true,
2192
2221
  model: null, // Will use simple hash-based fallback
2193
2222
  tokenizer: null,
2194
- dimensions: 128 // Smaller fallback dimensions
2223
+ dimensions: 128, // Smaller fallback dimensions
2224
+ degradedReason: sawRetryableFailure ? 'retryable' : 'permanent',
2225
+ lastAttemptAt: Date.now()
2195
2226
  };
2196
2227
  return {
2197
2228
  success: true,
@@ -2257,7 +2288,15 @@ export async function generateLocalEmbedding(text) {
2257
2288
  // loadEmbeddingModel(), which is bridge-first — when the bridge answered
2258
2289
  // there, no local model was ever loaded and this function always returned
2259
2290
  // the hash fallback.
2260
- if (!embeddingModelState?.loaded) {
2291
+ //
2292
+ // ADR-454: also re-attempt when the memoized state is a 'retryable'
2293
+ // degraded fallback whose retry interval has elapsed — otherwise this
2294
+ // guard short-circuits past loadLocalEmbeddingChain()'s own retry check
2295
+ // and the fix (e.g. a now-writable cache dir) never gets exercised.
2296
+ const shouldRetryLoad = !embeddingModelState?.loaded ||
2297
+ (embeddingModelState.degradedReason === 'retryable' &&
2298
+ Date.now() - (embeddingModelState.lastAttemptAt ?? 0) >= MODEL_INIT_RETRY_INTERVAL_MS);
2299
+ if (shouldRetryLoad) {
2261
2300
  await loadLocalEmbeddingChain();
2262
2301
  }
2263
2302
  // #2461: loadEmbeddingModel() can leave embeddingModelState null when an
@@ -0,0 +1,28 @@
1
+ export type MemoryRootSource = 'path' | 'CLAUDE_FLOW_DB_PATH' | 'CLAUDE_FLOW_MEMORY_PATH' | 'config' | 'default';
2
+ export interface MemoryPeekStore {
3
+ role: 'cli' | 'mcp';
4
+ path: string;
5
+ exists: boolean;
6
+ rows: number | null;
7
+ lastWrite: string | null;
8
+ sizeBytes: number | null;
9
+ walBytes: number | null;
10
+ encrypted?: boolean;
11
+ }
12
+ export interface MemoryPeekResult {
13
+ cwd: string;
14
+ root: {
15
+ path: string;
16
+ source: MemoryRootSource;
17
+ };
18
+ stores: MemoryPeekStore[];
19
+ sharedKeys: number | null;
20
+ }
21
+ /**
22
+ * Resolve the route and report both stores, read-only. Never creates,
23
+ * initializes, or migrates anything, on an empty project or any other.
24
+ */
25
+ export declare function memoryPeek(opts?: {
26
+ path?: string;
27
+ }): Promise<MemoryPeekResult>;
28
+ //# sourceMappingURL=memory-peek.d.ts.map