claude-flow 3.17.0 → 3.18.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.
- package/package.json +12 -6
- package/v3/@claude-flow/cli/dist/src/commands/neural.js +16 -6
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.js +52 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/browser-session-tools.d.ts +5 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/browser-session-tools.js +92 -18
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.d.ts +1 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +131 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.js +119 -84
- package/v3/@claude-flow/cli/dist/src/memory/intelligence.js +20 -5
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +15 -2
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +64 -10
- package/v3/@claude-flow/cli/dist/src/ruvector/lora-adapter.d.ts +9 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/lora-adapter.js +21 -3
- package/v3/@claude-flow/cli/dist/src/ruvector/output-verifier.d.ts +83 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/output-verifier.js +277 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/trajectory-tree.d.ts +113 -0
- package/v3/@claude-flow/cli/dist/src/ruvector/trajectory-tree.js +237 -0
- package/v3/@claude-flow/cli/package.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-flow",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.18.1",
|
|
4
4
|
"description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -98,12 +98,18 @@
|
|
|
98
98
|
"qs": ">=6.15.2",
|
|
99
99
|
"protobufjs": ">=8.2.0",
|
|
100
100
|
"uuid": ">=14.0.0",
|
|
101
|
-
"@opentelemetry/core": "
|
|
102
|
-
"@opentelemetry/resources": "
|
|
103
|
-
"@opentelemetry/sdk-trace-base": "
|
|
104
|
-
"@opentelemetry/sdk-node": ">=0.
|
|
101
|
+
"@opentelemetry/core": ">=2.8.0",
|
|
102
|
+
"@opentelemetry/resources": ">=2.8.0",
|
|
103
|
+
"@opentelemetry/sdk-trace-base": ">=2.8.0",
|
|
104
|
+
"@opentelemetry/sdk-node": ">=0.220.0",
|
|
105
105
|
"@opentelemetry/auto-instrumentations-node": ">=0.75.0",
|
|
106
|
-
"@opentelemetry/exporter-prometheus": ">=0.
|
|
106
|
+
"@opentelemetry/exporter-prometheus": ">=0.220.0",
|
|
107
|
+
"@opentelemetry/exporter-trace-otlp-grpc": ">=0.220.0",
|
|
108
|
+
"@opentelemetry/exporter-trace-otlp-http": ">=0.220.0",
|
|
109
|
+
"@opentelemetry/exporter-trace-otlp-proto": ">=0.220.0",
|
|
110
|
+
"@opentelemetry/otlp-exporter-base": ">=0.220.0",
|
|
111
|
+
"@opentelemetry/otlp-grpc-exporter-base": ">=0.220.0",
|
|
112
|
+
"@opentelemetry/otlp-transformer": ">=0.220.0",
|
|
107
113
|
"axios": ">=1.13.2",
|
|
108
114
|
"fast-uri": ">=3.1.0",
|
|
109
115
|
"vite": ">=8.0.16",
|
|
@@ -451,17 +451,27 @@ const statusCommand = {
|
|
|
451
451
|
},
|
|
452
452
|
{
|
|
453
453
|
component: 'Contrastive Trainer',
|
|
454
|
-
|
|
455
|
-
|
|
454
|
+
// #2549 — three states: live session (object with counts),
|
|
455
|
+
// 'available' (module resolves, no in-process session — the
|
|
456
|
+
// normal case for a read-only status process), 'unavailable'
|
|
457
|
+
// (module genuinely does not resolve).
|
|
458
|
+
status: typeof stats._contrastiveTrainer === 'object'
|
|
459
|
+
? output.success('Active')
|
|
460
|
+
: stats._contrastiveTrainer === 'available'
|
|
461
|
+
? output.success('Available')
|
|
462
|
+
: output.dim('Unavailable'),
|
|
463
|
+
details: typeof stats._contrastiveTrainer === 'object'
|
|
456
464
|
? `${stats._contrastiveTrainer.triplets ?? 0} triplets, ${stats._contrastiveTrainer.agents ?? 0} agents`
|
|
457
|
-
:
|
|
465
|
+
: stats._contrastiveTrainer === 'available'
|
|
466
|
+
? 'ready — trains in-process on demand'
|
|
467
|
+
: 'Install @ruvector/ruvllm',
|
|
458
468
|
},
|
|
459
469
|
{
|
|
460
470
|
component: 'Training Pipeline',
|
|
461
|
-
status: stats._trainingBackend === 'ruvllm' ? output.success('
|
|
471
|
+
status: stats._trainingBackend === 'ruvllm' ? output.success('Available') : output.dim(stats._trainingBackend || 'Unavailable'),
|
|
462
472
|
details: stats._trainingBackend === 'ruvllm'
|
|
463
|
-
? 'ruvllm
|
|
464
|
-
: 'JS fallback
|
|
473
|
+
? 'native @ruvector/ruvllm pipeline'
|
|
474
|
+
: 'JS fallback',
|
|
465
475
|
},
|
|
466
476
|
await (async () => {
|
|
467
477
|
try {
|
|
@@ -33,6 +33,21 @@ function validateScore(value, defaultVal) {
|
|
|
33
33
|
return defaultVal;
|
|
34
34
|
return Math.max(0, Math.min(1, value));
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Validate an optional ISO-8601 timestamp param (temporal validity fields).
|
|
38
|
+
* Returns { value } when absent or valid, { error } when present but unparseable.
|
|
39
|
+
*/
|
|
40
|
+
function validateIsoTimestamp(value, name) {
|
|
41
|
+
if (value === undefined || value === null)
|
|
42
|
+
return {};
|
|
43
|
+
if (typeof value !== 'string' || value.length === 0 || value.length > 64) {
|
|
44
|
+
return { error: `${name} must be a non-empty ISO-8601 timestamp string (max 64 chars)` };
|
|
45
|
+
}
|
|
46
|
+
if (Number.isNaN(Date.parse(value))) {
|
|
47
|
+
return { error: `${name} is not a parseable ISO-8601 timestamp: ${value.substring(0, 64)}` };
|
|
48
|
+
}
|
|
49
|
+
return { value };
|
|
50
|
+
}
|
|
36
51
|
function sanitizeError(error) {
|
|
37
52
|
if (error instanceof Error) {
|
|
38
53
|
// Strip filesystem paths from error messages
|
|
@@ -616,6 +631,18 @@ export const agentdbHierarchicalStore = {
|
|
|
616
631
|
enum: ['working', 'episodic', 'semantic'],
|
|
617
632
|
default: 'working',
|
|
618
633
|
},
|
|
634
|
+
validFrom: {
|
|
635
|
+
type: 'string',
|
|
636
|
+
description: 'Optional ISO-8601 timestamp from which this fact is valid (temporal validity — Zep/Graphiti-style). Omit for always-valid.',
|
|
637
|
+
},
|
|
638
|
+
validUntil: {
|
|
639
|
+
type: 'string',
|
|
640
|
+
description: 'Optional ISO-8601 timestamp after which this fact is no longer valid. Expired facts are hidden from recall unless includeExpired=true.',
|
|
641
|
+
},
|
|
642
|
+
supersedes: {
|
|
643
|
+
type: 'string',
|
|
644
|
+
description: 'Optional id (or key) of an existing entry this fact supersedes. The old entry is INVALIDATED (stamped validUntil=now + supersededBy=<new id>), not deleted — it stays auditable via recall includeExpired=true.',
|
|
645
|
+
},
|
|
619
646
|
},
|
|
620
647
|
required: ['key', 'value'],
|
|
621
648
|
},
|
|
@@ -642,8 +669,26 @@ export const agentdbHierarchicalStore = {
|
|
|
642
669
|
if (!['working', 'episodic', 'semantic'].includes(tier)) {
|
|
643
670
|
return { success: false, error: `Invalid tier: ${tier}. Must be working, episodic, or semantic` };
|
|
644
671
|
}
|
|
672
|
+
// Temporal validity fields (all optional, backward compatible)
|
|
673
|
+
const validFrom = validateIsoTimestamp(params.validFrom, 'validFrom');
|
|
674
|
+
if (validFrom.error)
|
|
675
|
+
return { success: false, error: validFrom.error };
|
|
676
|
+
const validUntil = validateIsoTimestamp(params.validUntil, 'validUntil');
|
|
677
|
+
if (validUntil.error)
|
|
678
|
+
return { success: false, error: validUntil.error };
|
|
679
|
+
const supersedes = params.supersedes !== undefined
|
|
680
|
+
? validateString(params.supersedes, 'supersedes', 1000)
|
|
681
|
+
: undefined;
|
|
682
|
+
if (params.supersedes !== undefined && !supersedes) {
|
|
683
|
+
return { success: false, error: 'supersedes must be a non-empty string (entry id or key, max 1KB)' };
|
|
684
|
+
}
|
|
645
685
|
const bridge = await getBridge();
|
|
646
|
-
const result = await bridge.bridgeHierarchicalStore({
|
|
686
|
+
const result = await bridge.bridgeHierarchicalStore({
|
|
687
|
+
key, value, tier,
|
|
688
|
+
validFrom: validFrom.value,
|
|
689
|
+
validUntil: validUntil.value,
|
|
690
|
+
supersedes: supersedes ?? undefined,
|
|
691
|
+
});
|
|
647
692
|
return result ?? { success: false, error: 'AgentDB bridge not available. Use memory_store/memory_search instead.' };
|
|
648
693
|
}
|
|
649
694
|
catch (error) {
|
|
@@ -661,6 +706,11 @@ export const agentdbHierarchicalRecall = {
|
|
|
661
706
|
query: { type: 'string', description: 'Recall query' },
|
|
662
707
|
tier: { type: 'string', description: 'Filter by tier (working, episodic, semantic)' },
|
|
663
708
|
topK: { type: 'number', description: 'Number of results (default: 5)' },
|
|
709
|
+
includeExpired: {
|
|
710
|
+
type: 'boolean',
|
|
711
|
+
description: 'Include temporally-invalid entries (superseded, expired, or not-yet-valid). Audit escape hatch — default false.',
|
|
712
|
+
default: false,
|
|
713
|
+
},
|
|
664
714
|
},
|
|
665
715
|
required: ['query'],
|
|
666
716
|
},
|
|
@@ -686,6 +736,7 @@ export const agentdbHierarchicalRecall = {
|
|
|
686
736
|
query,
|
|
687
737
|
tier: tier ?? undefined,
|
|
688
738
|
topK: validatePositiveInt(params.topK, 5, MAX_TOP_K),
|
|
739
|
+
includeExpired: params.includeExpired === true,
|
|
689
740
|
});
|
|
690
741
|
return result ?? { results: [], error: 'AgentDB bridge not available. Use memory_search instead.' };
|
|
691
742
|
}
|
|
@@ -12,7 +12,11 @@
|
|
|
12
12
|
* (for trajectory hooks + RVF), and the bridged `memory` namespace (for
|
|
13
13
|
* AgentDB index). They do not inline a replay engine; replay
|
|
14
14
|
* enumerates trajectory steps and returns them for the caller to dispatch.
|
|
15
|
-
* -
|
|
15
|
+
* - ruvector resolution is local-first: if a locally installed `ruvector`
|
|
16
|
+
* package is found (the CLI already depends on ruvector@0.2.27), its bin
|
|
17
|
+
* is spawned directly with `node`; otherwise we fall back to
|
|
18
|
+
* `npx -y ruvector@0.2.27`. The previous 0.2.25 pin forced cold npx
|
|
19
|
+
* downloads of a *second* ruvector version — 4 per browser session.
|
|
16
20
|
* - Best-effort: missing dependencies (no `ruvector`, no `agent-browser`,
|
|
17
21
|
* no AgentDB controller) degrade gracefully with a structured error
|
|
18
22
|
* rather than a process crash.
|
|
@@ -12,13 +12,21 @@
|
|
|
12
12
|
* (for trajectory hooks + RVF), and the bridged `memory` namespace (for
|
|
13
13
|
* AgentDB index). They do not inline a replay engine; replay
|
|
14
14
|
* enumerates trajectory steps and returns them for the caller to dispatch.
|
|
15
|
-
* -
|
|
15
|
+
* - ruvector resolution is local-first: if a locally installed `ruvector`
|
|
16
|
+
* package is found (the CLI already depends on ruvector@0.2.27), its bin
|
|
17
|
+
* is spawned directly with `node`; otherwise we fall back to
|
|
18
|
+
* `npx -y ruvector@0.2.27`. The previous 0.2.25 pin forced cold npx
|
|
19
|
+
* downloads of a *second* ruvector version — 4 per browser session.
|
|
16
20
|
* - Best-effort: missing dependencies (no `ruvector`, no `agent-browser`,
|
|
17
21
|
* no AgentDB controller) degrade gracefully with a structured error
|
|
18
22
|
* rather than a process crash.
|
|
19
23
|
*/
|
|
20
24
|
import { validateIdentifier, validateText } from './validate-input.js';
|
|
21
|
-
|
|
25
|
+
// Pin matches the version in this package's own dependency tree so the npx
|
|
26
|
+
// fallback never downloads a second ruvector. Subcommand surface verified
|
|
27
|
+
// against ruvector@0.2.27 --help: `rvf create/compact/status/derive/segments`
|
|
28
|
+
// and `hooks trajectory-begin/step/end` all exist with the flags used below.
|
|
29
|
+
const RUVECTOR_PIN = 'ruvector@0.2.27';
|
|
22
30
|
const RVF_DIR_DEFAULT = '.ruflo/browser-sessions';
|
|
23
31
|
async function shell(cmd, args, opts = {}) {
|
|
24
32
|
const { execFile } = await import('node:child_process');
|
|
@@ -41,6 +49,57 @@ async function shell(cmd, args, opts = {}) {
|
|
|
41
49
|
};
|
|
42
50
|
}
|
|
43
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Resolve the locally installed ruvector CLI (memoized). Returns the absolute
|
|
54
|
+
* path to its bin script, or null when ruvector is not installed locally.
|
|
55
|
+
* Spawning `node <bin>` directly avoids the ~2-8s cold `npx -y ruvector@…`
|
|
56
|
+
* download that previously hit every ruvector shell-out (4 per session).
|
|
57
|
+
*/
|
|
58
|
+
let ruvectorCliPromise = null;
|
|
59
|
+
function resolveLocalRuvectorCli() {
|
|
60
|
+
if (ruvectorCliPromise)
|
|
61
|
+
return ruvectorCliPromise;
|
|
62
|
+
ruvectorCliPromise = (async () => {
|
|
63
|
+
try {
|
|
64
|
+
const { createRequire } = await import('node:module');
|
|
65
|
+
const path = await import('node:path');
|
|
66
|
+
const { readFile } = await import('node:fs/promises');
|
|
67
|
+
const req = createRequire(import.meta.url);
|
|
68
|
+
const entry = req.resolve('ruvector');
|
|
69
|
+
// Walk up from the resolved entry to the package root (package.json
|
|
70
|
+
// itself may not be exported, so we can't require.resolve it directly).
|
|
71
|
+
let dir = path.dirname(entry);
|
|
72
|
+
for (let i = 0; i < 6; i++) {
|
|
73
|
+
try {
|
|
74
|
+
const pkg = JSON.parse(await readFile(path.join(dir, 'package.json'), 'utf-8'));
|
|
75
|
+
if (pkg.name === 'ruvector') {
|
|
76
|
+
const bin = typeof pkg.bin === 'string' ? pkg.bin : pkg.bin?.ruvector;
|
|
77
|
+
return bin ? path.join(dir, bin) : null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// no package.json at this level — keep walking
|
|
82
|
+
}
|
|
83
|
+
const parent = path.dirname(dir);
|
|
84
|
+
if (parent === dir)
|
|
85
|
+
break;
|
|
86
|
+
dir = parent;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// ruvector not installed locally
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
})();
|
|
94
|
+
return ruvectorCliPromise;
|
|
95
|
+
}
|
|
96
|
+
/** Run a ruvector CLI command: local install first, npx pin as fallback. */
|
|
97
|
+
async function ruvector(args, opts = {}) {
|
|
98
|
+
const cli = await resolveLocalRuvectorCli();
|
|
99
|
+
if (cli)
|
|
100
|
+
return shell(process.execPath, [cli, ...args], opts);
|
|
101
|
+
return shell('npx', ['-y', RUVECTOR_PIN, ...args], opts);
|
|
102
|
+
}
|
|
44
103
|
async function ensureSessionsDir() {
|
|
45
104
|
const { mkdir } = await import('node:fs/promises');
|
|
46
105
|
const path = await import('node:path');
|
|
@@ -110,11 +169,21 @@ export const browserSessionTools = [
|
|
|
110
169
|
//
|
|
111
170
|
// 384 matches the MiniLM-L6 default used elsewhere in the
|
|
112
171
|
// toolchain (ONNX embedder + AgentDB vector indexes).
|
|
113
|
-
const rvf = await
|
|
172
|
+
const rvf = await ruvector(['rvf', 'create', rvfPath, '--dimension', '384'], { timeout: 60000 });
|
|
114
173
|
if (!rvf.success)
|
|
115
174
|
return fail('rvf create failed', { detail: rvf.error, stderr: rvf.stderr, sessionId, rvfPath });
|
|
116
|
-
// 2. trajectory-begin
|
|
117
|
-
|
|
175
|
+
// 2. trajectory-begin.
|
|
176
|
+
// Flag names verified against ruvector 0.2.25 AND 0.2.27 --help:
|
|
177
|
+
// trajectory-begin takes `-c/--context` + `-a/--agent` — the
|
|
178
|
+
// `--session-id`/`--task` flags previously passed here were never
|
|
179
|
+
// valid on either version (commander exited with "unknown option"),
|
|
180
|
+
// so every trajectory call failed. Same bug class as the #2015
|
|
181
|
+
// `rvf create --kind` fix documented above. The session id is folded
|
|
182
|
+
// into the context string since ruvector tracks a single current
|
|
183
|
+
// trajectory per project.
|
|
184
|
+
const tb = await ruvector(['hooks', 'trajectory-begin',
|
|
185
|
+
'--context', `${sessionId}: ${input.task}`,
|
|
186
|
+
'--agent', 'browser-session']);
|
|
118
187
|
if (!tb.success)
|
|
119
188
|
return fail('trajectory-begin failed', { detail: tb.error, stderr: tb.stderr, sessionId, rvfPath });
|
|
120
189
|
// 3. browser_open via agent-browser
|
|
@@ -125,11 +194,11 @@ export const browserSessionTools = [
|
|
|
125
194
|
return fail('browser open failed', { detail: npxBo.error, stderr: npxBo.stderr, sessionId, rvfPath });
|
|
126
195
|
}
|
|
127
196
|
}
|
|
128
|
-
// 4. log the open as the first trajectory step
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
'--
|
|
197
|
+
// 4. log the open as the first trajectory step.
|
|
198
|
+
// trajectory-step takes `--action`/`--result` only (no --session-id /
|
|
199
|
+
// --args on 0.2.25 or 0.2.27) — args are folded into the action string.
|
|
200
|
+
await ruvector(['hooks', 'trajectory-step',
|
|
201
|
+
'--action', `browser_open ${JSON.stringify({ url: input.url })}`,
|
|
133
202
|
'--result', 'ok']);
|
|
134
203
|
return ok({
|
|
135
204
|
sessionId,
|
|
@@ -167,14 +236,19 @@ export const browserSessionTools = [
|
|
|
167
236
|
const verdict = input.verdict;
|
|
168
237
|
if (!['pass', 'fail', 'partial'].includes(verdict))
|
|
169
238
|
return fail(`invalid verdict: ${verdict}`);
|
|
170
|
-
// 1. trajectory-end
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
239
|
+
// 1. trajectory-end.
|
|
240
|
+
// trajectory-end takes `--success` + `--quality <0-1>` (no
|
|
241
|
+
// --session-id / --verdict on 0.2.25 or 0.2.27) — the verdict maps to
|
|
242
|
+
// quality: pass=1 (+ --success), partial=0.5, fail=0.
|
|
243
|
+
const teArgs = ['hooks', 'trajectory-end',
|
|
244
|
+
'--quality', verdict === 'pass' ? '1' : verdict === 'partial' ? '0.5' : '0'];
|
|
245
|
+
if (verdict === 'pass')
|
|
246
|
+
teArgs.push('--success');
|
|
247
|
+
const te = await ruvector(teArgs);
|
|
174
248
|
if (!te.success)
|
|
175
249
|
return fail('trajectory-end failed', { detail: te.error, stderr: te.stderr });
|
|
176
250
|
// 2. rvf compact
|
|
177
|
-
const compact = await
|
|
251
|
+
const compact = await ruvector(['rvf', 'compact', input.rvf_path]);
|
|
178
252
|
if (!compact.success)
|
|
179
253
|
return fail('rvf compact failed', { detail: compact.error, stderr: compact.stderr });
|
|
180
254
|
// 3. AgentDB index — best-effort via memory store (claude-flow bridges)
|
|
@@ -224,7 +298,7 @@ export const browserSessionTools = [
|
|
|
224
298
|
if (!vS.valid)
|
|
225
299
|
return fail(vS.error || 'invalid session');
|
|
226
300
|
// 1. Verify RVF container exists
|
|
227
|
-
const status = await
|
|
301
|
+
const status = await ruvector(['rvf', 'status', input.rvf_path]);
|
|
228
302
|
if (!status.success)
|
|
229
303
|
return fail('rvf status failed', { detail: status.error, stderr: status.stderr });
|
|
230
304
|
// 2. Derive child container if requested
|
|
@@ -236,13 +310,13 @@ export const browserSessionTools = [
|
|
|
236
310
|
const dir = path.dirname(input.rvf_path);
|
|
237
311
|
replayId = `${input.session}-replay-${Date.now()}`;
|
|
238
312
|
replayPath = path.join(dir, `${replayId}.rvf`);
|
|
239
|
-
const dr = await
|
|
313
|
+
const dr = await ruvector(['rvf', 'derive', input.rvf_path, replayPath]);
|
|
240
314
|
if (!dr.success)
|
|
241
315
|
return fail('rvf derive failed', { detail: dr.error, stderr: dr.stderr });
|
|
242
316
|
}
|
|
243
317
|
// 3. Surface the trajectory steps from the segments listing — the caller is
|
|
244
318
|
// expected to read trajectory.ndjson from the RVF container and dispatch.
|
|
245
|
-
const segments = await
|
|
319
|
+
const segments = await ruvector(['rvf', 'segments', input.rvf_path]);
|
|
246
320
|
return ok({
|
|
247
321
|
sourceSession: input.session,
|
|
248
322
|
sourceRvfPath: input.rvf_path,
|
|
@@ -48,6 +48,7 @@ export declare const hooksWorkerDetect: MCPTool;
|
|
|
48
48
|
export declare const hooksModelRoute: MCPTool;
|
|
49
49
|
export declare const hooksModelOutcome: MCPTool;
|
|
50
50
|
export declare const hooksModelStats: MCPTool;
|
|
51
|
+
export declare const hooksModelVerify: MCPTool;
|
|
51
52
|
export declare const hooksCodemod: MCPTool;
|
|
52
53
|
export declare const hooksWorkerCancel: MCPTool;
|
|
53
54
|
export declare const hooksTeammateIdle: MCPTool;
|
|
@@ -2384,12 +2384,14 @@ export const hooksTrajectoryStart = {
|
|
|
2384
2384
|
properties: {
|
|
2385
2385
|
task: { type: 'string', description: 'Task description' },
|
|
2386
2386
|
agent: { type: 'string', description: 'Agent type' },
|
|
2387
|
+
sessionId: { type: 'string', description: 'Session id for the execution-state tree (default: CLAUDE_FLOW_SESSION_ID or "default")' },
|
|
2387
2388
|
},
|
|
2388
2389
|
required: ['task'],
|
|
2389
2390
|
},
|
|
2390
2391
|
handler: async (params) => {
|
|
2391
2392
|
const task = params.task;
|
|
2392
2393
|
const agent = params.agent || 'coder';
|
|
2394
|
+
const sessionId = params.sessionId || process.env.CLAUDE_FLOW_SESSION_ID || 'default';
|
|
2393
2395
|
{
|
|
2394
2396
|
const v = validateText(task, 'task');
|
|
2395
2397
|
if (!v.valid)
|
|
@@ -2400,6 +2402,11 @@ export const hooksTrajectoryStart = {
|
|
|
2400
2402
|
if (!v.valid)
|
|
2401
2403
|
return { success: false, error: v.error };
|
|
2402
2404
|
}
|
|
2405
|
+
if (params.sessionId) {
|
|
2406
|
+
const v = validateIdentifier(params.sessionId, 'sessionId');
|
|
2407
|
+
if (!v.valid)
|
|
2408
|
+
return { success: false, error: v.error };
|
|
2409
|
+
}
|
|
2403
2410
|
const trajectoryId = `traj-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
2404
2411
|
const startedAt = new Date().toISOString();
|
|
2405
2412
|
// Create real trajectory entry in memory
|
|
@@ -2411,6 +2418,13 @@ export const hooksTrajectoryStart = {
|
|
|
2411
2418
|
startedAt,
|
|
2412
2419
|
};
|
|
2413
2420
|
activeTrajectories.set(trajectoryId, trajectory);
|
|
2421
|
+
// MAGE-style execution-state-tree mirror (prototype, ruvector/trajectory-tree.ts).
|
|
2422
|
+
// Non-fatal by design — the semantic trajectory path below is untouched.
|
|
2423
|
+
try {
|
|
2424
|
+
const { getTrajectoryTree } = await import('../ruvector/trajectory-tree.js');
|
|
2425
|
+
getTrajectoryTree().openTrajectory({ sessionId, trajectoryId, task, agent });
|
|
2426
|
+
}
|
|
2427
|
+
catch { /* prototype path — never blocks trajectory recording */ }
|
|
2414
2428
|
// Persist pending trajectory to disk so it survives MCP restarts
|
|
2415
2429
|
const storeFn = await getRealStoreFunction();
|
|
2416
2430
|
if (storeFn) {
|
|
@@ -2479,6 +2493,12 @@ export const hooksTrajectoryStep = {
|
|
|
2479
2493
|
timestamp,
|
|
2480
2494
|
});
|
|
2481
2495
|
}
|
|
2496
|
+
// MAGE-style execution-state-tree mirror (prototype, non-fatal)
|
|
2497
|
+
try {
|
|
2498
|
+
const { getTrajectoryTree } = await import('../ruvector/trajectory-tree.js');
|
|
2499
|
+
getTrajectoryTree().appendStep({ trajectoryId, stepId, action, quality });
|
|
2500
|
+
}
|
|
2501
|
+
catch { /* prototype path — never blocks step recording */ }
|
|
2482
2502
|
// ADR-130 Phase 3: fire-and-forget causal edge write
|
|
2483
2503
|
// trajectory context node → step node (relation: "trajectory-caused")
|
|
2484
2504
|
if (result) {
|
|
@@ -2563,6 +2583,12 @@ export const hooksTrajectoryEnd = {
|
|
|
2563
2583
|
// Remove from active trajectories
|
|
2564
2584
|
activeTrajectories.delete(trajectoryId);
|
|
2565
2585
|
}
|
|
2586
|
+
// MAGE-style execution-state-tree mirror (prototype, non-fatal)
|
|
2587
|
+
try {
|
|
2588
|
+
const { getTrajectoryTree } = await import('../ruvector/trajectory-tree.js');
|
|
2589
|
+
getTrajectoryTree().closeTrajectory({ trajectoryId, success });
|
|
2590
|
+
}
|
|
2591
|
+
catch { /* prototype path — never blocks trajectory finalization */ }
|
|
2566
2592
|
// SONA Learning - process trajectory outcome for routing optimization
|
|
2567
2593
|
let sonaResult = {
|
|
2568
2594
|
learned: false, patternKey: '', confidence: 0
|
|
@@ -2866,6 +2892,9 @@ export const hooksPatternSearch = {
|
|
|
2866
2892
|
topK: { type: 'number', description: 'Number of results' },
|
|
2867
2893
|
minConfidence: { type: 'number', description: 'Minimum similarity threshold (0-1)' },
|
|
2868
2894
|
namespace: { type: 'string', description: 'Namespace to search (default: pattern)' },
|
|
2895
|
+
strategy: { type: 'string', enum: ['semantic', 'state-tree'], description: 'Retrieval strategy. Default "semantic" (unchanged behavior). "state-tree" returns the MAGE-style root→current execution-state path for a session instead of embedding search (prototype).' },
|
|
2896
|
+
sessionId: { type: 'string', description: 'Session id for strategy="state-tree" (default: CLAUDE_FLOW_SESSION_ID or "default")' },
|
|
2897
|
+
depth: { type: 'number', description: 'For strategy="state-tree": max path nodes returned, counted from the current node upward' },
|
|
2869
2898
|
},
|
|
2870
2899
|
required: ['query'],
|
|
2871
2900
|
},
|
|
@@ -2874,6 +2903,7 @@ export const hooksPatternSearch = {
|
|
|
2874
2903
|
const topK = params.topK || 5;
|
|
2875
2904
|
const minConfidence = params.minConfidence || 0.3;
|
|
2876
2905
|
const namespace = params.namespace || 'pattern';
|
|
2906
|
+
const strategy = params.strategy || 'semantic';
|
|
2877
2907
|
{
|
|
2878
2908
|
const v = validateText(query, 'query');
|
|
2879
2909
|
if (!v.valid)
|
|
@@ -2884,6 +2914,44 @@ export const hooksPatternSearch = {
|
|
|
2884
2914
|
if (!v.valid)
|
|
2885
2915
|
return { success: false, error: v.error };
|
|
2886
2916
|
}
|
|
2917
|
+
// Opt-in MAGE-style positional retrieval (prototype). Default stays the
|
|
2918
|
+
// semantic vector path below — zero behavior change unless requested.
|
|
2919
|
+
if (strategy === 'state-tree') {
|
|
2920
|
+
const sessionId = params.sessionId || process.env.CLAUDE_FLOW_SESSION_ID || 'default';
|
|
2921
|
+
if (params.sessionId) {
|
|
2922
|
+
const v = validateIdentifier(params.sessionId, 'sessionId');
|
|
2923
|
+
if (!v.valid)
|
|
2924
|
+
return { success: false, error: v.error };
|
|
2925
|
+
}
|
|
2926
|
+
try {
|
|
2927
|
+
const { getTrajectoryTree } = await import('../ruvector/trajectory-tree.js');
|
|
2928
|
+
const recall = getTrajectoryTree().recallPath({
|
|
2929
|
+
sessionId,
|
|
2930
|
+
depth: typeof params.depth === 'number' ? params.depth : undefined,
|
|
2931
|
+
siblingWindow: topK,
|
|
2932
|
+
});
|
|
2933
|
+
return {
|
|
2934
|
+
query,
|
|
2935
|
+
strategy: 'state-tree',
|
|
2936
|
+
backend: 'state-tree',
|
|
2937
|
+
sessionId: recall.sessionId,
|
|
2938
|
+
currentId: recall.currentId,
|
|
2939
|
+
path: recall.path,
|
|
2940
|
+
siblings: recall.siblings,
|
|
2941
|
+
note: 'Positional root→current execution-state path (no embedding search). Prototype — see ruvector/trajectory-tree.ts limitations.',
|
|
2942
|
+
};
|
|
2943
|
+
}
|
|
2944
|
+
catch (error) {
|
|
2945
|
+
return {
|
|
2946
|
+
query,
|
|
2947
|
+
strategy: 'state-tree',
|
|
2948
|
+
backend: 'state-tree-unavailable',
|
|
2949
|
+
path: [],
|
|
2950
|
+
siblings: [],
|
|
2951
|
+
error: String(error),
|
|
2952
|
+
};
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2887
2955
|
// Phase 3: Try ReasoningBank search via bridge first
|
|
2888
2956
|
try {
|
|
2889
2957
|
const bridge = await import('../memory/memory-bridge.js');
|
|
@@ -4293,6 +4361,68 @@ export const hooksModelStats = {
|
|
|
4293
4361
|
};
|
|
4294
4362
|
},
|
|
4295
4363
|
};
|
|
4364
|
+
// Model verify — confidence-gated tier escalation (post-generation).
|
|
4365
|
+
// The heuristics live in ruvector/output-verifier.ts; this wrapper adds the
|
|
4366
|
+
// MCP surface and feeds the verdict into the SAME learning stream that
|
|
4367
|
+
// hooks_model-outcome uses (ModelRouter.recordOutcome → Thompson priors), so
|
|
4368
|
+
// the router learns from escalations. A dedicated tool (rather than a
|
|
4369
|
+
// "verify mode" on hooks_model-outcome) was chosen because outcome recording
|
|
4370
|
+
// is a terminal write while verify is a MID-LOOP decision point that returns
|
|
4371
|
+
// a verdict the agent acts on — overloading outcome would conflate the two
|
|
4372
|
+
// and break the route → generate → verify → (escalate) → outcome sequence.
|
|
4373
|
+
export const hooksModelVerify = {
|
|
4374
|
+
name: 'hooks_model-verify',
|
|
4375
|
+
description: 'Verify a generated output with CHEAP structural signals ($0, no LLM call) and get an escalation verdict — the post-generation half of confidence-gated tier routing (route → generate → verify → escalate on failure). Checks: empty/truncated output, refusal patterns, degenerate repetition, and real syntax parsing for code/JSON tasks (TypeScript compiler / JSON.parse). Returns {confident, reasons[], suggestedTier, suggestedModel, escalate}. By default the verdict is recorded into the model-routing learning stream (success when confident, escalated when not) so the bandit learns which task shapes the cheap tier fails on. Use when you just generated with the tier hooks_model-route picked and must decide accept-vs-escalate BEFORE acting on the output; accepting cheap-tier output unverified is wrong because structurally unusable results (refusals, truncation, unparseable code) silently propagate downstream, and pre-generation routing alone cannot catch them. Not a semantic-quality judge — it only catches structurally unusable outputs.',
|
|
4376
|
+
inputSchema: {
|
|
4377
|
+
type: 'object',
|
|
4378
|
+
properties: {
|
|
4379
|
+
task: { type: 'string', description: 'The task the output was generated for' },
|
|
4380
|
+
output: { type: 'string', description: 'The generated output to verify' },
|
|
4381
|
+
model: { type: 'string', enum: ['haiku', 'sonnet', 'opus'], description: 'Model that produced the output (drives the escalation ladder; default haiku)' },
|
|
4382
|
+
tierUsed: { type: 'number', enum: [1, 2, 3], description: 'Tier that produced the output; derived from model when absent' },
|
|
4383
|
+
taskKind: { type: 'string', enum: ['code', 'json', 'text', 'auto'], description: 'Force the task kind; default auto-detect' },
|
|
4384
|
+
record: { type: 'boolean', description: 'Record the verdict into the routing learning stream (default true; requires model)' },
|
|
4385
|
+
},
|
|
4386
|
+
required: ['task', 'output'],
|
|
4387
|
+
},
|
|
4388
|
+
handler: async (params) => {
|
|
4389
|
+
const task = params.task;
|
|
4390
|
+
const output = params.output;
|
|
4391
|
+
const model = params.model;
|
|
4392
|
+
{
|
|
4393
|
+
const v = validateText(task, 'task');
|
|
4394
|
+
if (!v.valid)
|
|
4395
|
+
return { success: false, error: v.error };
|
|
4396
|
+
}
|
|
4397
|
+
if (typeof output !== 'string')
|
|
4398
|
+
return { success: false, error: 'output must be a string' };
|
|
4399
|
+
const { verifyAndEscalate } = await import('../ruvector/output-verifier.js');
|
|
4400
|
+
const verdict = await verifyAndEscalate({
|
|
4401
|
+
task,
|
|
4402
|
+
output,
|
|
4403
|
+
model,
|
|
4404
|
+
tierUsed: params.tierUsed,
|
|
4405
|
+
taskKind: params.taskKind,
|
|
4406
|
+
});
|
|
4407
|
+
// Feed the verdict into the existing outcome/learning stream (same path
|
|
4408
|
+
// as hooks_model-outcome) so escalations update the Thompson priors.
|
|
4409
|
+
let recorded = false;
|
|
4410
|
+
if (params.record !== false && model) {
|
|
4411
|
+
const router = await getModelRouterInstance();
|
|
4412
|
+
if (router) {
|
|
4413
|
+
router.recordOutcome(task, model, verdict.confident ? 'success' : 'escalated');
|
|
4414
|
+
recorded = true;
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
return {
|
|
4418
|
+
...verdict,
|
|
4419
|
+
model: model ?? 'haiku',
|
|
4420
|
+
recorded,
|
|
4421
|
+
recordedOutcome: recorded ? (verdict.confident ? 'success' : 'escalated') : null,
|
|
4422
|
+
timestamp: new Date().toISOString(),
|
|
4423
|
+
};
|
|
4424
|
+
},
|
|
4425
|
+
};
|
|
4296
4426
|
// Supported source extensions for codemods.
|
|
4297
4427
|
const CODEMOD_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.mts', '.cts']);
|
|
4298
4428
|
const CODEMOD_MAX_FILES = 2000;
|
|
@@ -4686,6 +4816,7 @@ export const hooksTools = [
|
|
|
4686
4816
|
hooksModelRoute,
|
|
4687
4817
|
hooksModelOutcome,
|
|
4688
4818
|
hooksModelStats,
|
|
4819
|
+
hooksModelVerify,
|
|
4689
4820
|
// Deterministic Tier-1 codemod execution (ADR-143)
|
|
4690
4821
|
hooksCodemod,
|
|
4691
4822
|
];
|