metheus-governance-mcp-cli 0.2.145 → 0.2.147
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 +6 -1
- package/cli.mjs +7 -0
- package/lib/local-ai-adapters.mjs +267 -3
- package/lib/runner-execution.mjs +17 -0
- package/lib/runner-orchestration.mjs +931 -241
- package/lib/runner-trigger.mjs +9 -0
- package/lib/selftest-runner-scenarios.mjs +730 -0
- package/package.json +1 -1
- package/postinstall.mjs +38 -7
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ Install creates local provider settings templates here:
|
|
|
27
27
|
- `~/.metheus/slack.env`
|
|
28
28
|
- `~/.metheus/kakaotalk.env`
|
|
29
29
|
- `~/.metheus/bot-runner.json`
|
|
30
|
+
- `~/.metheus/project-workspaces.json`
|
|
30
31
|
|
|
31
32
|
These files are for local provider bot secrets, local transport options, and optional per-bot local AI binding.
|
|
32
33
|
|
|
@@ -181,6 +182,7 @@ metheus-governance-mcp-cli setup --project-id <project_uuid> --ctxpack-key "<ctx
|
|
|
181
182
|
- `~/.metheus/slack.env`
|
|
182
183
|
- `~/.metheus/kakaotalk.env`
|
|
183
184
|
- `~/.metheus/bot-runner.json`
|
|
185
|
+
- `~/.metheus/project-workspaces.json`
|
|
184
186
|
|
|
185
187
|
Bootstrap or setup does not create one file per server bot automatically. Per-bot Telegram files are created later when you run `bot add`, `bot verify`, or `bot edit` against a real server bot identity.
|
|
186
188
|
|
|
@@ -195,10 +197,13 @@ Fill provider bot secrets and provider-local transport options locally. Project
|
|
|
195
197
|
`~/.metheus/bot-runner.json` is the local automation profile for:
|
|
196
198
|
- which project to watch
|
|
197
199
|
- which provider/role bot profile to use
|
|
198
|
-
- which `project_id -> workspace_dir` mapping to apply locally
|
|
199
200
|
- which role profile maps to which local CLI/model/permission/reasoning policy
|
|
200
201
|
- which server bot maps to which local LLM execution profile via `telegram-bots/global.env`, `telegram-bots/*.env`, or fallback `bot_bindings`
|
|
201
202
|
|
|
203
|
+
`~/.metheus/project-workspaces.json` is the local project workspace registry for:
|
|
204
|
+
- which `project_id -> workspace_dir` mapping to apply locally
|
|
205
|
+
- which workspace source updated that mapping last
|
|
206
|
+
|
|
202
207
|
Built-in helper command for legacy fallback/testing:
|
|
203
208
|
|
|
204
209
|
```bash
|
package/cli.mjs
CHANGED
|
@@ -12,7 +12,9 @@ import https from "node:https";
|
|
|
12
12
|
import {
|
|
13
13
|
DEFAULT_LOCAL_AI_CLIENT,
|
|
14
14
|
analyzeHumanConversationIntentWithAI,
|
|
15
|
+
auditDirectHumanReplyWithAI,
|
|
15
16
|
normalizeExecutionArtifacts,
|
|
17
|
+
planRoleExecutionWithAI,
|
|
16
18
|
resolveLocalAIExecutionModel,
|
|
17
19
|
resolveGeminiReasoningConfig,
|
|
18
20
|
suggestLocalAIModelDisplayName,
|
|
@@ -142,6 +144,7 @@ import {
|
|
|
142
144
|
} from "./lib/runner-delivery.mjs";
|
|
143
145
|
import {
|
|
144
146
|
resolveRunnerExecutionPlan,
|
|
147
|
+
resolveRunnerExecutionPlanForRole,
|
|
145
148
|
resolveRunnerRoleProfile,
|
|
146
149
|
resolveRunnerWorkspaceSelection,
|
|
147
150
|
runRunnerAIExecution,
|
|
@@ -2998,16 +3001,20 @@ function buildRunnerDeliveryDeps() {
|
|
|
2998
3001
|
function buildRunnerExecutionDeps() {
|
|
2999
3002
|
return {
|
|
3000
3003
|
analyzeHumanConversationIntentWithAI,
|
|
3004
|
+
auditDirectHumanReplyWithAI,
|
|
3005
|
+
planRoleExecutionWithAI,
|
|
3001
3006
|
normalizeRunnerRoleProfileName,
|
|
3002
3007
|
normalizeRunnerRoleProfile,
|
|
3003
3008
|
normalizeBotRunnerProjectMapping,
|
|
3004
3009
|
sanitizeWorkspaceCandidate,
|
|
3005
3010
|
loadProviderEnvConfig,
|
|
3011
|
+
loadBotRunnerConfig,
|
|
3006
3012
|
canUseLegacyRunnerCommand,
|
|
3007
3013
|
hasLegacyRunnerCommand,
|
|
3008
3014
|
isLegacyRunnerCommandOptInEnabled,
|
|
3009
3015
|
legacyRunnerCommandDisabledMessage,
|
|
3010
3016
|
normalizeExecutionArtifacts,
|
|
3017
|
+
resolveRunnerExecutionPlanForRole,
|
|
3011
3018
|
validateWorkspaceArtifacts,
|
|
3012
3019
|
tryJsonParse,
|
|
3013
3020
|
};
|
|
@@ -718,6 +718,50 @@ function normalizeExecutionContract(rawContract) {
|
|
|
718
718
|
};
|
|
719
719
|
}
|
|
720
720
|
|
|
721
|
+
const RUNNER_EXECUTION_STEP_ROLES = new Set(["monitor", "worker", "review", "approval"]);
|
|
722
|
+
|
|
723
|
+
function normalizeRoleExecutionStepRole(rawValue) {
|
|
724
|
+
const normalized = String(rawValue || "").trim().toLowerCase();
|
|
725
|
+
return RUNNER_EXECUTION_STEP_ROLES.has(normalized) ? normalized : "";
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function normalizeRoleExecutionStep(rawStep) {
|
|
729
|
+
const step = safeObject(rawStep);
|
|
730
|
+
const role = normalizeRoleExecutionStepRole(step.role || step.profile || step.role_profile || step.roleProfile);
|
|
731
|
+
const goal = firstNonEmptyString([
|
|
732
|
+
step.goal,
|
|
733
|
+
step.task,
|
|
734
|
+
step.objective,
|
|
735
|
+
step.description,
|
|
736
|
+
]);
|
|
737
|
+
if (!role || !goal) {
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
return {
|
|
741
|
+
role,
|
|
742
|
+
goal,
|
|
743
|
+
artifactsRequired: step.artifacts_required === true || step.artifactsRequired === true,
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
export function normalizeRoleExecutionPlan(rawPlan) {
|
|
748
|
+
const plan = safeObject(rawPlan);
|
|
749
|
+
if (!Object.keys(plan).length) {
|
|
750
|
+
return null;
|
|
751
|
+
}
|
|
752
|
+
const steps = ensureArray(plan.steps)
|
|
753
|
+
.map((item) => normalizeRoleExecutionStep(item))
|
|
754
|
+
.filter(Boolean);
|
|
755
|
+
const summaryRole = normalizeRoleExecutionStepRole(plan.summary_role || plan.summaryRole);
|
|
756
|
+
const requiresExecution = plan.requires_execution === true || plan.requiresExecution === true;
|
|
757
|
+
return {
|
|
758
|
+
requiresExecution,
|
|
759
|
+
summaryRole: summaryRole || (steps.length > 0 ? steps[steps.length - 1].role : ""),
|
|
760
|
+
steps,
|
|
761
|
+
reason: String(plan.reason || "").trim(),
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
|
|
721
765
|
function normalizeModelAliasText(rawValue) {
|
|
722
766
|
return String(rawValue || "")
|
|
723
767
|
.trim()
|
|
@@ -1070,6 +1114,7 @@ function inferCurrentTurnPurpose({ trigger, conversation, selfBotUsername, other
|
|
|
1070
1114
|
|
|
1071
1115
|
export function buildLocalBotPrompt(payload, { terse = true } = {}) {
|
|
1072
1116
|
const safePayload = payload && typeof payload === "object" ? payload : {};
|
|
1117
|
+
const taskName = String(safePayload.task || "").trim();
|
|
1073
1118
|
const trigger = safePayload.trigger && typeof safePayload.trigger === "object" ? safePayload.trigger : {};
|
|
1074
1119
|
const responseContract = safePayload.response_contract && typeof safePayload.response_contract === "object"
|
|
1075
1120
|
? safePayload.response_contract
|
|
@@ -1156,9 +1201,14 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
|
|
|
1156
1201
|
const assignmentsForThisBot = currentExecutionAssignments.filter((item) => (
|
|
1157
1202
|
String(item.target_bot || item.targetBot || "").trim().replace(/^@+/, "").toLowerCase() === selfSelector.toLowerCase()
|
|
1158
1203
|
));
|
|
1204
|
+
const executionStep = safeObject(safePayload.execution_step);
|
|
1205
|
+
const executionPlan = safeObject(safePayload.execution_plan);
|
|
1206
|
+
const executionProgress = safeObject(safePayload.execution_progress);
|
|
1207
|
+
const isInternalExecutionStep = taskName === "execute_project_role_step";
|
|
1159
1208
|
|
|
1160
1209
|
const lines = [
|
|
1161
1210
|
"You are a Metheus local bot runner.",
|
|
1211
|
+
`Task: ${taskName || "reply_to_project_chat_message"}`,
|
|
1162
1212
|
`Provider: ${provider}`,
|
|
1163
1213
|
`Role: ${role}`,
|
|
1164
1214
|
`Role Profile: ${roleProfile || "-"}`,
|
|
@@ -1189,6 +1239,32 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
|
|
|
1189
1239
|
recentBotMessages.length ? recentBotMessages.map(formatContextCommentLine).join("\n") : "- none",
|
|
1190
1240
|
"",
|
|
1191
1241
|
];
|
|
1242
|
+
if (isInternalExecutionStep) {
|
|
1243
|
+
const stepIndex = intFromRawAllowZero(executionStep.index, 0);
|
|
1244
|
+
const stepTotal = intFromRawAllowZero(executionStep.total, 0);
|
|
1245
|
+
const stepRole = firstNonEmptyString([executionStep.role, roleProfile, role]);
|
|
1246
|
+
const stepGoal = String(executionStep.goal || "").trim();
|
|
1247
|
+
const priorArtifacts = ensureArray(executionProgress.produced_artifacts)
|
|
1248
|
+
.map((item) => firstNonEmptyString([safeObject(item).relativePath, safeObject(item).path]))
|
|
1249
|
+
.filter(Boolean);
|
|
1250
|
+
lines.push(
|
|
1251
|
+
"Internal execution step is active.",
|
|
1252
|
+
"This is not a placeholder chat reply task. Complete the assigned step now.",
|
|
1253
|
+
`Execution Step: ${stepIndex > 0 ? stepIndex : "-"} / ${stepTotal > 0 ? stepTotal : "-"}`,
|
|
1254
|
+
`Execution Step Role: ${stepRole || "-"}`,
|
|
1255
|
+
`Execution Step Goal: ${stepGoal || "-"}`,
|
|
1256
|
+
`Execution Step Requires Artifacts: ${executionStep.artifacts_required === true || executionStep.artifactsRequired === true ? "yes" : "no"}`,
|
|
1257
|
+
`Execution Plan Requires Execution: ${executionPlan.requires_execution === true || executionPlan.requiresExecution === true ? "yes" : "no"}`,
|
|
1258
|
+
`Execution Plan Summary Role: ${String(executionPlan.summary_role || executionPlan.summaryRole || "").trim() || "-"}`,
|
|
1259
|
+
`Prior Produced Artifacts: ${priorArtifacts.length ? priorArtifacts.join(", ") : "-"}`,
|
|
1260
|
+
"Do the work for this step now. Do not say you will start later, wait, come back later, or plan first.",
|
|
1261
|
+
"If this is a worker step, actually create/update the required project artifacts now and include them in artifacts.",
|
|
1262
|
+
"If this is a review step, provide the review findings now.",
|
|
1263
|
+
"If this is an approval step, provide the approval decision now.",
|
|
1264
|
+
"If this is the final step, the reply will be shown to humans. If it is not the final step, the reply is still an internal execution note and must describe what was actually completed in this step.",
|
|
1265
|
+
"",
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1192
1268
|
if (responseContract.must_reply === true) {
|
|
1193
1269
|
lines.push(
|
|
1194
1270
|
"This bot was explicitly addressed by the trigger message.",
|
|
@@ -1209,7 +1285,10 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
|
|
|
1209
1285
|
? `A previous attempt incorrectly skipped the reply. Previous skip reason: ${String(responseContract.previous_skip_reason || "").trim() || "-"}.`
|
|
1210
1286
|
: "Skip is not allowed for this message.",
|
|
1211
1287
|
responseContract.require_actionable_contract === true
|
|
1212
|
-
? "This reply must include an actionable execution contract. Do not promise future work without immediate delegation or an immediate result."
|
|
1288
|
+
? "The human intent requires immediate execution, not a placeholder status update. This reply must include an actionable execution contract. Do not promise future work without immediate delegation or an immediate result."
|
|
1289
|
+
: "",
|
|
1290
|
+
responseContract.require_actionable_contract === true && Array.isArray(responseContract.allowed_contract_types) && responseContract.allowed_contract_types.length > 0
|
|
1291
|
+
? `Allowed execution contract types for this reply: ${responseContract.allowed_contract_types.join(", ")}.`
|
|
1213
1292
|
: "",
|
|
1214
1293
|
"",
|
|
1215
1294
|
);
|
|
@@ -1229,7 +1308,9 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
|
|
|
1229
1308
|
"- If no project files changed, return artifacts: [].",
|
|
1230
1309
|
"- Do not claim that a file, plan, document, or code change is complete unless the corresponding artifact path is present in artifacts.",
|
|
1231
1310
|
"",
|
|
1232
|
-
|
|
1311
|
+
isInternalExecutionStep
|
|
1312
|
+
? "Return JSON only in one line: {\"reply\":\"what was completed in this step\",\"artifacts\":[{\"path\":\"relative/or/absolute/path\",\"kind\":\"plan|code|doc|spec|test\",\"operation\":\"create|update|delete\"}],\"contract\":{\"type\":\"direct_result|summary_request|final_summary\",\"actionable\":true,\"summary_bot\":\"username\",\"next_responders\":[\"username\"]}}. Use artifacts: [] only if this step truly changes no project files."
|
|
1313
|
+
: responseContract.must_reply === true
|
|
1233
1314
|
? "Return JSON only in one line: {\"reply\":\"...\",\"artifacts\":[]} or {\"reply\":\"...\",\"artifacts\":[],\"contract\":{\"type\":\"direct_result|delegation|summary_request|final_summary\",\"actionable\":true,\"assignments\":[{\"target_bot\":\"username\",\"task\":\"...\"}],\"summary_bot\":\"username\",\"next_responders\":[\"username\"]}}."
|
|
1234
1315
|
: terse
|
|
1235
1316
|
? "Return JSON only in one line: {\"reply\":\"...\",\"artifacts\":[]} or {\"skip\":true,\"reason\":\"...\"}."
|
|
@@ -1397,9 +1478,10 @@ function buildConversationIntentAnalysisPrompt({
|
|
|
1397
1478
|
"Infer the human's intended bot participation contract from the human message only.",
|
|
1398
1479
|
"Do not infer from prior bot replies. Do not invent bots outside managed_bots.",
|
|
1399
1480
|
"Be conservative. If the request is ambiguous, choose single_bot and keep only the directly addressed bot as the responder.",
|
|
1481
|
+
"Also decide whether the human is asking for immediate execution/work now or only asking for information/explanation.",
|
|
1400
1482
|
"",
|
|
1401
1483
|
"Return strict JSON only with this shape:",
|
|
1402
|
-
"{\"mode\":\"single_bot|delegated_single_lead|multi_bot_collab|multi_bot_direct\",\"lead_bot\":\"<username or empty>\",\"participants\":[\"...\"],\"initial_responders\":[\"...\"],\"allowed_responders\":[\"...\"],\"summary_bot\":\"<username or empty>\",\"allow_bot_to_bot\":true|false}",
|
|
1484
|
+
"{\"mode\":\"single_bot|delegated_single_lead|multi_bot_collab|multi_bot_direct\",\"lead_bot\":\"<username or empty>\",\"participants\":[\"...\"],\"initial_responders\":[\"...\"],\"allowed_responders\":[\"...\"],\"summary_bot\":\"<username or empty>\",\"allow_bot_to_bot\":true|false,\"reply_expectation\":\"informational|actionable\"}",
|
|
1403
1485
|
"",
|
|
1404
1486
|
"Mode definitions:",
|
|
1405
1487
|
"- single_bot: only one directly addressed bot should respond. No bot-to-bot relay.",
|
|
@@ -1414,12 +1496,96 @@ function buildConversationIntentAnalysisPrompt({
|
|
|
1414
1496
|
"- If mode is single_bot, initial_responders and allowed_responders should contain only that bot.",
|
|
1415
1497
|
"- If mode is delegated_single_lead, lead_bot must be set and initial_responders should contain only the lead bot.",
|
|
1416
1498
|
"- If the human explicitly asks one bot to summarize or finalize, set summary_bot to that bot.",
|
|
1499
|
+
"- reply_expectation=actionable when the human is asking the bot(s) to actually do work now, produce concrete results, create/update files, delegate concrete tasks, or otherwise execute immediately.",
|
|
1500
|
+
"- reply_expectation=informational when the human is only asking for explanation, status, location, clarification, or other non-execution information.",
|
|
1417
1501
|
"",
|
|
1418
1502
|
`managed_bots=${JSON.stringify(bots)}`,
|
|
1419
1503
|
`human_message=${JSON.stringify(String(messageText || "").trim())}`,
|
|
1420
1504
|
].join("\n");
|
|
1421
1505
|
}
|
|
1422
1506
|
|
|
1507
|
+
function buildRoleExecutionPlanPrompt({
|
|
1508
|
+
messageText,
|
|
1509
|
+
currentBotUsername,
|
|
1510
|
+
currentBotRole,
|
|
1511
|
+
managedBots,
|
|
1512
|
+
workspaceDir,
|
|
1513
|
+
currentAssignmentTasks = [],
|
|
1514
|
+
contextComments = [],
|
|
1515
|
+
conversationMode = "",
|
|
1516
|
+
}) {
|
|
1517
|
+
const bots = ensureArray(managedBots).map((item) => {
|
|
1518
|
+
const bot = safeObject(item);
|
|
1519
|
+
return {
|
|
1520
|
+
username: String(bot.username || "").trim().replace(/^@+/, ""),
|
|
1521
|
+
display_name: String(bot.display_name || bot.displayName || bot.username || "").trim(),
|
|
1522
|
+
};
|
|
1523
|
+
}).filter((item) => item.username);
|
|
1524
|
+
const normalizedContext = ensureArray(contextComments)
|
|
1525
|
+
.slice(0, 10)
|
|
1526
|
+
.map((item) => formatContextCommentLine(item));
|
|
1527
|
+
return [
|
|
1528
|
+
"You are an execution planner for managed local Telegram bots.",
|
|
1529
|
+
"Read the human request and recent room context, then decide whether actual project execution is required now.",
|
|
1530
|
+
"Judge by meaning, not by keywords or @mentions alone.",
|
|
1531
|
+
"If the request only asks for status, explanation, location, or clarification, then requires_execution should be false.",
|
|
1532
|
+
"If the request asks the bot to create/update/delete project files, write plans/docs/code, delegate concrete tasks, continue execution, or otherwise do work now, then requires_execution should be true.",
|
|
1533
|
+
"When requires_execution=true, return the minimal role step plan using only these roles: monitor, worker, review, approval.",
|
|
1534
|
+
"Role guidance:",
|
|
1535
|
+
"- monitor: understand context, scope the work, plan, coordinate, or summarize.",
|
|
1536
|
+
"- worker: actually create/update/delete project artifacts now.",
|
|
1537
|
+
"- review: inspect completed work and report findings, issues, or improvements.",
|
|
1538
|
+
"- approval: make a go/no-go, approval, or explicit decision when the request calls for it.",
|
|
1539
|
+
"worker is required if project artifacts must be created or updated now.",
|
|
1540
|
+
"review is required if the request implies checking or validating the created result before finalizing.",
|
|
1541
|
+
"approval is required only when the request asks for approval, release, confirmation, or a formal decision.",
|
|
1542
|
+
"",
|
|
1543
|
+
"Return strict JSON only with this shape:",
|
|
1544
|
+
"{\"requires_execution\":true|false,\"summary_role\":\"monitor|worker|review|approval\",\"reason\":\"short explanation\",\"steps\":[{\"role\":\"monitor|worker|review|approval\",\"goal\":\"short goal\",\"artifacts_required\":true|false}]}",
|
|
1545
|
+
"",
|
|
1546
|
+
`current_bot_username=${JSON.stringify(String(currentBotUsername || "").trim().replace(/^@+/, ""))}`,
|
|
1547
|
+
`current_bot_route_role=${JSON.stringify(String(currentBotRole || "").trim().toLowerCase())}`,
|
|
1548
|
+
`conversation_mode=${JSON.stringify(String(conversationMode || "").trim())}`,
|
|
1549
|
+
`workspace_dir=${JSON.stringify(String(workspaceDir || "").trim())}`,
|
|
1550
|
+
`managed_bots=${JSON.stringify(bots)}`,
|
|
1551
|
+
`current_assignment_tasks=${JSON.stringify(ensureArray(currentAssignmentTasks).map((item) => String(item || "").trim()).filter(Boolean))}`,
|
|
1552
|
+
`human_message=${JSON.stringify(String(messageText || "").trim())}`,
|
|
1553
|
+
"recent_context=",
|
|
1554
|
+
normalizedContext.length ? normalizedContext.join("\n") : "- none",
|
|
1555
|
+
].join("\n");
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
function buildDirectReplyGuardrailPrompt({
|
|
1559
|
+
humanMessageText,
|
|
1560
|
+
botReplyText,
|
|
1561
|
+
currentBotUsername,
|
|
1562
|
+
managedBots,
|
|
1563
|
+
}) {
|
|
1564
|
+
const bots = ensureArray(managedBots).map((item) => {
|
|
1565
|
+
const bot = safeObject(item);
|
|
1566
|
+
return {
|
|
1567
|
+
username: String(bot.username || "").trim().replace(/^@+/, ""),
|
|
1568
|
+
display_name: String(bot.display_name || bot.displayName || bot.username || "").trim(),
|
|
1569
|
+
};
|
|
1570
|
+
}).filter((item) => item.username);
|
|
1571
|
+
return [
|
|
1572
|
+
"You are an execution guardrail auditor for managed Telegram bots.",
|
|
1573
|
+
"Decide whether the human message requires immediate execution/action now, or whether a plain informational reply is sufficient.",
|
|
1574
|
+
"Then decide whether the candidate bot reply actually satisfies that requirement.",
|
|
1575
|
+
"Judge by meaning, not by keywords.",
|
|
1576
|
+
"A placeholder or status-only reply such as 'I will start', 'please wait', 'I will analyze and come back', or 'I will plan first' does NOT satisfy an actionable request.",
|
|
1577
|
+
"If the human is asking the bot to do work now, create/update files now, delegate concrete tasks now, or otherwise execute immediately, then requires_actionable_contract should be true.",
|
|
1578
|
+
"",
|
|
1579
|
+
"Return strict JSON only with this shape:",
|
|
1580
|
+
"{\"requires_actionable_contract\":true|false,\"reply_satisfies_request\":true|false,\"reason\":\"short explanation\"}",
|
|
1581
|
+
"",
|
|
1582
|
+
`current_bot_username=${JSON.stringify(String(currentBotUsername || "").trim().replace(/^@+/, ""))}`,
|
|
1583
|
+
`managed_bots=${JSON.stringify(bots)}`,
|
|
1584
|
+
`human_message=${JSON.stringify(String(humanMessageText || "").trim())}`,
|
|
1585
|
+
`candidate_reply=${JSON.stringify(String(botReplyText || "").trim())}`,
|
|
1586
|
+
].join("\n");
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1423
1589
|
function normalizeIntentContractSelectorList(values, managedSelectorSet) {
|
|
1424
1590
|
return Array.from(new Set(ensureArray(values)
|
|
1425
1591
|
.map((value) => String(value || "").trim().replace(/^@+/, "").toLowerCase())
|
|
@@ -1472,9 +1638,107 @@ export function analyzeHumanConversationIntentWithAI({
|
|
|
1472
1638
|
allowed_responders: normalizeIntentContractSelectorList(parsed.allowed_responders || parsed.allowedResponders, managedSelectorSet),
|
|
1473
1639
|
summary_bot: String(parsed.summary_bot || parsed.summaryBot || "").trim().replace(/^@+/, "").toLowerCase(),
|
|
1474
1640
|
allow_bot_to_bot: parsed.allow_bot_to_bot === true || parsed.allowBotToBot === true,
|
|
1641
|
+
reply_expectation: String(parsed.reply_expectation || parsed.replyExpectation || "").trim().toLowerCase(),
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
export function auditDirectHumanReplyWithAI({
|
|
1646
|
+
humanMessageText,
|
|
1647
|
+
botReplyText,
|
|
1648
|
+
currentBotUsername,
|
|
1649
|
+
managedBots,
|
|
1650
|
+
workspaceDir,
|
|
1651
|
+
client = "",
|
|
1652
|
+
model = "",
|
|
1653
|
+
env = process.env,
|
|
1654
|
+
}) {
|
|
1655
|
+
const bots = ensureArray(managedBots);
|
|
1656
|
+
if (!bots.length) {
|
|
1657
|
+
return null;
|
|
1658
|
+
}
|
|
1659
|
+
const parserClient = normalizeLocalAIClientName(
|
|
1660
|
+
String(client || env?.METHEUS_INTENT_PARSER_CLIENT || "").trim(),
|
|
1661
|
+
"gpt",
|
|
1662
|
+
);
|
|
1663
|
+
const parserModel = String(model || env?.METHEUS_INTENT_PARSER_MODEL || suggestLocalAIModelDisplayName(parserClient, "") || "").trim();
|
|
1664
|
+
const rawText = runLocalAIPromptRawText({
|
|
1665
|
+
client: parserClient,
|
|
1666
|
+
promptText: buildDirectReplyGuardrailPrompt({
|
|
1667
|
+
humanMessageText,
|
|
1668
|
+
botReplyText,
|
|
1669
|
+
currentBotUsername,
|
|
1670
|
+
managedBots: bots,
|
|
1671
|
+
}),
|
|
1672
|
+
workspaceDir,
|
|
1673
|
+
model: parserModel,
|
|
1674
|
+
permissionMode: "read_only",
|
|
1675
|
+
reasoningEffort: String(env?.METHEUS_INTENT_PARSER_REASONING_EFFORT || "low").trim() || "low",
|
|
1676
|
+
env,
|
|
1677
|
+
});
|
|
1678
|
+
const parsed = tryJsonParse(rawText) || tryParseEmbeddedJsonObject(rawText);
|
|
1679
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1680
|
+
throw new Error("direct reply guardrail auditor did not return a JSON object");
|
|
1681
|
+
}
|
|
1682
|
+
return {
|
|
1683
|
+
requires_actionable_contract: parsed.requires_actionable_contract === true || parsed.requiresActionableContract === true,
|
|
1684
|
+
reply_satisfies_request: parsed.reply_satisfies_request === true || parsed.replySatisfiesRequest === true,
|
|
1685
|
+
reason: String(parsed.reason || "").trim(),
|
|
1475
1686
|
};
|
|
1476
1687
|
}
|
|
1477
1688
|
|
|
1689
|
+
export function planRoleExecutionWithAI({
|
|
1690
|
+
messageText,
|
|
1691
|
+
currentBotUsername,
|
|
1692
|
+
currentBotRole = "",
|
|
1693
|
+
managedBots,
|
|
1694
|
+
workspaceDir,
|
|
1695
|
+
currentAssignmentTasks = [],
|
|
1696
|
+
contextComments = [],
|
|
1697
|
+
conversationMode = "",
|
|
1698
|
+
client = "",
|
|
1699
|
+
model = "",
|
|
1700
|
+
env = process.env,
|
|
1701
|
+
}) {
|
|
1702
|
+
const bots = ensureArray(managedBots);
|
|
1703
|
+
if (!bots.length) {
|
|
1704
|
+
return null;
|
|
1705
|
+
}
|
|
1706
|
+
const plannerClient = normalizeLocalAIClientName(
|
|
1707
|
+
String(client || env?.METHEUS_ROLE_PLANNER_CLIENT || env?.METHEUS_INTENT_PARSER_CLIENT || "").trim(),
|
|
1708
|
+
"gpt",
|
|
1709
|
+
);
|
|
1710
|
+
const plannerModel = String(
|
|
1711
|
+
model
|
|
1712
|
+
|| env?.METHEUS_ROLE_PLANNER_MODEL
|
|
1713
|
+
|| env?.METHEUS_INTENT_PARSER_MODEL
|
|
1714
|
+
|| suggestLocalAIModelDisplayName(plannerClient, "")
|
|
1715
|
+
|| "",
|
|
1716
|
+
).trim();
|
|
1717
|
+
const rawText = runLocalAIPromptRawText({
|
|
1718
|
+
client: plannerClient,
|
|
1719
|
+
promptText: buildRoleExecutionPlanPrompt({
|
|
1720
|
+
messageText,
|
|
1721
|
+
currentBotUsername,
|
|
1722
|
+
currentBotRole,
|
|
1723
|
+
managedBots: bots,
|
|
1724
|
+
workspaceDir,
|
|
1725
|
+
currentAssignmentTasks,
|
|
1726
|
+
contextComments,
|
|
1727
|
+
conversationMode,
|
|
1728
|
+
}),
|
|
1729
|
+
workspaceDir,
|
|
1730
|
+
model: plannerModel,
|
|
1731
|
+
permissionMode: "read_only",
|
|
1732
|
+
reasoningEffort: String(env?.METHEUS_ROLE_PLANNER_REASONING_EFFORT || "medium").trim() || "medium",
|
|
1733
|
+
env,
|
|
1734
|
+
});
|
|
1735
|
+
const parsed = tryJsonParse(rawText) || tryParseEmbeddedJsonObject(rawText);
|
|
1736
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1737
|
+
throw new Error("role execution planner did not return a JSON object");
|
|
1738
|
+
}
|
|
1739
|
+
return normalizeRoleExecutionPlan(parsed);
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1478
1742
|
export function runLocalAIClient({
|
|
1479
1743
|
client,
|
|
1480
1744
|
inputPayload,
|
package/lib/runner-execution.mjs
CHANGED
|
@@ -241,6 +241,23 @@ export function resolveRunnerExecutionPlan(route, bot, config, deps) {
|
|
|
241
241
|
throw new Error(detail.join("; "));
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
export function resolveRunnerExecutionPlanForRole(route, bot, roleName, config, deps) {
|
|
245
|
+
const normalizedRoleName = String(roleName || "").trim().toLowerCase();
|
|
246
|
+
if (!normalizedRoleName) {
|
|
247
|
+
throw new Error("roleName is required to resolve a role execution plan");
|
|
248
|
+
}
|
|
249
|
+
return resolveRunnerExecutionPlan(
|
|
250
|
+
{
|
|
251
|
+
...safeObject(route),
|
|
252
|
+
role: normalizedRoleName,
|
|
253
|
+
roleProfile: normalizedRoleName,
|
|
254
|
+
},
|
|
255
|
+
bot,
|
|
256
|
+
config,
|
|
257
|
+
deps,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
244
261
|
function buildRunnerExecutionEnv({ route, destination, executionPlan }) {
|
|
245
262
|
return {
|
|
246
263
|
...process.env,
|