@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
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// src/loader/world-resolver.ts
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync } from "fs";
|
|
3
|
+
import { join, resolve, isAbsolute } from "path";
|
|
4
|
+
var WORLDS_DIR = ".neuroverse/worlds";
|
|
5
|
+
var ACTIVE_WORLD_FILE = ".neuroverse/active_world";
|
|
6
|
+
function listWorlds(cwd = process.cwd()) {
|
|
7
|
+
const worldsDir = join(cwd, WORLDS_DIR);
|
|
8
|
+
if (!existsSync(worldsDir)) return [];
|
|
9
|
+
const activeName = getActiveWorldName(cwd);
|
|
10
|
+
const entries = readdirSync(worldsDir);
|
|
11
|
+
return entries.filter((name) => {
|
|
12
|
+
const worldJson = join(worldsDir, name, "world.json");
|
|
13
|
+
return existsSync(worldJson);
|
|
14
|
+
}).map((name) => ({
|
|
15
|
+
name,
|
|
16
|
+
path: join(worldsDir, name),
|
|
17
|
+
active: name === activeName
|
|
18
|
+
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
19
|
+
}
|
|
20
|
+
function getActiveWorldName(cwd = process.cwd()) {
|
|
21
|
+
const filePath = join(cwd, ACTIVE_WORLD_FILE);
|
|
22
|
+
try {
|
|
23
|
+
return readFileSync(filePath, "utf-8").trim() || void 0;
|
|
24
|
+
} catch {
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function setActiveWorld(name, cwd = process.cwd()) {
|
|
29
|
+
const worldsDir = join(cwd, WORLDS_DIR);
|
|
30
|
+
const worldPath = join(worldsDir, name, "world.json");
|
|
31
|
+
if (!existsSync(worldPath)) {
|
|
32
|
+
const available = listWorlds(cwd);
|
|
33
|
+
const names = available.map((w) => w.name).join(", ");
|
|
34
|
+
throw new Error(
|
|
35
|
+
`World "${name}" not found in ${WORLDS_DIR}/
|
|
36
|
+
` + (names ? `Available: ${names}` : "No worlds found. Run `neuroverse build` first.")
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
const dir = join(cwd, ".neuroverse");
|
|
40
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
41
|
+
writeFileSync(join(cwd, ACTIVE_WORLD_FILE), name + "\n", "utf-8");
|
|
42
|
+
}
|
|
43
|
+
function resolveWorldPath(explicit, cwd = process.cwd()) {
|
|
44
|
+
if (explicit) {
|
|
45
|
+
return resolveNameOrPath(explicit, cwd);
|
|
46
|
+
}
|
|
47
|
+
const envWorld = process.env.NEUROVERSE_WORLD;
|
|
48
|
+
if (envWorld) {
|
|
49
|
+
return resolveNameOrPath(envWorld, cwd);
|
|
50
|
+
}
|
|
51
|
+
const activeName = getActiveWorldName(cwd);
|
|
52
|
+
if (activeName) {
|
|
53
|
+
return resolveNameOrPath(activeName, cwd);
|
|
54
|
+
}
|
|
55
|
+
const worlds = listWorlds(cwd);
|
|
56
|
+
if (worlds.length === 1) {
|
|
57
|
+
return resolve(worlds[0].path);
|
|
58
|
+
}
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
function describeActiveWorld(explicit, cwd = process.cwd()) {
|
|
62
|
+
if (explicit) {
|
|
63
|
+
return { name: explicit, source: "--world flag" };
|
|
64
|
+
}
|
|
65
|
+
const envWorld = process.env.NEUROVERSE_WORLD;
|
|
66
|
+
if (envWorld) {
|
|
67
|
+
return { name: envWorld, source: "NEUROVERSE_WORLD env var" };
|
|
68
|
+
}
|
|
69
|
+
const activeName = getActiveWorldName(cwd);
|
|
70
|
+
if (activeName) {
|
|
71
|
+
return { name: activeName, source: ".neuroverse/active_world" };
|
|
72
|
+
}
|
|
73
|
+
const worlds = listWorlds(cwd);
|
|
74
|
+
if (worlds.length === 1) {
|
|
75
|
+
return { name: worlds[0].name, source: "auto-detected (only world)" };
|
|
76
|
+
}
|
|
77
|
+
return void 0;
|
|
78
|
+
}
|
|
79
|
+
function resolveNameOrPath(ref, cwd) {
|
|
80
|
+
if (ref.includes("/") || ref.includes("\\") || ref.startsWith(".") || isAbsolute(ref)) {
|
|
81
|
+
return resolve(cwd, ref);
|
|
82
|
+
}
|
|
83
|
+
const namedPath = join(cwd, WORLDS_DIR, ref);
|
|
84
|
+
if (existsSync(join(namedPath, "world.json"))) {
|
|
85
|
+
return resolve(namedPath);
|
|
86
|
+
}
|
|
87
|
+
return resolve(cwd, ref);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export {
|
|
91
|
+
listWorlds,
|
|
92
|
+
getActiveWorldName,
|
|
93
|
+
setActiveWorld,
|
|
94
|
+
resolveWorldPath,
|
|
95
|
+
describeActiveWorld
|
|
96
|
+
};
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
evaluateGuard
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-PQBJBVSW.js";
|
|
4
4
|
import {
|
|
5
5
|
loadWorld
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-JZPQGIKR.js";
|
|
7
|
+
import {
|
|
8
|
+
advancePlan,
|
|
9
|
+
evaluatePlan,
|
|
10
|
+
getPlanProgress
|
|
11
|
+
} from "./chunk-P74Y66ZV.js";
|
|
7
12
|
|
|
8
13
|
// src/adapters/langchain.ts
|
|
9
14
|
var GovernanceBlockedError = class extends Error {
|
|
@@ -31,12 +36,15 @@ var NeuroVerseCallbackHandler = class {
|
|
|
31
36
|
options;
|
|
32
37
|
engineOptions;
|
|
33
38
|
mapToolCall;
|
|
39
|
+
activePlan;
|
|
34
40
|
constructor(world, options = {}) {
|
|
35
41
|
this.world = world;
|
|
36
42
|
this.options = options;
|
|
43
|
+
this.activePlan = options.plan;
|
|
37
44
|
this.engineOptions = {
|
|
38
45
|
trace: options.trace ?? false,
|
|
39
|
-
level: options.level
|
|
46
|
+
level: options.level,
|
|
47
|
+
plan: this.activePlan
|
|
40
48
|
};
|
|
41
49
|
this.mapToolCall = options.mapToolCall ?? defaultMapToolCall;
|
|
42
50
|
}
|
|
@@ -55,6 +63,7 @@ var NeuroVerseCallbackHandler = class {
|
|
|
55
63
|
parsedInput = { raw: input };
|
|
56
64
|
}
|
|
57
65
|
const event = this.mapToolCall(tool.name, parsedInput);
|
|
66
|
+
this.engineOptions.plan = this.activePlan;
|
|
58
67
|
const verdict = evaluateGuard(event, this.world, this.engineOptions);
|
|
59
68
|
this.options.onEvaluate?.(verdict, event);
|
|
60
69
|
if (verdict.status === "BLOCK") {
|
|
@@ -67,6 +76,18 @@ var NeuroVerseCallbackHandler = class {
|
|
|
67
76
|
throw new GovernanceBlockedError(verdict, event);
|
|
68
77
|
}
|
|
69
78
|
}
|
|
79
|
+
if (verdict.status === "ALLOW" && this.activePlan) {
|
|
80
|
+
const planVerdict = evaluatePlan(event, this.activePlan);
|
|
81
|
+
if (planVerdict.matchedStep) {
|
|
82
|
+
this.activePlan = advancePlan(this.activePlan, planVerdict.matchedStep);
|
|
83
|
+
this.engineOptions.plan = this.activePlan;
|
|
84
|
+
const progress = getPlanProgress(this.activePlan);
|
|
85
|
+
this.options.onPlanProgress?.(progress);
|
|
86
|
+
if (progress.completed === progress.total) {
|
|
87
|
+
this.options.onPlanComplete?.();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
70
91
|
}
|
|
71
92
|
};
|
|
72
93
|
async function createNeuroVerseCallbackHandler(worldPath, options) {
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createProvider
|
|
2
|
+
createProvider
|
|
3
|
+
} from "./chunk-Q6O7ZLO2.js";
|
|
4
|
+
import {
|
|
3
5
|
loadConfig
|
|
4
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-OT6PXH54.js";
|
|
5
7
|
import {
|
|
6
8
|
emitWorldDefinition,
|
|
7
9
|
parseWorldMarkdown
|
|
8
10
|
} from "./chunk-XPDMYECO.js";
|
|
9
11
|
import {
|
|
10
12
|
validateWorld
|
|
11
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-7P3S7MAY.js";
|
|
12
14
|
|
|
13
15
|
// src/engine/derive-normalizer.ts
|
|
14
16
|
function findSections(lines) {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// src/providers/config-manager.ts
|
|
2
|
+
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
function getConfigDir() {
|
|
6
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
7
|
+
if (xdg) return join(xdg, "neuroverse");
|
|
8
|
+
return join(homedir(), ".neuroverse");
|
|
9
|
+
}
|
|
10
|
+
function getConfigPath() {
|
|
11
|
+
return join(getConfigDir(), "config.json");
|
|
12
|
+
}
|
|
13
|
+
async function loadConfig() {
|
|
14
|
+
try {
|
|
15
|
+
const raw = await readFile(getConfigPath(), "utf-8");
|
|
16
|
+
const parsed = JSON.parse(raw);
|
|
17
|
+
if (!parsed.provider || !parsed.model || !parsed.apiKey) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
provider: parsed.provider,
|
|
22
|
+
model: parsed.model,
|
|
23
|
+
apiKey: parsed.apiKey,
|
|
24
|
+
endpoint: parsed.endpoint ?? null
|
|
25
|
+
};
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function saveConfig(config) {
|
|
31
|
+
const dir = getConfigDir();
|
|
32
|
+
await mkdir(dir, { recursive: true });
|
|
33
|
+
const configPath = getConfigPath();
|
|
34
|
+
const content = JSON.stringify(
|
|
35
|
+
{
|
|
36
|
+
provider: config.provider,
|
|
37
|
+
model: config.model,
|
|
38
|
+
apiKey: config.apiKey,
|
|
39
|
+
endpoint: config.endpoint
|
|
40
|
+
},
|
|
41
|
+
null,
|
|
42
|
+
2
|
|
43
|
+
);
|
|
44
|
+
await writeFile(configPath, content, { mode: 384 });
|
|
45
|
+
await chmod(configPath, 384);
|
|
46
|
+
}
|
|
47
|
+
function redactConfig(config) {
|
|
48
|
+
return {
|
|
49
|
+
provider: config.provider,
|
|
50
|
+
model: config.model,
|
|
51
|
+
apiKey: config.apiKey ? `${config.apiKey.slice(0, 4)}...${config.apiKey.slice(-4)}` : "(not set)",
|
|
52
|
+
endpoint: config.endpoint
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export {
|
|
57
|
+
getConfigPath,
|
|
58
|
+
loadConfig,
|
|
59
|
+
saveConfig,
|
|
60
|
+
redactConfig
|
|
61
|
+
};
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// src/engine/plan-engine.ts
|
|
2
|
+
function keywordMatch(eventText, step) {
|
|
3
|
+
const stepText = [
|
|
4
|
+
step.label,
|
|
5
|
+
step.description ?? "",
|
|
6
|
+
...step.tags ?? []
|
|
7
|
+
].join(" ").toLowerCase();
|
|
8
|
+
const keywords = stepText.split(/\s+/).filter((w) => w.length > 3);
|
|
9
|
+
if (keywords.length === 0) return false;
|
|
10
|
+
const matched = keywords.filter((kw) => eventText.includes(kw));
|
|
11
|
+
return matched.length >= Math.ceil(keywords.length * 0.5);
|
|
12
|
+
}
|
|
13
|
+
function tokenSimilarity(a, b) {
|
|
14
|
+
const tokensA = new Set(a.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
|
|
15
|
+
const tokensB = new Set(b.toLowerCase().split(/\s+/).filter((w) => w.length > 2));
|
|
16
|
+
if (tokensA.size === 0 || tokensB.size === 0) return 0;
|
|
17
|
+
let intersection = 0;
|
|
18
|
+
for (const t of tokensA) {
|
|
19
|
+
if (tokensB.has(t)) intersection++;
|
|
20
|
+
}
|
|
21
|
+
const union = (/* @__PURE__ */ new Set([...tokensA, ...tokensB])).size;
|
|
22
|
+
return union > 0 ? intersection / union : 0;
|
|
23
|
+
}
|
|
24
|
+
function findMatchingStep(eventText, event, steps) {
|
|
25
|
+
const pendingOrActive = steps.filter((s) => s.status === "pending" || s.status === "active");
|
|
26
|
+
if (pendingOrActive.length === 0) {
|
|
27
|
+
return { matched: null, closest: null, closestScore: 0 };
|
|
28
|
+
}
|
|
29
|
+
for (const step of pendingOrActive) {
|
|
30
|
+
if (keywordMatch(eventText, step)) {
|
|
31
|
+
if (step.tools && event.tool && !step.tools.includes(event.tool)) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
return { matched: step, closest: step, closestScore: 1 };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const intentText = [event.intent, event.tool ?? "", event.scope ?? ""].join(" ");
|
|
38
|
+
let bestStep = null;
|
|
39
|
+
let bestScore = 0;
|
|
40
|
+
for (const step of pendingOrActive) {
|
|
41
|
+
const stepText = [step.label, step.description ?? "", ...step.tags ?? []].join(" ");
|
|
42
|
+
const score = tokenSimilarity(intentText, stepText);
|
|
43
|
+
if (score > bestScore) {
|
|
44
|
+
bestScore = score;
|
|
45
|
+
bestStep = step;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const SIMILARITY_THRESHOLD = 0.35;
|
|
49
|
+
if (bestScore >= SIMILARITY_THRESHOLD && bestStep) {
|
|
50
|
+
if (bestStep.tools && event.tool && !bestStep.tools.includes(event.tool)) {
|
|
51
|
+
return { matched: null, closest: bestStep, closestScore: bestScore };
|
|
52
|
+
}
|
|
53
|
+
return { matched: bestStep, closest: bestStep, closestScore: bestScore };
|
|
54
|
+
}
|
|
55
|
+
return { matched: null, closest: bestStep, closestScore: bestScore };
|
|
56
|
+
}
|
|
57
|
+
function isSequenceValid(step, plan) {
|
|
58
|
+
if (!plan.sequential) return true;
|
|
59
|
+
if (!step.requires || step.requires.length === 0) return true;
|
|
60
|
+
return step.requires.every((reqId) => {
|
|
61
|
+
const reqStep = plan.steps.find((s) => s.id === reqId);
|
|
62
|
+
return reqStep?.status === "completed";
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function checkConstraints(event, eventText, constraints) {
|
|
66
|
+
const checks = [];
|
|
67
|
+
for (const constraint of constraints) {
|
|
68
|
+
if (constraint.type === "approval") {
|
|
69
|
+
if (constraint.trigger && eventText.includes(constraint.trigger.substring(0, 10).toLowerCase())) {
|
|
70
|
+
checks.push({ constraintId: constraint.id, passed: false, reason: constraint.description });
|
|
71
|
+
return { violated: constraint, checks };
|
|
72
|
+
}
|
|
73
|
+
const keywords = constraint.description.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
|
|
74
|
+
const relevant = keywords.some((kw) => eventText.includes(kw));
|
|
75
|
+
if (relevant) {
|
|
76
|
+
checks.push({ constraintId: constraint.id, passed: false, reason: constraint.description });
|
|
77
|
+
return { violated: constraint, checks };
|
|
78
|
+
}
|
|
79
|
+
checks.push({ constraintId: constraint.id, passed: true });
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (constraint.type === "scope" && constraint.trigger) {
|
|
83
|
+
const keywords = constraint.trigger.split(/\s+/).filter((w) => w.length > 3);
|
|
84
|
+
const violated = keywords.length > 0 && keywords.every((kw) => eventText.includes(kw));
|
|
85
|
+
checks.push({
|
|
86
|
+
constraintId: constraint.id,
|
|
87
|
+
passed: !violated,
|
|
88
|
+
reason: violated ? constraint.description : void 0
|
|
89
|
+
});
|
|
90
|
+
if (violated) {
|
|
91
|
+
return { violated: constraint, checks };
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
checks.push({ constraintId: constraint.id, passed: true });
|
|
96
|
+
}
|
|
97
|
+
return { violated: null, checks };
|
|
98
|
+
}
|
|
99
|
+
function getPlanProgress(plan) {
|
|
100
|
+
const completed = plan.steps.filter((s) => s.status === "completed").length;
|
|
101
|
+
const total = plan.steps.length;
|
|
102
|
+
return {
|
|
103
|
+
completed,
|
|
104
|
+
total,
|
|
105
|
+
percentage: total > 0 ? Math.round(completed / total * 100) : 0
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function advancePlan(plan, stepId) {
|
|
109
|
+
return {
|
|
110
|
+
...plan,
|
|
111
|
+
steps: plan.steps.map(
|
|
112
|
+
(s) => s.id === stepId ? { ...s, status: "completed" } : s
|
|
113
|
+
)
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function evaluatePlan(event, plan) {
|
|
117
|
+
const progress = getPlanProgress(plan);
|
|
118
|
+
if (plan.expires_at) {
|
|
119
|
+
const expiresAt = new Date(plan.expires_at).getTime();
|
|
120
|
+
if (Date.now() > expiresAt) {
|
|
121
|
+
return {
|
|
122
|
+
allowed: true,
|
|
123
|
+
status: "PLAN_COMPLETE",
|
|
124
|
+
reason: "Plan has expired.",
|
|
125
|
+
progress
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (progress.completed === progress.total) {
|
|
130
|
+
return {
|
|
131
|
+
allowed: true,
|
|
132
|
+
status: "PLAN_COMPLETE",
|
|
133
|
+
reason: "All plan steps are completed.",
|
|
134
|
+
progress
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const eventText = [
|
|
138
|
+
event.intent,
|
|
139
|
+
event.tool ?? "",
|
|
140
|
+
event.scope ?? ""
|
|
141
|
+
].join(" ").toLowerCase();
|
|
142
|
+
const { matched, closest, closestScore } = findMatchingStep(eventText, event, plan.steps);
|
|
143
|
+
if (!matched) {
|
|
144
|
+
return {
|
|
145
|
+
allowed: false,
|
|
146
|
+
status: "OFF_PLAN",
|
|
147
|
+
reason: "Action does not match any plan step.",
|
|
148
|
+
closestStep: closest?.label,
|
|
149
|
+
similarityScore: closestScore,
|
|
150
|
+
progress
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (!isSequenceValid(matched, plan)) {
|
|
154
|
+
const pendingDeps = (matched.requires ?? []).filter((reqId) => plan.steps.find((s) => s.id === reqId)?.status !== "completed").join(", ");
|
|
155
|
+
return {
|
|
156
|
+
allowed: false,
|
|
157
|
+
status: "OFF_PLAN",
|
|
158
|
+
reason: `Step "${matched.label}" requires completion of: ${pendingDeps}`,
|
|
159
|
+
matchedStep: matched.id,
|
|
160
|
+
progress
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const { violated } = checkConstraints(event, eventText, plan.constraints);
|
|
164
|
+
if (violated) {
|
|
165
|
+
return {
|
|
166
|
+
allowed: false,
|
|
167
|
+
status: "CONSTRAINT_VIOLATED",
|
|
168
|
+
reason: violated.description,
|
|
169
|
+
matchedStep: matched.id,
|
|
170
|
+
progress
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
allowed: true,
|
|
175
|
+
status: "ON_PLAN",
|
|
176
|
+
reason: `Matches step: ${matched.label}`,
|
|
177
|
+
matchedStep: matched.id,
|
|
178
|
+
progress
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function buildPlanCheck(event, plan, verdict) {
|
|
182
|
+
const eventText = [event.intent, event.tool ?? "", event.scope ?? ""].join(" ").toLowerCase();
|
|
183
|
+
const { matched, closest, closestScore } = findMatchingStep(eventText, event, plan.steps);
|
|
184
|
+
const { checks: constraintChecks } = checkConstraints(event, eventText, plan.constraints);
|
|
185
|
+
const progress = getPlanProgress(plan);
|
|
186
|
+
return {
|
|
187
|
+
planId: plan.plan_id,
|
|
188
|
+
matched: !!matched,
|
|
189
|
+
matchedStepId: matched?.id,
|
|
190
|
+
matchedStepLabel: matched?.label,
|
|
191
|
+
closestStepId: !matched ? closest?.id : void 0,
|
|
192
|
+
closestStepLabel: !matched ? closest?.label : void 0,
|
|
193
|
+
similarityScore: !matched ? closestScore : void 0,
|
|
194
|
+
sequenceValid: matched ? isSequenceValid(matched, plan) : void 0,
|
|
195
|
+
constraintsChecked: constraintChecks,
|
|
196
|
+
progress: { completed: progress.completed, total: progress.total }
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export {
|
|
201
|
+
getPlanProgress,
|
|
202
|
+
advancePlan,
|
|
203
|
+
evaluatePlan,
|
|
204
|
+
buildPlanCheck
|
|
205
|
+
};
|