@neuroverseos/nv-sim 0.1.9 → 0.1.10
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/README.md +90 -3
- package/connectors/nv_mirofish_wrapper.py +841 -0
- package/connectors/nv_scienceclaw_wrapper.py +453 -0
- package/dist/adapters/scienceclaw.js +52 -2
- package/dist/assets/index-B43_0HyO.css +1 -0
- package/dist/assets/index-CdghpsS8.js +595 -0
- package/dist/assets/{reportEngine-D2ZrMny8.js → reportEngine-CYSZfooa.js} +1 -1
- package/dist/connectors/nv-scienceclaw-post.js +376 -0
- package/dist/engine/aiProvider.js +82 -3
- package/dist/engine/analyzer.js +12 -24
- package/dist/engine/cli.js +89 -114
- package/dist/engine/dynamicsGovernance.js +4 -0
- package/dist/engine/fullGovernedLoop.js +16 -1
- package/dist/engine/goalEngine.js +3 -4
- package/dist/engine/governance.js +18 -0
- package/dist/engine/index.js +19 -28
- package/dist/engine/intentTranslator.js +281 -0
- package/dist/engine/liveAdapter.js +100 -18
- package/dist/engine/liveVisualizer.js +2071 -1023
- package/dist/engine/primeRadiant.js +2 -8
- package/dist/engine/reasoningEngine.js +2 -7
- package/dist/engine/scenarioCapsule.js +5 -5
- package/dist/engine/swarmSimulation.js +1 -9
- package/dist/engine/worldBridge.js +22 -8
- package/dist/index.html +2 -2
- package/dist/lib/reasoningEngine.js +17 -1
- package/dist/lib/simulationAdapter.js +11 -11
- package/dist/lib/swarmParser.js +1 -1
- package/dist/runtime/govern.js +160 -7
- package/dist/runtime/index.js +1 -4
- package/dist/runtime/types.js +91 -0
- package/package.json +23 -6
- package/dist/adapters/mirofish.js +0 -461
- package/dist/assets/index-B64NuIXu.css +0 -1
- package/dist/assets/index-BMkPevVr.js +0 -532
- package/dist/assets/mirotir-logo-DUexumBH.svg +0 -185
- package/dist/engine/mirofish.js +0 -295
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Intent Translator — Natural Language → World Rules
|
|
4
|
+
*
|
|
5
|
+
* Scientists describe what they want in plain language.
|
|
6
|
+
* AI translates that into NeuroverseOS governance rules.
|
|
7
|
+
*
|
|
8
|
+
* Uses the same AI provider infrastructure (BYOM) as the rest of the system.
|
|
9
|
+
* Falls back to deterministic heuristics when no AI is configured.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.translateIntent = translateIntent;
|
|
13
|
+
const aiProvider_1 = require("./aiProvider");
|
|
14
|
+
// ============================================
|
|
15
|
+
// SYSTEM PROMPT
|
|
16
|
+
// ============================================
|
|
17
|
+
const TRANSLATE_INTENT_PROMPT = `You are a governance rule translator for NeuroverseOS, a system that governs how AI agent swarms behave during scientific discovery.
|
|
18
|
+
|
|
19
|
+
A scientist has described their research intent in five categories:
|
|
20
|
+
- EXPLORE: What agents should investigate (becomes ALLOW rules and domain scope)
|
|
21
|
+
- EXCLUDE: What should never be concluded or published (becomes BLOCK rules)
|
|
22
|
+
- ELEVATE: What makes results valuable (becomes PRIORITIZE rules)
|
|
23
|
+
- EXPERIMENT: If/then rules with explicit verdicts (becomes REWARD, PENALIZE, or NEUTRAL rules)
|
|
24
|
+
- OPEN-ENDED: Free-form ideas the scientist wants to test (translate into the most appropriate rule types)
|
|
25
|
+
|
|
26
|
+
Translate their intent into structured governance rules. Return ONLY valid JSON:
|
|
27
|
+
{
|
|
28
|
+
"worldName": "short name for this research world (max 50 chars)",
|
|
29
|
+
"thesis": "one-sentence thesis for this world",
|
|
30
|
+
"rules": [
|
|
31
|
+
{
|
|
32
|
+
"source": "explore|exclude|elevate|experiment|open_ended",
|
|
33
|
+
"naturalLanguage": "human-readable rule description",
|
|
34
|
+
"ruleType": "ALLOW|BLOCK|PRIORITIZE|GATE|REWARD|PENALIZE|NEUTRAL",
|
|
35
|
+
"condition": {
|
|
36
|
+
"field": "the state/result field to check (e.g. result.confidence, action.domain, result.sources_count, agent.behavior, agent.influence)",
|
|
37
|
+
"operator": ">|<|>=|<=|==|!=|contains|matches",
|
|
38
|
+
"value": "the threshold or match value"
|
|
39
|
+
},
|
|
40
|
+
"effect": "what happens — for REWARD: boost_influence, priority, faster_execution, weight_increase. For PENALIZE: freeze, reduce_influence, increase_risk, cooldown. Include magnitude and rounds where appropriate.",
|
|
41
|
+
"confidence": 0.0-1.0
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
"suggestedInvariants": ["immutable rules that should never be violated"]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
Guidelines:
|
|
48
|
+
- Each natural language sentence should produce at least one rule
|
|
49
|
+
- EXCLUDE items → BLOCK rules with specific, enforceable conditions
|
|
50
|
+
- ELEVATE items → PRIORITIZE rules that boost result ranking
|
|
51
|
+
- EXPLORE items → ALLOW rules that define the investigation scope
|
|
52
|
+
- EXPERIMENT if/then items → REWARD or PENALIZE rules with concrete consequences (e.g. "freeze for 3 rounds", "boost influence by 2x")
|
|
53
|
+
- OPEN-ENDED items → use your best judgment to create the most appropriate rule types. These are experimental — the scientist is exploring what's possible
|
|
54
|
+
- Be specific about conditions — use quantitative thresholds where the user implies them
|
|
55
|
+
- If the user says "confidence below 70%", the condition should be {field: "result.confidence", operator: "<", value: 0.7}
|
|
56
|
+
- suggestedInvariants are the hardest constraints — things that must NEVER be violated`;
|
|
57
|
+
// ============================================
|
|
58
|
+
// MAIN TRANSLATION FUNCTION
|
|
59
|
+
// ============================================
|
|
60
|
+
/**
|
|
61
|
+
* Translate a scientist's natural language intent into governance rules.
|
|
62
|
+
* Uses AI when available, falls back to deterministic parsing.
|
|
63
|
+
*/
|
|
64
|
+
async function translateIntent(intent) {
|
|
65
|
+
const providerName = (0, aiProvider_1.getDefaultProviderName)();
|
|
66
|
+
if (providerName === "deterministic") {
|
|
67
|
+
return deterministicTranslation(intent);
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const provider = (0, aiProvider_1.getAIProvider)(providerName);
|
|
71
|
+
return await aiTranslation(intent, provider);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// AI failed — fall back to deterministic
|
|
75
|
+
return deterministicTranslation(intent);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// ============================================
|
|
79
|
+
// AI-POWERED TRANSLATION
|
|
80
|
+
// ============================================
|
|
81
|
+
async function aiTranslation(intent, provider) {
|
|
82
|
+
const experimentText = intent.experiment.length > 0
|
|
83
|
+
? intent.experiment.map(r => `- IF ${r.condition} THEN ${r.outcome} [${r.verdict}]`).join("\n")
|
|
84
|
+
: "(none)";
|
|
85
|
+
const userMessage = `Translate this research intent into governance rules:
|
|
86
|
+
|
|
87
|
+
EXPLORE (what to investigate):
|
|
88
|
+
${intent.explore || "(not specified)"}
|
|
89
|
+
|
|
90
|
+
EXCLUDE (what to reject):
|
|
91
|
+
${intent.exclude || "(not specified)"}
|
|
92
|
+
|
|
93
|
+
ELEVATE (what to trust):
|
|
94
|
+
${intent.elevate || "(not specified)"}
|
|
95
|
+
|
|
96
|
+
EXPERIMENT (if/then rules):
|
|
97
|
+
${experimentText}
|
|
98
|
+
|
|
99
|
+
OPEN-ENDED (other things to try):
|
|
100
|
+
${intent.openEnded || "(not specified)"}`;
|
|
101
|
+
let text;
|
|
102
|
+
if (provider instanceof aiProvider_1.AnthropicProvider) {
|
|
103
|
+
const client = await provider.getClient();
|
|
104
|
+
const response = await client.messages.create({
|
|
105
|
+
model: provider.model,
|
|
106
|
+
max_tokens: 1024,
|
|
107
|
+
system: TRANSLATE_INTENT_PROMPT,
|
|
108
|
+
messages: [{ role: "user", content: userMessage }],
|
|
109
|
+
});
|
|
110
|
+
text = response.content[0]?.type === "text" ? response.content[0].text : "{}";
|
|
111
|
+
}
|
|
112
|
+
else if (provider instanceof aiProvider_1.OpenAICompatibleProvider) {
|
|
113
|
+
text = await provider.chatCompletion(TRANSLATE_INTENT_PROMPT, userMessage, 1024);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
// Generic provider — use translate method as proxy
|
|
117
|
+
return deterministicTranslation(intent);
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const parsed = JSON.parse(text);
|
|
121
|
+
return {
|
|
122
|
+
rules: (parsed.rules ?? []).map((r, i) => ({
|
|
123
|
+
id: `ai_rule_${i}`,
|
|
124
|
+
source: r.source ?? "explore",
|
|
125
|
+
naturalLanguage: r.naturalLanguage ?? "",
|
|
126
|
+
ruleType: r.ruleType ?? "ALLOW",
|
|
127
|
+
condition: r.condition ?? { field: "unknown", operator: "==", value: true },
|
|
128
|
+
effect: r.effect ?? "",
|
|
129
|
+
confidence: r.confidence ?? 0.7,
|
|
130
|
+
})),
|
|
131
|
+
worldName: parsed.worldName ?? deriveWorldName(intent),
|
|
132
|
+
thesis: parsed.thesis ?? `Research world for: ${intent.explore.slice(0, 80)}`,
|
|
133
|
+
suggestedInvariants: parsed.suggestedInvariants ?? [],
|
|
134
|
+
translatedBy: `ai:${provider.name}`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return deterministicTranslation(intent);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// ============================================
|
|
142
|
+
// DETERMINISTIC FALLBACK
|
|
143
|
+
// ============================================
|
|
144
|
+
function deterministicTranslation(intent) {
|
|
145
|
+
const rules = [];
|
|
146
|
+
let idx = 0;
|
|
147
|
+
// EXPLORE → ALLOW rules
|
|
148
|
+
for (const sentence of splitSentences(intent.explore)) {
|
|
149
|
+
rules.push({
|
|
150
|
+
id: `rule_${idx++}`,
|
|
151
|
+
source: "explore",
|
|
152
|
+
naturalLanguage: `Agents should investigate: ${sentence}`,
|
|
153
|
+
ruleType: "ALLOW",
|
|
154
|
+
condition: { field: "action.domain", operator: "matches", value: extractKeyTerms(sentence) },
|
|
155
|
+
effect: "Allow investigation with priority",
|
|
156
|
+
confidence: 0.8,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// EXCLUDE → BLOCK rules
|
|
160
|
+
for (const sentence of splitSentences(intent.exclude)) {
|
|
161
|
+
const { field, operator, value } = parseBlockCondition(sentence);
|
|
162
|
+
rules.push({
|
|
163
|
+
id: `rule_${idx++}`,
|
|
164
|
+
source: "exclude",
|
|
165
|
+
naturalLanguage: `Block: ${sentence}`,
|
|
166
|
+
ruleType: "BLOCK",
|
|
167
|
+
condition: { field, operator, value },
|
|
168
|
+
effect: "Block action or publication",
|
|
169
|
+
confidence: 0.85,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
// ELEVATE → PRIORITIZE rules
|
|
173
|
+
for (const sentence of splitSentences(intent.elevate)) {
|
|
174
|
+
rules.push({
|
|
175
|
+
id: `rule_${idx++}`,
|
|
176
|
+
source: "elevate",
|
|
177
|
+
naturalLanguage: `Prioritize: ${sentence}`,
|
|
178
|
+
ruleType: "PRIORITIZE",
|
|
179
|
+
condition: { field: "result.quality", operator: "matches", value: sentence.slice(0, 80) },
|
|
180
|
+
effect: "Boost priority score by 2x",
|
|
181
|
+
confidence: 0.75,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
// EXPERIMENT if/then → REWARD / PENALIZE / NEUTRAL
|
|
185
|
+
for (const ifThen of intent.experiment) {
|
|
186
|
+
const effectMap = {
|
|
187
|
+
REWARD: `boost_influence for 3 rounds: ${ifThen.outcome}`,
|
|
188
|
+
PENALIZE: `reduce_influence for 3 rounds: ${ifThen.outcome}`,
|
|
189
|
+
NEUTRAL: `log observation: ${ifThen.outcome}`,
|
|
190
|
+
};
|
|
191
|
+
rules.push({
|
|
192
|
+
id: `rule_${idx++}`,
|
|
193
|
+
source: "experiment",
|
|
194
|
+
naturalLanguage: `If ${ifThen.condition} → ${ifThen.outcome}`,
|
|
195
|
+
ruleType: ifThen.verdict,
|
|
196
|
+
condition: { field: "agent.behavior", operator: "matches", value: ifThen.condition.slice(0, 80) },
|
|
197
|
+
effect: effectMap[ifThen.verdict] ?? ifThen.outcome,
|
|
198
|
+
confidence: 0.9,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
// OPEN-ENDED → NEUTRAL rules (AI would do better)
|
|
202
|
+
for (const sentence of splitSentences(intent.openEnded)) {
|
|
203
|
+
rules.push({
|
|
204
|
+
id: `rule_${idx++}`,
|
|
205
|
+
source: "open_ended",
|
|
206
|
+
naturalLanguage: sentence,
|
|
207
|
+
ruleType: "NEUTRAL",
|
|
208
|
+
condition: { field: "scenario.context", operator: "matches", value: extractKeyTerms(sentence) },
|
|
209
|
+
effect: "Experimental rule — AI will refine into specific governance",
|
|
210
|
+
confidence: 0.6,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
// Derive invariants from EXCLUDE items
|
|
214
|
+
const invariants = splitSentences(intent.exclude)
|
|
215
|
+
.filter(s => {
|
|
216
|
+
const lower = s.toLowerCase();
|
|
217
|
+
return lower.includes("never") || lower.includes("must not") || lower.includes("contradict");
|
|
218
|
+
})
|
|
219
|
+
.map(s => s);
|
|
220
|
+
return {
|
|
221
|
+
rules,
|
|
222
|
+
worldName: deriveWorldName(intent),
|
|
223
|
+
thesis: intent.explore.trim()
|
|
224
|
+
? `Governed investigation of: ${intent.explore.split(/[.;\n]/)[0].trim().slice(0, 100)}`
|
|
225
|
+
: "Custom governed research world",
|
|
226
|
+
suggestedInvariants: invariants,
|
|
227
|
+
translatedBy: "deterministic",
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
// ============================================
|
|
231
|
+
// HELPERS
|
|
232
|
+
// ============================================
|
|
233
|
+
function splitSentences(text) {
|
|
234
|
+
if (!text.trim())
|
|
235
|
+
return [];
|
|
236
|
+
return text
|
|
237
|
+
.split(/[.;\n]+/)
|
|
238
|
+
.map(s => s.trim())
|
|
239
|
+
.filter(s => s.length > 3);
|
|
240
|
+
}
|
|
241
|
+
function extractKeyTerms(text) {
|
|
242
|
+
const stopWords = new Set([
|
|
243
|
+
"the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for",
|
|
244
|
+
"of", "with", "by", "that", "this", "is", "are", "was", "were", "be",
|
|
245
|
+
"been", "being", "have", "has", "had", "do", "does", "did", "will",
|
|
246
|
+
"would", "could", "should", "may", "might", "can",
|
|
247
|
+
]);
|
|
248
|
+
return text
|
|
249
|
+
.toLowerCase()
|
|
250
|
+
.split(/\s+/)
|
|
251
|
+
.filter(w => w.length > 2 && !stopWords.has(w))
|
|
252
|
+
.slice(0, 6)
|
|
253
|
+
.join(" ");
|
|
254
|
+
}
|
|
255
|
+
function parseBlockCondition(text) {
|
|
256
|
+
const lower = text.toLowerCase();
|
|
257
|
+
if (lower.includes("single") && lower.includes("source")) {
|
|
258
|
+
return { field: "result.sources_count", operator: "<", value: 2 };
|
|
259
|
+
}
|
|
260
|
+
if (lower.includes("confidence") || lower.includes("below")) {
|
|
261
|
+
const match = text.match(/(\d+)%?/);
|
|
262
|
+
const threshold = match ? parseInt(match[1]) / 100 : 0.7;
|
|
263
|
+
return { field: "result.confidence", operator: "<", value: threshold };
|
|
264
|
+
}
|
|
265
|
+
if (lower.includes("reproduc")) {
|
|
266
|
+
return { field: "result.reproducible", operator: "==", value: false };
|
|
267
|
+
}
|
|
268
|
+
if (lower.includes("contradict")) {
|
|
269
|
+
return { field: "result.contradicts_laws", operator: "==", value: true };
|
|
270
|
+
}
|
|
271
|
+
return { field: "result.excluded_pattern", operator: "matches", value: text.slice(0, 80) };
|
|
272
|
+
}
|
|
273
|
+
function deriveWorldName(intent) {
|
|
274
|
+
const explore = intent.explore.trim();
|
|
275
|
+
if (!explore)
|
|
276
|
+
return "Custom World";
|
|
277
|
+
const first = explore.split(/[.,;\n]/)[0].trim();
|
|
278
|
+
if (first.length <= 50)
|
|
279
|
+
return first;
|
|
280
|
+
return first.slice(0, 47) + "...";
|
|
281
|
+
}
|
|
@@ -24,11 +24,13 @@
|
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
25
|
exports.ADAPTER_REGISTRY = void 0;
|
|
26
26
|
exports.createProcessAdapter = createProcessAdapter;
|
|
27
|
-
exports.
|
|
27
|
+
exports.parseStepBasedLine = parseStepBasedLine;
|
|
28
28
|
exports.parseGenericJsonLine = parseGenericJsonLine;
|
|
29
|
+
exports.parseScienceClawLine = parseScienceClawLine;
|
|
29
30
|
exports.createAdapter = createAdapter;
|
|
30
31
|
const child_process_1 = require("child_process");
|
|
31
32
|
const events_1 = require("events");
|
|
33
|
+
const types_1 = require("../runtime/types");
|
|
32
34
|
function createParserState() {
|
|
33
35
|
return {
|
|
34
36
|
currentRound: 0,
|
|
@@ -44,10 +46,10 @@ function createParserState() {
|
|
|
44
46
|
* Usage:
|
|
45
47
|
* const adapter = createProcessAdapter({
|
|
46
48
|
* type: "process",
|
|
47
|
-
* label: "
|
|
49
|
+
* label: "My Simulation (Live)",
|
|
48
50
|
* command: "node",
|
|
49
|
-
* args: ["
|
|
50
|
-
* parser:
|
|
51
|
+
* args: ["sim.js"],
|
|
52
|
+
* parser: parseStepBasedLine,
|
|
51
53
|
* });
|
|
52
54
|
* adapter.on("round", (round) => { ... });
|
|
53
55
|
* await adapter.start();
|
|
@@ -145,12 +147,12 @@ function createProcessAdapter(config) {
|
|
|
145
147
|
return emitter;
|
|
146
148
|
}
|
|
147
149
|
// ============================================
|
|
148
|
-
//
|
|
150
|
+
// GENERIC STEP-BASED STDOUT PARSER
|
|
149
151
|
// ============================================
|
|
150
152
|
/**
|
|
151
|
-
* Parses
|
|
153
|
+
* Parses step-based stdout line-by-line.
|
|
152
154
|
*
|
|
153
|
-
* Expected
|
|
155
|
+
* Expected output formats:
|
|
154
156
|
*
|
|
155
157
|
* JSON-per-line (preferred):
|
|
156
158
|
* {"step":1,"agent_actions":[{"agent_id":"a1","action":"buy","sentiment":0.5,"confidence":0.8}]}
|
|
@@ -164,7 +166,7 @@ function createProcessAdapter(config) {
|
|
|
164
166
|
* We emit a LiveSimulationRound when we detect a new step number
|
|
165
167
|
* (meaning the previous step is complete).
|
|
166
168
|
*/
|
|
167
|
-
function
|
|
169
|
+
function parseStepBasedLine(line, state) {
|
|
168
170
|
// Try JSON first
|
|
169
171
|
if (line.startsWith("{")) {
|
|
170
172
|
try {
|
|
@@ -199,7 +201,7 @@ function parseMiroFishLine(line, state) {
|
|
|
199
201
|
// Complete step as JSON — emit immediately
|
|
200
202
|
return {
|
|
201
203
|
round: data.step,
|
|
202
|
-
source: "
|
|
204
|
+
source: "generic",
|
|
203
205
|
agentActions: data.agent_actions.map((a) => ({
|
|
204
206
|
agent: a.agent_id ?? a.agent ?? "unknown",
|
|
205
207
|
action: a.action ?? "",
|
|
@@ -227,7 +229,7 @@ function parseMiroFishLine(line, state) {
|
|
|
227
229
|
if (stepNum > state.currentRound && state.currentRound > 0) {
|
|
228
230
|
result = {
|
|
229
231
|
round: state.currentRound,
|
|
230
|
-
source: "
|
|
232
|
+
source: "generic",
|
|
231
233
|
agentActions: [...state.pendingActions],
|
|
232
234
|
systemEvents: state.pendingEvents.length > 0 ? [...state.pendingEvents] : undefined,
|
|
233
235
|
emergentDynamics: state.pendingDynamics.length > 0 ? [...state.pendingDynamics] : undefined,
|
|
@@ -303,18 +305,98 @@ function parseGenericJsonLine(line, _state) {
|
|
|
303
305
|
return null;
|
|
304
306
|
}
|
|
305
307
|
}
|
|
308
|
+
// ============================================
|
|
309
|
+
// SCIENCECLAW STDOUT PARSER
|
|
310
|
+
// ============================================
|
|
311
|
+
/**
|
|
312
|
+
* Parses structured JSON lines emitted by ScienceClaw.
|
|
313
|
+
*
|
|
314
|
+
* IMPORTANT: Schema-gated governance — only strict JSON lines are processed.
|
|
315
|
+
* Non-JSON output is passed through without evaluation (by design).
|
|
316
|
+
* No regex or heuristic parsing is used, for safety and determinism.
|
|
317
|
+
*
|
|
318
|
+
* Expected format (one JSON object per line):
|
|
319
|
+
* {"cycle":1,"artifacts":[{"agent_id":"a1","type":"hypothesis","description":"...","confidence":0.7}]}
|
|
320
|
+
*
|
|
321
|
+
* Also accepted (alternate field names, same structure):
|
|
322
|
+
* {"step":1,"agents":[...]}
|
|
323
|
+
* {"round":1,"agent_actions":[...]}
|
|
324
|
+
*
|
|
325
|
+
* Any future formats must be added as explicit structured cases — never inferred.
|
|
326
|
+
*/
|
|
327
|
+
function parseScienceClawLine(line, _state) {
|
|
328
|
+
if (!line.startsWith("{"))
|
|
329
|
+
return null;
|
|
330
|
+
let data;
|
|
331
|
+
try {
|
|
332
|
+
data = JSON.parse(line);
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
// ── Route through NV Governance Schema v1 ──
|
|
338
|
+
const validated = (0, types_1.validateGovernableCycle)(data, "scienceclaw");
|
|
339
|
+
// Schema validation failed — line is ungovernable
|
|
340
|
+
if (typeof validated === "string")
|
|
341
|
+
return null;
|
|
342
|
+
// Map validated NVGovernableEvents → LiveAgentActions
|
|
343
|
+
const agents = validated.events.map((ev) => {
|
|
344
|
+
// Confidence-weighted impact: higher confidence + reproduced = more constructive
|
|
345
|
+
const impact = ev.reproduced
|
|
346
|
+
? Math.min(1, ev.confidence * 0.8 + 0.2) // reproduced = positive signal
|
|
347
|
+
: (ev.confidence - 0.4) * 1.2; // unreproduced = depends on confidence
|
|
348
|
+
return {
|
|
349
|
+
agent: ev.agentId,
|
|
350
|
+
action: ev.actionType,
|
|
351
|
+
impact: Number(Math.max(-1, Math.min(1, impact)).toFixed(3)),
|
|
352
|
+
confidence: ev.confidence,
|
|
353
|
+
metadata: {
|
|
354
|
+
artifactType: ev.actionType,
|
|
355
|
+
reproduced: ev.reproduced,
|
|
356
|
+
citations: ev.references,
|
|
357
|
+
description: ev.description,
|
|
358
|
+
nvSchema: ev.schema,
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
});
|
|
362
|
+
// Map reactor matches to system events (pass-through, not schema-gated)
|
|
363
|
+
const matches = (data.matches ?? data.reactor_matches ?? []);
|
|
364
|
+
const systemEvents = matches.map((m) => ({
|
|
365
|
+
id: m.matchType ?? m.type ?? "match",
|
|
366
|
+
label: m.description ?? `${m.matchType ?? "match"} (strength: ${(m.strength ?? 0).toFixed(2)})`,
|
|
367
|
+
severity: (m.matchType === "contradiction" || m.type === "contradiction")
|
|
368
|
+
? "moderate"
|
|
369
|
+
: "minor",
|
|
370
|
+
}));
|
|
371
|
+
// Extract emergent dynamics (pass-through)
|
|
372
|
+
const dynamics = (data.dynamics ?? data.emergent ?? []);
|
|
373
|
+
if (data.convergence_detected) {
|
|
374
|
+
dynamics.push("convergence_detected");
|
|
375
|
+
}
|
|
376
|
+
if (data.risk_flags) {
|
|
377
|
+
const flags = data.risk_flags;
|
|
378
|
+
dynamics.push(...(Array.isArray(flags) ? flags : [flags]));
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
round: validated.cycle,
|
|
382
|
+
source: validated.source,
|
|
383
|
+
agentActions: agents,
|
|
384
|
+
systemEvents,
|
|
385
|
+
emergentDynamics: dynamics,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
306
388
|
exports.ADAPTER_REGISTRY = {
|
|
307
|
-
|
|
308
|
-
id: "
|
|
309
|
-
label: "
|
|
310
|
-
description: "Spawn
|
|
389
|
+
scienceclaw: {
|
|
390
|
+
id: "scienceclaw",
|
|
391
|
+
label: "ScienceClaw (Live)",
|
|
392
|
+
description: "Spawn ScienceClaw investigation and govern artifact cycles in real time",
|
|
311
393
|
createConfig: (opts = {}) => ({
|
|
312
394
|
type: "process",
|
|
313
|
-
label: "
|
|
314
|
-
command: opts.command ?? "
|
|
315
|
-
args: (opts.args ?? "
|
|
395
|
+
label: "ScienceClaw (Live)",
|
|
396
|
+
command: opts.command ?? "scienceclaw-post",
|
|
397
|
+
args: (opts.args ?? "").split(" ").filter(Boolean),
|
|
316
398
|
cwd: opts.cwd,
|
|
317
|
-
parser:
|
|
399
|
+
parser: parseScienceClawLine,
|
|
318
400
|
}),
|
|
319
401
|
},
|
|
320
402
|
generic: {
|