@neuroverseos/governance 0.1.5 → 0.2.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/README.md +279 -423
- package/dist/adapters/express.cjs +242 -2
- package/dist/adapters/express.d.cts +1 -1
- package/dist/adapters/express.d.ts +1 -1
- package/dist/adapters/express.js +5 -3
- package/dist/adapters/index.cjs +301 -5
- package/dist/adapters/index.d.cts +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +8 -6
- package/dist/adapters/langchain.cjs +267 -3
- package/dist/adapters/langchain.d.cts +8 -1
- package/dist/adapters/langchain.d.ts +8 -1
- package/dist/adapters/langchain.js +5 -3
- package/dist/adapters/openai.cjs +267 -3
- package/dist/adapters/openai.d.cts +8 -1
- package/dist/adapters/openai.d.ts +8 -1
- package/dist/adapters/openai.js +5 -3
- package/dist/adapters/openclaw.cjs +267 -3
- package/dist/adapters/openclaw.d.cts +8 -1
- package/dist/adapters/openclaw.d.ts +8 -1
- package/dist/adapters/openclaw.js +5 -3
- package/dist/{bootstrap-H4HHKQ5G.js → bootstrap-GXVDZNF7.js} +2 -1
- package/dist/{build-73KAVHEY.js → build-P42YFKQV.js} +34 -3
- package/dist/{chunk-FYPYZFV5.js → chunk-2JQJ5U5X.js} +1 -1
- package/dist/chunk-37JG24WH.js +161 -0
- package/dist/chunk-5EDDNJU6.js +321 -0
- package/dist/{chunk-O5OMJMIE.js → chunk-7P3S7MAY.js} +502 -2
- package/dist/chunk-A5W4GNQO.js +130 -0
- package/dist/{chunk-ITJ3LCPG.js → chunk-ADV7Q2LJ.js} +1 -1
- package/dist/chunk-AKW5YVCE.js +96 -0
- package/dist/{chunk-EIUHJXBB.js → chunk-GR6DGCZ2.js} +1 -1
- package/dist/{chunk-EQXFOKH2.js → chunk-IVPKFJX3.js} +24 -3
- package/dist/{chunk-D7BGWV2J.js → chunk-NF5POFCI.js} +5 -3
- package/dist/chunk-OT6PXH54.js +61 -0
- package/dist/chunk-P74Y66ZV.js +205 -0
- package/dist/chunk-PAX2P6ZP.js +601 -0
- package/dist/{chunk-B4NF3OLW.js → chunk-PQBJBVSW.js} +56 -2
- package/dist/{chunk-T4X42QXC.js → chunk-Q6O7ZLO2.js} +0 -59
- package/dist/{chunk-FZQCRGUU.js → chunk-TINSRYXQ.js} +24 -3
- package/dist/{chunk-CROPZ75A.js → chunk-UPJNTSVM.js} +24 -3
- package/dist/chunk-YZFATT7X.js +9 -0
- package/dist/{chunk-Z2S2HIV5.js → chunk-ZL4AHY4X.js} +2 -2
- package/dist/cli/neuroverse.cjs +5287 -740
- package/dist/cli/neuroverse.js +69 -13
- package/dist/cli/plan.cjs +1554 -0
- package/dist/cli/plan.d.cts +20 -0
- package/dist/cli/plan.d.ts +20 -0
- package/dist/cli/plan.js +346 -0
- package/dist/cli/run.cjs +1716 -0
- package/dist/cli/run.d.cts +20 -0
- package/dist/cli/run.d.ts +20 -0
- package/dist/cli/run.js +143 -0
- package/dist/{configure-ai-46JVG56I.js → configure-ai-TK67ZWZL.js} +5 -2
- package/dist/{derive-6NAEWLM5.js → derive-TLIV4OOU.js} +6 -4
- package/dist/doctor-V72UM2TC.js +170 -0
- package/dist/{explain-3B3VB6TL.js → explain-IDCRWMPX.js} +2 -1
- package/dist/{guard-67Y66P3I.js → guard-WA3FCCIO.js} +20 -6
- package/dist/{guard-contract-D_RQz9kt.d.ts → guard-contract-D-2LQInm.d.cts} +144 -2
- package/dist/{guard-contract-D_RQz9kt.d.cts → guard-contract-D-2LQInm.d.ts} +144 -2
- package/dist/guard-engine-D7X4CVAE.js +10 -0
- package/dist/{impact-CHERK3O6.js → impact-BWULZ5RP.js} +5 -3
- package/dist/{improve-YG6I6ERG.js → improve-GPUBKTEA.js} +4 -3
- package/dist/index.cjs +2095 -89
- package/dist/index.d.cts +466 -12
- package/dist/index.d.ts +466 -12
- package/dist/index.js +70 -20
- package/dist/{init-Z66T6TDI.js → init-PKPIYHYE.js} +2 -0
- package/dist/mcp-server-YUOQP4M5.js +13 -0
- package/dist/model-adapter-BB7G4MFI.js +11 -0
- package/dist/playground-CBXMAW2B.js +550 -0
- package/dist/redteam-SSNABQ7W.js +357 -0
- package/dist/session-MWRBTCYX.js +14 -0
- package/dist/{simulate-ETHHINZ4.js → simulate-VDOYQFRO.js} +2 -1
- package/dist/test-3GZSG5FR.js +217 -0
- package/dist/{trace-3YODSSIP.js → trace-TM4Z7G73.js} +4 -2
- package/dist/{validate-UVE6GKQU.js → validate-LLBWVPGV.js} +15 -6
- package/dist/validate-engine-UIABSIHD.js +7 -0
- package/dist/{world-WLNHL5XC.js → world-LAXO6DOX.js} +87 -7
- package/dist/world-loader-HMPTOEA2.js +9 -0
- package/package.json +19 -5
- package/dist/validate-engine-657D75OG.js +0 -6
- /package/dist/{chunk-M3TZFGHO.js → chunk-JZPQGIKR.js} +0 -0
package/dist/cli/run.cjs
ADDED
|
@@ -0,0 +1,1716 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// src/runtime/model-adapter.ts
|
|
34
|
+
var model_adapter_exports = {};
|
|
35
|
+
__export(model_adapter_exports, {
|
|
36
|
+
ModelAdapter: () => ModelAdapter,
|
|
37
|
+
PROVIDERS: () => PROVIDERS,
|
|
38
|
+
resolveProvider: () => resolveProvider
|
|
39
|
+
});
|
|
40
|
+
function resolveProvider(provider, overrides) {
|
|
41
|
+
const preset = PROVIDERS[provider];
|
|
42
|
+
if (!preset) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Unknown provider: "${provider}". Available: ${Object.keys(PROVIDERS).join(", ")}`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
const apiKey = overrides?.apiKey ?? (preset.envVar ? process.env[preset.envVar] : "") ?? "";
|
|
48
|
+
if (!apiKey && preset.envVar) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Missing API key. Set ${preset.envVar} or pass --api-key.`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
baseUrl: overrides?.baseUrl ?? preset.baseUrl,
|
|
55
|
+
apiKey,
|
|
56
|
+
model: overrides?.model ?? preset.defaultModel,
|
|
57
|
+
systemPrompt: overrides?.systemPrompt,
|
|
58
|
+
maxTokens: overrides?.maxTokens
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
var DEFAULT_SYSTEM_PROMPT, ModelAdapter, PROVIDERS;
|
|
62
|
+
var init_model_adapter = __esm({
|
|
63
|
+
"src/runtime/model-adapter.ts"() {
|
|
64
|
+
"use strict";
|
|
65
|
+
DEFAULT_SYSTEM_PROMPT = `You are an AI assistant operating under NeuroVerse governance.
|
|
66
|
+
All your tool calls are evaluated against governance rules before execution.
|
|
67
|
+
If an action is blocked, you will be told why. Adjust your approach accordingly.
|
|
68
|
+
Do not attempt to bypass governance rules.`;
|
|
69
|
+
ModelAdapter = class {
|
|
70
|
+
config;
|
|
71
|
+
messages;
|
|
72
|
+
tools;
|
|
73
|
+
constructor(config, tools = []) {
|
|
74
|
+
this.config = config;
|
|
75
|
+
this.tools = tools;
|
|
76
|
+
this.messages = [];
|
|
77
|
+
const systemPrompt = config.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
78
|
+
this.messages.push({ role: "system", content: systemPrompt });
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Send a user message and get the model's response.
|
|
82
|
+
*/
|
|
83
|
+
async chat(userMessage) {
|
|
84
|
+
this.messages.push({ role: "user", content: userMessage });
|
|
85
|
+
return this.complete();
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Send a tool result back to the model and get the next response.
|
|
89
|
+
*/
|
|
90
|
+
async sendToolResult(toolCallId, result) {
|
|
91
|
+
this.messages.push({
|
|
92
|
+
role: "tool",
|
|
93
|
+
content: result,
|
|
94
|
+
tool_call_id: toolCallId
|
|
95
|
+
});
|
|
96
|
+
return this.complete();
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Send a governance block message as a tool result.
|
|
100
|
+
*/
|
|
101
|
+
async sendBlockedResult(toolCallId, reason) {
|
|
102
|
+
return this.sendToolResult(
|
|
103
|
+
toolCallId,
|
|
104
|
+
`[GOVERNANCE BLOCKED] ${reason}. Please adjust your approach.`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Call the model API and parse the response.
|
|
109
|
+
*/
|
|
110
|
+
async complete() {
|
|
111
|
+
const url = `${this.config.baseUrl}/chat/completions`;
|
|
112
|
+
const body = {
|
|
113
|
+
model: this.config.model,
|
|
114
|
+
messages: this.messages,
|
|
115
|
+
max_tokens: this.config.maxTokens ?? 4096
|
|
116
|
+
};
|
|
117
|
+
if (this.tools.length > 0) {
|
|
118
|
+
body.tools = this.tools;
|
|
119
|
+
}
|
|
120
|
+
const response = await fetch(url, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: {
|
|
123
|
+
"Content-Type": "application/json",
|
|
124
|
+
"Authorization": `Bearer ${this.config.apiKey}`
|
|
125
|
+
},
|
|
126
|
+
body: JSON.stringify(body)
|
|
127
|
+
});
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
const text = await response.text();
|
|
130
|
+
throw new Error(`Model API error ${response.status}: ${text}`);
|
|
131
|
+
}
|
|
132
|
+
const data = await response.json();
|
|
133
|
+
const choice = data.choices?.[0];
|
|
134
|
+
if (!choice) {
|
|
135
|
+
throw new Error("Model returned no choices");
|
|
136
|
+
}
|
|
137
|
+
const message = choice.message;
|
|
138
|
+
this.messages.push(message);
|
|
139
|
+
return {
|
|
140
|
+
content: message.content ?? null,
|
|
141
|
+
toolCalls: message.tool_calls ?? [],
|
|
142
|
+
finishReason: choice.finish_reason ?? "stop"
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/** Get current message count (for context tracking). */
|
|
146
|
+
get messageCount() {
|
|
147
|
+
return this.messages.length;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
PROVIDERS = {
|
|
151
|
+
openai: {
|
|
152
|
+
baseUrl: "https://api.openai.com/v1",
|
|
153
|
+
defaultModel: "gpt-4o",
|
|
154
|
+
envVar: "OPENAI_API_KEY"
|
|
155
|
+
},
|
|
156
|
+
anthropic: {
|
|
157
|
+
baseUrl: "https://api.anthropic.com/v1",
|
|
158
|
+
defaultModel: "claude-sonnet-4-20250514",
|
|
159
|
+
envVar: "ANTHROPIC_API_KEY"
|
|
160
|
+
},
|
|
161
|
+
ollama: {
|
|
162
|
+
baseUrl: "http://localhost:11434/v1",
|
|
163
|
+
defaultModel: "llama3",
|
|
164
|
+
envVar: ""
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// src/engine/plan-engine.ts
|
|
171
|
+
function keywordMatch(eventText, step) {
|
|
172
|
+
const stepText = [
|
|
173
|
+
step.label,
|
|
174
|
+
step.description ?? "",
|
|
175
|
+
...step.tags ?? []
|
|
176
|
+
].join(" ").toLowerCase();
|
|
177
|
+
const keywords = stepText.split(/\s+/).filter((w) => w.length > 3);
|
|
178
|
+
if (keywords.length === 0) return false;
|
|
179
|
+
const matched = keywords.filter((kw) => eventText.includes(kw));
|
|
180
|
+
return matched.length >= Math.ceil(keywords.length * 0.5);
|
|
181
|
+
}
|
|
182
|
+
function tokenSimilarity(a, b) {
|
|
183
|
+
const tokensA = new Set(a.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
|
|
184
|
+
const tokensB = new Set(b.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
|
|
185
|
+
if (tokensA.size === 0 || tokensB.size === 0) return 0;
|
|
186
|
+
let intersection = 0;
|
|
187
|
+
for (const t of tokensA) {
|
|
188
|
+
if (tokensB.has(t)) intersection++;
|
|
189
|
+
}
|
|
190
|
+
const union = (/* @__PURE__ */ new Set([...tokensA, ...tokensB])).size;
|
|
191
|
+
return union > 0 ? intersection / union : 0;
|
|
192
|
+
}
|
|
193
|
+
function findMatchingStep(eventText, event, steps) {
|
|
194
|
+
const pendingOrActive = steps.filter((s) => s.status === "pending" || s.status === "active");
|
|
195
|
+
if (pendingOrActive.length === 0) {
|
|
196
|
+
return { matched: null, closest: null, closestScore: 0 };
|
|
197
|
+
}
|
|
198
|
+
for (const step of pendingOrActive) {
|
|
199
|
+
if (keywordMatch(eventText, step)) {
|
|
200
|
+
if (step.tools && event.tool && !step.tools.includes(event.tool)) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
return { matched: step, closest: step, closestScore: 1 };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const intentText = [event.intent, event.tool ?? "", event.scope ?? ""].join(" ");
|
|
207
|
+
let bestStep = null;
|
|
208
|
+
let bestScore = 0;
|
|
209
|
+
for (const step of pendingOrActive) {
|
|
210
|
+
const stepText = [step.label, step.description ?? "", ...step.tags ?? []].join(" ");
|
|
211
|
+
const score = tokenSimilarity(intentText, stepText);
|
|
212
|
+
if (score > bestScore) {
|
|
213
|
+
bestScore = score;
|
|
214
|
+
bestStep = step;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const SIMILARITY_THRESHOLD = 0.35;
|
|
218
|
+
if (bestScore >= SIMILARITY_THRESHOLD && bestStep) {
|
|
219
|
+
if (bestStep.tools && event.tool && !bestStep.tools.includes(event.tool)) {
|
|
220
|
+
return { matched: null, closest: bestStep, closestScore: bestScore };
|
|
221
|
+
}
|
|
222
|
+
return { matched: bestStep, closest: bestStep, closestScore: bestScore };
|
|
223
|
+
}
|
|
224
|
+
return { matched: null, closest: bestStep, closestScore: bestScore };
|
|
225
|
+
}
|
|
226
|
+
function isSequenceValid(step, plan) {
|
|
227
|
+
if (!plan.sequential) return true;
|
|
228
|
+
if (!step.requires || step.requires.length === 0) return true;
|
|
229
|
+
return step.requires.every((reqId) => {
|
|
230
|
+
const reqStep = plan.steps.find((s) => s.id === reqId);
|
|
231
|
+
return reqStep?.status === "completed";
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
function checkConstraints(event, eventText, constraints) {
|
|
235
|
+
const checks = [];
|
|
236
|
+
for (const constraint of constraints) {
|
|
237
|
+
if (constraint.type === "approval") {
|
|
238
|
+
if (constraint.trigger && eventText.includes(constraint.trigger.substring(0, 10).toLowerCase())) {
|
|
239
|
+
checks.push({ constraintId: constraint.id, passed: false, reason: constraint.description });
|
|
240
|
+
return { violated: constraint, checks };
|
|
241
|
+
}
|
|
242
|
+
const keywords = constraint.description.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
|
|
243
|
+
const relevant = keywords.some((kw) => eventText.includes(kw));
|
|
244
|
+
if (relevant) {
|
|
245
|
+
checks.push({ constraintId: constraint.id, passed: false, reason: constraint.description });
|
|
246
|
+
return { violated: constraint, checks };
|
|
247
|
+
}
|
|
248
|
+
checks.push({ constraintId: constraint.id, passed: true });
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (constraint.type === "scope" && constraint.trigger) {
|
|
252
|
+
const keywords = constraint.trigger.split(/\s+/).filter((w) => w.length > 3);
|
|
253
|
+
const violated = keywords.length > 0 && keywords.every((kw) => eventText.includes(kw));
|
|
254
|
+
checks.push({
|
|
255
|
+
constraintId: constraint.id,
|
|
256
|
+
passed: !violated,
|
|
257
|
+
reason: violated ? constraint.description : void 0
|
|
258
|
+
});
|
|
259
|
+
if (violated) {
|
|
260
|
+
return { violated: constraint, checks };
|
|
261
|
+
}
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
checks.push({ constraintId: constraint.id, passed: true });
|
|
265
|
+
}
|
|
266
|
+
return { violated: null, checks };
|
|
267
|
+
}
|
|
268
|
+
function getPlanProgress(plan) {
|
|
269
|
+
const completed = plan.steps.filter((s) => s.status === "completed").length;
|
|
270
|
+
const total = plan.steps.length;
|
|
271
|
+
return {
|
|
272
|
+
completed,
|
|
273
|
+
total,
|
|
274
|
+
percentage: total > 0 ? Math.round(completed / total * 100) : 0
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function advancePlan(plan, stepId) {
|
|
278
|
+
return {
|
|
279
|
+
...plan,
|
|
280
|
+
steps: plan.steps.map(
|
|
281
|
+
(s) => s.id === stepId ? { ...s, status: "completed" } : s
|
|
282
|
+
)
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function evaluatePlan(event, plan) {
|
|
286
|
+
const progress = getPlanProgress(plan);
|
|
287
|
+
if (plan.expires_at) {
|
|
288
|
+
const expiresAt = new Date(plan.expires_at).getTime();
|
|
289
|
+
if (Date.now() > expiresAt) {
|
|
290
|
+
return {
|
|
291
|
+
allowed: true,
|
|
292
|
+
status: "PLAN_COMPLETE",
|
|
293
|
+
reason: "Plan has expired.",
|
|
294
|
+
progress
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (progress.completed === progress.total) {
|
|
299
|
+
return {
|
|
300
|
+
allowed: true,
|
|
301
|
+
status: "PLAN_COMPLETE",
|
|
302
|
+
reason: "All plan steps are completed.",
|
|
303
|
+
progress
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
const eventText = [
|
|
307
|
+
event.intent,
|
|
308
|
+
event.tool ?? "",
|
|
309
|
+
event.scope ?? ""
|
|
310
|
+
].join(" ").toLowerCase();
|
|
311
|
+
const { matched, closest, closestScore } = findMatchingStep(eventText, event, plan.steps);
|
|
312
|
+
if (!matched) {
|
|
313
|
+
return {
|
|
314
|
+
allowed: false,
|
|
315
|
+
status: "OFF_PLAN",
|
|
316
|
+
reason: "Action does not match any plan step.",
|
|
317
|
+
closestStep: closest?.label,
|
|
318
|
+
similarityScore: closestScore,
|
|
319
|
+
progress
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
if (!isSequenceValid(matched, plan)) {
|
|
323
|
+
const pendingDeps = (matched.requires ?? []).filter((reqId) => plan.steps.find((s) => s.id === reqId)?.status !== "completed").join(", ");
|
|
324
|
+
return {
|
|
325
|
+
allowed: false,
|
|
326
|
+
status: "OFF_PLAN",
|
|
327
|
+
reason: `Step "${matched.label}" requires completion of: ${pendingDeps}`,
|
|
328
|
+
matchedStep: matched.id,
|
|
329
|
+
progress
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
const { violated } = checkConstraints(event, eventText, plan.constraints);
|
|
333
|
+
if (violated) {
|
|
334
|
+
return {
|
|
335
|
+
allowed: false,
|
|
336
|
+
status: "CONSTRAINT_VIOLATED",
|
|
337
|
+
reason: violated.description,
|
|
338
|
+
matchedStep: matched.id,
|
|
339
|
+
progress
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
allowed: true,
|
|
344
|
+
status: "ON_PLAN",
|
|
345
|
+
reason: `Matches step: ${matched.label}`,
|
|
346
|
+
matchedStep: matched.id,
|
|
347
|
+
progress
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function buildPlanCheck(event, plan, verdict) {
|
|
351
|
+
const eventText = [event.intent, event.tool ?? "", event.scope ?? ""].join(" ").toLowerCase();
|
|
352
|
+
const { matched, closest, closestScore } = findMatchingStep(eventText, event, plan.steps);
|
|
353
|
+
const { checks: constraintChecks } = checkConstraints(event, eventText, plan.constraints);
|
|
354
|
+
const progress = getPlanProgress(plan);
|
|
355
|
+
return {
|
|
356
|
+
planId: plan.plan_id,
|
|
357
|
+
matched: !!matched,
|
|
358
|
+
matchedStepId: matched?.id,
|
|
359
|
+
matchedStepLabel: matched?.label,
|
|
360
|
+
closestStepId: !matched ? closest?.id : void 0,
|
|
361
|
+
closestStepLabel: !matched ? closest?.label : void 0,
|
|
362
|
+
similarityScore: !matched ? closestScore : void 0,
|
|
363
|
+
sequenceValid: matched ? isSequenceValid(matched, plan) : void 0,
|
|
364
|
+
constraintsChecked: constraintChecks,
|
|
365
|
+
progress: { completed: progress.completed, total: progress.total }
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
var init_plan_engine = __esm({
|
|
369
|
+
"src/engine/plan-engine.ts"() {
|
|
370
|
+
"use strict";
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// src/engine/guard-engine.ts
|
|
375
|
+
function levelRequiresConfirmation(level, actionType) {
|
|
376
|
+
if (level === "strict") return true;
|
|
377
|
+
if (level === "standard") {
|
|
378
|
+
return actionType === "delete" || actionType === "credential-access";
|
|
379
|
+
}
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
function isExternalScope(scope) {
|
|
383
|
+
const internalPatterns = [
|
|
384
|
+
/^\.?\/?src\//i,
|
|
385
|
+
/^\.?\/?lib\//i,
|
|
386
|
+
/^\.?\/?app\//i,
|
|
387
|
+
/^\.?\/?components\//i,
|
|
388
|
+
/^\.?\/?pages\//i,
|
|
389
|
+
/^\.?\/?public\//i,
|
|
390
|
+
/^\.?\/?assets\//i,
|
|
391
|
+
/^\.\//
|
|
392
|
+
];
|
|
393
|
+
return !internalPatterns.some((p) => p.test(scope));
|
|
394
|
+
}
|
|
395
|
+
function evaluateGuard(event, world, options = {}) {
|
|
396
|
+
const startTime = performance.now();
|
|
397
|
+
const level = options.level ?? "standard";
|
|
398
|
+
const includeTrace = options.trace ?? false;
|
|
399
|
+
const eventText = (event.intent + " " + (event.tool ?? "") + " " + (event.scope ?? "")).toLowerCase();
|
|
400
|
+
const invariantChecks = [];
|
|
401
|
+
const safetyChecks = [];
|
|
402
|
+
let planCheckResult;
|
|
403
|
+
const roleChecks = [];
|
|
404
|
+
const guardChecks = [];
|
|
405
|
+
const kernelRuleChecks = [];
|
|
406
|
+
const levelChecks = [];
|
|
407
|
+
let decidingLayer = "default-allow";
|
|
408
|
+
let decidingId;
|
|
409
|
+
const guardsMatched = [];
|
|
410
|
+
const rulesMatched = [];
|
|
411
|
+
checkInvariantCoverage(world, invariantChecks);
|
|
412
|
+
if (options.sessionAllowlist) {
|
|
413
|
+
const key = eventToAllowlistKey(event);
|
|
414
|
+
if (options.sessionAllowlist.has(key)) {
|
|
415
|
+
decidingLayer = "session-allowlist";
|
|
416
|
+
decidingId = `allowlist:${key}`;
|
|
417
|
+
return buildVerdict(
|
|
418
|
+
"ALLOW",
|
|
419
|
+
void 0,
|
|
420
|
+
`allowlist:${key}`,
|
|
421
|
+
void 0,
|
|
422
|
+
world,
|
|
423
|
+
level,
|
|
424
|
+
invariantChecks,
|
|
425
|
+
guardsMatched,
|
|
426
|
+
rulesMatched,
|
|
427
|
+
includeTrace ? buildTrace(
|
|
428
|
+
invariantChecks,
|
|
429
|
+
safetyChecks,
|
|
430
|
+
planCheckResult,
|
|
431
|
+
roleChecks,
|
|
432
|
+
guardChecks,
|
|
433
|
+
kernelRuleChecks,
|
|
434
|
+
levelChecks,
|
|
435
|
+
decidingLayer,
|
|
436
|
+
decidingId,
|
|
437
|
+
startTime
|
|
438
|
+
) : void 0
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
const safetyVerdict = checkSafety(event, eventText, safetyChecks);
|
|
443
|
+
if (safetyVerdict) {
|
|
444
|
+
decidingLayer = "safety";
|
|
445
|
+
decidingId = safetyVerdict.ruleId;
|
|
446
|
+
return buildVerdict(
|
|
447
|
+
safetyVerdict.status,
|
|
448
|
+
safetyVerdict.reason,
|
|
449
|
+
safetyVerdict.ruleId,
|
|
450
|
+
void 0,
|
|
451
|
+
world,
|
|
452
|
+
level,
|
|
453
|
+
invariantChecks,
|
|
454
|
+
guardsMatched,
|
|
455
|
+
rulesMatched,
|
|
456
|
+
includeTrace ? buildTrace(
|
|
457
|
+
invariantChecks,
|
|
458
|
+
safetyChecks,
|
|
459
|
+
planCheckResult,
|
|
460
|
+
roleChecks,
|
|
461
|
+
guardChecks,
|
|
462
|
+
kernelRuleChecks,
|
|
463
|
+
levelChecks,
|
|
464
|
+
decidingLayer,
|
|
465
|
+
decidingId,
|
|
466
|
+
startTime
|
|
467
|
+
) : void 0
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
if (options.plan) {
|
|
471
|
+
const planVerdict = evaluatePlan(event, options.plan);
|
|
472
|
+
planCheckResult = buildPlanCheck(event, options.plan, planVerdict);
|
|
473
|
+
if (!planVerdict.allowed && planVerdict.status !== "PLAN_COMPLETE") {
|
|
474
|
+
decidingLayer = "plan-enforcement";
|
|
475
|
+
decidingId = `plan-${options.plan.plan_id}`;
|
|
476
|
+
const planStatus = planVerdict.status === "CONSTRAINT_VIOLATED" ? "PAUSE" : "BLOCK";
|
|
477
|
+
let reason = planVerdict.reason ?? "Action blocked by plan.";
|
|
478
|
+
if (planVerdict.status === "OFF_PLAN" && planVerdict.closestStep) {
|
|
479
|
+
reason += ` Closest step: "${planVerdict.closestStep}" (similarity: ${(planVerdict.similarityScore ?? 0).toFixed(2)})`;
|
|
480
|
+
}
|
|
481
|
+
return buildVerdict(
|
|
482
|
+
planStatus,
|
|
483
|
+
reason,
|
|
484
|
+
`plan-${options.plan.plan_id}`,
|
|
485
|
+
void 0,
|
|
486
|
+
world,
|
|
487
|
+
level,
|
|
488
|
+
invariantChecks,
|
|
489
|
+
guardsMatched,
|
|
490
|
+
rulesMatched,
|
|
491
|
+
includeTrace ? buildTrace(
|
|
492
|
+
invariantChecks,
|
|
493
|
+
safetyChecks,
|
|
494
|
+
planCheckResult,
|
|
495
|
+
roleChecks,
|
|
496
|
+
guardChecks,
|
|
497
|
+
kernelRuleChecks,
|
|
498
|
+
levelChecks,
|
|
499
|
+
decidingLayer,
|
|
500
|
+
decidingId,
|
|
501
|
+
startTime
|
|
502
|
+
) : void 0
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
const roleVerdict = checkRoleRules(event, eventText, world, roleChecks);
|
|
507
|
+
if (roleVerdict) {
|
|
508
|
+
decidingLayer = "role";
|
|
509
|
+
decidingId = roleVerdict.ruleId;
|
|
510
|
+
return buildVerdict(
|
|
511
|
+
roleVerdict.status,
|
|
512
|
+
roleVerdict.reason,
|
|
513
|
+
roleVerdict.ruleId,
|
|
514
|
+
void 0,
|
|
515
|
+
world,
|
|
516
|
+
level,
|
|
517
|
+
invariantChecks,
|
|
518
|
+
guardsMatched,
|
|
519
|
+
rulesMatched,
|
|
520
|
+
includeTrace ? buildTrace(
|
|
521
|
+
invariantChecks,
|
|
522
|
+
safetyChecks,
|
|
523
|
+
planCheckResult,
|
|
524
|
+
roleChecks,
|
|
525
|
+
guardChecks,
|
|
526
|
+
kernelRuleChecks,
|
|
527
|
+
levelChecks,
|
|
528
|
+
decidingLayer,
|
|
529
|
+
decidingId,
|
|
530
|
+
startTime
|
|
531
|
+
) : void 0
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
const guardVerdict = checkGuards(event, eventText, world, guardChecks, guardsMatched);
|
|
535
|
+
if (guardVerdict) {
|
|
536
|
+
if (guardVerdict.status !== "ALLOW") {
|
|
537
|
+
decidingLayer = "guard";
|
|
538
|
+
decidingId = guardVerdict.ruleId;
|
|
539
|
+
return buildVerdict(
|
|
540
|
+
guardVerdict.status,
|
|
541
|
+
guardVerdict.reason,
|
|
542
|
+
guardVerdict.ruleId,
|
|
543
|
+
void 0,
|
|
544
|
+
world,
|
|
545
|
+
level,
|
|
546
|
+
invariantChecks,
|
|
547
|
+
guardsMatched,
|
|
548
|
+
rulesMatched,
|
|
549
|
+
includeTrace ? buildTrace(
|
|
550
|
+
invariantChecks,
|
|
551
|
+
safetyChecks,
|
|
552
|
+
planCheckResult,
|
|
553
|
+
roleChecks,
|
|
554
|
+
guardChecks,
|
|
555
|
+
kernelRuleChecks,
|
|
556
|
+
levelChecks,
|
|
557
|
+
decidingLayer,
|
|
558
|
+
decidingId,
|
|
559
|
+
startTime
|
|
560
|
+
) : void 0
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
const kernelVerdict = checkKernelRules(eventText, world, kernelRuleChecks, rulesMatched);
|
|
565
|
+
if (kernelVerdict) {
|
|
566
|
+
decidingLayer = "kernel-rule";
|
|
567
|
+
decidingId = kernelVerdict.ruleId;
|
|
568
|
+
return buildVerdict(
|
|
569
|
+
kernelVerdict.status,
|
|
570
|
+
kernelVerdict.reason,
|
|
571
|
+
kernelVerdict.ruleId,
|
|
572
|
+
void 0,
|
|
573
|
+
world,
|
|
574
|
+
level,
|
|
575
|
+
invariantChecks,
|
|
576
|
+
guardsMatched,
|
|
577
|
+
rulesMatched,
|
|
578
|
+
includeTrace ? buildTrace(
|
|
579
|
+
invariantChecks,
|
|
580
|
+
safetyChecks,
|
|
581
|
+
planCheckResult,
|
|
582
|
+
roleChecks,
|
|
583
|
+
guardChecks,
|
|
584
|
+
kernelRuleChecks,
|
|
585
|
+
levelChecks,
|
|
586
|
+
decidingLayer,
|
|
587
|
+
decidingId,
|
|
588
|
+
startTime
|
|
589
|
+
) : void 0
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
const levelVerdict = checkLevelConstraints(event, level, levelChecks);
|
|
593
|
+
if (levelVerdict) {
|
|
594
|
+
decidingLayer = "level-constraint";
|
|
595
|
+
decidingId = levelVerdict.ruleId;
|
|
596
|
+
return buildVerdict(
|
|
597
|
+
levelVerdict.status,
|
|
598
|
+
levelVerdict.reason,
|
|
599
|
+
levelVerdict.ruleId,
|
|
600
|
+
void 0,
|
|
601
|
+
world,
|
|
602
|
+
level,
|
|
603
|
+
invariantChecks,
|
|
604
|
+
guardsMatched,
|
|
605
|
+
rulesMatched,
|
|
606
|
+
includeTrace ? buildTrace(
|
|
607
|
+
invariantChecks,
|
|
608
|
+
safetyChecks,
|
|
609
|
+
planCheckResult,
|
|
610
|
+
roleChecks,
|
|
611
|
+
guardChecks,
|
|
612
|
+
kernelRuleChecks,
|
|
613
|
+
levelChecks,
|
|
614
|
+
decidingLayer,
|
|
615
|
+
decidingId,
|
|
616
|
+
startTime
|
|
617
|
+
) : void 0
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
const warning = guardVerdict?.warning;
|
|
621
|
+
return buildVerdict(
|
|
622
|
+
"ALLOW",
|
|
623
|
+
void 0,
|
|
624
|
+
void 0,
|
|
625
|
+
warning,
|
|
626
|
+
world,
|
|
627
|
+
level,
|
|
628
|
+
invariantChecks,
|
|
629
|
+
guardsMatched,
|
|
630
|
+
rulesMatched,
|
|
631
|
+
includeTrace ? buildTrace(
|
|
632
|
+
invariantChecks,
|
|
633
|
+
safetyChecks,
|
|
634
|
+
planCheckResult,
|
|
635
|
+
roleChecks,
|
|
636
|
+
guardChecks,
|
|
637
|
+
kernelRuleChecks,
|
|
638
|
+
levelChecks,
|
|
639
|
+
decidingLayer,
|
|
640
|
+
decidingId,
|
|
641
|
+
startTime
|
|
642
|
+
) : void 0
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
function checkInvariantCoverage(world, checks) {
|
|
646
|
+
const invariants = world.invariants ?? [];
|
|
647
|
+
const guards = world.guards?.guards ?? [];
|
|
648
|
+
for (const invariant of invariants) {
|
|
649
|
+
const coveringGuard = guards.find(
|
|
650
|
+
(g) => g.invariant_ref === invariant.id && g.immutable
|
|
651
|
+
);
|
|
652
|
+
checks.push({
|
|
653
|
+
invariantId: invariant.id,
|
|
654
|
+
label: invariant.label,
|
|
655
|
+
hasGuardCoverage: !!coveringGuard,
|
|
656
|
+
coveringGuardId: coveringGuard?.id
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function checkSafety(event, eventText, checks) {
|
|
661
|
+
const textToCheck = event.intent + (event.payload ? JSON.stringify(event.payload) : "");
|
|
662
|
+
for (const { pattern, label } of PROMPT_INJECTION_PATTERNS) {
|
|
663
|
+
const triggered = pattern.test(textToCheck);
|
|
664
|
+
checks.push({
|
|
665
|
+
checkType: "prompt-injection",
|
|
666
|
+
triggered,
|
|
667
|
+
matchedPattern: triggered ? label : void 0
|
|
668
|
+
});
|
|
669
|
+
if (triggered) {
|
|
670
|
+
for (const remaining of PROMPT_INJECTION_PATTERNS.filter((p) => p.label !== label)) {
|
|
671
|
+
checks.push({
|
|
672
|
+
checkType: "prompt-injection",
|
|
673
|
+
triggered: remaining.pattern.test(textToCheck),
|
|
674
|
+
matchedPattern: remaining.pattern.test(textToCheck) ? remaining.label : void 0
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
return {
|
|
678
|
+
status: "PAUSE",
|
|
679
|
+
reason: NEUTRAL_MESSAGES["prompt-injection"],
|
|
680
|
+
ruleId: `safety-injection-${label}`
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
const scopeToCheck = event.scope ?? event.intent;
|
|
685
|
+
for (const { pattern, label } of SCOPE_ESCAPE_PATTERNS) {
|
|
686
|
+
const triggered = pattern.test(scopeToCheck);
|
|
687
|
+
checks.push({
|
|
688
|
+
checkType: "scope-escape",
|
|
689
|
+
triggered,
|
|
690
|
+
matchedPattern: triggered ? label : void 0
|
|
691
|
+
});
|
|
692
|
+
if (triggered) {
|
|
693
|
+
for (const remaining of SCOPE_ESCAPE_PATTERNS.filter((p) => p.label !== label)) {
|
|
694
|
+
checks.push({
|
|
695
|
+
checkType: "scope-escape",
|
|
696
|
+
triggered: remaining.pattern.test(scopeToCheck),
|
|
697
|
+
matchedPattern: remaining.pattern.test(scopeToCheck) ? remaining.label : void 0
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
return {
|
|
701
|
+
status: "PAUSE",
|
|
702
|
+
reason: NEUTRAL_MESSAGES["scope-escape"],
|
|
703
|
+
ruleId: `safety-scope-${label}`
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (event.direction === "output") {
|
|
708
|
+
for (const { pattern, label } of EXECUTION_CLAIM_PATTERNS) {
|
|
709
|
+
const triggered = pattern.test(textToCheck);
|
|
710
|
+
checks.push({
|
|
711
|
+
checkType: "execution-claim",
|
|
712
|
+
triggered,
|
|
713
|
+
matchedPattern: triggered ? label : void 0
|
|
714
|
+
});
|
|
715
|
+
if (triggered) {
|
|
716
|
+
for (const remaining of EXECUTION_CLAIM_PATTERNS.filter((p) => p.label !== label)) {
|
|
717
|
+
checks.push({
|
|
718
|
+
checkType: "execution-claim",
|
|
719
|
+
triggered: remaining.pattern.test(textToCheck),
|
|
720
|
+
matchedPattern: remaining.pattern.test(textToCheck) ? remaining.label : void 0
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
return {
|
|
724
|
+
status: "PAUSE",
|
|
725
|
+
reason: NEUTRAL_MESSAGES["execution-claim"],
|
|
726
|
+
ruleId: `safety-execution-claim-${label}`
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (event.direction === "input") {
|
|
732
|
+
const intentTrimmed = event.intent.trim();
|
|
733
|
+
for (const { pattern, label } of EXECUTION_INTENT_PATTERNS) {
|
|
734
|
+
const triggered = pattern.test(intentTrimmed);
|
|
735
|
+
checks.push({
|
|
736
|
+
checkType: "execution-intent",
|
|
737
|
+
triggered,
|
|
738
|
+
matchedPattern: triggered ? label : void 0
|
|
739
|
+
});
|
|
740
|
+
if (triggered) {
|
|
741
|
+
for (const remaining of EXECUTION_INTENT_PATTERNS.filter((p) => p.label !== label)) {
|
|
742
|
+
checks.push({
|
|
743
|
+
checkType: "execution-intent",
|
|
744
|
+
triggered: remaining.pattern.test(intentTrimmed),
|
|
745
|
+
matchedPattern: remaining.pattern.test(intentTrimmed) ? remaining.label : void 0
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
return {
|
|
749
|
+
status: "PAUSE",
|
|
750
|
+
reason: NEUTRAL_MESSAGES["execution-intent"],
|
|
751
|
+
ruleId: `safety-execution-intent-${label}`
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
function checkRoleRules(event, eventText, world, checks) {
|
|
759
|
+
if (!event.roleId || !world.roles) return null;
|
|
760
|
+
const role = world.roles.roles.find((r) => r.id === event.roleId);
|
|
761
|
+
if (!role) return null;
|
|
762
|
+
if (role.requiresApproval) {
|
|
763
|
+
checks.push({
|
|
764
|
+
roleId: role.id,
|
|
765
|
+
roleName: role.name,
|
|
766
|
+
rule: "All actions require approval",
|
|
767
|
+
ruleType: "requiresApproval",
|
|
768
|
+
matched: true
|
|
769
|
+
});
|
|
770
|
+
return {
|
|
771
|
+
status: "PAUSE",
|
|
772
|
+
reason: `Role "${role.name}" requires approval for all actions.`,
|
|
773
|
+
ruleId: `role-${role.id}-requires-approval`
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
for (const rule of role.cannotDo) {
|
|
777
|
+
const matched = matchesKeywords(eventText, rule);
|
|
778
|
+
checks.push({
|
|
779
|
+
roleId: role.id,
|
|
780
|
+
roleName: role.name,
|
|
781
|
+
rule,
|
|
782
|
+
ruleType: "cannotDo",
|
|
783
|
+
matched
|
|
784
|
+
});
|
|
785
|
+
if (matched) {
|
|
786
|
+
return {
|
|
787
|
+
status: "BLOCK",
|
|
788
|
+
reason: `Role "${role.name}" cannot: ${rule}`,
|
|
789
|
+
ruleId: `role-${role.id}-cannotdo`
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
for (const rule of role.canDo) {
|
|
794
|
+
checks.push({
|
|
795
|
+
roleId: role.id,
|
|
796
|
+
roleName: role.name,
|
|
797
|
+
rule,
|
|
798
|
+
ruleType: "canDo",
|
|
799
|
+
matched: matchesKeywords(eventText, rule)
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
function checkGuards(event, eventText, world, checks, guardsMatched) {
|
|
805
|
+
if (!world.guards) return null;
|
|
806
|
+
const guardsConfig = world.guards;
|
|
807
|
+
let warnResult = null;
|
|
808
|
+
const compiledPatterns = /* @__PURE__ */ new Map();
|
|
809
|
+
for (const [key, def] of Object.entries(guardsConfig.intent_vocabulary)) {
|
|
810
|
+
try {
|
|
811
|
+
compiledPatterns.set(key, new RegExp(def.pattern, "i"));
|
|
812
|
+
} catch {
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
const eventTool = (event.tool ?? "").toLowerCase();
|
|
816
|
+
for (const guard of guardsConfig.guards) {
|
|
817
|
+
if (guard.appliesTo && guard.appliesTo.length > 0) {
|
|
818
|
+
const normalizedAppliesTo = guard.appliesTo.map((t) => t.toLowerCase());
|
|
819
|
+
if (!normalizedAppliesTo.includes(eventTool)) {
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
const enabled = guard.immutable || guard.default_enabled !== false;
|
|
824
|
+
const matchedPatterns = [];
|
|
825
|
+
for (const patternKey of guard.intent_patterns) {
|
|
826
|
+
const regex = compiledPatterns.get(patternKey);
|
|
827
|
+
if (regex?.test(eventText)) {
|
|
828
|
+
matchedPatterns.push(patternKey);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
const matched = matchedPatterns.length > 0 && enabled;
|
|
832
|
+
let roleGated = false;
|
|
833
|
+
if (matched && guard.required_roles && guard.required_roles.length > 0 && event.roleId && guard.required_roles.includes(event.roleId)) {
|
|
834
|
+
roleGated = true;
|
|
835
|
+
}
|
|
836
|
+
checks.push({
|
|
837
|
+
guardId: guard.id,
|
|
838
|
+
label: guard.label,
|
|
839
|
+
category: guard.category,
|
|
840
|
+
enabled,
|
|
841
|
+
matched: matched && !roleGated,
|
|
842
|
+
enforcement: guard.enforcement,
|
|
843
|
+
matchedPatterns,
|
|
844
|
+
roleGated
|
|
845
|
+
});
|
|
846
|
+
if (!matched || roleGated) continue;
|
|
847
|
+
guardsMatched.push(guard.id);
|
|
848
|
+
const actionMode = guard.player_modes?.action ?? guard.enforcement;
|
|
849
|
+
const reason = guard.redirect ? `${guard.description} \u2014 ${guard.redirect}` : guard.description;
|
|
850
|
+
if (actionMode === "block") {
|
|
851
|
+
return { status: "BLOCK", reason, ruleId: `guard-${guard.id}` };
|
|
852
|
+
}
|
|
853
|
+
if (actionMode === "pause") {
|
|
854
|
+
return { status: "PAUSE", reason, ruleId: `guard-${guard.id}` };
|
|
855
|
+
}
|
|
856
|
+
if (actionMode === "warn" && !warnResult) {
|
|
857
|
+
warnResult = { status: "ALLOW", warning: reason, ruleId: `guard-${guard.id}` };
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return warnResult;
|
|
861
|
+
}
|
|
862
|
+
function checkKernelRules(eventText, world, checks, rulesMatched) {
|
|
863
|
+
if (!world.kernel) return null;
|
|
864
|
+
const forbidden = world.kernel.input_boundaries?.forbidden_patterns ?? [];
|
|
865
|
+
const output = world.kernel.output_boundaries?.forbidden_patterns ?? [];
|
|
866
|
+
for (const rule of forbidden) {
|
|
867
|
+
let matched = false;
|
|
868
|
+
let matchMethod = "none";
|
|
869
|
+
if (rule.pattern) {
|
|
870
|
+
try {
|
|
871
|
+
matched = new RegExp(rule.pattern, "i").test(eventText);
|
|
872
|
+
matchMethod = "pattern";
|
|
873
|
+
} catch {
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
if (!matched && rule.reason) {
|
|
877
|
+
matched = matchesKeywords(eventText, rule.reason);
|
|
878
|
+
if (matched) matchMethod = "keyword";
|
|
879
|
+
}
|
|
880
|
+
checks.push({
|
|
881
|
+
ruleId: rule.id,
|
|
882
|
+
text: rule.reason,
|
|
883
|
+
category: "forbidden",
|
|
884
|
+
matched,
|
|
885
|
+
matchMethod
|
|
886
|
+
});
|
|
887
|
+
if (matched) {
|
|
888
|
+
rulesMatched.push(rule.id);
|
|
889
|
+
if (rule.action === "BLOCK") {
|
|
890
|
+
return {
|
|
891
|
+
status: "BLOCK",
|
|
892
|
+
reason: rule.reason,
|
|
893
|
+
ruleId: `kernel-${rule.id}`
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
900
|
+
function checkLevelConstraints(event, level, checks) {
|
|
901
|
+
if (level === "basic") return null;
|
|
902
|
+
const intent = event.intent.toLowerCase();
|
|
903
|
+
const tool = (event.tool ?? "").toLowerCase();
|
|
904
|
+
const isDelete = intent.includes("delete") || intent.includes("remove") || intent.includes("rm ") || tool === "delete";
|
|
905
|
+
const deleteTriggered = isDelete && levelRequiresConfirmation(level, "delete");
|
|
906
|
+
checks.push({
|
|
907
|
+
checkType: "delete",
|
|
908
|
+
level,
|
|
909
|
+
triggered: deleteTriggered,
|
|
910
|
+
reason: deleteTriggered ? NEUTRAL_MESSAGES["delete"] : void 0
|
|
911
|
+
});
|
|
912
|
+
if (deleteTriggered) {
|
|
913
|
+
return { status: "PAUSE", reason: NEUTRAL_MESSAGES["delete"], ruleId: "level-delete-check" };
|
|
914
|
+
}
|
|
915
|
+
const isExternal = event.scope ? isExternalScope(event.scope) : false;
|
|
916
|
+
const externalTriggered = isExternal && levelRequiresConfirmation(level, "write-external");
|
|
917
|
+
checks.push({
|
|
918
|
+
checkType: "write-external",
|
|
919
|
+
level,
|
|
920
|
+
triggered: externalTriggered,
|
|
921
|
+
reason: externalTriggered ? NEUTRAL_MESSAGES["write-external"] : void 0
|
|
922
|
+
});
|
|
923
|
+
if (externalTriggered) {
|
|
924
|
+
return { status: "PAUSE", reason: NEUTRAL_MESSAGES["write-external"], ruleId: "level-external-write-check" };
|
|
925
|
+
}
|
|
926
|
+
const isNetwork = tool === "http" || tool === "fetch" || tool === "request" || intent.includes("post ") || intent.includes("sending");
|
|
927
|
+
const networkTriggered = isNetwork && levelRequiresConfirmation(level, "network-mutate");
|
|
928
|
+
checks.push({
|
|
929
|
+
checkType: "network-mutate",
|
|
930
|
+
level,
|
|
931
|
+
triggered: networkTriggered,
|
|
932
|
+
reason: networkTriggered ? NEUTRAL_MESSAGES["network-mutate"] : void 0
|
|
933
|
+
});
|
|
934
|
+
if (networkTriggered) {
|
|
935
|
+
return { status: "PAUSE", reason: NEUTRAL_MESSAGES["network-mutate"], ruleId: "level-network-mutate-check" };
|
|
936
|
+
}
|
|
937
|
+
const isCredential = intent.includes("credential") || intent.includes("password") || intent.includes("secret") || intent.includes("api key") || intent.includes("token");
|
|
938
|
+
const credentialTriggered = isCredential && levelRequiresConfirmation(level, "credential-access");
|
|
939
|
+
checks.push({
|
|
940
|
+
checkType: "credential-access",
|
|
941
|
+
level,
|
|
942
|
+
triggered: credentialTriggered,
|
|
943
|
+
reason: credentialTriggered ? NEUTRAL_MESSAGES["credential-access"] : void 0
|
|
944
|
+
});
|
|
945
|
+
if (credentialTriggered) {
|
|
946
|
+
return { status: "PAUSE", reason: NEUTRAL_MESSAGES["credential-access"], ruleId: "level-credential-check" };
|
|
947
|
+
}
|
|
948
|
+
const irreversibleTriggered = !!event.irreversible && level !== "basic";
|
|
949
|
+
checks.push({
|
|
950
|
+
checkType: "irreversible",
|
|
951
|
+
level,
|
|
952
|
+
triggered: irreversibleTriggered,
|
|
953
|
+
reason: irreversibleTriggered ? "This action is marked as irreversible." : void 0
|
|
954
|
+
});
|
|
955
|
+
if (irreversibleTriggered) {
|
|
956
|
+
return {
|
|
957
|
+
status: "PAUSE",
|
|
958
|
+
reason: "This action is marked as irreversible.",
|
|
959
|
+
ruleId: "level-irreversible-check"
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
return null;
|
|
963
|
+
}
|
|
964
|
+
function matchesKeywords(eventText, ruleText) {
|
|
965
|
+
const keywords = ruleText.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
|
|
966
|
+
if (keywords.length === 0) return false;
|
|
967
|
+
return keywords.every((kw) => eventText.includes(kw));
|
|
968
|
+
}
|
|
969
|
+
function eventToAllowlistKey(event) {
|
|
970
|
+
return `${(event.tool ?? "*").toLowerCase()}::${event.intent.toLowerCase().trim()}`;
|
|
971
|
+
}
|
|
972
|
+
function buildTrace(invariantChecks, safetyChecks, planCheck, roleChecks, guardChecks, kernelRuleChecks, levelChecks, decidingLayer, decidingId, startTime) {
|
|
973
|
+
const trace = {
|
|
974
|
+
invariantChecks,
|
|
975
|
+
safetyChecks,
|
|
976
|
+
roleChecks,
|
|
977
|
+
guardChecks,
|
|
978
|
+
kernelRuleChecks,
|
|
979
|
+
levelChecks,
|
|
980
|
+
precedenceResolution: {
|
|
981
|
+
decidingLayer,
|
|
982
|
+
decidingId,
|
|
983
|
+
strategy: "first-match-wins",
|
|
984
|
+
chainOrder: [
|
|
985
|
+
"invariant-coverage",
|
|
986
|
+
"session-allowlist",
|
|
987
|
+
"safety-injection",
|
|
988
|
+
"safety-scope-escape",
|
|
989
|
+
"safety-execution-claim",
|
|
990
|
+
"safety-execution-intent",
|
|
991
|
+
"plan-enforcement",
|
|
992
|
+
"role-rules",
|
|
993
|
+
"declarative-guards",
|
|
994
|
+
"kernel-rules",
|
|
995
|
+
"level-constraints",
|
|
996
|
+
"default-allow"
|
|
997
|
+
]
|
|
998
|
+
},
|
|
999
|
+
durationMs: performance.now() - startTime
|
|
1000
|
+
};
|
|
1001
|
+
if (planCheck) {
|
|
1002
|
+
trace.planCheck = planCheck;
|
|
1003
|
+
}
|
|
1004
|
+
return trace;
|
|
1005
|
+
}
|
|
1006
|
+
function buildVerdict(status, reason, ruleId, warning, world, level, invariantChecks, guardsMatched, rulesMatched, trace) {
|
|
1007
|
+
const evidence = {
|
|
1008
|
+
worldId: world.world.world_id,
|
|
1009
|
+
worldName: world.world.name,
|
|
1010
|
+
worldVersion: world.world.version,
|
|
1011
|
+
evaluatedAt: Date.now(),
|
|
1012
|
+
invariantsSatisfied: invariantChecks.filter((c) => c.hasGuardCoverage).length,
|
|
1013
|
+
invariantsTotal: invariantChecks.length,
|
|
1014
|
+
guardsMatched,
|
|
1015
|
+
rulesMatched,
|
|
1016
|
+
enforcementLevel: level
|
|
1017
|
+
};
|
|
1018
|
+
const verdict = {
|
|
1019
|
+
status,
|
|
1020
|
+
evidence
|
|
1021
|
+
};
|
|
1022
|
+
if (reason) verdict.reason = reason;
|
|
1023
|
+
if (ruleId) verdict.ruleId = ruleId;
|
|
1024
|
+
if (warning) verdict.warning = warning;
|
|
1025
|
+
if (trace) verdict.trace = trace;
|
|
1026
|
+
return verdict;
|
|
1027
|
+
}
|
|
1028
|
+
var PROMPT_INJECTION_PATTERNS, EXECUTION_CLAIM_PATTERNS, EXECUTION_INTENT_PATTERNS, SCOPE_ESCAPE_PATTERNS, NEUTRAL_MESSAGES;
|
|
1029
|
+
var init_guard_engine = __esm({
|
|
1030
|
+
"src/engine/guard-engine.ts"() {
|
|
1031
|
+
"use strict";
|
|
1032
|
+
init_plan_engine();
|
|
1033
|
+
PROMPT_INJECTION_PATTERNS = [
|
|
1034
|
+
// Instruction override
|
|
1035
|
+
{ pattern: /ignore\s+(previous|all|prior|above)\s+(instructions?|rules?)/i, label: "ignore-instructions" },
|
|
1036
|
+
{ pattern: /disregard\s+(your|the)\s+(rules|constraints)/i, label: "disregard-rules" },
|
|
1037
|
+
{ pattern: /new\s+instructions?:/i, label: "new-instructions" },
|
|
1038
|
+
// Identity manipulation
|
|
1039
|
+
{ pattern: /you\s+are\s+now/i, label: "identity-override" },
|
|
1040
|
+
{ pattern: /new\s+persona/i, label: "new-persona" },
|
|
1041
|
+
{ pattern: /act\s+as\s+if/i, label: "act-as-if" },
|
|
1042
|
+
{ pattern: /pretend\s+(you|to\s+be|you\s+are\s+unrestricted)/i, label: "pretend-to-be" },
|
|
1043
|
+
// Context reset
|
|
1044
|
+
{ pattern: /forget\s+(everything|all|your)/i, label: "forget-context" },
|
|
1045
|
+
{ pattern: /system\s*:\s*override/i, label: "system-override" },
|
|
1046
|
+
// Constraint bypass
|
|
1047
|
+
{ pattern: /override\s+(your|the)\s+(programming|constraints)/i, label: "override-constraints" },
|
|
1048
|
+
{ pattern: /bypass\s+(your|the)\s+(filters|constraints|rules)/i, label: "bypass-filters" },
|
|
1049
|
+
// Prompt extraction
|
|
1050
|
+
{ pattern: /system\s+prompt/i, label: "system-prompt-probe" },
|
|
1051
|
+
{ pattern: /reveal\s+your\s+(instructions?|prompt|rules)/i, label: "reveal-instructions" },
|
|
1052
|
+
// Known jailbreak terms
|
|
1053
|
+
{ pattern: /jailbreak/i, label: "jailbreak" },
|
|
1054
|
+
{ pattern: /DAN\s+mode/i, label: "dan-mode" },
|
|
1055
|
+
{ pattern: /developer\s+mode/i, label: "developer-mode" }
|
|
1056
|
+
];
|
|
1057
|
+
EXECUTION_CLAIM_PATTERNS = [
|
|
1058
|
+
{ pattern: /I have (executed|completed|performed|done|made|created|sent|deleted|modified|updated)/i, label: "claim-i-have" },
|
|
1059
|
+
{ pattern: /Successfully (created|deleted|modified|updated|sent|executed|performed)/i, label: "claim-successfully" },
|
|
1060
|
+
{ pattern: /The file has been/i, label: "claim-file-modified" },
|
|
1061
|
+
{ pattern: /I've made the changes/i, label: "claim-made-changes" },
|
|
1062
|
+
{ pattern: /I('ve| have) (sent|posted|submitted|uploaded|downloaded)/i, label: "claim-sent" },
|
|
1063
|
+
{ pattern: /Your (email|message|file|request) has been (sent|submitted)/i, label: "claim-your-sent" },
|
|
1064
|
+
{ pattern: /Transaction complete/i, label: "claim-transaction" },
|
|
1065
|
+
{ pattern: /Order placed/i, label: "claim-order" },
|
|
1066
|
+
{ pattern: /Payment processed/i, label: "claim-payment" }
|
|
1067
|
+
];
|
|
1068
|
+
EXECUTION_INTENT_PATTERNS = [
|
|
1069
|
+
{ pattern: /^(execute|run|perform|do this)/i, label: "intent-execute" },
|
|
1070
|
+
{ pattern: /^(create|write|delete|modify) (a |the )?(file|folder|document)/i, label: "intent-file-ops" },
|
|
1071
|
+
{ pattern: /^(send|post|submit) (a |an |the )?(email|message|tweet|post)/i, label: "intent-send" },
|
|
1072
|
+
{ pattern: /^(search|look up|browse) (the )?web/i, label: "intent-web-search" },
|
|
1073
|
+
{ pattern: /^(make|call|invoke) (a |an )?(api|http|rest) (call|request)/i, label: "intent-api-call" },
|
|
1074
|
+
{ pattern: /^(buy|purchase|order|pay|transfer|send money)/i, label: "intent-financial" },
|
|
1075
|
+
{ pattern: /^(book|schedule|reserve)/i, label: "intent-booking" },
|
|
1076
|
+
{ pattern: /^(download|upload|save to|export to)/i, label: "intent-transfer" }
|
|
1077
|
+
];
|
|
1078
|
+
SCOPE_ESCAPE_PATTERNS = [
|
|
1079
|
+
{ pattern: /\.\.\//, label: "parent-traversal" },
|
|
1080
|
+
{ pattern: /^\/(?!home|project|workspace)/i, label: "absolute-path-outside-safe" },
|
|
1081
|
+
{ pattern: /~\//, label: "home-directory" },
|
|
1082
|
+
{ pattern: /\/etc\//i, label: "system-config" },
|
|
1083
|
+
{ pattern: /\/usr\//i, label: "system-binaries" },
|
|
1084
|
+
{ pattern: /\/var\//i, label: "system-variable-data" }
|
|
1085
|
+
];
|
|
1086
|
+
NEUTRAL_MESSAGES = {
|
|
1087
|
+
"prompt-injection": "This input contains patterns that could alter agent behavior.",
|
|
1088
|
+
"scope-escape": "This action would affect resources outside the declared scope.",
|
|
1089
|
+
"execution-claim": "This response claims to have performed an action.",
|
|
1090
|
+
"execution-intent": "This input requests execution in a thinking-only environment.",
|
|
1091
|
+
"delete": "This action would remove files. Confirmation needed.",
|
|
1092
|
+
"write-external": "This action would write outside the project folder.",
|
|
1093
|
+
"network-mutate": "This action would send data to an external service.",
|
|
1094
|
+
"credential-access": "This action would access stored credentials."
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
// src/loader/world-loader.ts
|
|
1100
|
+
async function loadWorldFromDirectory(dirPath) {
|
|
1101
|
+
const { readFile } = await import("fs/promises");
|
|
1102
|
+
const { join: join3 } = await import("path");
|
|
1103
|
+
const { readdirSync: readdirSync3 } = await import("fs");
|
|
1104
|
+
async function readJson(filename) {
|
|
1105
|
+
try {
|
|
1106
|
+
const content = await readFile(join3(dirPath, filename), "utf-8");
|
|
1107
|
+
return JSON.parse(content);
|
|
1108
|
+
} catch {
|
|
1109
|
+
return void 0;
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
const worldJson = await readJson("world.json");
|
|
1113
|
+
if (!worldJson) {
|
|
1114
|
+
throw new Error(`Cannot read world.json in ${dirPath}`);
|
|
1115
|
+
}
|
|
1116
|
+
const invariantsJson = await readJson("invariants.json");
|
|
1117
|
+
const assumptionsJson = await readJson("assumptions.json");
|
|
1118
|
+
const stateSchemaJson = await readJson("state-schema.json");
|
|
1119
|
+
const gatesJson = await readJson("gates.json");
|
|
1120
|
+
const outcomesJson = await readJson("outcomes.json");
|
|
1121
|
+
const guardsJson = await readJson("guards.json");
|
|
1122
|
+
const rolesJson = await readJson("roles.json");
|
|
1123
|
+
const kernelJson = await readJson("kernel.json");
|
|
1124
|
+
const metadataJson = await readJson("metadata.json");
|
|
1125
|
+
const rules = [];
|
|
1126
|
+
try {
|
|
1127
|
+
const rulesDir = join3(dirPath, "rules");
|
|
1128
|
+
const ruleFiles = readdirSync3(rulesDir).filter((f) => f.endsWith(".json")).sort();
|
|
1129
|
+
for (const file of ruleFiles) {
|
|
1130
|
+
const content = await readFile(join3(rulesDir, file), "utf-8");
|
|
1131
|
+
rules.push(JSON.parse(content));
|
|
1132
|
+
}
|
|
1133
|
+
} catch {
|
|
1134
|
+
}
|
|
1135
|
+
return {
|
|
1136
|
+
world: worldJson,
|
|
1137
|
+
invariants: invariantsJson?.invariants ?? [],
|
|
1138
|
+
assumptions: assumptionsJson ?? { profiles: {}, parameter_definitions: {} },
|
|
1139
|
+
stateSchema: stateSchemaJson ?? { variables: {}, presets: {} },
|
|
1140
|
+
rules,
|
|
1141
|
+
gates: gatesJson ?? {
|
|
1142
|
+
viability_classification: [],
|
|
1143
|
+
structural_override: { description: "", enforcement: "mandatory" },
|
|
1144
|
+
sustainability_threshold: 0,
|
|
1145
|
+
collapse_visual: { background: "", text: "", border: "", label: "" }
|
|
1146
|
+
},
|
|
1147
|
+
outcomes: outcomesJson ?? {
|
|
1148
|
+
computed_outcomes: [],
|
|
1149
|
+
comparison_layout: { primary_card: "", status_badge: "", structural_indicators: [] }
|
|
1150
|
+
},
|
|
1151
|
+
guards: guardsJson,
|
|
1152
|
+
roles: rolesJson,
|
|
1153
|
+
kernel: kernelJson,
|
|
1154
|
+
metadata: metadataJson ?? {
|
|
1155
|
+
format_version: "1.0.0",
|
|
1156
|
+
created_at: "",
|
|
1157
|
+
last_modified: "",
|
|
1158
|
+
authoring_method: "manual-authoring"
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
async function loadWorld(worldPath) {
|
|
1163
|
+
const { stat } = await import("fs/promises");
|
|
1164
|
+
const info = await stat(worldPath);
|
|
1165
|
+
if (info.isDirectory()) {
|
|
1166
|
+
return loadWorldFromDirectory(worldPath);
|
|
1167
|
+
}
|
|
1168
|
+
if (worldPath.endsWith(".nv-world.zip")) {
|
|
1169
|
+
throw new Error(".nv-world.zip loading not yet implemented \u2014 use a world directory");
|
|
1170
|
+
}
|
|
1171
|
+
throw new Error(`Cannot load world from: ${worldPath} \u2014 expected a directory or .nv-world.zip`);
|
|
1172
|
+
}
|
|
1173
|
+
var init_world_loader = __esm({
|
|
1174
|
+
"src/loader/world-loader.ts"() {
|
|
1175
|
+
"use strict";
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
// src/runtime/session.ts
|
|
1180
|
+
var session_exports = {};
|
|
1181
|
+
__export(session_exports, {
|
|
1182
|
+
SessionManager: () => SessionManager,
|
|
1183
|
+
runInteractiveMode: () => runInteractiveMode,
|
|
1184
|
+
runPipeMode: () => runPipeMode
|
|
1185
|
+
});
|
|
1186
|
+
async function defaultToolExecutor(name, args) {
|
|
1187
|
+
return `Tool "${name}" executed successfully with args: ${JSON.stringify(args)}`;
|
|
1188
|
+
}
|
|
1189
|
+
async function runPipeMode(config) {
|
|
1190
|
+
const session = new SessionManager(config);
|
|
1191
|
+
await session.start();
|
|
1192
|
+
const state = session.getState();
|
|
1193
|
+
process.stderr.write(`[neuroverse] Pipe mode active
|
|
1194
|
+
`);
|
|
1195
|
+
process.stderr.write(`[neuroverse] World: ${state.world.world.name}
|
|
1196
|
+
`);
|
|
1197
|
+
if (state.plan) {
|
|
1198
|
+
process.stderr.write(`[neuroverse] Plan: ${state.plan.plan_id} (${state.plan.objective})
|
|
1199
|
+
`);
|
|
1200
|
+
}
|
|
1201
|
+
return new Promise((resolve2, reject) => {
|
|
1202
|
+
let buffer = "";
|
|
1203
|
+
process.stdin.setEncoding("utf-8");
|
|
1204
|
+
process.stdin.on("data", (chunk) => {
|
|
1205
|
+
buffer += chunk;
|
|
1206
|
+
const lines = buffer.split("\n");
|
|
1207
|
+
buffer = lines.pop() ?? "";
|
|
1208
|
+
for (const line of lines) {
|
|
1209
|
+
const trimmed = line.trim();
|
|
1210
|
+
if (!trimmed) continue;
|
|
1211
|
+
try {
|
|
1212
|
+
const event = JSON.parse(trimmed);
|
|
1213
|
+
if (!event.intent) {
|
|
1214
|
+
process.stderr.write(`[neuroverse] Warning: event missing "intent" field
|
|
1215
|
+
`);
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
const verdict = session.evaluate(event);
|
|
1219
|
+
process.stdout.write(JSON.stringify(verdict) + "\n");
|
|
1220
|
+
} catch (err) {
|
|
1221
|
+
process.stderr.write(`[neuroverse] Error parsing line: ${err}
|
|
1222
|
+
`);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
});
|
|
1226
|
+
process.stdin.on("end", () => {
|
|
1227
|
+
if (buffer.trim()) {
|
|
1228
|
+
try {
|
|
1229
|
+
const event = JSON.parse(buffer.trim());
|
|
1230
|
+
if (event.intent) {
|
|
1231
|
+
const verdict = session.evaluate(event);
|
|
1232
|
+
process.stdout.write(JSON.stringify(verdict) + "\n");
|
|
1233
|
+
}
|
|
1234
|
+
} catch {
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
const finalState = session.stop();
|
|
1238
|
+
process.stderr.write(
|
|
1239
|
+
`[neuroverse] Session complete: ${finalState.actionsEvaluated} evaluated, ${finalState.actionsAllowed} allowed, ${finalState.actionsBlocked} blocked, ${finalState.actionsPaused} paused
|
|
1240
|
+
`
|
|
1241
|
+
);
|
|
1242
|
+
resolve2();
|
|
1243
|
+
});
|
|
1244
|
+
process.stdin.on("error", reject);
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
async function runInteractiveMode(config, model) {
|
|
1248
|
+
const session = new SessionManager(config);
|
|
1249
|
+
await session.start();
|
|
1250
|
+
const state = session.getState();
|
|
1251
|
+
process.stdout.write("\n");
|
|
1252
|
+
process.stdout.write(` World: ${state.world.world.name}
|
|
1253
|
+
`);
|
|
1254
|
+
if (state.plan) {
|
|
1255
|
+
process.stdout.write(` Plan: ${state.plan.plan_id}
|
|
1256
|
+
`);
|
|
1257
|
+
process.stdout.write(` Goal: ${state.plan.objective}
|
|
1258
|
+
`);
|
|
1259
|
+
process.stdout.write(` Steps: ${state.progress?.total ?? 0}
|
|
1260
|
+
`);
|
|
1261
|
+
}
|
|
1262
|
+
process.stdout.write(` Type "exit" to end session.
|
|
1263
|
+
`);
|
|
1264
|
+
process.stdout.write("\n");
|
|
1265
|
+
const readline = await import("readline");
|
|
1266
|
+
const rl = readline.createInterface({
|
|
1267
|
+
input: process.stdin,
|
|
1268
|
+
output: process.stdout,
|
|
1269
|
+
prompt: "> "
|
|
1270
|
+
});
|
|
1271
|
+
const printProgress = () => {
|
|
1272
|
+
const s = session.getState();
|
|
1273
|
+
if (s.progress) {
|
|
1274
|
+
process.stdout.write(
|
|
1275
|
+
` [plan: ${s.progress.completed}/${s.progress.total} (${s.progress.percentage}%)]
|
|
1276
|
+
`
|
|
1277
|
+
);
|
|
1278
|
+
}
|
|
1279
|
+
};
|
|
1280
|
+
rl.prompt();
|
|
1281
|
+
rl.on("line", async (input) => {
|
|
1282
|
+
const trimmed = input.trim();
|
|
1283
|
+
if (!trimmed) {
|
|
1284
|
+
rl.prompt();
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
if (trimmed === "exit" || trimmed === "quit") {
|
|
1288
|
+
const finalState = session.stop();
|
|
1289
|
+
process.stdout.write("\n");
|
|
1290
|
+
process.stdout.write(` Session complete.
|
|
1291
|
+
`);
|
|
1292
|
+
process.stdout.write(` Actions: ${finalState.actionsEvaluated} evaluated`);
|
|
1293
|
+
process.stdout.write(`, ${finalState.actionsAllowed} allowed`);
|
|
1294
|
+
process.stdout.write(`, ${finalState.actionsBlocked} blocked
|
|
1295
|
+
`);
|
|
1296
|
+
if (finalState.progress) {
|
|
1297
|
+
process.stdout.write(
|
|
1298
|
+
` Plan: ${finalState.progress.completed}/${finalState.progress.total} steps completed
|
|
1299
|
+
`
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
process.stdout.write("\n");
|
|
1303
|
+
rl.close();
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
if (trimmed === "status") {
|
|
1307
|
+
const s = session.getState();
|
|
1308
|
+
process.stdout.write(`
|
|
1309
|
+
World: ${s.world.world.name}
|
|
1310
|
+
`);
|
|
1311
|
+
process.stdout.write(` Actions: ${s.actionsEvaluated} evaluated
|
|
1312
|
+
`);
|
|
1313
|
+
process.stdout.write(` Allowed: ${s.actionsAllowed} | Blocked: ${s.actionsBlocked} | Paused: ${s.actionsPaused}
|
|
1314
|
+
`);
|
|
1315
|
+
if (s.progress && s.plan) {
|
|
1316
|
+
process.stdout.write(` Plan: ${s.plan.plan_id} \u2014 ${s.progress.completed}/${s.progress.total} (${s.progress.percentage}%)
|
|
1317
|
+
`);
|
|
1318
|
+
for (const step of s.plan.steps) {
|
|
1319
|
+
const icon = step.status === "completed" ? "[x]" : "[ ]";
|
|
1320
|
+
process.stdout.write(` ${icon} ${step.label}
|
|
1321
|
+
`);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
process.stdout.write("\n");
|
|
1325
|
+
rl.prompt();
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
try {
|
|
1329
|
+
const response = await model.chat(trimmed);
|
|
1330
|
+
if (response.toolCalls.length > 0) {
|
|
1331
|
+
const finalResponse = await session.processModelResponse(response, model);
|
|
1332
|
+
if (finalResponse.content) {
|
|
1333
|
+
process.stdout.write(`
|
|
1334
|
+
${finalResponse.content}
|
|
1335
|
+
|
|
1336
|
+
`);
|
|
1337
|
+
}
|
|
1338
|
+
printProgress();
|
|
1339
|
+
} else if (response.content) {
|
|
1340
|
+
process.stdout.write(`
|
|
1341
|
+
${response.content}
|
|
1342
|
+
|
|
1343
|
+
`);
|
|
1344
|
+
}
|
|
1345
|
+
} catch (err) {
|
|
1346
|
+
process.stderr.write(`
|
|
1347
|
+
Error: ${err}
|
|
1348
|
+
|
|
1349
|
+
`);
|
|
1350
|
+
}
|
|
1351
|
+
rl.prompt();
|
|
1352
|
+
});
|
|
1353
|
+
rl.on("close", () => {
|
|
1354
|
+
session.stop();
|
|
1355
|
+
});
|
|
1356
|
+
return new Promise((resolve2) => {
|
|
1357
|
+
rl.on("close", resolve2);
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
var SessionManager;
|
|
1361
|
+
var init_session = __esm({
|
|
1362
|
+
"src/runtime/session.ts"() {
|
|
1363
|
+
"use strict";
|
|
1364
|
+
init_guard_engine();
|
|
1365
|
+
init_plan_engine();
|
|
1366
|
+
init_world_loader();
|
|
1367
|
+
SessionManager = class {
|
|
1368
|
+
config;
|
|
1369
|
+
state;
|
|
1370
|
+
engineOptions;
|
|
1371
|
+
executor;
|
|
1372
|
+
constructor(config) {
|
|
1373
|
+
this.config = config;
|
|
1374
|
+
this.executor = config.toolExecutor ?? defaultToolExecutor;
|
|
1375
|
+
this.engineOptions = {
|
|
1376
|
+
trace: config.trace ?? false,
|
|
1377
|
+
level: config.level,
|
|
1378
|
+
plan: config.plan
|
|
1379
|
+
};
|
|
1380
|
+
this.state = {
|
|
1381
|
+
active: false,
|
|
1382
|
+
world: config.world,
|
|
1383
|
+
plan: config.plan,
|
|
1384
|
+
progress: config.plan ? getPlanProgress(config.plan) : void 0,
|
|
1385
|
+
actionsEvaluated: 0,
|
|
1386
|
+
actionsAllowed: 0,
|
|
1387
|
+
actionsBlocked: 0,
|
|
1388
|
+
actionsPaused: 0
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Initialize the session — load world from disk if needed.
|
|
1393
|
+
*/
|
|
1394
|
+
async start() {
|
|
1395
|
+
if (this.config.worldPath && !this.config.world) {
|
|
1396
|
+
this.state.world = await loadWorld(this.config.worldPath);
|
|
1397
|
+
}
|
|
1398
|
+
if (!this.state.world) {
|
|
1399
|
+
throw new Error("No world provided. Use --world or pass a world definition.");
|
|
1400
|
+
}
|
|
1401
|
+
this.state.active = true;
|
|
1402
|
+
return this.getState();
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Evaluate a single event against governance.
|
|
1406
|
+
* Returns the verdict without executing anything.
|
|
1407
|
+
*/
|
|
1408
|
+
evaluate(event) {
|
|
1409
|
+
this.engineOptions.plan = this.state.plan;
|
|
1410
|
+
const verdict = evaluateGuard(event, this.state.world, this.engineOptions);
|
|
1411
|
+
this.state.actionsEvaluated++;
|
|
1412
|
+
if (verdict.status === "ALLOW") this.state.actionsAllowed++;
|
|
1413
|
+
if (verdict.status === "BLOCK") this.state.actionsBlocked++;
|
|
1414
|
+
if (verdict.status === "PAUSE") this.state.actionsPaused++;
|
|
1415
|
+
this.config.onVerdict?.(verdict, event);
|
|
1416
|
+
return verdict;
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* Evaluate and execute a tool call.
|
|
1420
|
+
* Returns the execution result or block reason.
|
|
1421
|
+
*/
|
|
1422
|
+
async executeToolCall(toolCall) {
|
|
1423
|
+
let args;
|
|
1424
|
+
try {
|
|
1425
|
+
args = JSON.parse(toolCall.function.arguments);
|
|
1426
|
+
} catch {
|
|
1427
|
+
args = { raw: toolCall.function.arguments };
|
|
1428
|
+
}
|
|
1429
|
+
const event = {
|
|
1430
|
+
intent: toolCall.function.name,
|
|
1431
|
+
tool: toolCall.function.name,
|
|
1432
|
+
args,
|
|
1433
|
+
direction: "input"
|
|
1434
|
+
};
|
|
1435
|
+
const verdict = this.evaluate(event);
|
|
1436
|
+
if (verdict.status === "BLOCK") {
|
|
1437
|
+
return { allowed: false, verdict };
|
|
1438
|
+
}
|
|
1439
|
+
if (verdict.status === "PAUSE") {
|
|
1440
|
+
return { allowed: false, verdict };
|
|
1441
|
+
}
|
|
1442
|
+
const result = await this.executor(toolCall.function.name, args);
|
|
1443
|
+
this.config.onToolResult?.(toolCall.function.name, result);
|
|
1444
|
+
if (this.state.plan) {
|
|
1445
|
+
const planVerdict = evaluatePlan(event, this.state.plan);
|
|
1446
|
+
if (planVerdict.matchedStep) {
|
|
1447
|
+
this.state.plan = advancePlan(this.state.plan, planVerdict.matchedStep);
|
|
1448
|
+
this.engineOptions.plan = this.state.plan;
|
|
1449
|
+
this.state.progress = getPlanProgress(this.state.plan);
|
|
1450
|
+
this.config.onPlanProgress?.(this.state.progress);
|
|
1451
|
+
if (this.state.progress.completed === this.state.progress.total) {
|
|
1452
|
+
this.config.onPlanComplete?.();
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
return { allowed: true, verdict, result };
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* Process a model response — evaluate and execute all tool calls.
|
|
1460
|
+
* Returns results for each tool call.
|
|
1461
|
+
*/
|
|
1462
|
+
async processModelResponse(response, model) {
|
|
1463
|
+
if (response.toolCalls.length === 0) {
|
|
1464
|
+
return response;
|
|
1465
|
+
}
|
|
1466
|
+
for (const toolCall of response.toolCalls) {
|
|
1467
|
+
const { allowed, verdict, result } = await this.executeToolCall(toolCall);
|
|
1468
|
+
if (allowed && result) {
|
|
1469
|
+
const nextResponse = await model.sendToolResult(toolCall.id, result);
|
|
1470
|
+
if (nextResponse.toolCalls.length > 0) {
|
|
1471
|
+
return this.processModelResponse(nextResponse, model);
|
|
1472
|
+
}
|
|
1473
|
+
return nextResponse;
|
|
1474
|
+
} else {
|
|
1475
|
+
const reason = verdict.reason ?? "Action blocked by governance.";
|
|
1476
|
+
const nextResponse = await model.sendBlockedResult(toolCall.id, reason);
|
|
1477
|
+
if (nextResponse.toolCalls.length > 0) {
|
|
1478
|
+
return this.processModelResponse(nextResponse, model);
|
|
1479
|
+
}
|
|
1480
|
+
return nextResponse;
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
return response;
|
|
1484
|
+
}
|
|
1485
|
+
/** Get current session state. */
|
|
1486
|
+
getState() {
|
|
1487
|
+
return { ...this.state };
|
|
1488
|
+
}
|
|
1489
|
+
/** Stop the session. */
|
|
1490
|
+
stop() {
|
|
1491
|
+
this.state.active = false;
|
|
1492
|
+
return this.getState();
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
});
|
|
1497
|
+
|
|
1498
|
+
// src/cli/run.ts
|
|
1499
|
+
var run_exports = {};
|
|
1500
|
+
__export(run_exports, {
|
|
1501
|
+
main: () => main
|
|
1502
|
+
});
|
|
1503
|
+
module.exports = __toCommonJS(run_exports);
|
|
1504
|
+
var import_fs2 = require("fs");
|
|
1505
|
+
var import_path2 = require("path");
|
|
1506
|
+
|
|
1507
|
+
// src/loader/world-resolver.ts
|
|
1508
|
+
var import_fs = require("fs");
|
|
1509
|
+
var import_path = require("path");
|
|
1510
|
+
var WORLDS_DIR = ".neuroverse/worlds";
|
|
1511
|
+
var ACTIVE_WORLD_FILE = ".neuroverse/active_world";
|
|
1512
|
+
function listWorlds(cwd = process.cwd()) {
|
|
1513
|
+
const worldsDir = (0, import_path.join)(cwd, WORLDS_DIR);
|
|
1514
|
+
if (!(0, import_fs.existsSync)(worldsDir)) return [];
|
|
1515
|
+
const activeName = getActiveWorldName(cwd);
|
|
1516
|
+
const entries = (0, import_fs.readdirSync)(worldsDir);
|
|
1517
|
+
return entries.filter((name) => {
|
|
1518
|
+
const worldJson = (0, import_path.join)(worldsDir, name, "world.json");
|
|
1519
|
+
return (0, import_fs.existsSync)(worldJson);
|
|
1520
|
+
}).map((name) => ({
|
|
1521
|
+
name,
|
|
1522
|
+
path: (0, import_path.join)(worldsDir, name),
|
|
1523
|
+
active: name === activeName
|
|
1524
|
+
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
1525
|
+
}
|
|
1526
|
+
function getActiveWorldName(cwd = process.cwd()) {
|
|
1527
|
+
const filePath = (0, import_path.join)(cwd, ACTIVE_WORLD_FILE);
|
|
1528
|
+
try {
|
|
1529
|
+
return (0, import_fs.readFileSync)(filePath, "utf-8").trim() || void 0;
|
|
1530
|
+
} catch {
|
|
1531
|
+
return void 0;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
function resolveWorldPath(explicit, cwd = process.cwd()) {
|
|
1535
|
+
if (explicit) {
|
|
1536
|
+
return resolveNameOrPath(explicit, cwd);
|
|
1537
|
+
}
|
|
1538
|
+
const envWorld = process.env.NEUROVERSE_WORLD;
|
|
1539
|
+
if (envWorld) {
|
|
1540
|
+
return resolveNameOrPath(envWorld, cwd);
|
|
1541
|
+
}
|
|
1542
|
+
const activeName = getActiveWorldName(cwd);
|
|
1543
|
+
if (activeName) {
|
|
1544
|
+
return resolveNameOrPath(activeName, cwd);
|
|
1545
|
+
}
|
|
1546
|
+
const worlds = listWorlds(cwd);
|
|
1547
|
+
if (worlds.length === 1) {
|
|
1548
|
+
return (0, import_path.resolve)(worlds[0].path);
|
|
1549
|
+
}
|
|
1550
|
+
return void 0;
|
|
1551
|
+
}
|
|
1552
|
+
function describeActiveWorld(explicit, cwd = process.cwd()) {
|
|
1553
|
+
if (explicit) {
|
|
1554
|
+
return { name: explicit, source: "--world flag" };
|
|
1555
|
+
}
|
|
1556
|
+
const envWorld = process.env.NEUROVERSE_WORLD;
|
|
1557
|
+
if (envWorld) {
|
|
1558
|
+
return { name: envWorld, source: "NEUROVERSE_WORLD env var" };
|
|
1559
|
+
}
|
|
1560
|
+
const activeName = getActiveWorldName(cwd);
|
|
1561
|
+
if (activeName) {
|
|
1562
|
+
return { name: activeName, source: ".neuroverse/active_world" };
|
|
1563
|
+
}
|
|
1564
|
+
const worlds = listWorlds(cwd);
|
|
1565
|
+
if (worlds.length === 1) {
|
|
1566
|
+
return { name: worlds[0].name, source: "auto-detected (only world)" };
|
|
1567
|
+
}
|
|
1568
|
+
return void 0;
|
|
1569
|
+
}
|
|
1570
|
+
function resolveNameOrPath(ref, cwd) {
|
|
1571
|
+
if (ref.includes("/") || ref.includes("\\") || ref.startsWith(".") || (0, import_path.isAbsolute)(ref)) {
|
|
1572
|
+
return (0, import_path.resolve)(cwd, ref);
|
|
1573
|
+
}
|
|
1574
|
+
const namedPath = (0, import_path.join)(cwd, WORLDS_DIR, ref);
|
|
1575
|
+
if ((0, import_fs.existsSync)((0, import_path.join)(namedPath, "world.json"))) {
|
|
1576
|
+
return (0, import_path.resolve)(namedPath);
|
|
1577
|
+
}
|
|
1578
|
+
return (0, import_path.resolve)(cwd, ref);
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
// src/cli/run.ts
|
|
1582
|
+
function parseArg(args, flag) {
|
|
1583
|
+
const idx = args.indexOf(flag);
|
|
1584
|
+
return idx >= 0 && idx + 1 < args.length ? args[idx + 1] : void 0;
|
|
1585
|
+
}
|
|
1586
|
+
function hasFlag(args, flag) {
|
|
1587
|
+
return args.includes(flag);
|
|
1588
|
+
}
|
|
1589
|
+
function autoDetectPlan() {
|
|
1590
|
+
const nvDir = ".neuroverse/plans";
|
|
1591
|
+
if (!(0, import_fs2.existsSync)(nvDir)) return void 0;
|
|
1592
|
+
const entries = (0, import_fs2.readdirSync)(nvDir).filter((e) => e.endsWith(".json"));
|
|
1593
|
+
if (entries.length === 1) {
|
|
1594
|
+
try {
|
|
1595
|
+
return JSON.parse((0, import_fs2.readFileSync)((0, import_path2.join)(nvDir, entries[0]), "utf-8"));
|
|
1596
|
+
} catch {
|
|
1597
|
+
return void 0;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
return void 0;
|
|
1601
|
+
}
|
|
1602
|
+
function loadPlan(path) {
|
|
1603
|
+
return JSON.parse((0, import_fs2.readFileSync)(path, "utf-8"));
|
|
1604
|
+
}
|
|
1605
|
+
var RUN_USAGE = `
|
|
1606
|
+
neuroverse run \u2014 Governed runtime for AI agents.
|
|
1607
|
+
|
|
1608
|
+
Modes:
|
|
1609
|
+
--pipe JSON lines in \u2192 verdicts out (default if stdin is piped)
|
|
1610
|
+
--interactive Chat session with model + governance
|
|
1611
|
+
|
|
1612
|
+
Options:
|
|
1613
|
+
--world <path> Path to world directory
|
|
1614
|
+
--plan <path> Path to plan.json
|
|
1615
|
+
--level <level> Enforcement level (basic|standard|strict)
|
|
1616
|
+
--trace Include evaluation trace in verdicts
|
|
1617
|
+
--provider <name> Model provider (openai|anthropic|ollama)
|
|
1618
|
+
--model <name> Model name override
|
|
1619
|
+
--api-key <key> API key (or set via env var)
|
|
1620
|
+
|
|
1621
|
+
Usage:
|
|
1622
|
+
# Pipe mode \u2014 works with any agent
|
|
1623
|
+
my_agent | neuroverse run --world ./world/ --plan plan.json
|
|
1624
|
+
|
|
1625
|
+
# Interactive mode \u2014 governed chat session
|
|
1626
|
+
neuroverse run --interactive --world ./world/ --provider openai
|
|
1627
|
+
|
|
1628
|
+
# Auto-detect world and plan
|
|
1629
|
+
neuroverse run
|
|
1630
|
+
`.trim();
|
|
1631
|
+
async function main(args) {
|
|
1632
|
+
if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
|
|
1633
|
+
process.stdout.write(RUN_USAGE + "\n");
|
|
1634
|
+
process.exit(0);
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
const worldPath = resolveWorldPath(parseArg(args, "--world"));
|
|
1638
|
+
if (!worldPath) {
|
|
1639
|
+
process.stderr.write(
|
|
1640
|
+
"Error: No world found.\nUse --world <path>, set NEUROVERSE_WORLD, or run `neuroverse world use <name>`\n"
|
|
1641
|
+
);
|
|
1642
|
+
process.exit(1);
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
const explicitWorld = parseArg(args, "--world");
|
|
1646
|
+
const worldInfo = describeActiveWorld(explicitWorld);
|
|
1647
|
+
if (worldInfo) {
|
|
1648
|
+
process.stderr.write(`Using world: ${worldInfo.name}
|
|
1649
|
+
`);
|
|
1650
|
+
}
|
|
1651
|
+
const planPath = parseArg(args, "--plan");
|
|
1652
|
+
const plan = planPath ? loadPlan(planPath) : autoDetectPlan();
|
|
1653
|
+
const level = parseArg(args, "--level");
|
|
1654
|
+
const trace = hasFlag(args, "--trace");
|
|
1655
|
+
const isPipeMode = hasFlag(args, "--pipe") || !process.stdin.isTTY;
|
|
1656
|
+
const isInteractive = hasFlag(args, "--interactive");
|
|
1657
|
+
if (isInteractive) {
|
|
1658
|
+
const providerName = parseArg(args, "--provider");
|
|
1659
|
+
if (!providerName) {
|
|
1660
|
+
process.stderr.write(
|
|
1661
|
+
"Error: Interactive mode requires --provider (openai|anthropic|ollama)\n"
|
|
1662
|
+
);
|
|
1663
|
+
process.exit(1);
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
const { resolveProvider: resolveProvider2, ModelAdapter: ModelAdapter2 } = await Promise.resolve().then(() => (init_model_adapter(), model_adapter_exports));
|
|
1667
|
+
const { runInteractiveMode: runInteractiveMode2 } = await Promise.resolve().then(() => (init_session(), session_exports));
|
|
1668
|
+
const modelConfig = resolveProvider2(providerName, {
|
|
1669
|
+
model: parseArg(args, "--model"),
|
|
1670
|
+
apiKey: parseArg(args, "--api-key")
|
|
1671
|
+
});
|
|
1672
|
+
const model = new ModelAdapter2(modelConfig);
|
|
1673
|
+
await runInteractiveMode2(
|
|
1674
|
+
{
|
|
1675
|
+
worldPath,
|
|
1676
|
+
plan,
|
|
1677
|
+
level,
|
|
1678
|
+
trace,
|
|
1679
|
+
onVerdict: (verdict, event) => {
|
|
1680
|
+
if (verdict.status !== "ALLOW") {
|
|
1681
|
+
process.stderr.write(
|
|
1682
|
+
` [${verdict.status}] ${event.intent} \u2014 ${verdict.reason ?? verdict.ruleId ?? "governance rule"}
|
|
1683
|
+
`
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1686
|
+
},
|
|
1687
|
+
onPlanProgress: (progress) => {
|
|
1688
|
+
process.stderr.write(
|
|
1689
|
+
` [plan] ${progress.completed}/${progress.total} (${progress.percentage}%)
|
|
1690
|
+
`
|
|
1691
|
+
);
|
|
1692
|
+
},
|
|
1693
|
+
onPlanComplete: () => {
|
|
1694
|
+
process.stderr.write(` [plan] Complete!
|
|
1695
|
+
`);
|
|
1696
|
+
}
|
|
1697
|
+
},
|
|
1698
|
+
model
|
|
1699
|
+
);
|
|
1700
|
+
} else if (isPipeMode) {
|
|
1701
|
+
const { runPipeMode: runPipeMode2 } = await Promise.resolve().then(() => (init_session(), session_exports));
|
|
1702
|
+
await runPipeMode2({
|
|
1703
|
+
worldPath,
|
|
1704
|
+
plan,
|
|
1705
|
+
level,
|
|
1706
|
+
trace
|
|
1707
|
+
});
|
|
1708
|
+
} else {
|
|
1709
|
+
process.stdout.write(RUN_USAGE + "\n");
|
|
1710
|
+
process.exit(0);
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1714
|
+
0 && (module.exports = {
|
|
1715
|
+
main
|
|
1716
|
+
});
|