metheus-governance-mcp-cli 0.2.225 → 0.2.227
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/cli.mjs +104 -14
- package/lib/local-ai-adapters.mjs +19 -6
- package/lib/runner-orchestration.mjs +268 -90
- package/lib/selftest-runner-scenarios.mjs +161 -10
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -2073,13 +2073,38 @@ function writeTextFileAtomic(filePath, text) {
|
|
|
2073
2073
|
`.${path.basename(normalizedPath)}.${process.pid}.${Date.now()}.tmp`,
|
|
2074
2074
|
);
|
|
2075
2075
|
fs.writeFileSync(tempPath, text, "utf8");
|
|
2076
|
+
const renameRetryDelaysMs = [0, 20, 50, 100, 200];
|
|
2077
|
+
const sleepBuffer = new SharedArrayBuffer(4);
|
|
2078
|
+
const sleepArray = new Int32Array(sleepBuffer);
|
|
2079
|
+
let lastRenameError = null;
|
|
2076
2080
|
try {
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2081
|
+
for (const delayMs of renameRetryDelaysMs) {
|
|
2082
|
+
if (delayMs > 0) {
|
|
2083
|
+
Atomics.wait(sleepArray, 0, 0, delayMs);
|
|
2084
|
+
}
|
|
2085
|
+
try {
|
|
2086
|
+
fs.renameSync(tempPath, normalizedPath);
|
|
2087
|
+
lastRenameError = null;
|
|
2088
|
+
break;
|
|
2089
|
+
} catch (error) {
|
|
2090
|
+
lastRenameError = error;
|
|
2091
|
+
try {
|
|
2092
|
+
fs.rmSync(normalizedPath, { force: true });
|
|
2093
|
+
fs.renameSync(tempPath, normalizedPath);
|
|
2094
|
+
lastRenameError = null;
|
|
2095
|
+
break;
|
|
2096
|
+
} catch (retryError) {
|
|
2097
|
+
lastRenameError = retryError;
|
|
2098
|
+
const errorCode = String(retryError?.code || error?.code || "").trim().toUpperCase();
|
|
2099
|
+
if (!["EPERM", "EBUSY", "ENOTEMPTY"].includes(errorCode)) {
|
|
2100
|
+
throw retryError;
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
if (lastRenameError) {
|
|
2106
|
+
throw lastRenameError;
|
|
2107
|
+
}
|
|
2083
2108
|
} finally {
|
|
2084
2109
|
try {
|
|
2085
2110
|
if (fs.existsSync(tempPath)) {
|
|
@@ -2397,6 +2422,11 @@ function normalizeBotRunnerRequests(rawRequests, nowMs = Date.now()) {
|
|
|
2397
2422
|
response_contract_validation_targets: ensureArray(entry.response_contract_validation_targets || entry.responseContractValidationTargets)
|
|
2398
2423
|
.map((value) => normalizeTelegramMentionUsername(value))
|
|
2399
2424
|
.filter(Boolean),
|
|
2425
|
+
assignment_validation_status: String(entry.assignment_validation_status || entry.assignmentValidationStatus || "").trim().toLowerCase(),
|
|
2426
|
+
assignment_validation_reason: String(entry.assignment_validation_reason || entry.assignmentValidationReason || "").trim(),
|
|
2427
|
+
assignment_validation_modes: ensureArray(entry.assignment_validation_modes || entry.assignmentValidationModes)
|
|
2428
|
+
.map((value) => String(value || "").trim().toLowerCase())
|
|
2429
|
+
.filter(Boolean),
|
|
2400
2430
|
delivery_status: String(entry.delivery_status || entry.deliveryStatus || "").trim().toLowerCase(),
|
|
2401
2431
|
archive_status: String(entry.archive_status || entry.archiveStatus || "").trim().toLowerCase(),
|
|
2402
2432
|
transport_error: String(entry.transport_error || entry.transportError || "").trim(),
|
|
@@ -2427,15 +2457,17 @@ function normalizeBotRunnerRequests(rawRequests, nowMs = Date.now()) {
|
|
|
2427
2457
|
function normalizeBotRunnerConsumedComments(rawConsumed, nowMs = Date.now()) {
|
|
2428
2458
|
const normalized = {};
|
|
2429
2459
|
for (const [commentIDRaw, entryRaw] of Object.entries(safeObject(rawConsumed))) {
|
|
2430
|
-
const commentID = String(commentIDRaw || safeObject(entryRaw).comment_id || "").trim();
|
|
2431
|
-
if (!commentID) continue;
|
|
2432
2460
|
const entry = safeObject(entryRaw);
|
|
2461
|
+
const commentID = String(entry.comment_id || commentIDRaw || "").trim();
|
|
2462
|
+
if (!commentID) continue;
|
|
2463
|
+
const ledgerKey = String(commentIDRaw || commentID).trim();
|
|
2433
2464
|
const consumedAt = firstNonEmptyString([entry.consumed_at, entry.consumedAt, entry.updated_at, entry.updatedAt]);
|
|
2434
2465
|
const consumedAtMs = Date.parse(consumedAt);
|
|
2435
2466
|
if (Number.isFinite(consumedAtMs) && nowMs - consumedAtMs > BOT_RUNNER_REQUEST_KEEP_MS) {
|
|
2436
2467
|
continue;
|
|
2437
2468
|
}
|
|
2438
|
-
normalized[
|
|
2469
|
+
normalized[ledgerKey] = {
|
|
2470
|
+
ledger_key: ledgerKey,
|
|
2439
2471
|
comment_id: commentID,
|
|
2440
2472
|
project_id: String(entry.project_id || entry.projectID || "").trim(),
|
|
2441
2473
|
provider: String(entry.provider || "").trim(),
|
|
@@ -2451,6 +2483,38 @@ function normalizeBotRunnerConsumedComments(rawConsumed, nowMs = Date.now()) {
|
|
|
2451
2483
|
return normalized;
|
|
2452
2484
|
}
|
|
2453
2485
|
|
|
2486
|
+
function buildRunnerConsumedCommentLedgerKey(commentIDRaw, routeKeyRaw = "", commentKindRaw = "") {
|
|
2487
|
+
const commentID = String(commentIDRaw || "").trim();
|
|
2488
|
+
if (!commentID) return "";
|
|
2489
|
+
const routeKey = String(routeKeyRaw || "").trim();
|
|
2490
|
+
const commentKind = String(commentKindRaw || "").trim().toLowerCase();
|
|
2491
|
+
if (commentKind === "bot_reply" && routeKey) {
|
|
2492
|
+
return `${commentID}::${routeKey}`;
|
|
2493
|
+
}
|
|
2494
|
+
return commentID;
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
function findRunnerConsumedCommentEntry(rawConsumed, commentIDRaw, { routeKey = "", commentKind = "" } = {}) {
|
|
2498
|
+
const commentID = String(commentIDRaw || "").trim();
|
|
2499
|
+
if (!commentID) return {};
|
|
2500
|
+
const consumedComments = normalizeBotRunnerConsumedComments(rawConsumed);
|
|
2501
|
+
const ledgerKey = buildRunnerConsumedCommentLedgerKey(commentID, routeKey, commentKind);
|
|
2502
|
+
const directEntry = safeObject(consumedComments[ledgerKey]);
|
|
2503
|
+
if (Object.keys(directEntry).length) {
|
|
2504
|
+
return directEntry;
|
|
2505
|
+
}
|
|
2506
|
+
const normalizedKind = String(commentKind || "").trim().toLowerCase();
|
|
2507
|
+
if (normalizedKind === "bot_reply" && String(routeKey || "").trim()) {
|
|
2508
|
+
const routeEntry = Object.values(consumedComments).find((entryRaw) => {
|
|
2509
|
+
const entry = safeObject(entryRaw);
|
|
2510
|
+
return String(entry.comment_id || "").trim() === commentID
|
|
2511
|
+
&& String(entry.route_key || "").trim() === String(routeKey || "").trim();
|
|
2512
|
+
});
|
|
2513
|
+
return safeObject(routeEntry);
|
|
2514
|
+
}
|
|
2515
|
+
return safeObject(consumedComments[commentID]);
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2454
2518
|
function runnerRouteMatchesProjectConversationScope(candidateRouteRaw, normalizedRouteRaw) {
|
|
2455
2519
|
const candidateRoute = normalizeRunnerRoute(candidateRouteRaw);
|
|
2456
2520
|
const normalizedRoute = normalizeRunnerRoute(normalizedRouteRaw);
|
|
@@ -3320,7 +3384,8 @@ function upsertRunnerConsumedComment(state, commentIDRaw, patch = {}) {
|
|
|
3320
3384
|
const commentID = String(commentIDRaw || "").trim();
|
|
3321
3385
|
const currentState = safeObject(state);
|
|
3322
3386
|
const consumedComments = normalizeBotRunnerConsumedComments(currentState.consumedComments || currentState.consumed_comments);
|
|
3323
|
-
const
|
|
3387
|
+
const ledgerKey = buildRunnerConsumedCommentLedgerKey(commentID, patch.route_key, patch.comment_kind);
|
|
3388
|
+
const existing = safeObject(consumedComments[ledgerKey]);
|
|
3324
3389
|
const nextEntry = {
|
|
3325
3390
|
...existing,
|
|
3326
3391
|
...safeObject(patch),
|
|
@@ -3330,7 +3395,7 @@ function upsertRunnerConsumedComment(state, commentIDRaw, patch = {}) {
|
|
|
3330
3395
|
consumed_at: new Date().toISOString(),
|
|
3331
3396
|
request_status: normalizeRunnerRequestStatus(patch.request_status || existing.request_status),
|
|
3332
3397
|
};
|
|
3333
|
-
consumedComments[
|
|
3398
|
+
consumedComments[ledgerKey] = nextEntry;
|
|
3334
3399
|
return {
|
|
3335
3400
|
consumedComments,
|
|
3336
3401
|
consumedComment: nextEntry,
|
|
@@ -4516,6 +4581,9 @@ function markRunnerRequestLifecycle({
|
|
|
4516
4581
|
responseContractValidationStatus = "",
|
|
4517
4582
|
responseContractValidationReason = "",
|
|
4518
4583
|
responseContractValidationTargets = [],
|
|
4584
|
+
assignmentValidationStatus = "",
|
|
4585
|
+
assignmentValidationReason = "",
|
|
4586
|
+
assignmentValidationModes = [],
|
|
4519
4587
|
deliveryStatus = "",
|
|
4520
4588
|
archiveStatus = "",
|
|
4521
4589
|
transportError = "",
|
|
@@ -4643,6 +4711,18 @@ function markRunnerRequestLifecycle({
|
|
|
4643
4711
|
: existing.response_contract_validation_targets,
|
|
4644
4712
|
normalizeTelegramMentionUsername,
|
|
4645
4713
|
),
|
|
4714
|
+
assignment_validation_status: String(
|
|
4715
|
+
assignmentValidationStatus || existing.assignment_validation_status || "",
|
|
4716
|
+
).trim().toLowerCase(),
|
|
4717
|
+
assignment_validation_reason: String(
|
|
4718
|
+
assignmentValidationReason || existing.assignment_validation_reason || "",
|
|
4719
|
+
).trim(),
|
|
4720
|
+
assignment_validation_modes: uniqueOrderedStrings(
|
|
4721
|
+
ensureArray(assignmentValidationModes).length
|
|
4722
|
+
? assignmentValidationModes
|
|
4723
|
+
: existing.assignment_validation_modes,
|
|
4724
|
+
(value) => String(value || "").trim().toLowerCase(),
|
|
4725
|
+
),
|
|
4646
4726
|
delivery_status: String(deliveryStatus || existing.delivery_status || "").trim().toLowerCase(),
|
|
4647
4727
|
archive_status: String(archiveStatus || existing.archive_status || "").trim().toLowerCase(),
|
|
4648
4728
|
transport_error: String(transportError || existing.transport_error || "").trim(),
|
|
@@ -8074,14 +8154,18 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
8074
8154
|
const commentsForPending = ensureArray(comments).filter((comment) => {
|
|
8075
8155
|
const normalizedComment = normalizeArchiveCommentRecord(comment, parseArchivedChatComment);
|
|
8076
8156
|
const commentID = String(normalizedComment.id || "").trim();
|
|
8157
|
+
const parsed = safeObject(normalizedComment.parsedArchive);
|
|
8158
|
+
const commentKind = String(parsed.kind || "").trim().toLowerCase();
|
|
8077
8159
|
if (commentID && safeObject(excludedComments)[commentID]) {
|
|
8078
8160
|
return false;
|
|
8079
8161
|
}
|
|
8080
|
-
if (commentID &&
|
|
8162
|
+
if (commentID && Object.keys(findRunnerConsumedCommentEntry(consumedComments, commentID, {
|
|
8163
|
+
routeKey,
|
|
8164
|
+
commentKind,
|
|
8165
|
+
})).length > 0) {
|
|
8081
8166
|
return false;
|
|
8082
8167
|
}
|
|
8083
|
-
|
|
8084
|
-
if (String(parsed.kind || "").trim().toLowerCase() === "bot_reply") {
|
|
8168
|
+
if (commentKind === "bot_reply") {
|
|
8085
8169
|
const conversationID = String(parsed.conversationID || "").trim();
|
|
8086
8170
|
if (!conversationID) {
|
|
8087
8171
|
return false;
|
|
@@ -8873,6 +8957,9 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
8873
8957
|
responseContractValidationStatus: String(processed.result?.response_contract_validation_status || "").trim(),
|
|
8874
8958
|
responseContractValidationReason: String(processed.result?.response_contract_validation_reason || "").trim(),
|
|
8875
8959
|
responseContractValidationTargets: ensureArray(processed.result?.response_contract_validation_targets),
|
|
8960
|
+
assignmentValidationStatus: String(processed.result?.assignment_validation_status || "").trim(),
|
|
8961
|
+
assignmentValidationReason: String(processed.result?.assignment_validation_reason || "").trim(),
|
|
8962
|
+
assignmentValidationModes: ensureArray(processed.result?.assignment_validation_modes),
|
|
8876
8963
|
deliveryStatus: String(processed.result?.delivery_status || "").trim(),
|
|
8877
8964
|
archiveStatus: String(processed.result?.archive_status || "").trim(),
|
|
8878
8965
|
transportError: String(processed.result?.transport_error || "").trim(),
|
|
@@ -11293,6 +11380,9 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
11293
11380
|
responseContractValidationStatus: String(processed.result?.response_contract_validation_status || "").trim(),
|
|
11294
11381
|
responseContractValidationReason: String(processed.result?.response_contract_validation_reason || "").trim(),
|
|
11295
11382
|
responseContractValidationTargets: ensureArray(processed.result?.response_contract_validation_targets),
|
|
11383
|
+
assignmentValidationStatus: String(processed.result?.assignment_validation_status || "").trim(),
|
|
11384
|
+
assignmentValidationReason: String(processed.result?.assignment_validation_reason || "").trim(),
|
|
11385
|
+
assignmentValidationModes: ensureArray(processed.result?.assignment_validation_modes),
|
|
11296
11386
|
deliveryStatus: String(processed.result?.delivery_status || "").trim(),
|
|
11297
11387
|
archiveStatus: String(processed.result?.archive_status || "").trim(),
|
|
11298
11388
|
transportError: String(processed.result?.transport_error || "").trim(),
|
|
@@ -1918,7 +1918,10 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
|
|
|
1918
1918
|
"Do not mention another managed bot unless the contract explicitly names that bot in assignments or next_responders.",
|
|
1919
1919
|
"Without a matching contract, newly mentioned bots will not act.",
|
|
1920
1920
|
"When delegating to another managed bot, use contract.type=\"delegation\" with actionable=true, assignments, and next_responders.",
|
|
1921
|
-
"
|
|
1921
|
+
"Each assignment must declare whether it is a conversational contribution or a real execution task.",
|
|
1922
|
+
"Use assignment.mode=\"conversation_contribution\" for opinions, discussion, review, comparison, synthesis, greetings, or other room-visible contributions that do not require workspace artifacts.",
|
|
1923
|
+
"Use assignment.mode=\"execution_task\" only when the delegated bot must change workspace files, create artifacts, update ctxpack, or produce other concrete project outputs. If it is an execution task, also set artifacts_required=true.",
|
|
1924
|
+
"Delegation contract example: {\"type\":\"delegation\",\"actionable\":true,\"assignments\":[{\"target_bot\":\"ryoai2_bot\",\"task\":\"briefly greet in one line\",\"mode\":\"conversation_contribution\",\"artifacts_required\":false}],\"next_responders\":[\"ryoai2_bot\"]}.",
|
|
1922
1925
|
ensureArray(responseContract.required_delegation_targets).length > 0
|
|
1923
1926
|
? `This reply must delegate to these exact managed bots now: ${ensureArray(responseContract.required_delegation_targets).map((item) => `@${String(item || "").trim().replace(/^@+/, "")}`).join(", ")}.`
|
|
1924
1927
|
: "",
|
|
@@ -1970,7 +1973,10 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
|
|
|
1970
1973
|
"Do not mention another managed bot unless the contract explicitly names that bot in assignments or next_responders.",
|
|
1971
1974
|
"Without a matching contract, mentioned bots will not act.",
|
|
1972
1975
|
"When delegating to another managed bot, use contract.type=\"delegation\" with actionable=true, assignments, and next_responders.",
|
|
1973
|
-
"
|
|
1976
|
+
"Each assignment must declare whether it is a conversational contribution or a real execution task.",
|
|
1977
|
+
"Use assignment.mode=\"conversation_contribution\" for opinions, discussion, review, comparison, synthesis, greetings, or other room-visible contributions that do not require workspace artifacts.",
|
|
1978
|
+
"Use assignment.mode=\"execution_task\" only when the delegated bot must change workspace files, create artifacts, update ctxpack, or produce other concrete project outputs. If it is an execution task, also set artifacts_required=true.",
|
|
1979
|
+
"Delegation contract example: {\"type\":\"delegation\",\"actionable\":true,\"assignments\":[{\"target_bot\":\"ryoai2_bot\",\"task\":\"briefly greet in one line\",\"mode\":\"conversation_contribution\",\"artifacts_required\":false}],\"next_responders\":[\"ryoai2_bot\"]}.",
|
|
1974
1980
|
ensureArray(responseContract.required_delegation_targets).length > 0
|
|
1975
1981
|
? `This reply must delegate to these exact managed bots now: ${ensureArray(responseContract.required_delegation_targets).map((item) => `@${String(item || "").trim().replace(/^@+/, "")}`).join(", ")}.`
|
|
1976
1982
|
: "",
|
|
@@ -2006,7 +2012,7 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
|
|
|
2006
2012
|
isInternalExecutionStep
|
|
2007
2013
|
? "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\"}],\"ctxpack_files\":[{\"path\":\"relative/path.md\",\"content\":\"full document text\",\"doc_type\":\"guide|readme|agenda|rule|architecture|manifest\",\"operation\":\"create|update|delete\"}],\"work_items\":[{\"title\":\"short atomic task\",\"description\":\"useful implementation detail\"}],\"contract\":{\"type\":\"direct_result|summary_request|final_summary\",\"actionable\":true,\"summary_bot\":\"username\",\"next_responders\":[\"username\"]}}. Use ctxpack_files when ctxpack-backed guidance/instruction files must be authored. If execution_step.ctxpack_update_required is true, ctxpack_files must not be empty. Use artifacts: [] only if this step truly changes no project files, and use work_items: [] only if this step truly creates no governance tasks."
|
|
2008
2014
|
: responseContract.is_current_bot_candidate === true
|
|
2009
|
-
? "Return JSON only in one line: {\"reply\":\"...\",\"artifacts\":[],\"ctxpack_files\":[],\"work_items\":[]} or {\"clarify\":\"...\"} or {\"skip\":true,\"reason\":\"...\"} or {\"reply\":\"...\",\"artifacts\":[],\"ctxpack_files\":[],\"work_items\":[],\"contract\":{\"type\":\"direct_result|delegation|summary_request|final_summary\",\"actionable\":true,\"assignments\":[{\"target_bot\":\"username\",\"task\":\"...\"}],\"summary_bot\":\"username\",\"next_responders\":[\"username\"]},\"context_suggestion\":{\"should_store\":true,\"title\":\"...\",\"body\":\"...\",\"category\":\"bot_role|operating_rule|project_fact|current_focus|risk|general\",\"importance\":\"low|normal|high|critical\"}}."
|
|
2015
|
+
? "Return JSON only in one line: {\"reply\":\"...\",\"artifacts\":[],\"ctxpack_files\":[],\"work_items\":[]} or {\"clarify\":\"...\"} or {\"skip\":true,\"reason\":\"...\"} or {\"reply\":\"...\",\"artifacts\":[],\"ctxpack_files\":[],\"work_items\":[],\"contract\":{\"type\":\"direct_result|delegation|summary_request|final_summary\",\"actionable\":true,\"assignments\":[{\"target_bot\":\"username\",\"task\":\"...\",\"mode\":\"conversation_contribution|execution_task\",\"artifacts_required\":true|false}],\"summary_bot\":\"username\",\"next_responders\":[\"username\"]},\"context_suggestion\":{\"should_store\":true,\"title\":\"...\",\"body\":\"...\",\"category\":\"bot_role|operating_rule|project_fact|current_focus|risk|general\",\"importance\":\"low|normal|high|critical\"}}."
|
|
2010
2016
|
: terse
|
|
2011
2017
|
? "Return JSON only in one line: {\"reply\":\"...\",\"artifacts\":[],\"ctxpack_files\":[],\"work_items\":[],\"context_suggestion\":{\"should_store\":true,\"title\":\"...\",\"body\":\"...\",\"category\":\"bot_role|operating_rule|project_fact|current_focus|risk|general\",\"importance\":\"low|normal|high|critical\"}} or {\"clarify\":\"...\"} or {\"skip\":true,\"reason\":\"...\"}."
|
|
2012
2018
|
: "Return JSON only: {\"reply\":\"...\",\"artifacts\":[],\"ctxpack_files\":[],\"work_items\":[],\"context_suggestion\":{\"should_store\":true,\"title\":\"...\",\"body\":\"...\",\"category\":\"bot_role|operating_rule|project_fact|current_focus|risk|general\",\"importance\":\"low|normal|high|critical\"}} or {\"clarify\":\"...\"} or {\"skip\":true,\"reason\":\"...\"}. Keep the reply concise and directly useful in a group chat.",
|
|
@@ -2061,7 +2067,12 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
|
|
|
2061
2067
|
}
|
|
2062
2068
|
if (assignmentsForThisBot.length > 0) {
|
|
2063
2069
|
lines.push(
|
|
2064
|
-
`Current Assignment For This Bot: ${assignmentsForThisBot.map((item) =>
|
|
2070
|
+
`Current Assignment For This Bot: ${assignmentsForThisBot.map((item) => {
|
|
2071
|
+
const task = String(item.task || "").trim();
|
|
2072
|
+
const mode = String(item.mode || "").trim();
|
|
2073
|
+
const artifactsRequired = item.artifactsRequired === true || item.artifacts_required === true;
|
|
2074
|
+
return `${task}${mode ? ` [mode=${mode}]` : ""}${artifactsRequired ? " [artifacts_required=yes]" : ""}`;
|
|
2075
|
+
}).join(" | ")}`,
|
|
2065
2076
|
);
|
|
2066
2077
|
}
|
|
2067
2078
|
if (String(currentExecutionContract.summary_bot || currentExecutionContract.summaryBot || "").trim()) {
|
|
@@ -2149,11 +2160,13 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
|
|
|
2149
2160
|
"A valid follow-up may be a concise opinion, analysis, review, comparison, synthesis, or direct delegated result.",
|
|
2150
2161
|
"Mentioning the lead bot for acknowledgment or handoff does not require a new delegation contract.",
|
|
2151
2162
|
"Do not wake any third bot. Only the lead bot may delegate new public work in this conversation.",
|
|
2152
|
-
"If
|
|
2163
|
+
"If any other expected responder besides this bot still remains in the current execution contract, do not emit contract.type=\"summary_request\" yet.",
|
|
2164
|
+
"Only the final remaining contributor may hand control back to the designated summary bot with contract.type=\"summary_request\".",
|
|
2153
2165
|
);
|
|
2154
2166
|
} else {
|
|
2155
2167
|
lines.push(
|
|
2156
|
-
"If
|
|
2168
|
+
"If any other expected responder besides this bot still remains in the current execution contract, do not emit contract.type=\"summary_request\" yet.",
|
|
2169
|
+
"Only the final remaining contributor may hand control back to the designated summary bot with contract.type=\"summary_request\".",
|
|
2157
2170
|
"Do not wake any third bot. Only the lead bot may delegate work publicly in this conversation.",
|
|
2158
2171
|
);
|
|
2159
2172
|
}
|
|
@@ -710,6 +710,112 @@ function normalizeReplyExpectation(value, fallback = "informational") {
|
|
|
710
710
|
return fallback;
|
|
711
711
|
}
|
|
712
712
|
|
|
713
|
+
function boolFromRunnerRaw(raw, fallback = false) {
|
|
714
|
+
if (raw === true || raw === false) {
|
|
715
|
+
return raw;
|
|
716
|
+
}
|
|
717
|
+
const normalized = String(raw ?? "").trim().toLowerCase();
|
|
718
|
+
if (!normalized) return fallback;
|
|
719
|
+
if (["1", "true", "yes", "y", "on"].includes(normalized)) {
|
|
720
|
+
return true;
|
|
721
|
+
}
|
|
722
|
+
if (["0", "false", "no", "n", "off"].includes(normalized)) {
|
|
723
|
+
return false;
|
|
724
|
+
}
|
|
725
|
+
return fallback;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function normalizeExecutionAssignmentMode(rawValue, fallback = "conversation_contribution") {
|
|
729
|
+
const normalized = String(rawValue || "").trim().toLowerCase();
|
|
730
|
+
if (!normalized) {
|
|
731
|
+
return fallback;
|
|
732
|
+
}
|
|
733
|
+
if ([
|
|
734
|
+
"execution_task",
|
|
735
|
+
"workspace_action",
|
|
736
|
+
"artifact_work",
|
|
737
|
+
"artifact_task",
|
|
738
|
+
"file_work",
|
|
739
|
+
"ctxpack_update",
|
|
740
|
+
"workitem_task",
|
|
741
|
+
].includes(normalized)) {
|
|
742
|
+
return "execution_task";
|
|
743
|
+
}
|
|
744
|
+
if ([
|
|
745
|
+
"conversation_contribution",
|
|
746
|
+
"discussion_contribution",
|
|
747
|
+
"opinion",
|
|
748
|
+
"analysis",
|
|
749
|
+
"review",
|
|
750
|
+
"comparison",
|
|
751
|
+
"summary_contribution",
|
|
752
|
+
"greeting",
|
|
753
|
+
"reply",
|
|
754
|
+
].includes(normalized)) {
|
|
755
|
+
return "conversation_contribution";
|
|
756
|
+
}
|
|
757
|
+
return fallback;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function normalizeExecutionAssignment(rawAssignment, {
|
|
761
|
+
allowedResponderSet = null,
|
|
762
|
+
currentBotSelector = "",
|
|
763
|
+
excludeCurrentTargetAssignments = false,
|
|
764
|
+
} = {}) {
|
|
765
|
+
const assignment = safeObject(rawAssignment);
|
|
766
|
+
const targetBot = normalizeMentionSelector(
|
|
767
|
+
assignment.targetBot
|
|
768
|
+
|| assignment.target_bot
|
|
769
|
+
|| assignment.bot
|
|
770
|
+
|| assignment.username,
|
|
771
|
+
);
|
|
772
|
+
const task = String(
|
|
773
|
+
assignment.task
|
|
774
|
+
|| assignment.instruction
|
|
775
|
+
|| assignment.assignment
|
|
776
|
+
|| assignment.work
|
|
777
|
+
|| assignment.description
|
|
778
|
+
|| "",
|
|
779
|
+
).trim();
|
|
780
|
+
if (!targetBot || !task) {
|
|
781
|
+
return null;
|
|
782
|
+
}
|
|
783
|
+
if (allowedResponderSet?.size && !allowedResponderSet.has(targetBot)) {
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
if (
|
|
787
|
+
excludeCurrentTargetAssignments === true
|
|
788
|
+
&& currentBotSelector
|
|
789
|
+
&& targetBot === normalizeMentionSelector(currentBotSelector)
|
|
790
|
+
) {
|
|
791
|
+
return null;
|
|
792
|
+
}
|
|
793
|
+
const artifactsRequired = boolFromRunnerRaw(
|
|
794
|
+
assignment.artifactsRequired ?? assignment.artifacts_required,
|
|
795
|
+
false,
|
|
796
|
+
);
|
|
797
|
+
const workspaceAction = boolFromRunnerRaw(
|
|
798
|
+
assignment.workspaceAction ?? assignment.workspace_action,
|
|
799
|
+
false,
|
|
800
|
+
);
|
|
801
|
+
const mode = normalizeExecutionAssignmentMode(
|
|
802
|
+
assignment.mode
|
|
803
|
+
|| assignment.assignment_mode
|
|
804
|
+
|| assignment.kind
|
|
805
|
+
|| assignment.type,
|
|
806
|
+
(artifactsRequired || workspaceAction) ? "execution_task" : "conversation_contribution",
|
|
807
|
+
);
|
|
808
|
+
const requiresExecution = mode === "execution_task" || artifactsRequired || workspaceAction;
|
|
809
|
+
return {
|
|
810
|
+
targetBot,
|
|
811
|
+
task,
|
|
812
|
+
mode,
|
|
813
|
+
artifactsRequired,
|
|
814
|
+
workspaceAction,
|
|
815
|
+
requiresExecution,
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
|
|
713
819
|
function normalizeHumanIntentType(value, fallback = "") {
|
|
714
820
|
const normalized = String(value || "").trim().toLowerCase();
|
|
715
821
|
if ([
|
|
@@ -1811,14 +1917,84 @@ function buildFallbackArchivedBotReplyConversationContext({
|
|
|
1811
1917
|
};
|
|
1812
1918
|
}
|
|
1813
1919
|
|
|
1814
|
-
function
|
|
1920
|
+
function extractCurrentAssignmentsForBot(conversationContext, currentBotSelector) {
|
|
1815
1921
|
const currentSelector = normalizeMentionSelector(currentBotSelector);
|
|
1816
1922
|
if (!currentSelector) return [];
|
|
1817
1923
|
return ensureArray(safeObject(conversationContext?.executionContract).assignments)
|
|
1818
1924
|
.map((item) => safeObject(item))
|
|
1819
1925
|
.filter((item) => normalizeMentionSelector(item.targetBot || item.target_bot) === currentSelector)
|
|
1820
|
-
.map((item) =>
|
|
1821
|
-
|
|
1926
|
+
.map((item) => ({
|
|
1927
|
+
targetBot: normalizeMentionSelector(item.targetBot || item.target_bot),
|
|
1928
|
+
task: String(item.task || item.instruction || "").trim(),
|
|
1929
|
+
mode: normalizeExecutionAssignmentMode(
|
|
1930
|
+
item.mode || item.assignment_mode || item.kind || item.type,
|
|
1931
|
+
item.requiresExecution === true
|
|
1932
|
+
|| item.artifactsRequired === true
|
|
1933
|
+
|| item.workspaceAction === true
|
|
1934
|
+
|| item.artifacts_required === true
|
|
1935
|
+
|| item.workspace_action === true
|
|
1936
|
+
? "execution_task"
|
|
1937
|
+
: "conversation_contribution",
|
|
1938
|
+
),
|
|
1939
|
+
artifactsRequired: item.artifactsRequired === true || item.artifacts_required === true,
|
|
1940
|
+
workspaceAction: item.workspaceAction === true || item.workspace_action === true,
|
|
1941
|
+
requiresExecution: item.requiresExecution === true
|
|
1942
|
+
|| item.artifactsRequired === true
|
|
1943
|
+
|| item.workspaceAction === true
|
|
1944
|
+
|| item.artifacts_required === true
|
|
1945
|
+
|| item.workspace_action === true
|
|
1946
|
+
|| normalizeExecutionAssignmentMode(
|
|
1947
|
+
item.mode || item.assignment_mode || item.kind || item.type,
|
|
1948
|
+
"conversation_contribution",
|
|
1949
|
+
) === "execution_task",
|
|
1950
|
+
}))
|
|
1951
|
+
.filter((item) => item.task);
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
function summarizeCurrentAssignmentExecutionValidation(conversationContext, currentBotSelector) {
|
|
1955
|
+
const assignments = extractCurrentAssignmentsForBot(conversationContext, currentBotSelector);
|
|
1956
|
+
const assignmentModes = uniqueOrdered(
|
|
1957
|
+
assignments.map((item) => String(item.mode || "").trim()).filter(Boolean),
|
|
1958
|
+
);
|
|
1959
|
+
const executionAssignments = assignments.filter((item) => item.requiresExecution === true);
|
|
1960
|
+
const conversationAssignments = assignments.filter((item) => item.requiresExecution !== true);
|
|
1961
|
+
if (!assignments.length) {
|
|
1962
|
+
return {
|
|
1963
|
+
status: "no_assignment",
|
|
1964
|
+
reason: "no assignment for current bot in the active execution contract",
|
|
1965
|
+
assignmentModes: [],
|
|
1966
|
+
assignments: [],
|
|
1967
|
+
executionAssignments: [],
|
|
1968
|
+
conversationAssignments: [],
|
|
1969
|
+
executionTasks: [],
|
|
1970
|
+
allTasks: [],
|
|
1971
|
+
requiresPlanner: false,
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
if (executionAssignments.length > 0) {
|
|
1975
|
+
return {
|
|
1976
|
+
status: "execution_assignment",
|
|
1977
|
+
reason: "active assignment explicitly requires workspace/artifact execution",
|
|
1978
|
+
assignmentModes,
|
|
1979
|
+
assignments,
|
|
1980
|
+
executionAssignments,
|
|
1981
|
+
conversationAssignments,
|
|
1982
|
+
executionTasks: executionAssignments.map((item) => String(item.task || "").trim()).filter(Boolean),
|
|
1983
|
+
allTasks: assignments.map((item) => String(item.task || "").trim()).filter(Boolean),
|
|
1984
|
+
requiresPlanner: true,
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1987
|
+
return {
|
|
1988
|
+
status: "conversation_assignment",
|
|
1989
|
+
reason: "active assignment is a conversational contribution and does not require planner/worker execution",
|
|
1990
|
+
assignmentModes,
|
|
1991
|
+
assignments,
|
|
1992
|
+
executionAssignments: [],
|
|
1993
|
+
conversationAssignments,
|
|
1994
|
+
executionTasks: [],
|
|
1995
|
+
allTasks: assignments.map((item) => String(item.task || "").trim()).filter(Boolean),
|
|
1996
|
+
requiresPlanner: false,
|
|
1997
|
+
};
|
|
1822
1998
|
}
|
|
1823
1999
|
|
|
1824
2000
|
function requiresArtifactsForExecutionStep(step) {
|
|
@@ -2557,6 +2733,7 @@ async function maybeExecuteDynamicRolePlan({
|
|
|
2557
2733
|
saveRunnerRouteState,
|
|
2558
2734
|
runRunnerAIExecution,
|
|
2559
2735
|
validateWorkspaceArtifacts,
|
|
2736
|
+
assignmentExecutionValidation = null,
|
|
2560
2737
|
}) {
|
|
2561
2738
|
const planner = typeof executionDeps.planRoleExecutionWithAI === "function"
|
|
2562
2739
|
? executionDeps.planRoleExecutionWithAI
|
|
@@ -2576,8 +2753,12 @@ async function maybeExecuteDynamicRolePlan({
|
|
|
2576
2753
|
if (!planner || !resolveRunnerExecutionPlanForRole) {
|
|
2577
2754
|
return null;
|
|
2578
2755
|
}
|
|
2579
|
-
const
|
|
2580
|
-
const
|
|
2756
|
+
const assignmentValidation = safeObject(assignmentExecutionValidation);
|
|
2757
|
+
const assignmentValidationStatus = String(assignmentValidation.status || "").trim();
|
|
2758
|
+
const assignmentValidationReason = String(assignmentValidation.reason || "").trim();
|
|
2759
|
+
const assignmentTasks = ensureArray(assignmentValidation.executionTasks)
|
|
2760
|
+
.map((item) => String(item || "").trim())
|
|
2761
|
+
.filter(Boolean);
|
|
2581
2762
|
const humanIntentType = normalizeHumanIntentType(
|
|
2582
2763
|
safeObject(directHumanResponseContract).intentType
|
|
2583
2764
|
|| safeObject(safeObject(humanIntentContext).humanIntent).intentType,
|
|
@@ -2610,7 +2791,7 @@ async function maybeExecuteDynamicRolePlan({
|
|
|
2610
2791
|
) {
|
|
2611
2792
|
return null;
|
|
2612
2793
|
}
|
|
2613
|
-
const shouldPlanExecution =
|
|
2794
|
+
const shouldPlanExecution = assignmentValidation.requiresPlanner === true || (
|
|
2614
2795
|
triggerDecision.requiresDirectReply === true
|
|
2615
2796
|
&& humanIntentMode === "single_bot"
|
|
2616
2797
|
&& actionableReplyExpectation
|
|
@@ -2756,6 +2937,8 @@ async function maybeExecuteDynamicRolePlan({
|
|
|
2756
2937
|
last_conversation_id: String(conversationContext?.id || "").trim(),
|
|
2757
2938
|
last_conversation_stage: String(conversationContext?.stage || "").trim(),
|
|
2758
2939
|
last_workspace_dir: String(executionPlan.workspaceDir || "").trim(),
|
|
2940
|
+
last_assignment_validation_status: assignmentValidationStatus,
|
|
2941
|
+
last_assignment_validation_reason: assignmentValidationReason,
|
|
2759
2942
|
...safeObject(intentStatePatch),
|
|
2760
2943
|
}),
|
|
2761
2944
|
);
|
|
@@ -2769,6 +2952,9 @@ async function maybeExecuteDynamicRolePlan({
|
|
|
2769
2952
|
thread_id: archiveThread.threadID,
|
|
2770
2953
|
comment_id: selectedRecord.id,
|
|
2771
2954
|
trigger_kind: String(triggerDecision.trigger || "").trim(),
|
|
2955
|
+
assignment_validation_status: assignmentValidationStatus,
|
|
2956
|
+
assignment_validation_reason: assignmentValidationReason,
|
|
2957
|
+
assignment_validation_modes: ensureArray(assignmentValidation.assignmentModes),
|
|
2772
2958
|
},
|
|
2773
2959
|
};
|
|
2774
2960
|
}
|
|
@@ -2786,6 +2972,8 @@ async function maybeExecuteDynamicRolePlan({
|
|
|
2786
2972
|
last_conversation_id: String(conversationContext?.id || "").trim(),
|
|
2787
2973
|
last_conversation_stage: String(conversationContext?.stage || "").trim(),
|
|
2788
2974
|
last_workspace_dir: String(executionPlan.workspaceDir || "").trim(),
|
|
2975
|
+
last_assignment_validation_status: assignmentValidationStatus,
|
|
2976
|
+
last_assignment_validation_reason: assignmentValidationReason,
|
|
2789
2977
|
...safeObject(intentStatePatch),
|
|
2790
2978
|
}),
|
|
2791
2979
|
);
|
|
@@ -2799,6 +2987,9 @@ async function maybeExecuteDynamicRolePlan({
|
|
|
2799
2987
|
thread_id: archiveThread.threadID,
|
|
2800
2988
|
comment_id: selectedRecord.id,
|
|
2801
2989
|
trigger_kind: String(triggerDecision.trigger || "").trim(),
|
|
2990
|
+
assignment_validation_status: assignmentValidationStatus,
|
|
2991
|
+
assignment_validation_reason: assignmentValidationReason,
|
|
2992
|
+
assignment_validation_modes: ensureArray(assignmentValidation.assignmentModes),
|
|
2802
2993
|
},
|
|
2803
2994
|
};
|
|
2804
2995
|
}
|
|
@@ -2833,6 +3024,8 @@ async function maybeExecuteDynamicRolePlan({
|
|
|
2833
3024
|
last_conversation_id: String(conversationContext?.id || "").trim(),
|
|
2834
3025
|
last_conversation_stage: String(conversationContext?.stage || "").trim(),
|
|
2835
3026
|
last_workspace_dir: String(executionPlan.workspaceDir || "").trim(),
|
|
3027
|
+
last_assignment_validation_status: assignmentValidationStatus,
|
|
3028
|
+
last_assignment_validation_reason: assignmentValidationReason,
|
|
2836
3029
|
...safeObject(intentStatePatch),
|
|
2837
3030
|
}),
|
|
2838
3031
|
);
|
|
@@ -2846,6 +3039,9 @@ async function maybeExecuteDynamicRolePlan({
|
|
|
2846
3039
|
thread_id: archiveThread.threadID,
|
|
2847
3040
|
comment_id: selectedRecord.id,
|
|
2848
3041
|
trigger_kind: String(triggerDecision.trigger || "").trim(),
|
|
3042
|
+
assignment_validation_status: assignmentValidationStatus,
|
|
3043
|
+
assignment_validation_reason: assignmentValidationReason,
|
|
3044
|
+
assignment_validation_modes: ensureArray(assignmentValidation.assignmentModes),
|
|
2849
3045
|
},
|
|
2850
3046
|
};
|
|
2851
3047
|
}
|
|
@@ -2862,6 +3058,8 @@ async function maybeExecuteDynamicRolePlan({
|
|
|
2862
3058
|
last_conversation_id: String(conversationContext?.id || "").trim(),
|
|
2863
3059
|
last_conversation_stage: String(conversationContext?.stage || "").trim(),
|
|
2864
3060
|
last_workspace_dir: String(stepExecutionPlan.workspaceDir || executionPlan.workspaceDir || "").trim(),
|
|
3061
|
+
last_assignment_validation_status: assignmentValidationStatus,
|
|
3062
|
+
last_assignment_validation_reason: assignmentValidationReason,
|
|
2865
3063
|
...safeObject(intentStatePatch),
|
|
2866
3064
|
}),
|
|
2867
3065
|
);
|
|
@@ -2875,6 +3073,9 @@ async function maybeExecuteDynamicRolePlan({
|
|
|
2875
3073
|
thread_id: archiveThread.threadID,
|
|
2876
3074
|
comment_id: selectedRecord.id,
|
|
2877
3075
|
trigger_kind: String(triggerDecision.trigger || "").trim(),
|
|
3076
|
+
assignment_validation_status: assignmentValidationStatus,
|
|
3077
|
+
assignment_validation_reason: assignmentValidationReason,
|
|
3078
|
+
assignment_validation_modes: ensureArray(assignmentValidation.assignmentModes),
|
|
2878
3079
|
},
|
|
2879
3080
|
};
|
|
2880
3081
|
}
|
|
@@ -2918,6 +3119,8 @@ async function maybeExecuteDynamicRolePlan({
|
|
|
2918
3119
|
last_conversation_id: String(conversationContext?.id || "").trim(),
|
|
2919
3120
|
last_conversation_stage: String(conversationContext?.stage || "").trim(),
|
|
2920
3121
|
last_workspace_dir: String(stepExecutionPlan.workspaceDir || executionPlan.workspaceDir || "").trim(),
|
|
3122
|
+
last_assignment_validation_status: assignmentValidationStatus,
|
|
3123
|
+
last_assignment_validation_reason: assignmentValidationReason,
|
|
2921
3124
|
...safeObject(intentStatePatch),
|
|
2922
3125
|
}),
|
|
2923
3126
|
);
|
|
@@ -2931,6 +3134,9 @@ async function maybeExecuteDynamicRolePlan({
|
|
|
2931
3134
|
thread_id: archiveThread.threadID,
|
|
2932
3135
|
comment_id: selectedRecord.id,
|
|
2933
3136
|
trigger_kind: String(triggerDecision.trigger || "").trim(),
|
|
3137
|
+
assignment_validation_status: assignmentValidationStatus,
|
|
3138
|
+
assignment_validation_reason: assignmentValidationReason,
|
|
3139
|
+
assignment_validation_modes: ensureArray(assignmentValidation.assignmentModes),
|
|
2934
3140
|
},
|
|
2935
3141
|
};
|
|
2936
3142
|
}
|
|
@@ -3057,6 +3263,8 @@ async function maybeExecuteDynamicRolePlan({
|
|
|
3057
3263
|
last_artifact_errors: stepErrors,
|
|
3058
3264
|
last_boundary_violations: stepBoundaryViolations,
|
|
3059
3265
|
last_workspace_dir: String(stepExecutionPlan.workspaceDir || executionPlan.workspaceDir || "").trim(),
|
|
3266
|
+
last_assignment_validation_status: assignmentValidationStatus,
|
|
3267
|
+
last_assignment_validation_reason: assignmentValidationReason,
|
|
3060
3268
|
...safeObject(intentStatePatch),
|
|
3061
3269
|
}),
|
|
3062
3270
|
);
|
|
@@ -3073,6 +3281,9 @@ async function maybeExecuteDynamicRolePlan({
|
|
|
3073
3281
|
artifact_validation: String(stepValidation.status || "").trim() || "execution_failed",
|
|
3074
3282
|
artifact_paths: summarizeValidatedArtifactPaths(stepValidation),
|
|
3075
3283
|
artifact_errors: stepErrors,
|
|
3284
|
+
assignment_validation_status: assignmentValidationStatus,
|
|
3285
|
+
assignment_validation_reason: assignmentValidationReason,
|
|
3286
|
+
assignment_validation_modes: ensureArray(assignmentValidation.assignmentModes),
|
|
3076
3287
|
},
|
|
3077
3288
|
};
|
|
3078
3289
|
}
|
|
@@ -3117,21 +3328,13 @@ async function maybeExecuteDynamicRolePlan({
|
|
|
3117
3328
|
mergedWorkItems,
|
|
3118
3329
|
);
|
|
3119
3330
|
const summaryBotSelector = normalizeMentionSelector(conversationContext?.summaryBotUsername || conversationContext?.leadBotUsername);
|
|
3120
|
-
const finalContract =
|
|
3121
|
-
?
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
}
|
|
3128
|
-
: {
|
|
3129
|
-
type: summaryRole === "approval" ? "final_summary" : "direct_result",
|
|
3130
|
-
actionable: true,
|
|
3131
|
-
assignments: [],
|
|
3132
|
-
summaryBot: summaryBotSelector,
|
|
3133
|
-
nextResponders: [],
|
|
3134
|
-
};
|
|
3331
|
+
const finalContract = {
|
|
3332
|
+
type: summaryRole === "approval" ? "final_summary" : "direct_result",
|
|
3333
|
+
actionable: true,
|
|
3334
|
+
assignments: [],
|
|
3335
|
+
summaryBot: summaryBotSelector,
|
|
3336
|
+
nextResponders: [],
|
|
3337
|
+
};
|
|
3135
3338
|
return {
|
|
3136
3339
|
kind: "executed",
|
|
3137
3340
|
aiResult: {
|
|
@@ -3380,47 +3583,21 @@ function normalizeConversationExecutionContract(
|
|
|
3380
3583
|
|| contract.contractType
|
|
3381
3584
|
|| "",
|
|
3382
3585
|
).trim().toLowerCase();
|
|
3383
|
-
const normalizedType = [
|
|
3384
|
-
"direct_result",
|
|
3385
|
-
"delegation",
|
|
3386
|
-
"summary_request",
|
|
3387
|
-
"final_summary",
|
|
3388
|
-
].includes(type)
|
|
3389
|
-
? type
|
|
3390
|
-
: "";
|
|
3391
|
-
const assignments = ensureArray(contract.assignments)
|
|
3392
|
-
.map((item) => {
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|| assignment.username,
|
|
3399
|
-
);
|
|
3400
|
-
const task = String(
|
|
3401
|
-
assignment.task
|
|
3402
|
-
|| assignment.instruction
|
|
3403
|
-
|| assignment.assignment
|
|
3404
|
-
|| assignment.work
|
|
3405
|
-
|| assignment.description
|
|
3406
|
-
|| "",
|
|
3407
|
-
).trim();
|
|
3408
|
-
if (!targetBot || !task) {
|
|
3409
|
-
return null;
|
|
3410
|
-
}
|
|
3411
|
-
if (allowedResponderSet.size && !allowedResponderSet.has(targetBot)) {
|
|
3412
|
-
return null;
|
|
3413
|
-
}
|
|
3414
|
-
if (
|
|
3415
|
-
excludeCurrentTargetAssignments === true
|
|
3416
|
-
&& currentBotSelector
|
|
3417
|
-
&& targetBot === normalizeMentionSelector(currentBotSelector)
|
|
3418
|
-
) {
|
|
3419
|
-
return null;
|
|
3420
|
-
}
|
|
3421
|
-
return { targetBot, task };
|
|
3422
|
-
})
|
|
3423
|
-
.filter(Boolean);
|
|
3586
|
+
const normalizedType = [
|
|
3587
|
+
"direct_result",
|
|
3588
|
+
"delegation",
|
|
3589
|
+
"summary_request",
|
|
3590
|
+
"final_summary",
|
|
3591
|
+
].includes(type)
|
|
3592
|
+
? type
|
|
3593
|
+
: "";
|
|
3594
|
+
const assignments = ensureArray(contract.assignments)
|
|
3595
|
+
.map((item) => normalizeExecutionAssignment(item, {
|
|
3596
|
+
allowedResponderSet,
|
|
3597
|
+
currentBotSelector,
|
|
3598
|
+
excludeCurrentTargetAssignments,
|
|
3599
|
+
}))
|
|
3600
|
+
.filter(Boolean);
|
|
3424
3601
|
const summaryBot = normalizeMentionSelector(
|
|
3425
3602
|
contract.summaryBot
|
|
3426
3603
|
|| contract.summary_bot
|
|
@@ -3483,30 +3660,10 @@ function normalizeResponseExecutionContract(rawContract, responseContract, { cur
|
|
|
3483
3660
|
return null;
|
|
3484
3661
|
}
|
|
3485
3662
|
const assignments = ensureArray(contract.assignments)
|
|
3486
|
-
.map((item) => {
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|| assignment.target_bot
|
|
3491
|
-
|| assignment.bot
|
|
3492
|
-
|| assignment.username,
|
|
3493
|
-
);
|
|
3494
|
-
const task = String(
|
|
3495
|
-
assignment.task
|
|
3496
|
-
|| assignment.instruction
|
|
3497
|
-
|| assignment.assignment
|
|
3498
|
-
|| assignment.work
|
|
3499
|
-
|| assignment.description
|
|
3500
|
-
|| "",
|
|
3501
|
-
).trim();
|
|
3502
|
-
if (!targetBot || !task) {
|
|
3503
|
-
return null;
|
|
3504
|
-
}
|
|
3505
|
-
if (currentBotSelector && targetBot === currentBotSelector) {
|
|
3506
|
-
return null;
|
|
3507
|
-
}
|
|
3508
|
-
return { targetBot, task };
|
|
3509
|
-
})
|
|
3663
|
+
.map((item) => normalizeExecutionAssignment(item, {
|
|
3664
|
+
currentBotSelector,
|
|
3665
|
+
excludeCurrentTargetAssignments: true,
|
|
3666
|
+
}))
|
|
3510
3667
|
.filter(Boolean);
|
|
3511
3668
|
const summaryBot = normalizeMentionSelector(
|
|
3512
3669
|
contract.summaryBot
|
|
@@ -4826,6 +4983,10 @@ export async function processRunnerSelectedRecord({
|
|
|
4826
4983
|
directQueryReply: directInformationalReply,
|
|
4827
4984
|
responderAdjudication,
|
|
4828
4985
|
});
|
|
4986
|
+
const assignmentExecutionValidation = summarizeCurrentAssignmentExecutionValidation(
|
|
4987
|
+
conversationContext,
|
|
4988
|
+
currentBotSelector,
|
|
4989
|
+
);
|
|
4829
4990
|
const emitRunnerStage = (phase, detail) => {
|
|
4830
4991
|
if (!reportRunnerStage) return;
|
|
4831
4992
|
try {
|
|
@@ -4981,6 +5142,7 @@ export async function processRunnerSelectedRecord({
|
|
|
4981
5142
|
saveRunnerRouteState,
|
|
4982
5143
|
runRunnerAIExecution,
|
|
4983
5144
|
validateWorkspaceArtifacts,
|
|
5145
|
+
assignmentExecutionValidation,
|
|
4984
5146
|
});
|
|
4985
5147
|
if (dynamicRoleExecution?.kind === "error") {
|
|
4986
5148
|
dynamicExecutionError = dynamicRoleExecution.result;
|
|
@@ -5317,13 +5479,19 @@ export async function processRunnerSelectedRecord({
|
|
|
5317
5479
|
if (effectiveConversationContext?.mode === "public_multi_bot") {
|
|
5318
5480
|
const currentBotSelector = normalizeMentionSelector(bot?.username || bot?.name);
|
|
5319
5481
|
const currentSession = safeObject(effectiveConversationContext.session);
|
|
5482
|
+
const currentExecutionContract = safeObject(effectiveConversationContext.executionContract);
|
|
5320
5483
|
const normalizedReplyFingerprint = normalizeConversationReplyFingerprint(sanitizedReplyText);
|
|
5321
5484
|
const lastSpeaker = normalizeMentionSelector(currentSession.last_speaker_bot_username);
|
|
5322
5485
|
const lastReplyFingerprintByBot = safeObject(currentSession.last_reply_fingerprint_by_bot);
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5486
|
+
const contractAuthorizedResponders = uniqueOrdered([
|
|
5487
|
+
...collectExecutionContractNextResponders(currentExecutionContract),
|
|
5488
|
+
normalizeMentionSelector(currentExecutionContract.summaryBot || currentExecutionContract.summary_bot),
|
|
5489
|
+
].filter(Boolean));
|
|
5490
|
+
const contractAuthorizesCurrentBotReply = contractAuthorizedResponders.includes(currentBotSelector);
|
|
5491
|
+
if (lastSpeaker && lastSpeaker === currentBotSelector && !contractAuthorizesCurrentBotReply) {
|
|
5492
|
+
const reason = "conversation guard blocked consecutive reply from the same bot";
|
|
5493
|
+
saveRunnerRouteState(
|
|
5494
|
+
routeKey,
|
|
5327
5495
|
buildRunnerRouteStateFromComment(selectedRecord, {
|
|
5328
5496
|
last_action: "conversation_skipped",
|
|
5329
5497
|
last_reason: reason,
|
|
@@ -5433,6 +5601,8 @@ export async function processRunnerSelectedRecord({
|
|
|
5433
5601
|
last_contract_validation_status: String(responseContractValidation.status || "").trim(),
|
|
5434
5602
|
last_contract_validation_reason: String(responseContractValidation.reason || "").trim(),
|
|
5435
5603
|
last_contract_validation_targets: ensureArray(responseContractValidation.targets),
|
|
5604
|
+
last_assignment_validation_status: String(assignmentExecutionValidation.status || "").trim(),
|
|
5605
|
+
last_assignment_validation_reason: String(assignmentExecutionValidation.reason || "").trim(),
|
|
5436
5606
|
last_speaker_bot_username: normalizeMentionSelector(bot?.username || bot?.name),
|
|
5437
5607
|
last_workspace_dir: String(effectiveExecutionPlan.workspaceDir || "").trim(),
|
|
5438
5608
|
...intentStatePatch,
|
|
@@ -5474,6 +5644,9 @@ export async function processRunnerSelectedRecord({
|
|
|
5474
5644
|
response_contract_validation_status: String(responseContractValidation.status || "").trim(),
|
|
5475
5645
|
response_contract_validation_reason: String(responseContractValidation.reason || "").trim(),
|
|
5476
5646
|
response_contract_validation_targets: ensureArray(responseContractValidation.targets),
|
|
5647
|
+
assignment_validation_status: String(assignmentExecutionValidation.status || "").trim(),
|
|
5648
|
+
assignment_validation_reason: String(assignmentExecutionValidation.reason || "").trim(),
|
|
5649
|
+
assignment_validation_modes: ensureArray(assignmentExecutionValidation.assignmentModes),
|
|
5477
5650
|
reply_chars: String(sanitizedReplyText || "").length,
|
|
5478
5651
|
execution_mode: effectiveExecutionPlan.mode,
|
|
5479
5652
|
role_profile: effectiveExecutionPlan.roleProfileName,
|
|
@@ -5564,6 +5737,8 @@ export async function processRunnerSelectedRecord({
|
|
|
5564
5737
|
last_contract_validation_status: String(responseContractValidation.status || "").trim(),
|
|
5565
5738
|
last_contract_validation_reason: String(responseContractValidation.reason || "").trim(),
|
|
5566
5739
|
last_contract_validation_targets: ensureArray(responseContractValidation.targets),
|
|
5740
|
+
last_assignment_validation_status: String(assignmentExecutionValidation.status || "").trim(),
|
|
5741
|
+
last_assignment_validation_reason: String(assignmentExecutionValidation.reason || "").trim(),
|
|
5567
5742
|
last_trigger: String(effectiveTriggerDecision.trigger || "").trim(),
|
|
5568
5743
|
last_reason: effectiveConversationContext?.mode === "public_multi_bot"
|
|
5569
5744
|
? [
|
|
@@ -5664,6 +5839,9 @@ export async function processRunnerSelectedRecord({
|
|
|
5664
5839
|
response_contract_validation_status: String(responseContractValidation.status || "").trim(),
|
|
5665
5840
|
response_contract_validation_reason: String(responseContractValidation.reason || "").trim(),
|
|
5666
5841
|
response_contract_validation_targets: ensureArray(responseContractValidation.targets),
|
|
5842
|
+
assignment_validation_status: String(assignmentExecutionValidation.status || "").trim(),
|
|
5843
|
+
assignment_validation_reason: String(assignmentExecutionValidation.reason || "").trim(),
|
|
5844
|
+
assignment_validation_modes: ensureArray(assignmentExecutionValidation.assignmentModes),
|
|
5667
5845
|
delivery_status: deliveryResult.delivery.dryRun ? "dry_run" : "delivered",
|
|
5668
5846
|
execution_mode: effectiveExecutionPlan.mode,
|
|
5669
5847
|
role_profile: effectiveExecutionPlan.roleProfileName,
|
|
@@ -5483,9 +5483,9 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
5483
5483
|
push("single_bot_execution_failure_uses_ai_failure_explainer_when_available", false, String(err?.message || err));
|
|
5484
5484
|
}
|
|
5485
5485
|
|
|
5486
|
-
try {
|
|
5487
|
-
let aiCalls = 0;
|
|
5488
|
-
const processed = await processRunnerSelectedRecord({
|
|
5486
|
+
try {
|
|
5487
|
+
let aiCalls = 0;
|
|
5488
|
+
const processed = await processRunnerSelectedRecord({
|
|
5489
5489
|
routeKey: "single-bot-informational-human-request-key",
|
|
5490
5490
|
normalizedRoute: normalizeRunnerRoute({
|
|
5491
5491
|
name: "telegram-monitor-single-bot-informational",
|
|
@@ -9087,13 +9087,164 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
9087
9087
|
&& String(processed.result?.execution_contract_type || "") === "summary_request",
|
|
9088
9088
|
`kind=${String(processed.kind || "(none)")} ai_calls=${aiCalls} contract=${String(processed.result?.execution_contract_type || "(none)")} reason=${String(processed.skippedRecord?.reason || "(none)")}`,
|
|
9089
9089
|
);
|
|
9090
|
-
} catch (err) {
|
|
9091
|
-
push("delegated_single_lead_lead_reply_can_wake_named_peer", false, String(err?.message || err));
|
|
9092
|
-
}
|
|
9093
|
-
|
|
9094
|
-
try {
|
|
9095
|
-
let aiCalls = 0;
|
|
9096
|
-
|
|
9090
|
+
} catch (err) {
|
|
9091
|
+
push("delegated_single_lead_lead_reply_can_wake_named_peer", false, String(err?.message || err));
|
|
9092
|
+
}
|
|
9093
|
+
|
|
9094
|
+
try {
|
|
9095
|
+
let aiCalls = 0;
|
|
9096
|
+
let plannerCalls = 0;
|
|
9097
|
+
const processed = await processRunnerSelectedRecord({
|
|
9098
|
+
routeKey: "delegated-single-lead-conversation-assignment-skips-planner-key",
|
|
9099
|
+
normalizedRoute: normalizeRunnerRoute({
|
|
9100
|
+
name: "telegram-monitor-delegated-single-lead-conversation-assignment-skips-planner",
|
|
9101
|
+
project_id: selftestProjectID,
|
|
9102
|
+
provider: "telegram",
|
|
9103
|
+
role: "monitor",
|
|
9104
|
+
role_profile: "monitor",
|
|
9105
|
+
destination_id: "dest-1",
|
|
9106
|
+
destination_label: "Main Room",
|
|
9107
|
+
server_bot_name: "RyoAI2_bot",
|
|
9108
|
+
server_bot_id: "bot-peer-1",
|
|
9109
|
+
trigger_policy: {
|
|
9110
|
+
mentions_only: true,
|
|
9111
|
+
direct_messages: true,
|
|
9112
|
+
reply_to_bot_messages: true,
|
|
9113
|
+
},
|
|
9114
|
+
archive_policy: {
|
|
9115
|
+
mirror_replies: true,
|
|
9116
|
+
dedupe_inbound: true,
|
|
9117
|
+
dedupe_outbound: true,
|
|
9118
|
+
skip_bot_messages: true,
|
|
9119
|
+
},
|
|
9120
|
+
dry_run_delivery: true,
|
|
9121
|
+
}),
|
|
9122
|
+
selectedRecord: {
|
|
9123
|
+
id: "comment-delegated-single-lead-conversation-assignment-skips-planner",
|
|
9124
|
+
createdAt: "2026-03-16T00:02:12.000Z",
|
|
9125
|
+
parsedArchive: {
|
|
9126
|
+
kind: "bot_reply",
|
|
9127
|
+
conversationID: "comment-delegated-single-lead-conversation-open",
|
|
9128
|
+
conversationMode: "public_multi_bot",
|
|
9129
|
+
conversationStage: "bot_reply",
|
|
9130
|
+
conversationIntentMode: "delegated_single_lead",
|
|
9131
|
+
conversationAllowBotToBot: true,
|
|
9132
|
+
conversationLeadBotUsername: "ryoai_bot",
|
|
9133
|
+
conversationParticipants: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
9134
|
+
conversationInitialResponders: ["ryoai_bot"],
|
|
9135
|
+
conversationAllowedResponders: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
9136
|
+
conversationSummaryBotUsername: "ryoai_bot",
|
|
9137
|
+
botUsername: "RyoAI_bot",
|
|
9138
|
+
botName: "RyoAI_bot",
|
|
9139
|
+
sender: "RyoAI_bot",
|
|
9140
|
+
body: "@RyoAI2_bot 관점 하나만 짧게 말해줘.",
|
|
9141
|
+
mentionUsernames: ["ryoai2_bot"],
|
|
9142
|
+
executionContract: {
|
|
9143
|
+
type: "delegation",
|
|
9144
|
+
actionable: true,
|
|
9145
|
+
assignments: [
|
|
9146
|
+
{ target_bot: "ryoai2_bot", task: "관점 하나만 짧게 말해줘.", mode: "conversation_contribution", artifacts_required: false },
|
|
9147
|
+
],
|
|
9148
|
+
summary_bot: "ryoai_bot",
|
|
9149
|
+
next_responders: ["ryoai2_bot"],
|
|
9150
|
+
},
|
|
9151
|
+
},
|
|
9152
|
+
},
|
|
9153
|
+
pendingOrdered: [],
|
|
9154
|
+
bot: {
|
|
9155
|
+
id: "bot-peer-1",
|
|
9156
|
+
name: "RyoAI2_bot",
|
|
9157
|
+
username: "RyoAI2_bot",
|
|
9158
|
+
role: "monitor",
|
|
9159
|
+
provider: "telegram",
|
|
9160
|
+
},
|
|
9161
|
+
destination: {
|
|
9162
|
+
id: "dest-1",
|
|
9163
|
+
label: "Main Room",
|
|
9164
|
+
provider: "telegram",
|
|
9165
|
+
chatID: "-100123",
|
|
9166
|
+
},
|
|
9167
|
+
archiveThread: {
|
|
9168
|
+
threadID: "thread-1",
|
|
9169
|
+
workItemID: "work-item-1",
|
|
9170
|
+
},
|
|
9171
|
+
executionPlan: {
|
|
9172
|
+
mode: "role_profile",
|
|
9173
|
+
roleProfileName: "monitor",
|
|
9174
|
+
roleProfile: {
|
|
9175
|
+
client: "sample",
|
|
9176
|
+
model: "",
|
|
9177
|
+
permissionMode: "read_only",
|
|
9178
|
+
reasoningEffort: "low",
|
|
9179
|
+
},
|
|
9180
|
+
workspaceDir: path.join(os.tmpdir(), "metheus-runner-selftest-conversation-assignment-skips-planner"),
|
|
9181
|
+
workspaceSource: "selftest",
|
|
9182
|
+
usedCommandFallback: false,
|
|
9183
|
+
},
|
|
9184
|
+
runtime: {
|
|
9185
|
+
baseURL: "https://example.test",
|
|
9186
|
+
token: "selftest-token",
|
|
9187
|
+
timeoutSeconds: 30,
|
|
9188
|
+
actor: { user_id: "user-1" },
|
|
9189
|
+
},
|
|
9190
|
+
deps: {
|
|
9191
|
+
saveRunnerRouteState: () => {},
|
|
9192
|
+
startRunnerTypingHeartbeat: () => ({ async stop() {} }),
|
|
9193
|
+
runRunnerAIExecution: async () => {
|
|
9194
|
+
aiCalls += 1;
|
|
9195
|
+
return {
|
|
9196
|
+
skip: false,
|
|
9197
|
+
reply: "제 관점에서는 범위와 우선순위를 먼저 고정하는 게 맞습니다.",
|
|
9198
|
+
contract: {
|
|
9199
|
+
type: "summary_request",
|
|
9200
|
+
actionable: true,
|
|
9201
|
+
assignments: [],
|
|
9202
|
+
summary_bot: "ryoai_bot",
|
|
9203
|
+
next_responders: ["ryoai_bot"],
|
|
9204
|
+
},
|
|
9205
|
+
};
|
|
9206
|
+
},
|
|
9207
|
+
performLocalBotDelivery: async () => ({
|
|
9208
|
+
delivery: { dryRun: true, body: {} },
|
|
9209
|
+
archive: {},
|
|
9210
|
+
}),
|
|
9211
|
+
serializeRunnerTriggerPolicy: (value) => value,
|
|
9212
|
+
serializeRunnerArchivePolicy: (value) => value,
|
|
9213
|
+
buildRunnerExecutionDeps: () => ({
|
|
9214
|
+
planRoleExecutionWithAI: async () => {
|
|
9215
|
+
plannerCalls += 1;
|
|
9216
|
+
return {
|
|
9217
|
+
requiresExecution: true,
|
|
9218
|
+
summaryRole: "worker",
|
|
9219
|
+
steps: [{ role: "worker", goal: "unexpected", artifactsRequired: true }],
|
|
9220
|
+
};
|
|
9221
|
+
},
|
|
9222
|
+
}),
|
|
9223
|
+
buildRunnerDeliveryDeps: () => ({}),
|
|
9224
|
+
buildRunnerRuntimeDeps: () => ({}),
|
|
9225
|
+
resolveConversationPeerBots: () => [
|
|
9226
|
+
{ id: "bot-lead-1", name: "RyoAI_bot" },
|
|
9227
|
+
{ id: "bot-peer-1", name: "RyoAI2_bot" },
|
|
9228
|
+
{ id: "bot-peer-2", name: "RyoAI3_bot" },
|
|
9229
|
+
],
|
|
9230
|
+
},
|
|
9231
|
+
});
|
|
9232
|
+
push(
|
|
9233
|
+
"delegated_single_lead_conversation_assignment_skips_planner",
|
|
9234
|
+
processed.kind === "replied"
|
|
9235
|
+
&& aiCalls === 1
|
|
9236
|
+
&& plannerCalls === 0
|
|
9237
|
+
&& String(processed.result?.assignment_validation_status || "") === "conversation_assignment"
|
|
9238
|
+
&& ensureArray(processed.result?.assignment_validation_modes).includes("conversation_contribution"),
|
|
9239
|
+
`kind=${String(processed.kind || "(none)")} ai_calls=${aiCalls} planner_calls=${plannerCalls} assignment_status=${String(processed.result?.assignment_validation_status || "(none)")} modes=${JSON.stringify(processed.result?.assignment_validation_modes || [])}`,
|
|
9240
|
+
);
|
|
9241
|
+
} catch (err) {
|
|
9242
|
+
push("delegated_single_lead_conversation_assignment_skips_planner", false, String(err?.message || err));
|
|
9243
|
+
}
|
|
9244
|
+
|
|
9245
|
+
try {
|
|
9246
|
+
let aiCalls = 0;
|
|
9247
|
+
const deliveredConversation = [];
|
|
9097
9248
|
const processed = await processRunnerSelectedRecord({
|
|
9098
9249
|
routeKey: "delegated-single-lead-restored-bot-reply-key",
|
|
9099
9250
|
normalizedRoute: normalizeRunnerRoute({
|