0xray 4.0.22 → 4.0.23
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/.grok-plugin/plugin.json +1 -1
- package/.opencode/skills/orchestrator/SKILL.md +7 -7
- package/.xray/features.json +1 -1
- package/dist/CHANGELOG.md +24 -1
- package/dist/inference/inference-accumulator.js +36 -4
- package/dist/inference/inference-cycle.d.ts +12 -0
- package/dist/inference/inference-cycle.js +158 -16
- package/dist/inference/inference-proposal-generator.js +148 -5
- package/dist/inference/session-capture.d.ts +4 -0
- package/dist/integrations/governance/index.d.ts +5 -0
- package/dist/integrations/governance/index.js +22 -2
- package/dist/integrations/governance/types.d.ts +11 -0
- package/dist/integrations/governance/types.js +16 -0
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/dist/mcps/governance.server.d.ts +2 -0
- package/dist/mcps/governance.server.js +12 -1
- package/dist/memory-routing/index.d.ts +1 -1
- package/dist/memory-routing/index.js +1 -1
- package/dist/memory-routing/lesson-pickup.d.ts +2 -0
- package/dist/memory-routing/lesson-pickup.js +57 -0
- package/dist/memory-routing/provider-loader.d.ts +5 -0
- package/dist/memory-routing/provider-loader.js +46 -0
- package/dist/memory-routing/provider-registry.d.ts +7 -2
- package/dist/memory-routing/provider-registry.js +26 -6
- package/dist/memory-routing/record-lesson.d.ts +16 -0
- package/dist/memory-routing/record-lesson.js +26 -0
- package/dist/memory-routing/types.d.ts +15 -0
- package/dist/nucleus/thin-dispatch.d.ts +2 -0
- package/dist/nucleus/thin-dispatch.js +3 -2
- package/dist/skills/orchestrator/SKILL.md +7 -7
- package/package.json +1 -1
- package/scripts/foundry/reconcile-version.mjs +23 -1
- package/scripts/foundry/release.mjs +1 -1
- package/scripts/foundry/version-manager.mjs +53 -0
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/src/mcps/governance.server.ts +12 -1
- package/src/skills/orchestrator/SKILL.md +7 -7
- package/vendor/@0xray/repertoire/data/stack-overlay.json +4 -4
- package/vendor/@0xray/repertoire/data/subject-overlay.json +3 -3
- package/vendor/@0xray/repertoire/dist/RepertoireService.d.ts +4 -0
- package/vendor/@0xray/repertoire/dist/RepertoireService.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/dist/RepertoireService.js +4 -4
- package/vendor/@0xray/repertoire/dist/RepertoireService.js.map +1 -1
- package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.js.map +1 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.js +5 -0
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.js.map +1 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.js +16 -0
- package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.js.map +1 -1
- package/vendor/@0xray/repertoire/dist/paths.d.ts +3 -3
- package/vendor/@0xray/repertoire/dist/paths.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/dist/paths.js.map +1 -1
- package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.d.ts +11 -0
- package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.js +2 -0
- package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.js.map +1 -1
- package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.d.ts +54 -4
- package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.d.ts.map +1 -1
- package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js +326 -11
- package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js.map +1 -1
- package/vendor/@0xray/repertoire/dist/types.d.ts +21 -0
- package/vendor/@0xray/repertoire/dist/types.d.ts.map +1 -1
- package/xray/features.json +1 -1
|
@@ -1,3 +1,87 @@
|
|
|
1
|
+
function primitivesForSessions(corpus, sessionIds) {
|
|
2
|
+
const wanted = new Set(sessionIds);
|
|
3
|
+
const names = [];
|
|
4
|
+
for (const session of corpus.sessions) {
|
|
5
|
+
if (!wanted.has(session.sessionId))
|
|
6
|
+
continue;
|
|
7
|
+
const listed = session.matched_primitives ?? session.matchedPrimitives ?? [];
|
|
8
|
+
for (const name of listed)
|
|
9
|
+
names.push(name);
|
|
10
|
+
}
|
|
11
|
+
return [...new Set(names.filter((name) => name.length > 0))];
|
|
12
|
+
}
|
|
13
|
+
function signalsForPattern(corpus, pattern) {
|
|
14
|
+
const names = primitivesForSessions(corpus, pattern.sessions);
|
|
15
|
+
if (pattern.name.length > 0)
|
|
16
|
+
names.push(pattern.name);
|
|
17
|
+
return [...new Set(names)];
|
|
18
|
+
}
|
|
19
|
+
/** Solid band starts at 0.82. Landed work with no wrong turn clears it. */
|
|
20
|
+
const LANDED_RESONANCE = 0.85;
|
|
21
|
+
const WRONG_TURN_RESONANCE = 0.4;
|
|
22
|
+
function resonanceForNamedSignal(corpus, sessionIds) {
|
|
23
|
+
const sessions = corpus.sessions.filter((session) => sessionIds.includes(session.sessionId));
|
|
24
|
+
if (sessions.some((session) => (session.wrongTurns?.length ?? 0) > 0))
|
|
25
|
+
return WRONG_TURN_RESONANCE;
|
|
26
|
+
if (sessions.some((session) => (session.approaches?.length ?? 0) > 0 || (session.solutions?.length ?? 0) > 0)) {
|
|
27
|
+
return LANDED_RESONANCE;
|
|
28
|
+
}
|
|
29
|
+
let confidence = 0.7;
|
|
30
|
+
for (const session of sessions) {
|
|
31
|
+
for (const pattern of session.patterns) {
|
|
32
|
+
if (pattern.confidence > confidence)
|
|
33
|
+
confidence = pattern.confidence;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return confidence;
|
|
37
|
+
}
|
|
38
|
+
function sessionsForWrongTurn(corpus, turn) {
|
|
39
|
+
return corpus.sessions
|
|
40
|
+
.filter((session) => session.wrongTurns.includes(turn))
|
|
41
|
+
.map((session) => session.sessionId);
|
|
42
|
+
}
|
|
43
|
+
const LESSON_TEXT_CAP = 400;
|
|
44
|
+
/** The trace a graded line stores. Wrong turns, then approaches, then solutions. */
|
|
45
|
+
function lessonTrace(corpus, sessionIds) {
|
|
46
|
+
const wanted = new Set(sessionIds);
|
|
47
|
+
const lines = [];
|
|
48
|
+
for (const session of corpus.sessions) {
|
|
49
|
+
if (!wanted.has(session.sessionId))
|
|
50
|
+
continue;
|
|
51
|
+
for (const turn of session.wrongTurns ?? []) {
|
|
52
|
+
if (turn.trim())
|
|
53
|
+
lines.push(turn.trim());
|
|
54
|
+
}
|
|
55
|
+
for (const line of [...(session.approaches ?? []), ...(session.solutions ?? [])]) {
|
|
56
|
+
if (line.trim())
|
|
57
|
+
lines.push(line.trim());
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return [...new Set(lines)].join("; ").slice(0, LESSON_TEXT_CAP);
|
|
61
|
+
}
|
|
62
|
+
function sortedSessionIds(sessionIds) {
|
|
63
|
+
return [...new Set(sessionIds.filter((sessionId) => sessionId.length > 0))].sort();
|
|
64
|
+
}
|
|
65
|
+
/** Sessions already included in a prior proposal of this exact prefix. */
|
|
66
|
+
function coveredSessions(history, prefix) {
|
|
67
|
+
const seen = new Set();
|
|
68
|
+
for (const cycle of history ?? []) {
|
|
69
|
+
for (const proposal of cycle.proposals) {
|
|
70
|
+
if (!proposal.id.startsWith(prefix))
|
|
71
|
+
continue;
|
|
72
|
+
for (const sessionId of proposal.id.slice(prefix.length).split(",")) {
|
|
73
|
+
if (sessionId.length > 0)
|
|
74
|
+
seen.add(sessionId);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return seen;
|
|
79
|
+
}
|
|
80
|
+
/** Sessions that have not already been graded under this prefix. */
|
|
81
|
+
function freshSessions(history, prefix, sessionIds) {
|
|
82
|
+
const covered = coveredSessions(history, prefix);
|
|
83
|
+
return sortedSessionIds(sessionIds).filter((sessionId) => !covered.has(sessionId));
|
|
84
|
+
}
|
|
1
85
|
function classifyProposalType(problemPattern) {
|
|
2
86
|
const lower = problemPattern.toLowerCase();
|
|
3
87
|
if (lower.includes("bug") || lower.includes("fix") || lower.includes("stability"))
|
|
@@ -63,6 +147,8 @@ function adjustConfidenceFromHistory(proposals, history) {
|
|
|
63
147
|
}
|
|
64
148
|
}
|
|
65
149
|
for (const proposal of proposals) {
|
|
150
|
+
if (proposal.id.startsWith("named:"))
|
|
151
|
+
continue;
|
|
66
152
|
const approved = approvedTypes.get(proposal.type) ?? 0;
|
|
67
153
|
const rejected = rejectedTypes.get(proposal.type) ?? 0;
|
|
68
154
|
const total = approved + rejected;
|
|
@@ -80,15 +166,21 @@ function adjustConfidenceFromHistory(proposals, history) {
|
|
|
80
166
|
export function generateProposals(corpus, history) {
|
|
81
167
|
const proposals = [];
|
|
82
168
|
for (const problem of corpus.recurringProblems) {
|
|
169
|
+
const prefix = `problem:${problem.pattern}:`;
|
|
170
|
+
const sessions = freshSessions(history, prefix, problem.sessions);
|
|
171
|
+
if (sessions.length === 0)
|
|
172
|
+
continue;
|
|
83
173
|
proposals.push({
|
|
84
|
-
id:
|
|
174
|
+
id: `${prefix}${sessions.join(",")}`,
|
|
85
175
|
type: classifyProposalType(problem.pattern),
|
|
86
176
|
title: generateTitle(problem),
|
|
87
177
|
description: `Recurring across ${problem.occurrences} sessions: ${problem.pattern}`,
|
|
88
|
-
evidence:
|
|
178
|
+
evidence: sessions.map((s) => `Seen in session ${s}`),
|
|
89
179
|
confidence: Math.min(0.95, 0.5 + problem.occurrences * 0.15),
|
|
90
180
|
source: "recurring_problem",
|
|
91
181
|
status: "pending",
|
|
182
|
+
namedSignals: primitivesForSessions(corpus, sessions),
|
|
183
|
+
lesson: lessonTrace(corpus, sessions),
|
|
92
184
|
});
|
|
93
185
|
}
|
|
94
186
|
const seenPatterns = new Set(corpus.recurringProblems.map((p) => p.pattern));
|
|
@@ -99,8 +191,14 @@ export function generateProposals(corpus, history) {
|
|
|
99
191
|
continue;
|
|
100
192
|
if (proposals.length >= 5)
|
|
101
193
|
break;
|
|
194
|
+
const prefix = `problem:${normalized}:`;
|
|
195
|
+
const sessions = freshSessions(history, prefix, [session]);
|
|
196
|
+
if (sessions.length === 0) {
|
|
197
|
+
seenPatterns.add(normalized);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
102
200
|
proposals.push({
|
|
103
|
-
id:
|
|
201
|
+
id: `${prefix}${sessions.join(",")}`,
|
|
104
202
|
type: classifyProposalType(problem),
|
|
105
203
|
title: `Investigate: ${problem.substring(0, 80)}`,
|
|
106
204
|
description: `Observed in session ${session}: ${problem}`,
|
|
@@ -108,15 +206,21 @@ export function generateProposals(corpus, history) {
|
|
|
108
206
|
confidence: 0.4,
|
|
109
207
|
source: "recurring_problem",
|
|
110
208
|
status: "pending",
|
|
209
|
+
namedSignals: primitivesForSessions(corpus, sessions),
|
|
210
|
+
lesson: lessonTrace(corpus, sessions),
|
|
111
211
|
});
|
|
112
212
|
seenPatterns.add(normalized);
|
|
113
213
|
}
|
|
114
214
|
for (const pattern of corpus.recurringPatterns) {
|
|
115
215
|
if (pattern.occurrences < 2)
|
|
116
216
|
continue;
|
|
217
|
+
const prefix = `pattern:${pattern.name}:`;
|
|
218
|
+
const sessions = freshSessions(history, prefix, pattern.sessions);
|
|
219
|
+
if (sessions.length === 0)
|
|
220
|
+
continue;
|
|
117
221
|
const type = patternToProposalType(pattern);
|
|
118
222
|
proposals.push({
|
|
119
|
-
id:
|
|
223
|
+
id: `${prefix}${sessions.join(",")}`,
|
|
120
224
|
type,
|
|
121
225
|
title: `Codify ${pattern.name} pattern`,
|
|
122
226
|
description: `${pattern.name} detected across ${pattern.occurrences} sessions (avg confidence: ${Math.round(pattern.avgConfidence * 100)}%). ${pattern.description}`,
|
|
@@ -124,13 +228,19 @@ export function generateProposals(corpus, history) {
|
|
|
124
228
|
confidence: pattern.avgConfidence,
|
|
125
229
|
source: "recurring_pattern",
|
|
126
230
|
status: "pending",
|
|
231
|
+
namedSignals: signalsForPattern(corpus, { ...pattern, sessions }),
|
|
232
|
+
lesson: lessonTrace(corpus, sessions),
|
|
127
233
|
});
|
|
128
234
|
}
|
|
129
235
|
const wrongTurns = corpus.allWrongTurns.slice(0, 2);
|
|
130
236
|
for (const wt of wrongTurns) {
|
|
131
237
|
const summary = wt.length > 50 ? `${wt.substring(0, 47)}...` : wt;
|
|
238
|
+
const prefix = `wrong:${wt}:`;
|
|
239
|
+
const sessions = freshSessions(history, prefix, sessionsForWrongTurn(corpus, wt));
|
|
240
|
+
if (sessions.length === 0)
|
|
241
|
+
continue;
|
|
132
242
|
proposals.push({
|
|
133
|
-
id:
|
|
243
|
+
id: `${prefix}${sessions.join(",")}`,
|
|
134
244
|
type: "guard",
|
|
135
245
|
title: `Guard against: ${summary}`,
|
|
136
246
|
description: `Recurring wrong turn detected: ${wt}. Add a guard or validation to prevent this pattern.`,
|
|
@@ -138,9 +248,42 @@ export function generateProposals(corpus, history) {
|
|
|
138
248
|
confidence: 0.7,
|
|
139
249
|
source: "wrong_turn",
|
|
140
250
|
status: "pending",
|
|
251
|
+
namedSignals: primitivesForSessions(corpus, sessions),
|
|
252
|
+
lesson: lessonTrace(corpus, sessions),
|
|
141
253
|
});
|
|
142
254
|
}
|
|
143
255
|
const sorted = proposals.sort((a, b) => b.confidence - a.confidence).slice(0, 3);
|
|
256
|
+
const alreadyNamed = new Set(sorted.flatMap((proposal) => proposal.namedSignals ?? []));
|
|
257
|
+
const bySignal = new Map();
|
|
258
|
+
for (const session of corpus.sessions) {
|
|
259
|
+
const listed = session.matched_primitives ?? session.matchedPrimitives ?? [];
|
|
260
|
+
for (const name of listed) {
|
|
261
|
+
if (name.length === 0 || alreadyNamed.has(name))
|
|
262
|
+
continue;
|
|
263
|
+
const sessions = bySignal.get(name) ?? [];
|
|
264
|
+
if (!sessions.includes(session.sessionId))
|
|
265
|
+
sessions.push(session.sessionId);
|
|
266
|
+
bySignal.set(name, sessions);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
for (const [name, sessionIds] of bySignal) {
|
|
270
|
+
const sessions = freshSessions(history, `named:${name}:`, sessionIds);
|
|
271
|
+
if (sessions.length === 0)
|
|
272
|
+
continue;
|
|
273
|
+
const id = `named:${name}:${sessions.join(",")}`;
|
|
274
|
+
sorted.push({
|
|
275
|
+
id,
|
|
276
|
+
type: "codify",
|
|
277
|
+
title: `Grade named signal ${name}`,
|
|
278
|
+
description: `Sessions ${sessions.join(", ")} named ${name}.`,
|
|
279
|
+
evidence: sessions.map((sessionId) => `Seen in session ${sessionId}`),
|
|
280
|
+
confidence: resonanceForNamedSignal(corpus, sessions),
|
|
281
|
+
source: "recurring_pattern",
|
|
282
|
+
status: "pending",
|
|
283
|
+
namedSignals: [name],
|
|
284
|
+
lesson: lessonTrace(corpus, sessions),
|
|
285
|
+
});
|
|
286
|
+
}
|
|
144
287
|
if (history && history.length > 0) {
|
|
145
288
|
adjustConfidenceFromHistory(sorted, history);
|
|
146
289
|
sorted.sort((a, b) => b.confidence - a.confidence);
|
|
@@ -13,6 +13,10 @@ export interface SessionInference {
|
|
|
13
13
|
reasoningChain: ReasoningLink[];
|
|
14
14
|
patterns: StructuralPattern[];
|
|
15
15
|
metrics: SessionMetrics;
|
|
16
|
+
/** Signals this session already named. Session JSON stores this list. */
|
|
17
|
+
matched_primitives?: string[];
|
|
18
|
+
/** Same list after load, so callers can read either field. */
|
|
19
|
+
matchedPrimitives?: string[];
|
|
16
20
|
}
|
|
17
21
|
export interface ReasoningLink {
|
|
18
22
|
from: string;
|
|
@@ -39,6 +39,11 @@ export declare class InferenceGovernanceIntegration extends BaseIntegration {
|
|
|
39
39
|
* Check if governance is enabled and available
|
|
40
40
|
*/
|
|
41
41
|
isAvailable(): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Start the Dynamo client when a caller required external governance
|
|
44
|
+
* and boot skipped it because inference_governance.enabled is false.
|
|
45
|
+
*/
|
|
46
|
+
ensureDynamoClient(): Promise<void>;
|
|
42
47
|
/**
|
|
43
48
|
* Check a single inference proposal via the appropriate governance tool
|
|
44
49
|
*
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @since 2026-05-11
|
|
8
8
|
*/
|
|
9
9
|
import { BaseIntegration } from '../base/index.js';
|
|
10
|
-
import { DEFAULT_GOVERNANCE_CONFIG } from './types.js';
|
|
10
|
+
import { DEFAULT_GOVERNANCE_CONFIG, resolveDynamoBaseUrl } from './types.js';
|
|
11
11
|
import { GovernanceClient } from './governance-client.js';
|
|
12
12
|
import { frameworkLogger } from '../../core/framework-logger.js';
|
|
13
13
|
import { featuresConfigLoader } from '../../core/features-config.js';
|
|
@@ -84,7 +84,27 @@ export class InferenceGovernanceIntegration extends BaseIntegration {
|
|
|
84
84
|
* Check if governance is enabled and available
|
|
85
85
|
*/
|
|
86
86
|
isAvailable() {
|
|
87
|
-
return this.
|
|
87
|
+
return this.client !== null && this.status === 'initialized';
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Start the Dynamo client when a caller required external governance
|
|
91
|
+
* and boot skipped it because inference_governance.enabled is false.
|
|
92
|
+
*/
|
|
93
|
+
async ensureDynamoClient() {
|
|
94
|
+
if (this.status !== 'initialized') {
|
|
95
|
+
await this.initialize();
|
|
96
|
+
}
|
|
97
|
+
if (this.client !== null) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const endpoint = resolveDynamoBaseUrl(this.configData.endpointUrl);
|
|
101
|
+
this.client = new GovernanceClient({
|
|
102
|
+
baseUrl: endpoint,
|
|
103
|
+
timeoutMs: this.configData.requestTimeoutMs,
|
|
104
|
+
});
|
|
105
|
+
await this.log('info', 'Dynamo client armed for required external governance', {
|
|
106
|
+
endpoint,
|
|
107
|
+
});
|
|
88
108
|
}
|
|
89
109
|
/**
|
|
90
110
|
* Check a single inference proposal via the appropriate governance tool
|
|
@@ -56,6 +56,17 @@ export interface GovernanceIntegrationConfig {
|
|
|
56
56
|
voteWeightMultiplier: number;
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Public Dynamo Solar SSOT. Used only when a caller requires external
|
|
61
|
+
* governance and neither features nor GOVERNANCE_ENDPOINT names a host.
|
|
62
|
+
* The client posts to /call_connected_tool on this origin.
|
|
63
|
+
*/
|
|
64
|
+
export declare const PUBLIC_DYNAMO_SSOT = "https://mcp-production-80e2.up.railway.app";
|
|
65
|
+
/**
|
|
66
|
+
* Origin for POST /call_connected_tool.
|
|
67
|
+
* Preference: configured endpoint, then GOVERNANCE_ENDPOINT, then the public SSOT.
|
|
68
|
+
*/
|
|
69
|
+
export declare function resolveDynamoBaseUrl(configured: string): string;
|
|
59
70
|
/**
|
|
60
71
|
* Default governance configuration
|
|
61
72
|
*/
|
|
@@ -6,6 +6,22 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @since 2026-05-11
|
|
8
8
|
*/
|
|
9
|
+
/**
|
|
10
|
+
* Public Dynamo Solar SSOT. Used only when a caller requires external
|
|
11
|
+
* governance and neither features nor GOVERNANCE_ENDPOINT names a host.
|
|
12
|
+
* The client posts to /call_connected_tool on this origin.
|
|
13
|
+
*/
|
|
14
|
+
export const PUBLIC_DYNAMO_SSOT = 'https://mcp-production-80e2.up.railway.app';
|
|
15
|
+
/**
|
|
16
|
+
* Origin for POST /call_connected_tool.
|
|
17
|
+
* Preference: configured endpoint, then GOVERNANCE_ENDPOINT, then the public SSOT.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveDynamoBaseUrl(configured) {
|
|
20
|
+
const fromConfig = configured.trim();
|
|
21
|
+
const fromEnv = (process.env.GOVERNANCE_ENDPOINT || '').trim();
|
|
22
|
+
const raw = fromConfig || fromEnv || PUBLIC_DYNAMO_SSOT;
|
|
23
|
+
return raw.replace(/\/governance\/?$/, '').replace(/\/$/, '');
|
|
24
|
+
}
|
|
9
25
|
/**
|
|
10
26
|
* Default governance configuration
|
|
11
27
|
*/
|
|
@@ -20,6 +20,8 @@ declare class GovernanceServer extends XrayKnowledgeSkillBase {
|
|
|
20
20
|
protected setupToolHandlers(): void;
|
|
21
21
|
/** Align tool default with features.json inference_governance.enabled (P0.2). */
|
|
22
22
|
private resolveRequireExternal;
|
|
23
|
+
/** Boot stays flag-gated. A required external call starts the client on demand. */
|
|
24
|
+
private armRequiredDynamo;
|
|
23
25
|
private handleGovernProposals;
|
|
24
26
|
private handleGovernReflection;
|
|
25
27
|
private handleGetActiveCodex;
|
|
@@ -196,8 +196,19 @@ class GovernanceServer extends XrayKnowledgeSkillBase {
|
|
|
196
196
|
return false;
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
|
+
/** Boot stays flag-gated. A required external call starts the client on demand. */
|
|
200
|
+
async armRequiredDynamo(requireExternal) {
|
|
201
|
+
if (!requireExternal)
|
|
202
|
+
return;
|
|
203
|
+
const integration = await initializeGovernanceIntegration();
|
|
204
|
+
if (!integration.isAvailable()) {
|
|
205
|
+
await integration.ensureDynamoClient();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
199
208
|
async handleGovernProposals(args) {
|
|
200
209
|
const service = getGovernanceService();
|
|
210
|
+
const requireExternal = this.resolveRequireExternal(args.options?.require_external);
|
|
211
|
+
await this.armRequiredDynamo(requireExternal);
|
|
201
212
|
const request = {
|
|
202
213
|
proposals: args.proposals.map((p, i) => ({
|
|
203
214
|
id: p.id || `prop-${Date.now()}-${i}`,
|
|
@@ -210,7 +221,7 @@ class GovernanceServer extends XrayKnowledgeSkillBase {
|
|
|
210
221
|
})),
|
|
211
222
|
context: args.context || {},
|
|
212
223
|
options: {
|
|
213
|
-
requireExternalDynamo:
|
|
224
|
+
requireExternalDynamo: requireExternal,
|
|
214
225
|
},
|
|
215
226
|
};
|
|
216
227
|
frameworkLogger.log("governance-mcp", "delegating-to-governance-service", "info", {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { MemoryRoutingProvider, MemoryRoutingContext, MemoryInheritedContext, MemoryAgentCapability, MemoryOrchestrationTask, MemoryThinDispatchResult, MemoryRoutingConfig, OrchestratorFeedbackEntry, } from './types.js';
|
|
2
|
-
export { getMemoryRoutingProvider, getMemoryRoutingProviderSync, getMemoryRoutingConfig, resetMemoryRoutingProvider, initializeMemoryRouting, } from './provider-registry.js';
|
|
2
|
+
export { getMemoryRoutingProvider, getMemoryRoutingProviderSync, ensureMemoryRoutingProviderSync, getMemoryRoutingConfig, resetMemoryRoutingProvider, initializeMemoryRouting, } from './provider-registry.js';
|
|
3
3
|
export { NullMemoryRoutingProvider, createMemoryRoutingProvider } from './null-provider.js';
|
|
4
4
|
export { loadMemoryRoutingProvider, isLeftoverMemoryRoutingOff, isExplicitMemoryRoutingOptOut, resolveLeftoverEnabledConfig, } from './provider-loader.js';
|
|
5
5
|
export { validateMemoryRoutingConfig } from './validate-config.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { getMemoryRoutingProvider, getMemoryRoutingProviderSync, getMemoryRoutingConfig, resetMemoryRoutingProvider, initializeMemoryRouting, } from './provider-registry.js';
|
|
1
|
+
export { getMemoryRoutingProvider, getMemoryRoutingProviderSync, ensureMemoryRoutingProviderSync, getMemoryRoutingConfig, resetMemoryRoutingProvider, initializeMemoryRouting, } from './provider-registry.js';
|
|
2
2
|
export { NullMemoryRoutingProvider, createMemoryRoutingProvider } from './null-provider.js';
|
|
3
3
|
export { loadMemoryRoutingProvider, isLeftoverMemoryRoutingOff, isExplicitMemoryRoutingOptOut, resolveLeftoverEnabledConfig, } from './provider-loader.js';
|
|
4
4
|
export { validateMemoryRoutingConfig } from './validate-config.js';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
const START = "<!-- lessons -->";
|
|
4
|
+
const END = "<!-- /lessons -->";
|
|
5
|
+
const DEST = path.join(".xray", "state", "repertoire", "curated_signals.json");
|
|
6
|
+
const FLOOR = 0.55;
|
|
7
|
+
function aboveFloor(value) {
|
|
8
|
+
return typeof value === "number" && value > FLOOR && Math.abs(value - FLOOR) > 1e-4;
|
|
9
|
+
}
|
|
10
|
+
function lessonBlock(signals) {
|
|
11
|
+
const lines = ["## Lessons"];
|
|
12
|
+
const graded = signals.filter((signal) => typeof signal.name === "string"
|
|
13
|
+
&& aboveFloor(signal.observation_stats?.avg_confidence)
|
|
14
|
+
&& (signal.lessons?.length ?? 0) > 0);
|
|
15
|
+
if (graded.length === 0) {
|
|
16
|
+
lines.push("No graded lines above the floor.");
|
|
17
|
+
return lines.join("\n");
|
|
18
|
+
}
|
|
19
|
+
for (const signal of graded) {
|
|
20
|
+
lines.push(`### ${signal.name}`);
|
|
21
|
+
if (typeof signal.definition === "string" && signal.definition.length > 0) {
|
|
22
|
+
lines.push(signal.definition);
|
|
23
|
+
}
|
|
24
|
+
for (const lesson of signal.lessons ?? []) {
|
|
25
|
+
const text = lesson.text.length > 0 ? lesson.text : "graded";
|
|
26
|
+
lines.push(`- ${lesson.decision} \`${lesson.taskId}\` — ${text}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return lines.join("\n");
|
|
30
|
+
}
|
|
31
|
+
/** Refresh the Station lessons section from dest. Leaves the rest of the card alone. */
|
|
32
|
+
export function writeLessonPickup(projectRoot) {
|
|
33
|
+
const stationPath = path.join(projectRoot, ".xray", "state", "STATION.md");
|
|
34
|
+
if (!fs.existsSync(stationPath))
|
|
35
|
+
return;
|
|
36
|
+
const destPath = path.join(projectRoot, DEST);
|
|
37
|
+
let signals = [];
|
|
38
|
+
if (fs.existsSync(destPath)) {
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(fs.readFileSync(destPath, "utf-8"));
|
|
41
|
+
if (parsed && typeof parsed === "object" && "signals" in parsed && Array.isArray(parsed.signals)) {
|
|
42
|
+
signals = parsed.signals;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
signals = [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const section = `${START}\n${lessonBlock(signals)}\n${END}`;
|
|
50
|
+
const current = fs.readFileSync(stationPath, "utf-8");
|
|
51
|
+
const pattern = /<!-- lessons -->[\s\S]*?<!-- \/lessons -->/;
|
|
52
|
+
const next = pattern.test(current)
|
|
53
|
+
? current.replace(pattern, section)
|
|
54
|
+
: `${current.trimEnd()}\n\n${section}\n`;
|
|
55
|
+
if (next !== current)
|
|
56
|
+
fs.writeFileSync(stationPath, next);
|
|
57
|
+
}
|
|
@@ -5,4 +5,9 @@ export declare function isExplicitMemoryRoutingOptOut(config: MemoryRoutingConfi
|
|
|
5
5
|
* Explicit opt-out is enabled: false + provider: repertoire. */
|
|
6
6
|
export declare function isLeftoverMemoryRoutingOff(config: MemoryRoutingConfig): boolean;
|
|
7
7
|
export declare function resolveLeftoverEnabledConfig(config: MemoryRoutingConfig, cwd: string): MemoryRoutingConfig | null;
|
|
8
|
+
/**
|
|
9
|
+
* First route cannot wait on the async import. Node loads this ESM with require.
|
|
10
|
+
* Returns null when routing is off or the module cannot be constructed.
|
|
11
|
+
*/
|
|
12
|
+
export declare function loadMemoryRoutingProviderSync(config: MemoryRoutingConfig, cwd?: string): MemoryRoutingProvider | null;
|
|
8
13
|
export declare function loadMemoryRoutingProvider(config: MemoryRoutingConfig, cwd?: string): Promise<MemoryRoutingProvider>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
2
3
|
import { resolve, dirname, join } from 'node:path';
|
|
3
4
|
import { pathToFileURL } from 'node:url';
|
|
4
5
|
import { frameworkLogger } from '../core/framework-logger.js';
|
|
@@ -19,6 +20,7 @@ function resolveUnavailableReason(provider) {
|
|
|
19
20
|
return null;
|
|
20
21
|
}
|
|
21
22
|
}
|
|
23
|
+
const require = createRequire(import.meta.url);
|
|
22
24
|
const REPERTOIRE_CANDIDATE_PATHS = [
|
|
23
25
|
'node_modules/@0xray/repertoire/dist/provider/memory-routing-provider.js',
|
|
24
26
|
'vendor/@0xray/repertoire/dist/provider/memory-routing-provider.js',
|
|
@@ -99,6 +101,50 @@ export function resolveLeftoverEnabledConfig(config, cwd) {
|
|
|
99
101
|
enabled.config = config.config;
|
|
100
102
|
return enabled;
|
|
101
103
|
}
|
|
104
|
+
function providerFromModule(mod, modulePath, effective) {
|
|
105
|
+
const factory = mod.createMemoryRoutingProvider ?? mod.default?.createMemoryRoutingProvider;
|
|
106
|
+
if (typeof factory !== 'function') {
|
|
107
|
+
throw new Error(`Module ${modulePath} must export createMemoryRoutingProvider()`);
|
|
108
|
+
}
|
|
109
|
+
const provider = factory((effective.config ?? {}));
|
|
110
|
+
if (!provider?.id || typeof provider.isAvailable !== 'function') {
|
|
111
|
+
throw new Error(`Invalid memory routing provider from ${modulePath}`);
|
|
112
|
+
}
|
|
113
|
+
return provider;
|
|
114
|
+
}
|
|
115
|
+
function resolveEffectiveProvider(config, cwd) {
|
|
116
|
+
const validation = validateMemoryRoutingConfig(config);
|
|
117
|
+
if (!validation.valid)
|
|
118
|
+
return null;
|
|
119
|
+
const leftover = resolveLeftoverEnabledConfig(validation.normalized, cwd);
|
|
120
|
+
const effective = leftover ?? validation.normalized;
|
|
121
|
+
if (!effective.enabled || effective.provider === 'null')
|
|
122
|
+
return null;
|
|
123
|
+
const modulePath = resolveModulePath(effective.module_path, cwd) ??
|
|
124
|
+
defaultPathForProvider(effective.provider, cwd);
|
|
125
|
+
if (!modulePath)
|
|
126
|
+
return null;
|
|
127
|
+
return { effective, modulePath };
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* First route cannot wait on the async import. Node loads this ESM with require.
|
|
131
|
+
* Returns null when routing is off or the module cannot be constructed.
|
|
132
|
+
*/
|
|
133
|
+
export function loadMemoryRoutingProviderSync(config, cwd = process.cwd()) {
|
|
134
|
+
const resolved = resolveEffectiveProvider(config, cwd);
|
|
135
|
+
if (!resolved)
|
|
136
|
+
return null;
|
|
137
|
+
try {
|
|
138
|
+
const mod = require(resolved.modulePath);
|
|
139
|
+
const provider = providerFromModule(mod, resolved.modulePath, resolved.effective);
|
|
140
|
+
if (!provider.isAvailable())
|
|
141
|
+
return null;
|
|
142
|
+
return provider;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
102
148
|
export async function loadMemoryRoutingProvider(config, cwd = process.cwd()) {
|
|
103
149
|
const validation = validateMemoryRoutingConfig(config);
|
|
104
150
|
if (!validation.valid) {
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import type { MemoryRoutingConfig, MemoryRoutingProvider } from './types.js';
|
|
2
2
|
export declare function getMemoryRoutingConfig(): MemoryRoutingConfig;
|
|
3
3
|
export declare function getMemoryRoutingProvider(forceReload?: boolean): Promise<MemoryRoutingProvider>;
|
|
4
|
-
/** Synchronous accessor — returns shared null singleton until
|
|
4
|
+
/** Synchronous accessor — returns shared null singleton until a load completes. */
|
|
5
5
|
export declare function getMemoryRoutingProviderSync(): MemoryRoutingProvider;
|
|
6
|
+
/**
|
|
7
|
+
* The first scoreAndRoute call loads the organ before it routes.
|
|
8
|
+
* A later sync read sees the same provider. Off or a missing module stays null.
|
|
9
|
+
*/
|
|
10
|
+
export declare function ensureMemoryRoutingProviderSync(): MemoryRoutingProvider;
|
|
6
11
|
export declare function resetMemoryRoutingProvider(): void;
|
|
7
|
-
/** Kick off
|
|
12
|
+
/** Kick off load at module init. Sync require first so a later route does not require() an ESM module while its dynamic import is still in flight. */
|
|
8
13
|
export declare function initializeMemoryRouting(): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { featuresConfigLoader } from '../core/features-config.js';
|
|
2
2
|
import { NullMemoryRoutingProvider } from './null-provider.js';
|
|
3
|
-
import { loadMemoryRoutingProvider, resolveLeftoverEnabledConfig } from './provider-loader.js';
|
|
3
|
+
import { loadMemoryRoutingProvider, loadMemoryRoutingProviderSync, resolveLeftoverEnabledConfig, } from './provider-loader.js';
|
|
4
4
|
import { validateMemoryRoutingConfig } from './validate-config.js';
|
|
5
5
|
let cachedProvider = null;
|
|
6
6
|
let loadPromise = null;
|
|
@@ -12,30 +12,50 @@ export function getMemoryRoutingConfig() {
|
|
|
12
12
|
validation.normalized);
|
|
13
13
|
}
|
|
14
14
|
export async function getMemoryRoutingProvider(forceReload = false) {
|
|
15
|
-
if (!forceReload && cachedProvider)
|
|
15
|
+
if (!forceReload && cachedProvider && cachedProvider.id !== 'null')
|
|
16
16
|
return cachedProvider;
|
|
17
17
|
if (!forceReload && loadPromise)
|
|
18
18
|
return loadPromise;
|
|
19
19
|
const config = getMemoryRoutingConfig();
|
|
20
20
|
loadPromise = loadMemoryRoutingProvider(config).then((provider) => {
|
|
21
|
+
if (provider.id === 'null' && cachedProvider && cachedProvider.id !== 'null') {
|
|
22
|
+
loadPromise = null;
|
|
23
|
+
return cachedProvider;
|
|
24
|
+
}
|
|
21
25
|
cachedProvider = provider;
|
|
22
26
|
loadPromise = null;
|
|
23
27
|
return provider;
|
|
24
28
|
});
|
|
25
29
|
return loadPromise;
|
|
26
30
|
}
|
|
27
|
-
/** Synchronous accessor — returns shared null singleton until
|
|
31
|
+
/** Synchronous accessor — returns shared null singleton until a load completes. */
|
|
28
32
|
export function getMemoryRoutingProviderSync() {
|
|
29
33
|
return cachedProvider ?? nullProviderSingleton;
|
|
30
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* The first scoreAndRoute call loads the organ before it routes.
|
|
37
|
+
* A later sync read sees the same provider. Off or a missing module stays null.
|
|
38
|
+
*/
|
|
39
|
+
export function ensureMemoryRoutingProviderSync() {
|
|
40
|
+
if (cachedProvider && cachedProvider.id !== 'null')
|
|
41
|
+
return cachedProvider;
|
|
42
|
+
const config = getMemoryRoutingConfig();
|
|
43
|
+
if (!config.enabled)
|
|
44
|
+
return cachedProvider ?? nullProviderSingleton;
|
|
45
|
+
const loaded = loadMemoryRoutingProviderSync(config);
|
|
46
|
+
if (!loaded || loaded.id === 'null')
|
|
47
|
+
return cachedProvider ?? nullProviderSingleton;
|
|
48
|
+
cachedProvider = loaded;
|
|
49
|
+
return loaded;
|
|
50
|
+
}
|
|
31
51
|
export function resetMemoryRoutingProvider() {
|
|
32
52
|
cachedProvider = null;
|
|
33
53
|
loadPromise = null;
|
|
34
54
|
}
|
|
35
|
-
/** Kick off
|
|
55
|
+
/** Kick off load at module init. Sync require first so a later route does not require() an ESM module while its dynamic import is still in flight. */
|
|
36
56
|
export function initializeMemoryRouting() {
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
57
|
+
const loaded = ensureMemoryRoutingProviderSync();
|
|
58
|
+
if (loaded.id === 'null' && getMemoryRoutingConfig().enabled) {
|
|
39
59
|
void getMemoryRoutingProvider();
|
|
40
60
|
}
|
|
41
61
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface LessonInput {
|
|
2
|
+
operation: string;
|
|
3
|
+
success: boolean;
|
|
4
|
+
taskId: string;
|
|
5
|
+
assignedAgent: string;
|
|
6
|
+
sessionId: string;
|
|
7
|
+
/** Signals the finished task already named. When set, text matching is not used. */
|
|
8
|
+
signals?: string[];
|
|
9
|
+
/** Approaches, solutions, or the wrong turn from the sessions that named the law. */
|
|
10
|
+
lesson?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A finished task names the signals its text actually matched and says
|
|
14
|
+
* whether the outcome was good. That is the one-tenth step. No match, no write.
|
|
15
|
+
*/
|
|
16
|
+
export declare function recordLesson(input: LessonInput): string[];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ensureMemoryRoutingProviderSync } from "./provider-registry.js";
|
|
2
|
+
/**
|
|
3
|
+
* A finished task names the signals its text actually matched and says
|
|
4
|
+
* whether the outcome was good. That is the one-tenth step. No match, no write.
|
|
5
|
+
*/
|
|
6
|
+
export function recordLesson(input) {
|
|
7
|
+
const provider = ensureMemoryRoutingProviderSync();
|
|
8
|
+
if (provider.id === "null" || !provider.ingestFeedback)
|
|
9
|
+
return [];
|
|
10
|
+
const named = input.signals ?? provider.buildRoutingContext(input.operation).matchedSignals;
|
|
11
|
+
const names = [...new Set(named.filter((name) => name.length > 0))];
|
|
12
|
+
if (names.length === 0)
|
|
13
|
+
return [];
|
|
14
|
+
provider.ingestFeedback({
|
|
15
|
+
timestamp: new Date().toISOString(),
|
|
16
|
+
sessionId: input.sessionId,
|
|
17
|
+
taskId: input.taskId,
|
|
18
|
+
assignedAgent: input.assignedAgent,
|
|
19
|
+
memorySignals: names,
|
|
20
|
+
complexity: 0,
|
|
21
|
+
success: input.success,
|
|
22
|
+
durationMs: 0,
|
|
23
|
+
lesson: input.lesson ?? "",
|
|
24
|
+
});
|
|
25
|
+
return names;
|
|
26
|
+
}
|