@claude-flow/cli 3.57.2 → 3.58.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 (55) hide show
  1. package/.claude/helpers/helpers.manifest.json +2 -2
  2. package/README.md +1 -0
  3. package/catalog-manifest.json +2 -2
  4. package/dist/src/commands/agent.js +4 -1
  5. package/dist/src/commands/mcp.js +66 -6
  6. package/dist/src/commands/memory.js +53 -2
  7. package/dist/src/mcp-server.d.ts +5 -0
  8. package/dist/src/mcp-server.js +39 -0
  9. package/dist/src/mcp-tools/agent-tools.js +28 -0
  10. package/dist/src/mcp-tools/claims-tools.js +25 -15
  11. package/dist/src/memory/memory-initializer.js +43 -4
  12. package/dist/src/memory/memory-peek.d.ts +28 -0
  13. package/dist/src/memory/memory-peek.js +222 -0
  14. package/dist/src/memory/model-cache-dir.d.ts +11 -0
  15. package/dist/src/memory/model-cache-dir.js +17 -0
  16. package/dist/src/ruvector/task-embedder.js +38 -4
  17. package/node_modules/@claude-flow/codex/README.md +78 -0
  18. package/node_modules/@claude-flow/codex/dist/activity/cli.d.ts +3 -0
  19. package/node_modules/@claude-flow/codex/dist/activity/cli.d.ts.map +1 -0
  20. package/node_modules/@claude-flow/codex/dist/activity/cli.js +115 -0
  21. package/node_modules/@claude-flow/codex/dist/activity/cli.js.map +1 -0
  22. package/node_modules/@claude-flow/codex/dist/activity/files.d.ts +14 -0
  23. package/node_modules/@claude-flow/codex/dist/activity/files.d.ts.map +1 -0
  24. package/node_modules/@claude-flow/codex/dist/activity/files.js +142 -0
  25. package/node_modules/@claude-flow/codex/dist/activity/files.js.map +1 -0
  26. package/node_modules/@claude-flow/codex/dist/activity/parser.d.ts +21 -0
  27. package/node_modules/@claude-flow/codex/dist/activity/parser.d.ts.map +1 -0
  28. package/node_modules/@claude-flow/codex/dist/activity/parser.js +179 -0
  29. package/node_modules/@claude-flow/codex/dist/activity/parser.js.map +1 -0
  30. package/node_modules/@claude-flow/codex/dist/activity/reader.d.ts +20 -0
  31. package/node_modules/@claude-flow/codex/dist/activity/reader.d.ts.map +1 -0
  32. package/node_modules/@claude-flow/codex/dist/activity/reader.js +106 -0
  33. package/node_modules/@claude-flow/codex/dist/activity/reader.js.map +1 -0
  34. package/node_modules/@claude-flow/codex/dist/activity/sanitize.d.ts +4 -0
  35. package/node_modules/@claude-flow/codex/dist/activity/sanitize.d.ts.map +1 -0
  36. package/node_modules/@claude-flow/codex/dist/activity/sanitize.js +27 -0
  37. package/node_modules/@claude-flow/codex/dist/activity/sanitize.js.map +1 -0
  38. package/node_modules/@claude-flow/codex/dist/activity/types.d.ts +31 -0
  39. package/node_modules/@claude-flow/codex/dist/activity/types.d.ts.map +1 -0
  40. package/node_modules/@claude-flow/codex/dist/activity/types.js +5 -0
  41. package/node_modules/@claude-flow/codex/dist/activity/types.js.map +1 -0
  42. package/node_modules/@claude-flow/codex/dist/activity/viewer.d.ts +21 -0
  43. package/node_modules/@claude-flow/codex/dist/activity/viewer.d.ts.map +1 -0
  44. package/node_modules/@claude-flow/codex/dist/activity/viewer.js +168 -0
  45. package/node_modules/@claude-flow/codex/dist/activity/viewer.js.map +1 -0
  46. package/node_modules/@claude-flow/codex/dist/cli.js +3 -0
  47. package/node_modules/@claude-flow/codex/dist/cli.js.map +1 -1
  48. package/node_modules/@claude-flow/mcp/dist/server.d.ts.map +1 -1
  49. package/node_modules/@claude-flow/mcp/dist/server.js +5 -3
  50. package/node_modules/@claude-flow/mcp/dist/server.js.map +1 -1
  51. package/node_modules/@claude-flow/mcp/dist/types.d.ts +11 -0
  52. package/node_modules/@claude-flow/mcp/dist/types.d.ts.map +1 -1
  53. package/node_modules/@claude-flow/mcp/dist/types.js.map +1 -1
  54. package/package.json +1 -1
  55. package/plugins/ruflo-console/dist/tui.mjs +124 -124
@@ -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
@@ -0,0 +1,222 @@
1
+ /**
2
+ * #3508: read-only memory route/peek.
3
+ *
4
+ * A health check needs to know which memory store files a cwd resolves to,
5
+ * and what is in them, without changing anything to find out. `memory stats`
6
+ * (even after #3463/#3448) still calls `ensureInitialized()` when no explicit
7
+ * path is given, and answers for one file per call. This module reports
8
+ * BOTH files — the sql.js `memory.db` the CLI writes and its native
9
+ * `agentdb-memory.db` sibling the MCP/bridge path writes — in one read-only
10
+ * pass, and says which resolution rule picked the root.
11
+ *
12
+ * Contract (do not relax without re-reading #3508):
13
+ * - never creates, initializes, or migrates anything;
14
+ * - never opens a file that does not exist (`existsSync` gated before any
15
+ * open attempt, for both engines);
16
+ * - every open is `readonly`/`fileMustExist` (native) or a one-shot byte
17
+ * buffer handed to sql.js, which is never written back to disk;
18
+ * - `rows: null` means "could not read" and is never a count of 0, so an
19
+ * empty store and an unreadable one never look the same;
20
+ * - an encrypted-at-rest `memory.db` reports `encrypted: true` and
21
+ * `rows: null`, but its size is still reported.
22
+ */
23
+ import { closeSync, existsSync, openSync, readFileSync, readSync, statSync } from 'node:fs';
24
+ import path from 'node:path';
25
+ import { getMemoryRoot, resolveDbPath } from './memory-initializer.js';
26
+ import { siblingAgentDbPath } from './memory-bridge.js';
27
+ import { isEncryptedBlob } from '../encryption/vault.js';
28
+ // Mirrors the `ACTIVE_MEMORY_ROW_SQL` convention in memory-initializer.ts:
29
+ // legacy rows with a NULL status predate soft-delete semantics and count.
30
+ const ACTIVE_ROW_SQL = `(status = 'active' OR status IS NULL)`;
31
+ const ENCRYPTION_SNIFF_LEN = 64; // RFE1 magic(4)+iv(12)+tag(16); 64 gives headroom.
32
+ function safeStatSize(filePath) {
33
+ try {
34
+ return statSync(filePath).size;
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
40
+ function readHeaderBytes(filePath, len) {
41
+ const fd = openSync(filePath, 'r');
42
+ try {
43
+ const buf = Buffer.alloc(len);
44
+ const bytesRead = readSync(fd, buf, 0, len, 0);
45
+ return buf.subarray(0, bytesRead);
46
+ }
47
+ finally {
48
+ closeSync(fd);
49
+ }
50
+ }
51
+ function latestMillis(values) {
52
+ let latest = -Infinity;
53
+ for (const raw of values) {
54
+ const millis = typeof raw === 'number' ? raw : Number(raw);
55
+ if (Number.isFinite(millis))
56
+ latest = Math.max(latest, millis);
57
+ }
58
+ return latest;
59
+ }
60
+ /** sql.js is read as a one-shot in-memory byte buffer — nothing is ever
61
+ * written back, so reading this file has no on-disk side effect. */
62
+ async function probeSqlJsStore(dbPath) {
63
+ try {
64
+ if (isEncryptedBlob(readHeaderBytes(dbPath, ENCRYPTION_SNIFF_LEN))) {
65
+ return { rows: null, lastWrite: null, keys: null, encrypted: true };
66
+ }
67
+ }
68
+ catch {
69
+ /* header read failed (permissions/race) — fall through to the open attempt */
70
+ }
71
+ try {
72
+ const initSqlJs = (await import('sql.js')).default ?? (await import('sql.js'));
73
+ const SQL = await (typeof initSqlJs === 'function' ? initSqlJs() : initSqlJs.default());
74
+ const buf = readFileSync(dbPath);
75
+ const db = new SQL.Database(new Uint8Array(buf));
76
+ try {
77
+ const tables = db.exec("SELECT name FROM sqlite_master WHERE type='table' AND name='memory_entries'");
78
+ if (!tables.length)
79
+ return { rows: null, lastWrite: null, keys: null };
80
+ const result = db.exec(`SELECT namespace, key, created_at, updated_at FROM memory_entries WHERE ${ACTIVE_ROW_SQL}`);
81
+ const values = result[0]?.values ?? [];
82
+ const keys = new Set();
83
+ let last = -Infinity;
84
+ for (const [namespace, key, createdAt, updatedAt] of values) {
85
+ keys.add(`${String(namespace)}\u0000${String(key)}`);
86
+ last = Math.max(last, latestMillis([createdAt, updatedAt]));
87
+ }
88
+ return {
89
+ rows: values.length,
90
+ lastWrite: Number.isFinite(last) ? new Date(last).toISOString() : null,
91
+ keys,
92
+ };
93
+ }
94
+ finally {
95
+ try {
96
+ db.close();
97
+ }
98
+ catch { /* best effort */ }
99
+ }
100
+ }
101
+ catch {
102
+ return { rows: null, lastWrite: null, keys: null };
103
+ }
104
+ }
105
+ /** better-sqlite3 is an optional native dependency — absence must degrade
106
+ * to "could not read", never throw. Opened `readonly`/`fileMustExist`, same
107
+ * as `countSiblingStoreRows` in sibling-store.ts. */
108
+ async function probeNativeStore(dbPath) {
109
+ try {
110
+ const require = (await import('node:module')).createRequire(import.meta.url);
111
+ const Database = require('better-sqlite3');
112
+ const db = new Database(dbPath, { readonly: true, fileMustExist: true });
113
+ try {
114
+ const table = db
115
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='memory_entries'")
116
+ .get();
117
+ if (!table)
118
+ return { rows: null, lastWrite: null, keys: null };
119
+ const rows = db
120
+ .prepare(`SELECT namespace, key, created_at, updated_at FROM memory_entries WHERE ${ACTIVE_ROW_SQL}`)
121
+ .all();
122
+ const keys = new Set();
123
+ let last = -Infinity;
124
+ for (const row of rows) {
125
+ keys.add(`${String(row.namespace)}\u0000${String(row.key)}`);
126
+ last = Math.max(last, latestMillis([row.created_at, row.updated_at]));
127
+ }
128
+ return {
129
+ rows: rows.length,
130
+ lastWrite: Number.isFinite(last) ? new Date(last).toISOString() : null,
131
+ keys,
132
+ };
133
+ }
134
+ finally {
135
+ try {
136
+ db.close();
137
+ }
138
+ catch { /* best effort */ }
139
+ }
140
+ }
141
+ catch {
142
+ return { rows: null, lastWrite: null, keys: null };
143
+ }
144
+ }
145
+ /** Same precedence `getMemoryRoot()`/`resolveDbPath()` already own — this
146
+ * only *labels* which rule decided, it never re-derives the path itself. */
147
+ function decideSource(cliFlag) {
148
+ if (cliFlag && cliFlag.trim().length > 0)
149
+ return 'path';
150
+ if (process.env.CLAUDE_FLOW_DB_PATH && process.env.CLAUDE_FLOW_DB_PATH.trim().length > 0) {
151
+ return 'CLAUDE_FLOW_DB_PATH';
152
+ }
153
+ if (process.env.CLAUDE_FLOW_MEMORY_PATH && process.env.CLAUDE_FLOW_MEMORY_PATH.trim().length > 0) {
154
+ return 'CLAUDE_FLOW_MEMORY_PATH';
155
+ }
156
+ const configCandidates = [
157
+ path.resolve(process.cwd(), 'claude-flow.config.json'),
158
+ path.resolve(process.cwd(), '.claude-flow', 'config.json'),
159
+ ];
160
+ for (const configPath of configCandidates) {
161
+ if (!existsSync(configPath))
162
+ continue;
163
+ try {
164
+ const raw = JSON.parse(readFileSync(configPath, 'utf-8'));
165
+ const fromConfig = raw?.memory?.persistPath ?? raw?.memory?.path;
166
+ if (typeof fromConfig === 'string' && fromConfig.trim().length > 0)
167
+ return 'config';
168
+ }
169
+ catch {
170
+ /* malformed config — getMemoryRoot() falls through to default too */
171
+ }
172
+ }
173
+ return 'default';
174
+ }
175
+ async function describeStore(role, storePath, probe) {
176
+ if (!existsSync(storePath)) {
177
+ return {
178
+ store: { role, path: storePath, exists: false, rows: null, lastWrite: null, sizeBytes: null, walBytes: null },
179
+ keys: null,
180
+ };
181
+ }
182
+ const result = await probe(storePath);
183
+ const sizeBytes = safeStatSize(storePath);
184
+ const walBytes = safeStatSize(`${storePath}-wal`) ?? 0;
185
+ return {
186
+ store: {
187
+ role,
188
+ path: storePath,
189
+ exists: true,
190
+ rows: result.rows,
191
+ lastWrite: result.lastWrite,
192
+ sizeBytes,
193
+ walBytes,
194
+ ...(result.encrypted ? { encrypted: true } : {}),
195
+ },
196
+ keys: result.keys,
197
+ };
198
+ }
199
+ /**
200
+ * Resolve the route and report both stores, read-only. Never creates,
201
+ * initializes, or migrates anything, on an empty project or any other.
202
+ */
203
+ export async function memoryPeek(opts) {
204
+ const cliFlag = opts?.path;
205
+ const cliPath = resolveDbPath(cliFlag);
206
+ const mcpPath = siblingAgentDbPath(cliPath);
207
+ const root = { path: cliFlag ? path.dirname(cliPath) : getMemoryRoot(), source: decideSource(cliFlag) };
208
+ const cliDescribed = await describeStore('cli', cliPath, probeSqlJsStore);
209
+ const stores = [cliDescribed.store];
210
+ let mcpKeys = null;
211
+ if (mcpPath) {
212
+ const mcpDescribed = await describeStore('mcp', mcpPath, probeNativeStore);
213
+ stores.push(mcpDescribed.store);
214
+ mcpKeys = mcpDescribed.keys;
215
+ }
216
+ const cliKeys = cliDescribed.keys;
217
+ const sharedKeys = cliKeys && mcpKeys
218
+ ? [...cliKeys].filter((key) => mcpKeys.has(key)).length
219
+ : null;
220
+ return { cwd: process.cwd(), root, stores, sharedKeys };
221
+ }
222
+ //# sourceMappingURL=memory-peek.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Resolve a writable cache directory for ONNX model downloads.
3
+ *
4
+ * ADR-454: duplicated intentionally from
5
+ * `@claude-flow/embeddings/src/transformers-loader.ts`'s
6
+ * `resolveModelCacheDir()` rather than imported, since `@claude-flow/cli`
7
+ * has no dependency on `@claude-flow/embeddings` (see the circular
8
+ * optional-dep note in `memory-initializer.ts`).
9
+ */
10
+ export declare function resolveModelCacheDir(): string;
11
+ //# sourceMappingURL=model-cache-dir.d.ts.map
@@ -0,0 +1,17 @@
1
+ import * as os from 'node:os';
2
+ import * as path from 'node:path';
3
+ /**
4
+ * Resolve a writable cache directory for ONNX model downloads.
5
+ *
6
+ * ADR-454: duplicated intentionally from
7
+ * `@claude-flow/embeddings/src/transformers-loader.ts`'s
8
+ * `resolveModelCacheDir()` rather than imported, since `@claude-flow/cli`
9
+ * has no dependency on `@claude-flow/embeddings` (see the circular
10
+ * optional-dep note in `memory-initializer.ts`).
11
+ */
12
+ export function resolveModelCacheDir() {
13
+ return (process.env.TRANSFORMERS_CACHE ||
14
+ process.env.HF_HOME ||
15
+ path.join(os.homedir(), '.cache', 'ruflo', 'models'));
16
+ }
17
+ //# sourceMappingURL=model-cache-dir.js.map
@@ -19,6 +19,8 @@
19
19
  *
20
20
  * @module task-embedder
21
21
  */
22
+ import * as fs from 'node:fs';
23
+ import { resolveModelCacheDir } from '../memory/model-cache-dir.js';
22
24
  // ============================================================================
23
25
  // FNV-1a-32 hash (matches scripts/gen-seed-corpus.mjs + router-trajectory.ts)
24
26
  // ============================================================================
@@ -38,18 +40,48 @@ function cacheKey(task) {
38
40
  // same function (xenova's pipeline returns a callable that accepts either),
39
41
  // so we don't pay the ONNX model-load cost twice.
40
42
  let _extractorPromise = null;
43
+ /**
44
+ * ADR-454: distinguishes a null-extractor result caused by a genuinely
45
+ * missing dependency (permanent — nothing changes without a reinstall) from
46
+ * one caused by an environmental/fixable failure during `pipeline(...)`
47
+ * init, such as EACCES on the model cache dir (retryable).
48
+ */
49
+ let _extractorDegradedReason = null;
50
+ let _extractorLastAttemptAt = 0;
51
+ const EXTRACTOR_RETRY_INTERVAL_MS = 60_000;
41
52
  function loadExtractor() {
42
- if (_extractorPromise !== null)
43
- return _extractorPromise;
53
+ if (_extractorPromise !== null) {
54
+ const isRetryableDue = _extractorDegradedReason === 'retryable' &&
55
+ Date.now() - _extractorLastAttemptAt >= EXTRACTOR_RETRY_INTERVAL_MS;
56
+ if (!isRetryableDue)
57
+ return _extractorPromise;
58
+ // Fall through to re-attempt — the retryable failure may now be fixed.
59
+ }
60
+ _extractorLastAttemptAt = Date.now();
44
61
  _extractorPromise = (async () => {
62
+ let packageImportable = false;
45
63
  try {
46
64
  const specifier = '@xenova/transformers';
47
65
  const mod = await import(/* @vite-ignore */ specifier).catch(() => null);
48
- if (!mod || typeof mod.pipeline !== 'function')
66
+ if (!mod || typeof mod.pipeline !== 'function') {
67
+ _extractorDegradedReason = 'permanent';
49
68
  return null;
50
- return await mod.pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2', { quantized: true });
69
+ }
70
+ packageImportable = true;
71
+ // ADR-454: route model downloads to a user-writable cache dir — the
72
+ // package-relative default is root-owned under a system-prefix
73
+ // global install, which throws EACCES for the runtime user.
74
+ const cacheDir = resolveModelCacheDir();
75
+ fs.mkdirSync(cacheDir, { recursive: true });
76
+ const env = mod.env;
77
+ if (env)
78
+ env.cacheDir = cacheDir;
79
+ const extractor = await mod.pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2', { quantized: true });
80
+ _extractorDegradedReason = null;
81
+ return extractor;
51
82
  }
52
83
  catch {
84
+ _extractorDegradedReason = packageImportable ? 'retryable' : 'permanent';
53
85
  return null;
54
86
  }
55
87
  })();
@@ -233,5 +265,7 @@ export function __resetTaskEmbedderForTests() {
233
265
  _hits = 0;
234
266
  _misses = 0;
235
267
  _extractorPromise = null;
268
+ _extractorDegradedReason = null;
269
+ _extractorLastAttemptAt = 0;
236
270
  }
237
271
  //# sourceMappingURL=task-embedder.js.map
@@ -1042,6 +1042,84 @@ npx claude-flow memory optimize --rebuild-index
1042
1042
 
1043
1043
  ---
1044
1044
 
1045
+ ## Inspect an existing Codex session
1046
+
1047
+ The `activity` command lets you pick a helper and read its assignment, recorded
1048
+ state, public progress messages, and tool calls/results. It reads local rollout
1049
+ files without starting model sessions or sending commands to agents.
1050
+
1051
+ From a checkout containing this change:
1052
+
1053
+ ```bash
1054
+ npm install --prefix v3/@claude-flow/codex --workspaces=false --ignore-scripts --legacy-peer-deps --package-lock=false
1055
+ npm run build --prefix v3/@claude-flow/codex
1056
+ node v3/@claude-flow/codex/dist/cli.js activity \
1057
+ --session-file /path/to/sessions/parent-rollout.jsonl \
1058
+ --sessions-dir /path/to/sessions
1059
+ ```
1060
+
1061
+ Choose the parent rollout explicitly. Only helpers whose recorded parent chain
1062
+ leads back to that session appear. The selected directory must contain both the
1063
+ parent and helper files; use the smallest directory that contains them all.
1064
+
1065
+ Use **j/k** or the arrow keys to select a helper, then **Enter** to open it.
1066
+ In the detail view, **j/k** scroll, **Space** moves down a page, **g** goes to the
1067
+ top, **G** follows the latest activity, and **b** or **Escape** returns to the list.
1068
+ Press **q** to quit; the Codex session keeps running.
1069
+
1070
+ Use `--agent <thread-id>` to open a specific helper, `--once` to print a snapshot
1071
+ without an interactive terminal, or `--interval <seconds>` to change the refresh
1072
+ interval (default: 2 seconds; range: 0.2–60).
1073
+
1074
+ These screenshots use made-up session records:
1075
+
1076
+ ![Agent picker with a parser-review helper](docs/images/activity-picker.png)
1077
+
1078
+ ![Helper assignment, public progress, command and result](docs/images/activity-detail.png)
1079
+
1080
+ ### What the viewer can read
1081
+
1082
+ The parser supports CLI rollout files with a `session_meta` header, helper ancestry
1083
+ in `source.subagent.thread_spawn.parent_thread_id`, and an own-thread marker
1084
+ (`payload.thread_id`) before helper activity. Public message, function/custom tool
1085
+ call and result records, and supported task/item events are shown. Formats without
1086
+ these attribution fields may produce an empty list or unavailable activity.
1087
+
1088
+ Copied parent history, reasoning, and system/developer messages are excluded.
1089
+ Ambiguous identities and paths through symlinks are rejected. The viewer strips
1090
+ terminal controls and masks common credential patterns before shortening previews.
1091
+ Masking cannot catch every secret in arbitrary command output: check the visible
1092
+ content before sharing a screenshot.
1093
+
1094
+ State is the last **recorded** state, not a heartbeat. Discovery stops after 4,000
1095
+ entries or eight directory levels; ancestry is limited to 64 links. Each detail
1096
+ refresh reads at most 1 MiB, lines are limited to 64 KiB, previews to 2,400 characters,
1097
+ and history to 80 events. The metadata header must include its terminating newline
1098
+ within the first 64 KiB; an oversized or incomplete header excludes that file.
1099
+ The screen labels incomplete scans, omitted records and logs that are still being
1100
+ read. Missing data stays unavailable.
1101
+
1102
+ ### Check the viewer locally
1103
+
1104
+ ```bash
1105
+ cd v3/@claude-flow/codex
1106
+ npm test -- --run tests/activity-files.test.ts tests/activity-parser.test.ts tests/activity-viewer.test.ts
1107
+ cd ../../..
1108
+ node scripts/smoke-codex-activity.mjs
1109
+ ```
1110
+
1111
+ The Node smoke check runs the built CLI against synthetic files and checks the
1112
+ picker, detail output, history isolation, invalid input and read-only access.
1113
+ The screenshots above were captured from the interactive viewer using synthetic
1114
+ records; screenshot generation is not part of CI.
1115
+
1116
+ ### Release integration
1117
+
1118
+ Ship the viewer in `@claude-flow/codex` with the next coordinated Ruflo release.
1119
+ At release, bump the package version and exported `VERSION` together, update the
1120
+ bundled dependency pins and lockfile, and include the built package in the release
1121
+ train.
1122
+
1045
1123
  ## Related Packages
1046
1124
 
1047
1125
  | Package | Description |
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createActivityCommand(): Command;
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/activity/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAwB,MAAM,WAAW,CAAC;AAO1D,wBAAgB,qBAAqB,IAAI,OAAO,CA6B/C"}