metheus-governance-mcp-cli 0.2.204 → 0.2.206
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 +396 -31
- package/lib/local-ai-adapters.mjs +5 -4
- package/lib/runner-orchestration.mjs +62 -9
- package/lib/selftest-runner-scenarios.mjs +231 -0
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -184,6 +184,7 @@ import {
|
|
|
184
184
|
} from "./lib/runner-execution.mjs";
|
|
185
185
|
import {
|
|
186
186
|
processRunnerSelectedRecord,
|
|
187
|
+
resolveHumanIntentContext,
|
|
187
188
|
resolveRunnerResponderAdjudication,
|
|
188
189
|
resolveRunnerStartupLoopAdjudication,
|
|
189
190
|
selectRunnerPendingWork,
|
|
@@ -2064,14 +2065,176 @@ function saveBotRunnerState(nextState) {
|
|
|
2064
2065
|
try {
|
|
2065
2066
|
current = safeObject(tryJsonParse(fs.readFileSync(filePath, "utf8")));
|
|
2066
2067
|
} catch {}
|
|
2068
|
+
const stateEntryTimestampMs = (...values) => {
|
|
2069
|
+
for (const value of values) {
|
|
2070
|
+
const ms = Date.parse(String(value || "").trim());
|
|
2071
|
+
if (Number.isFinite(ms)) {
|
|
2072
|
+
return ms;
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
return 0;
|
|
2076
|
+
};
|
|
2077
|
+
const mergeRunnerStateRoutes = (currentRoutesRaw, nextRoutesRaw) => {
|
|
2078
|
+
const currentRoutes = safeObject(currentRoutesRaw);
|
|
2079
|
+
const nextRoutes = safeObject(nextRoutesRaw);
|
|
2080
|
+
const merged = {
|
|
2081
|
+
...currentRoutes,
|
|
2082
|
+
};
|
|
2083
|
+
const mergeConversationSessions = (currentSessionsRaw, nextSessionsRaw) => {
|
|
2084
|
+
const currentSessions = safeObject(currentSessionsRaw);
|
|
2085
|
+
const nextSessions = safeObject(nextSessionsRaw);
|
|
2086
|
+
const mergedSessions = {
|
|
2087
|
+
...currentSessions,
|
|
2088
|
+
};
|
|
2089
|
+
for (const [conversationID, nextSessionRaw] of Object.entries(nextSessions)) {
|
|
2090
|
+
const currentSession = safeObject(currentSessions[conversationID]);
|
|
2091
|
+
const nextSession = safeObject(nextSessionRaw);
|
|
2092
|
+
if (!Object.keys(currentSession).length) {
|
|
2093
|
+
mergedSessions[conversationID] = nextSession;
|
|
2094
|
+
continue;
|
|
2095
|
+
}
|
|
2096
|
+
const currentMs = stateEntryTimestampMs(
|
|
2097
|
+
currentSession.updated_at,
|
|
2098
|
+
currentSession.last_activity_at,
|
|
2099
|
+
currentSession.closed_at,
|
|
2100
|
+
currentSession.started_at,
|
|
2101
|
+
currentSession.expires_at,
|
|
2102
|
+
);
|
|
2103
|
+
const nextMs = stateEntryTimestampMs(
|
|
2104
|
+
nextSession.updated_at,
|
|
2105
|
+
nextSession.last_activity_at,
|
|
2106
|
+
nextSession.closed_at,
|
|
2107
|
+
nextSession.started_at,
|
|
2108
|
+
nextSession.expires_at,
|
|
2109
|
+
);
|
|
2110
|
+
mergedSessions[conversationID] = nextMs >= currentMs
|
|
2111
|
+
? {
|
|
2112
|
+
...currentSession,
|
|
2113
|
+
...nextSession,
|
|
2114
|
+
}
|
|
2115
|
+
: {
|
|
2116
|
+
...nextSession,
|
|
2117
|
+
...currentSession,
|
|
2118
|
+
};
|
|
2119
|
+
}
|
|
2120
|
+
return mergedSessions;
|
|
2121
|
+
};
|
|
2122
|
+
for (const [routeKey, nextRouteRaw] of Object.entries(nextRoutes)) {
|
|
2123
|
+
const currentRoute = safeObject(currentRoutes[routeKey]);
|
|
2124
|
+
const nextRoute = safeObject(nextRouteRaw);
|
|
2125
|
+
if (!Object.keys(currentRoute).length) {
|
|
2126
|
+
merged[routeKey] = nextRoute;
|
|
2127
|
+
continue;
|
|
2128
|
+
}
|
|
2129
|
+
const preferredRoute = prefersRunnerStateRecord(nextRoute, currentRoute) ? nextRoute : currentRoute;
|
|
2130
|
+
const fallbackRoute = preferredRoute === nextRoute ? currentRoute : nextRoute;
|
|
2131
|
+
merged[routeKey] = cleanupRunnerStateRecord({
|
|
2132
|
+
...mergeRunnerStateRecords(preferredRoute, fallbackRoute),
|
|
2133
|
+
conversation_sessions: mergeConversationSessions(
|
|
2134
|
+
currentRoute.conversation_sessions,
|
|
2135
|
+
nextRoute.conversation_sessions,
|
|
2136
|
+
),
|
|
2137
|
+
});
|
|
2138
|
+
}
|
|
2139
|
+
return merged;
|
|
2140
|
+
};
|
|
2141
|
+
const mergeRunnerStateRequests = (currentRequestsRaw, nextRequestsRaw) => {
|
|
2142
|
+
const currentRequests = normalizeBotRunnerRequests(currentRequestsRaw);
|
|
2143
|
+
const nextRequests = normalizeBotRunnerRequests(nextRequestsRaw);
|
|
2144
|
+
const merged = {
|
|
2145
|
+
...currentRequests,
|
|
2146
|
+
};
|
|
2147
|
+
for (const [requestKey, nextRequestRaw] of Object.entries(nextRequests)) {
|
|
2148
|
+
const currentRequest = safeObject(currentRequests[requestKey]);
|
|
2149
|
+
const nextRequest = safeObject(nextRequestRaw);
|
|
2150
|
+
const currentMs = stateEntryTimestampMs(
|
|
2151
|
+
currentRequest.updated_at,
|
|
2152
|
+
currentRequest.completed_at,
|
|
2153
|
+
currentRequest.closed_at,
|
|
2154
|
+
currentRequest.claimed_at,
|
|
2155
|
+
);
|
|
2156
|
+
const nextMs = stateEntryTimestampMs(
|
|
2157
|
+
nextRequest.updated_at,
|
|
2158
|
+
nextRequest.completed_at,
|
|
2159
|
+
nextRequest.closed_at,
|
|
2160
|
+
nextRequest.claimed_at,
|
|
2161
|
+
);
|
|
2162
|
+
if (!Object.keys(currentRequest).length) {
|
|
2163
|
+
merged[requestKey] = nextRequest;
|
|
2164
|
+
continue;
|
|
2165
|
+
}
|
|
2166
|
+
merged[requestKey] = nextMs >= currentMs
|
|
2167
|
+
? {
|
|
2168
|
+
...currentRequest,
|
|
2169
|
+
...nextRequest,
|
|
2170
|
+
}
|
|
2171
|
+
: currentRequest;
|
|
2172
|
+
}
|
|
2173
|
+
return normalizeBotRunnerRequests(merged);
|
|
2174
|
+
};
|
|
2175
|
+
const mergeRunnerStateExcludedComments = (currentExcludedRaw, nextExcludedRaw) => {
|
|
2176
|
+
const currentExcluded = normalizeBotRunnerExcludedComments(currentExcludedRaw);
|
|
2177
|
+
const nextExcluded = normalizeBotRunnerExcludedComments(nextExcludedRaw);
|
|
2178
|
+
const merged = {
|
|
2179
|
+
...currentExcluded,
|
|
2180
|
+
};
|
|
2181
|
+
for (const [commentID, nextEntryRaw] of Object.entries(nextExcluded)) {
|
|
2182
|
+
const currentEntry = safeObject(currentExcluded[commentID]);
|
|
2183
|
+
const nextEntry = safeObject(nextEntryRaw);
|
|
2184
|
+
const currentMs = stateEntryTimestampMs(currentEntry.excluded_at);
|
|
2185
|
+
const nextMs = stateEntryTimestampMs(nextEntry.excluded_at);
|
|
2186
|
+
if (!Object.keys(currentEntry).length || nextMs >= currentMs) {
|
|
2187
|
+
merged[commentID] = {
|
|
2188
|
+
...currentEntry,
|
|
2189
|
+
...nextEntry,
|
|
2190
|
+
};
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
return normalizeBotRunnerExcludedComments(merged);
|
|
2194
|
+
};
|
|
2195
|
+
const mergeRunnerStateConsumedComments = (currentConsumedRaw, nextConsumedRaw) => {
|
|
2196
|
+
const currentConsumed = normalizeBotRunnerConsumedComments(currentConsumedRaw);
|
|
2197
|
+
const nextConsumed = normalizeBotRunnerConsumedComments(nextConsumedRaw);
|
|
2198
|
+
const merged = {
|
|
2199
|
+
...currentConsumed,
|
|
2200
|
+
};
|
|
2201
|
+
for (const [commentID, nextEntryRaw] of Object.entries(nextConsumed)) {
|
|
2202
|
+
const currentEntry = safeObject(currentConsumed[commentID]);
|
|
2203
|
+
const nextEntry = safeObject(nextEntryRaw);
|
|
2204
|
+
const currentMs = stateEntryTimestampMs(currentEntry.consumed_at);
|
|
2205
|
+
const nextMs = stateEntryTimestampMs(nextEntry.consumed_at);
|
|
2206
|
+
if (!Object.keys(currentEntry).length || nextMs >= currentMs) {
|
|
2207
|
+
merged[commentID] = {
|
|
2208
|
+
...currentEntry,
|
|
2209
|
+
...nextEntry,
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
return normalizeBotRunnerConsumedComments(merged);
|
|
2214
|
+
};
|
|
2067
2215
|
const payload = {
|
|
2068
2216
|
version: 1,
|
|
2069
2217
|
updated_at: new Date().toISOString(),
|
|
2070
|
-
routes:
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2218
|
+
routes: mergeRunnerStateRoutes(
|
|
2219
|
+
current.routes,
|
|
2220
|
+
nextState?.routes ?? current.routes,
|
|
2221
|
+
),
|
|
2222
|
+
shared_inboxes: {
|
|
2223
|
+
...safeObject(current.shared_inboxes ?? current.sharedInboxes),
|
|
2224
|
+
...safeObject(nextState?.sharedInboxes ?? nextState?.shared_inboxes),
|
|
2225
|
+
},
|
|
2226
|
+
excluded_comments: mergeRunnerStateExcludedComments(
|
|
2227
|
+
current.excluded_comments ?? current.excludedComments,
|
|
2228
|
+
nextState?.excludedComments ?? nextState?.excluded_comments ?? current.excluded_comments ?? current.excludedComments,
|
|
2229
|
+
),
|
|
2230
|
+
requests: mergeRunnerStateRequests(
|
|
2231
|
+
current.requests,
|
|
2232
|
+
nextState?.requests ?? current.requests,
|
|
2233
|
+
),
|
|
2234
|
+
consumed_comments: mergeRunnerStateConsumedComments(
|
|
2235
|
+
current.consumed_comments ?? current.consumedComments,
|
|
2236
|
+
nextState?.consumedComments ?? nextState?.consumed_comments ?? current.consumed_comments ?? current.consumedComments,
|
|
2237
|
+
),
|
|
2075
2238
|
};
|
|
2076
2239
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
2077
2240
|
fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
@@ -2506,6 +2669,89 @@ function findRunnerRequestsForScope(state, normalizedRoute, selectors = {}) {
|
|
|
2506
2669
|
});
|
|
2507
2670
|
}
|
|
2508
2671
|
|
|
2672
|
+
function findScopedConversationSessionState(state, normalizedRoute, conversationIDRaw = "") {
|
|
2673
|
+
const conversationID = String(conversationIDRaw || "").trim();
|
|
2674
|
+
if (!conversationID) {
|
|
2675
|
+
return {};
|
|
2676
|
+
}
|
|
2677
|
+
const config = loadBotRunnerConfig({ persistIfNeeded: true });
|
|
2678
|
+
const nextRoutes = safeObject(state?.routes);
|
|
2679
|
+
const candidates = [];
|
|
2680
|
+
const seenRouteKeys = new Set();
|
|
2681
|
+
const pushCandidateRoute = (routeRaw) => {
|
|
2682
|
+
const routeObject = safeObject(routeRaw);
|
|
2683
|
+
if (!Object.keys(routeObject).length) {
|
|
2684
|
+
return;
|
|
2685
|
+
}
|
|
2686
|
+
const candidateRoute = normalizeRunnerRoute(routeObject);
|
|
2687
|
+
const candidateRouteKey = runnerRouteKey(candidateRoute);
|
|
2688
|
+
if (seenRouteKeys.has(candidateRouteKey)) {
|
|
2689
|
+
return;
|
|
2690
|
+
}
|
|
2691
|
+
seenRouteKeys.add(candidateRouteKey);
|
|
2692
|
+
candidates.push({
|
|
2693
|
+
route: candidateRoute,
|
|
2694
|
+
routeKey: candidateRouteKey,
|
|
2695
|
+
routeState: safeObject(nextRoutes[candidateRouteKey]),
|
|
2696
|
+
});
|
|
2697
|
+
};
|
|
2698
|
+
pushCandidateRoute(normalizedRoute);
|
|
2699
|
+
for (const candidateRouteRaw of ensureArray(config.routes)) {
|
|
2700
|
+
if (!runnerRouteMatchesProjectConversationScope(candidateRouteRaw, normalizedRoute)) {
|
|
2701
|
+
continue;
|
|
2702
|
+
}
|
|
2703
|
+
pushCandidateRoute(candidateRouteRaw);
|
|
2704
|
+
}
|
|
2705
|
+
const ranked = candidates
|
|
2706
|
+
.map((candidate) => {
|
|
2707
|
+
const session = safeObject(safeObject(candidate.routeState.conversation_sessions)[conversationID]);
|
|
2708
|
+
if (!Object.keys(session).length) {
|
|
2709
|
+
return null;
|
|
2710
|
+
}
|
|
2711
|
+
const status = String(session.status || "").trim().toLowerCase();
|
|
2712
|
+
const lastActivity = firstNonEmptyString([session.last_activity_at, session.closed_at, session.started_at]);
|
|
2713
|
+
return {
|
|
2714
|
+
...candidate,
|
|
2715
|
+
session,
|
|
2716
|
+
status,
|
|
2717
|
+
lastActivity,
|
|
2718
|
+
};
|
|
2719
|
+
})
|
|
2720
|
+
.filter(Boolean)
|
|
2721
|
+
.sort((left, right) => {
|
|
2722
|
+
if (left.status !== right.status) {
|
|
2723
|
+
if (left.status === "open") return -1;
|
|
2724
|
+
if (right.status === "open") return 1;
|
|
2725
|
+
}
|
|
2726
|
+
if (left.lastActivity && right.lastActivity && left.lastActivity !== right.lastActivity) {
|
|
2727
|
+
return left.lastActivity < right.lastActivity ? 1 : -1;
|
|
2728
|
+
}
|
|
2729
|
+
return String(left.routeKey || "").localeCompare(String(right.routeKey || ""));
|
|
2730
|
+
});
|
|
2731
|
+
return safeObject(ranked[0]);
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
function sessionAllowsConversationResponder(sessionRaw, responderSelectorRaw = "") {
|
|
2735
|
+
const session = safeObject(sessionRaw);
|
|
2736
|
+
const responderSelector = normalizeTelegramMentionUsername(responderSelectorRaw);
|
|
2737
|
+
if (!responderSelector) {
|
|
2738
|
+
return false;
|
|
2739
|
+
}
|
|
2740
|
+
if (String(session.status || "").trim().toLowerCase() !== "open") {
|
|
2741
|
+
return false;
|
|
2742
|
+
}
|
|
2743
|
+
const nextExpectedResponders = ensureArray(session.next_expected_responders)
|
|
2744
|
+
.map((value) => normalizeTelegramMentionUsername(value))
|
|
2745
|
+
.filter(Boolean);
|
|
2746
|
+
if (nextExpectedResponders.length > 0) {
|
|
2747
|
+
return nextExpectedResponders.includes(responderSelector);
|
|
2748
|
+
}
|
|
2749
|
+
const allowedResponders = ensureArray(session.allowed_responders)
|
|
2750
|
+
.map((value) => normalizeTelegramMentionUsername(value))
|
|
2751
|
+
.filter(Boolean);
|
|
2752
|
+
return allowedResponders.includes(responderSelector);
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2509
2755
|
function findRunnerRequestsForMessageID(state, normalizedRoute, selectors = {}) {
|
|
2510
2756
|
const chatID = String(selectors.chatID || "").trim();
|
|
2511
2757
|
const messageID = intFromRawAllowZero(selectors.messageID, 0);
|
|
@@ -2986,6 +3232,7 @@ async function claimRunnerRequestForHumanComment({
|
|
|
2986
3232
|
selectedRecord,
|
|
2987
3233
|
selectedBotUsernames = [],
|
|
2988
3234
|
normalizedIntent = "",
|
|
3235
|
+
sharedHumanIntent = null,
|
|
2989
3236
|
runtime = null,
|
|
2990
3237
|
archiveThreadID = "",
|
|
2991
3238
|
}) {
|
|
@@ -3039,6 +3286,7 @@ async function claimRunnerRequestForHumanComment({
|
|
|
3039
3286
|
}
|
|
3040
3287
|
const requests = normalizeBotRunnerRequests(stateForClaim.requests);
|
|
3041
3288
|
const existing = safeObject(requests[requestKey]);
|
|
3289
|
+
const normalizedSharedHumanIntent = safeObject(sharedHumanIntent);
|
|
3042
3290
|
const currentMessageID = intFromRawAllowZero(parsed.messageID, 0);
|
|
3043
3291
|
let sharedConversationSource = currentMessageID > 0
|
|
3044
3292
|
? pickRunnerSharedConversationSourceRequest(
|
|
@@ -3097,20 +3345,22 @@ async function claimRunnerRequestForHumanComment({
|
|
|
3097
3345
|
conversation_id: resolvedConversationID,
|
|
3098
3346
|
selected_bot_usernames: uniqueOrderedStrings(selectedBotUsernames, normalizeTelegramMentionUsername),
|
|
3099
3347
|
conversation_intent_mode: String(
|
|
3100
|
-
existing.conversation_intent_mode || sharedConversationSource.conversation_intent_mode || referencedRequest.conversation_intent_mode || "",
|
|
3348
|
+
existing.conversation_intent_mode || sharedConversationSource.conversation_intent_mode || referencedRequest.conversation_intent_mode || normalizedSharedHumanIntent.intentMode || "",
|
|
3101
3349
|
).trim().toLowerCase(),
|
|
3102
3350
|
conversation_lead_bot: normalizeTelegramMentionUsername(
|
|
3103
|
-
existing.conversation_lead_bot || sharedConversationSource.conversation_lead_bot || referencedRequest.conversation_lead_bot,
|
|
3351
|
+
existing.conversation_lead_bot || sharedConversationSource.conversation_lead_bot || referencedRequest.conversation_lead_bot || normalizedSharedHumanIntent.leadBotSelector,
|
|
3104
3352
|
),
|
|
3105
3353
|
conversation_summary_bot: normalizeTelegramMentionUsername(
|
|
3106
|
-
existing.conversation_summary_bot || sharedConversationSource.conversation_summary_bot || referencedRequest.conversation_summary_bot,
|
|
3354
|
+
existing.conversation_summary_bot || sharedConversationSource.conversation_summary_bot || referencedRequest.conversation_summary_bot || normalizedSharedHumanIntent.summaryBotSelector,
|
|
3107
3355
|
),
|
|
3108
3356
|
conversation_participants: uniqueOrderedStrings(
|
|
3109
3357
|
ensureArray(existing.conversation_participants).length
|
|
3110
3358
|
? existing.conversation_participants
|
|
3111
3359
|
: ensureArray(sharedConversationSource.conversation_participants).length
|
|
3112
3360
|
? sharedConversationSource.conversation_participants
|
|
3113
|
-
: referencedRequest.conversation_participants
|
|
3361
|
+
: ensureArray(referencedRequest.conversation_participants).length
|
|
3362
|
+
? referencedRequest.conversation_participants
|
|
3363
|
+
: normalizedSharedHumanIntent.participantSelectors,
|
|
3114
3364
|
normalizeTelegramMentionUsername,
|
|
3115
3365
|
),
|
|
3116
3366
|
conversation_initial_responders: uniqueOrderedStrings(
|
|
@@ -3118,7 +3368,9 @@ async function claimRunnerRequestForHumanComment({
|
|
|
3118
3368
|
? existing.conversation_initial_responders
|
|
3119
3369
|
: ensureArray(sharedConversationSource.conversation_initial_responders).length
|
|
3120
3370
|
? sharedConversationSource.conversation_initial_responders
|
|
3121
|
-
: referencedRequest.conversation_initial_responders
|
|
3371
|
+
: ensureArray(referencedRequest.conversation_initial_responders).length
|
|
3372
|
+
? referencedRequest.conversation_initial_responders
|
|
3373
|
+
: normalizedSharedHumanIntent.initialResponderSelectors,
|
|
3122
3374
|
normalizeTelegramMentionUsername,
|
|
3123
3375
|
),
|
|
3124
3376
|
conversation_allowed_responders: uniqueOrderedStrings(
|
|
@@ -3126,14 +3378,17 @@ async function claimRunnerRequestForHumanComment({
|
|
|
3126
3378
|
? existing.conversation_allowed_responders
|
|
3127
3379
|
: ensureArray(sharedConversationSource.conversation_allowed_responders).length
|
|
3128
3380
|
? sharedConversationSource.conversation_allowed_responders
|
|
3129
|
-
: referencedRequest.conversation_allowed_responders
|
|
3381
|
+
: ensureArray(referencedRequest.conversation_allowed_responders).length
|
|
3382
|
+
? referencedRequest.conversation_allowed_responders
|
|
3383
|
+
: normalizedSharedHumanIntent.allowedResponderSelectors,
|
|
3130
3384
|
normalizeTelegramMentionUsername,
|
|
3131
3385
|
),
|
|
3132
3386
|
conversation_allow_bot_to_bot: existing.conversation_allow_bot_to_bot === true
|
|
3133
3387
|
|| sharedConversationSource.conversation_allow_bot_to_bot === true
|
|
3134
|
-
|| referencedRequest.conversation_allow_bot_to_bot === true
|
|
3388
|
+
|| referencedRequest.conversation_allow_bot_to_bot === true
|
|
3389
|
+
|| normalizedSharedHumanIntent.allowBotToBot === true,
|
|
3135
3390
|
conversation_reply_expectation: String(
|
|
3136
|
-
existing.conversation_reply_expectation || sharedConversationSource.conversation_reply_expectation || referencedRequest.conversation_reply_expectation || "",
|
|
3391
|
+
existing.conversation_reply_expectation || sharedConversationSource.conversation_reply_expectation || referencedRequest.conversation_reply_expectation || normalizedSharedHumanIntent.replyExpectation || "",
|
|
3137
3392
|
).trim().toLowerCase(),
|
|
3138
3393
|
execution_contract_type: String(
|
|
3139
3394
|
existing.execution_contract_type || sharedConversationSource.execution_contract_type || referencedRequest.execution_contract_type || "",
|
|
@@ -3154,7 +3409,9 @@ async function claimRunnerRequestForHumanComment({
|
|
|
3154
3409
|
? existing.next_expected_responders
|
|
3155
3410
|
: ensureArray(sharedConversationSource.next_expected_responders).length
|
|
3156
3411
|
? sharedConversationSource.next_expected_responders
|
|
3157
|
-
: referencedRequest.next_expected_responders
|
|
3412
|
+
: ensureArray(referencedRequest.next_expected_responders).length
|
|
3413
|
+
? referencedRequest.next_expected_responders
|
|
3414
|
+
: normalizedSharedHumanIntent.initialResponderSelectors,
|
|
3158
3415
|
normalizeTelegramMentionUsername,
|
|
3159
3416
|
),
|
|
3160
3417
|
normalized_intent: resolvedNormalizedIntent,
|
|
@@ -3883,11 +4140,80 @@ function resolveRunnerContinuationRequestForBotReply({
|
|
|
3883
4140
|
};
|
|
3884
4141
|
}
|
|
3885
4142
|
const currentState = loadBotRunnerState();
|
|
3886
|
-
|
|
4143
|
+
let requests = findRunnerRequestsForScope(currentState, normalizedRoute, {
|
|
3887
4144
|
conversationID,
|
|
3888
4145
|
chatID: String(parsed.chatID || parsed.chatId || "").trim(),
|
|
3889
4146
|
}).filter((entry) => isActiveRunnerRequestStatus(entry.status));
|
|
3890
|
-
|
|
4147
|
+
let request = safeObject(requests[0]);
|
|
4148
|
+
if (!Object.keys(request).length) {
|
|
4149
|
+
const sessionMatch = findScopedConversationSessionState(currentState, normalizedRoute, conversationID);
|
|
4150
|
+
const session = safeObject(sessionMatch.session);
|
|
4151
|
+
const fallbackRequestKey = String(
|
|
4152
|
+
safeObject(sessionMatch.routeState).active_request_key
|
|
4153
|
+
|| safeObject(sessionMatch.routeState).last_request_key
|
|
4154
|
+
|| "",
|
|
4155
|
+
).trim();
|
|
4156
|
+
if (
|
|
4157
|
+
fallbackRequestKey
|
|
4158
|
+
&& String(session.status || "").trim().toLowerCase() === "open"
|
|
4159
|
+
) {
|
|
4160
|
+
const fallbackRequest = safeObject(normalizeBotRunnerRequests(currentState.requests)[fallbackRequestKey]);
|
|
4161
|
+
const nowISO = new Date().toISOString();
|
|
4162
|
+
const seedRequest = Object.keys(fallbackRequest).length
|
|
4163
|
+
? fallbackRequest
|
|
4164
|
+
: {
|
|
4165
|
+
request_key: fallbackRequestKey,
|
|
4166
|
+
project_id: String(normalizedRoute?.projectID || "").trim(),
|
|
4167
|
+
provider: String(normalizedRoute?.provider || "").trim(),
|
|
4168
|
+
chat_id: String(parsed.chatID || parsed.chatId || "").trim(),
|
|
4169
|
+
conversation_id: conversationID,
|
|
4170
|
+
selected_bot_usernames: ensureArray(session.participants),
|
|
4171
|
+
conversation_allowed_responders: ensureArray(session.allowed_responders),
|
|
4172
|
+
conversation_intent_mode: String(session.intent_mode || "").trim().toLowerCase(),
|
|
4173
|
+
conversation_lead_bot: normalizeTelegramMentionUsername(session.lead_bot_username),
|
|
4174
|
+
conversation_summary_bot: normalizeTelegramMentionUsername(session.summary_bot_username),
|
|
4175
|
+
conversation_participants: ensureArray(session.participants),
|
|
4176
|
+
conversation_initial_responders: ensureArray(session.initial_responders),
|
|
4177
|
+
conversation_allow_bot_to_bot: session.allow_bot_to_bot === true,
|
|
4178
|
+
conversation_reply_expectation: "",
|
|
4179
|
+
execution_contract_type: String(session.last_execution_contract_type || "").trim().toLowerCase(),
|
|
4180
|
+
execution_contract_actionable: session.last_execution_contract_actionable === true,
|
|
4181
|
+
execution_contract_targets: ensureArray(session.last_execution_contract_targets),
|
|
4182
|
+
next_expected_responders: ensureArray(session.next_expected_responders),
|
|
4183
|
+
normalized_intent: String(safeObject(sessionMatch.routeState).last_intent_type || "").trim().toLowerCase(),
|
|
4184
|
+
status: "running",
|
|
4185
|
+
claimed_by_route: String(sessionMatch.routeKey || "").trim(),
|
|
4186
|
+
claimed_at: firstNonEmptyString([session.started_at, nowISO]),
|
|
4187
|
+
started_at: firstNonEmptyString([session.started_at, nowISO]),
|
|
4188
|
+
root_work_item_id: String(
|
|
4189
|
+
safeObject(sessionMatch.routeState).active_root_work_item_id
|
|
4190
|
+
|| safeObject(sessionMatch.routeState).last_root_work_item_id
|
|
4191
|
+
|| "",
|
|
4192
|
+
).trim(),
|
|
4193
|
+
root_work_item_title: String(
|
|
4194
|
+
safeObject(sessionMatch.routeState).active_root_work_item_title
|
|
4195
|
+
|| safeObject(sessionMatch.routeState).last_root_work_item_title
|
|
4196
|
+
|| "",
|
|
4197
|
+
).trim(),
|
|
4198
|
+
root_work_item_status: String(
|
|
4199
|
+
safeObject(sessionMatch.routeState).active_root_work_item_status
|
|
4200
|
+
|| safeObject(sessionMatch.routeState).last_root_work_item_status
|
|
4201
|
+
|| "",
|
|
4202
|
+
).trim().toLowerCase(),
|
|
4203
|
+
};
|
|
4204
|
+
const seededRequest = upsertRunnerRequest(currentState, fallbackRequestKey, seedRequest);
|
|
4205
|
+
currentState.requests = seededRequest.requests;
|
|
4206
|
+
request = safeObject(seededRequest.request);
|
|
4207
|
+
saveBotRunnerState({
|
|
4208
|
+
routes: currentState.routes,
|
|
4209
|
+
sharedInboxes: currentState.sharedInboxes || currentState.shared_inboxes,
|
|
4210
|
+
excludedComments: currentState.excludedComments || currentState.excluded_comments,
|
|
4211
|
+
requests: currentState.requests,
|
|
4212
|
+
consumedComments: currentState.consumedComments || currentState.consumed_comments,
|
|
4213
|
+
});
|
|
4214
|
+
requests = [request];
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
3891
4217
|
if (!Object.keys(request).length) {
|
|
3892
4218
|
return {
|
|
3893
4219
|
ok: false,
|
|
@@ -4137,7 +4463,10 @@ function cleanupBotRunnerRequestState({
|
|
|
4137
4463
|
&& String(entry.conversation_id || "").trim() === String(conversationID || "").trim()
|
|
4138
4464
|
&& isActiveRunnerRequestStatus(entry.status)
|
|
4139
4465
|
));
|
|
4140
|
-
|
|
4466
|
+
const pendingContinuationResponders = ensureArray(session.next_expected_responders)
|
|
4467
|
+
.map((value) => normalizeTelegramMentionUsername(value))
|
|
4468
|
+
.filter(Boolean);
|
|
4469
|
+
if (!expired && (activeRequests.length > 0 || pendingContinuationResponders.length > 0)) {
|
|
4141
4470
|
continue;
|
|
4142
4471
|
}
|
|
4143
4472
|
const closedReason = expired ? "expired_session" : "orphaned_open_session";
|
|
@@ -7475,7 +7804,11 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
7475
7804
|
&& String(entry.conversation_id || "").trim() === conversationID
|
|
7476
7805
|
&& isActiveRunnerRequestStatus(entry.status)
|
|
7477
7806
|
));
|
|
7478
|
-
|
|
7807
|
+
if (activeRequests.length > 0) {
|
|
7808
|
+
return true;
|
|
7809
|
+
}
|
|
7810
|
+
const sessionMatch = findScopedConversationSessionState(latestRunnerState, normalizedRoute, conversationID);
|
|
7811
|
+
return sessionAllowsConversationResponder(sessionMatch.session, currentBotSelector);
|
|
7479
7812
|
}
|
|
7480
7813
|
return true;
|
|
7481
7814
|
});
|
|
@@ -7592,7 +7925,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
7592
7925
|
}
|
|
7593
7926
|
return null;
|
|
7594
7927
|
};
|
|
7595
|
-
const prepareRunnerRequestClaim = async (selectedRecord, selectedResponderSelectors = []) => {
|
|
7928
|
+
const prepareRunnerRequestClaim = async (selectedRecord, selectedResponderSelectors = [], sharedHumanIntent = null) => {
|
|
7596
7929
|
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
7597
7930
|
const kind = String(parsed.kind || "").trim().toLowerCase();
|
|
7598
7931
|
if (kind === "bot_reply") {
|
|
@@ -7622,6 +7955,8 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
7622
7955
|
routeKey,
|
|
7623
7956
|
selectedRecord,
|
|
7624
7957
|
selectedBotUsernames: selectedResponderSelectors,
|
|
7958
|
+
normalizedIntent: String(safeObject(sharedHumanIntent).intentType || "").trim().toLowerCase(),
|
|
7959
|
+
sharedHumanIntent,
|
|
7625
7960
|
runtime,
|
|
7626
7961
|
archiveThreadID: archiveThread.threadID,
|
|
7627
7962
|
});
|
|
@@ -7679,17 +8014,26 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
7679
8014
|
skippedRecords.push(startupLoopSkipped.skippedRecord);
|
|
7680
8015
|
continue;
|
|
7681
8016
|
}
|
|
8017
|
+
const routingExecutionDeps = {
|
|
8018
|
+
...buildRunnerExecutionDeps(),
|
|
8019
|
+
managedConversationBots,
|
|
8020
|
+
resolveConversationPeerBots: resolveRunnerConversationPeers,
|
|
8021
|
+
};
|
|
8022
|
+
const sharedHumanIntentContext = await resolveHumanIntentContext({
|
|
8023
|
+
selectedRecord,
|
|
8024
|
+
normalizedRoute,
|
|
8025
|
+
bot,
|
|
8026
|
+
executionPlan,
|
|
8027
|
+
deps: routingExecutionDeps,
|
|
8028
|
+
});
|
|
7682
8029
|
const adjudication = await resolveRunnerResponderAdjudication({
|
|
7683
8030
|
selectedRecord,
|
|
7684
8031
|
pendingOrdered: pending.ordered,
|
|
7685
8032
|
normalizedRoute,
|
|
7686
8033
|
bot,
|
|
7687
8034
|
executionPlan,
|
|
7688
|
-
deps:
|
|
7689
|
-
|
|
7690
|
-
managedConversationBots,
|
|
7691
|
-
resolveConversationPeerBots: resolveRunnerConversationPeers,
|
|
7692
|
-
},
|
|
8035
|
+
deps: routingExecutionDeps,
|
|
8036
|
+
precomputedHumanIntent: safeObject(sharedHumanIntentContext).humanIntent || null,
|
|
7693
8037
|
});
|
|
7694
8038
|
const currentBotSelected = ensureArray(adjudication.selected_bot_usernames)
|
|
7695
8039
|
.map((value) => String(value || "").trim().replace(/^@+/, "").toLowerCase())
|
|
@@ -7710,7 +8054,11 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
7710
8054
|
});
|
|
7711
8055
|
continue;
|
|
7712
8056
|
}
|
|
7713
|
-
const requestClaim = await prepareRunnerRequestClaim(
|
|
8057
|
+
const requestClaim = await prepareRunnerRequestClaim(
|
|
8058
|
+
selectedRecord,
|
|
8059
|
+
adjudication.selected_bot_usernames,
|
|
8060
|
+
safeObject(sharedHumanIntentContext).humanIntent || null,
|
|
8061
|
+
);
|
|
7714
8062
|
if (!requestClaim.ok) {
|
|
7715
8063
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
7716
8064
|
normalizedRoute,
|
|
@@ -7836,6 +8184,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
7836
8184
|
requestKey: String(requestClaim.requestKey || "").trim(),
|
|
7837
8185
|
triggerDecision,
|
|
7838
8186
|
responderAdjudication: adjudication,
|
|
8187
|
+
humanIntentContext: sharedHumanIntentContext,
|
|
7839
8188
|
},
|
|
7840
8189
|
};
|
|
7841
8190
|
}
|
|
@@ -7918,17 +8267,26 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
7918
8267
|
skippedRecords.push(startupLoopSkipped.skippedRecord);
|
|
7919
8268
|
continue;
|
|
7920
8269
|
}
|
|
8270
|
+
const routingExecutionDeps = {
|
|
8271
|
+
...buildRunnerExecutionDeps(),
|
|
8272
|
+
managedConversationBots,
|
|
8273
|
+
resolveConversationPeerBots: resolveRunnerConversationPeers,
|
|
8274
|
+
};
|
|
8275
|
+
const sharedHumanIntentContext = await resolveHumanIntentContext({
|
|
8276
|
+
selectedRecord,
|
|
8277
|
+
normalizedRoute,
|
|
8278
|
+
bot,
|
|
8279
|
+
executionPlan,
|
|
8280
|
+
deps: routingExecutionDeps,
|
|
8281
|
+
});
|
|
7921
8282
|
const inlineAdjudication = await resolveRunnerResponderAdjudication({
|
|
7922
8283
|
selectedRecord,
|
|
7923
8284
|
pendingOrdered: pending.ordered,
|
|
7924
8285
|
normalizedRoute,
|
|
7925
8286
|
bot,
|
|
7926
8287
|
executionPlan,
|
|
7927
|
-
deps:
|
|
7928
|
-
|
|
7929
|
-
managedConversationBots,
|
|
7930
|
-
resolveConversationPeerBots: resolveRunnerConversationPeers,
|
|
7931
|
-
},
|
|
8288
|
+
deps: routingExecutionDeps,
|
|
8289
|
+
precomputedHumanIntent: safeObject(sharedHumanIntentContext).humanIntent || null,
|
|
7932
8290
|
});
|
|
7933
8291
|
const inlineCurrentBotSelected = ensureArray(inlineAdjudication.selected_bot_usernames)
|
|
7934
8292
|
.map((value) => String(value || "").trim().replace(/^@+/, "").toLowerCase())
|
|
@@ -7950,7 +8308,11 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
7950
8308
|
continue;
|
|
7951
8309
|
}
|
|
7952
8310
|
const currentRouteState = safeObject(loadBotRunnerState().routes[routeKey]);
|
|
7953
|
-
const requestClaim = await prepareRunnerRequestClaim(
|
|
8311
|
+
const requestClaim = await prepareRunnerRequestClaim(
|
|
8312
|
+
selectedRecord,
|
|
8313
|
+
inlineAdjudication.selected_bot_usernames,
|
|
8314
|
+
safeObject(sharedHumanIntentContext).humanIntent || null,
|
|
8315
|
+
);
|
|
7954
8316
|
if (!requestClaim.ok) {
|
|
7955
8317
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
7956
8318
|
normalizedRoute,
|
|
@@ -8096,6 +8458,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
8096
8458
|
triggerDecision,
|
|
8097
8459
|
responderAdjudication: inlineAdjudication,
|
|
8098
8460
|
persistedHumanIntentRequest: claimedRequest,
|
|
8461
|
+
precomputedHumanIntentContext: sharedHumanIntentContext,
|
|
8099
8462
|
deps: {
|
|
8100
8463
|
saveRunnerRouteState,
|
|
8101
8464
|
startRunnerTypingHeartbeat,
|
|
@@ -10465,6 +10828,7 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
10465
10828
|
triggerDecision: deferredExecution.triggerDecision,
|
|
10466
10829
|
responderAdjudication: deferredExecution.responderAdjudication,
|
|
10467
10830
|
persistedHumanIntentRequest: loadRunnerRequestByKey(deferredExecution.requestKey),
|
|
10831
|
+
precomputedHumanIntentContext: safeObject(deferredExecution.humanIntentContext),
|
|
10468
10832
|
deps: {
|
|
10469
10833
|
saveRunnerRouteState,
|
|
10470
10834
|
startRunnerTypingHeartbeat,
|
|
@@ -14951,6 +15315,7 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
14951
15315
|
safeObject,
|
|
14952
15316
|
normalizeRunnerTriggerPolicy,
|
|
14953
15317
|
evaluateTelegramRunnerTrigger,
|
|
15318
|
+
resolveRunnerResponderAdjudication,
|
|
14954
15319
|
selectPendingArchiveComments,
|
|
14955
15320
|
selectRunnerPendingWork,
|
|
14956
15321
|
processRunnerSelectedRecord,
|
|
@@ -2067,7 +2067,7 @@ function buildConversationIntentAnalysisPrompt({
|
|
|
2067
2067
|
"You are a conversation intent contract parser for a public Telegram room with managed bots.",
|
|
2068
2068
|
"Infer the human's intended bot participation contract from the human message only.",
|
|
2069
2069
|
"Do not infer from prior bot replies. Do not invent bots outside managed_bots.",
|
|
2070
|
-
"Be conservative. If the
|
|
2070
|
+
"Be conservative only when the human truly does not indicate collaboration. If the message clearly asks multiple managed bots to discuss, review, brainstorm, compare perspectives, or talk together, do not collapse it to single_bot.",
|
|
2071
2071
|
"Also decide whether the human is asking for immediate execution/work now or only asking for information/explanation.",
|
|
2072
2072
|
"Also classify the current message intent type.",
|
|
2073
2073
|
"",
|
|
@@ -2092,12 +2092,13 @@ function buildConversationIntentAnalysisPrompt({
|
|
|
2092
2092
|
"- intent_type=bot_role_query for asking about bot roles, bot ownership, or how managed bots are meant to collaborate.",
|
|
2093
2093
|
"- intent_type=workspace_query for asking about the current workspace, project folder, or working directory.",
|
|
2094
2094
|
"- intent_type=artifact_location_query for asking where a file, document, pdf, guide, or artifact is located.",
|
|
2095
|
-
"- intent_type=explanation_query for explanation, clarification,
|
|
2095
|
+
"- intent_type=explanation_query for explanation, clarification, informational questions, discussion requests, review requests, brainstorming requests, or requests for bots to talk together about a topic when no concrete work execution is explicitly requested.",
|
|
2096
2096
|
"- intent_type=ctxpack_mutation for requests to create/update project guidance, instructions, rules, policies, or other ctxpack-backed source documents now.",
|
|
2097
2097
|
"- intent_type=workitem_mutation for requests to create/update actionable work items, backlog tasks, or task breakdowns now.",
|
|
2098
|
-
"- intent_type=general_execution for
|
|
2098
|
+
"- intent_type=general_execution only for concrete work requests that should execute now, such as creating/updating files, running tools/commands, modifying project artifacts, or producing validated deliverables.",
|
|
2099
|
+
"- If the human asks bots to discuss, debate, review, brainstorm, compare opinions, or hold a conversation about a topic, default to reply_expectation=informational unless they explicitly ask for concrete execution/output now.",
|
|
2099
2100
|
"- 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.",
|
|
2100
|
-
"- reply_expectation=informational when the human is only asking for explanation, status, location, clarification, or other non-execution information.",
|
|
2101
|
+
"- reply_expectation=informational when the human is only asking for explanation, status, location, clarification, discussion, review, brainstorming, or other non-execution information.",
|
|
2101
2102
|
"",
|
|
2102
2103
|
`managed_bots=${JSON.stringify(bots)}`,
|
|
2103
2104
|
`human_message=${JSON.stringify(String(messageText || "").trim())}`,
|
|
@@ -1004,7 +1004,7 @@ function buildHumanIntentFromPersistedRunnerRequest({
|
|
|
1004
1004
|
};
|
|
1005
1005
|
}
|
|
1006
1006
|
|
|
1007
|
-
async function resolveHumanIntentContext({
|
|
1007
|
+
export async function resolveHumanIntentContext({
|
|
1008
1008
|
selectedRecord,
|
|
1009
1009
|
normalizedRoute,
|
|
1010
1010
|
bot,
|
|
@@ -3957,6 +3957,7 @@ export async function resolveRunnerResponderAdjudication({
|
|
|
3957
3957
|
bot,
|
|
3958
3958
|
executionPlan,
|
|
3959
3959
|
deps,
|
|
3960
|
+
precomputedHumanIntent = null,
|
|
3960
3961
|
}) {
|
|
3961
3962
|
const cacheKey = buildRunnerResponderAdjudicationCacheKey({ normalizedRoute, selectedRecord });
|
|
3962
3963
|
if (runnerResponderAdjudicationPromises.has(cacheKey)) {
|
|
@@ -3972,6 +3973,11 @@ export async function resolveRunnerResponderAdjudication({
|
|
|
3972
3973
|
bot,
|
|
3973
3974
|
deps,
|
|
3974
3975
|
});
|
|
3976
|
+
const managedBotSelectors = new Set(
|
|
3977
|
+
managedBots
|
|
3978
|
+
.map((entry) => normalizeMentionSelector(entry.username))
|
|
3979
|
+
.filter(Boolean),
|
|
3980
|
+
);
|
|
3975
3981
|
const triggerFacts = {
|
|
3976
3982
|
message_kind: String(safeObject(selectedRecord?.parsedArchive).kind || "").trim(),
|
|
3977
3983
|
chat_type: String(safeObject(selectedRecord?.parsedArchive).chatType || "").trim(),
|
|
@@ -3987,6 +3993,47 @@ export async function resolveRunnerResponderAdjudication({
|
|
|
3987
3993
|
trigger_reason: String(entry.trigger_reason || "").trim(),
|
|
3988
3994
|
})),
|
|
3989
3995
|
};
|
|
3996
|
+
const humanIntent = safeObject(precomputedHumanIntent);
|
|
3997
|
+
const contractInitialResponders = uniqueOrdered(
|
|
3998
|
+
ensureArray(humanIntent.initialResponderSelectors)
|
|
3999
|
+
.map((value) => normalizeMentionSelector(value))
|
|
4000
|
+
.filter((value) => value && managedBotSelectors.has(value)),
|
|
4001
|
+
);
|
|
4002
|
+
const contractAllowedResponders = uniqueOrdered(
|
|
4003
|
+
ensureArray(humanIntent.allowedResponderSelectors)
|
|
4004
|
+
.map((value) => normalizeMentionSelector(value))
|
|
4005
|
+
.filter((value) => value && managedBotSelectors.has(value)),
|
|
4006
|
+
);
|
|
4007
|
+
const contractParticipants = uniqueOrdered(
|
|
4008
|
+
ensureArray(humanIntent.participantSelectors)
|
|
4009
|
+
.map((value) => normalizeMentionSelector(value))
|
|
4010
|
+
.filter((value) => value && managedBotSelectors.has(value)),
|
|
4011
|
+
);
|
|
4012
|
+
const contractLeadBot = normalizeMentionSelector(humanIntent.leadBotSelector);
|
|
4013
|
+
const selectedFromContract = uniqueOrdered(
|
|
4014
|
+
(
|
|
4015
|
+
contractInitialResponders.length
|
|
4016
|
+
? contractInitialResponders
|
|
4017
|
+
: contractAllowedResponders.length
|
|
4018
|
+
? contractAllowedResponders
|
|
4019
|
+
: contractParticipants.length
|
|
4020
|
+
? contractParticipants
|
|
4021
|
+
: contractLeadBot && managedBotSelectors.has(contractLeadBot)
|
|
4022
|
+
? [contractLeadBot]
|
|
4023
|
+
: []
|
|
4024
|
+
).filter(Boolean),
|
|
4025
|
+
);
|
|
4026
|
+
if (selectedFromContract.length > 0) {
|
|
4027
|
+
return {
|
|
4028
|
+
decision: selectedFromContract.length > 1 ? "multiple_responders" : "single_responder",
|
|
4029
|
+
selected_bot_usernames: selectedFromContract,
|
|
4030
|
+
referenced_bot_usernames: triggerFacts.mentioned_bot_usernames,
|
|
4031
|
+
confidence: "high",
|
|
4032
|
+
reason_code: "precomputed_human_intent_contract",
|
|
4033
|
+
clarification: "",
|
|
4034
|
+
managed_bots: managedBots,
|
|
4035
|
+
};
|
|
4036
|
+
}
|
|
3990
4037
|
if (!adjudicator) {
|
|
3991
4038
|
const fallbackSelected = managedBots
|
|
3992
4039
|
.filter((entry) => entry.trigger_eligible === true)
|
|
@@ -4109,6 +4156,7 @@ export async function processRunnerSelectedRecord({
|
|
|
4109
4156
|
triggerDecision: precomputedTriggerDecision = null,
|
|
4110
4157
|
responderAdjudication: precomputedResponderAdjudication = null,
|
|
4111
4158
|
persistedHumanIntentRequest = null,
|
|
4159
|
+
precomputedHumanIntentContext = null,
|
|
4112
4160
|
deps,
|
|
4113
4161
|
}) {
|
|
4114
4162
|
const saveRunnerRouteState = requireDependency(deps, "saveRunnerRouteState");
|
|
@@ -4128,6 +4176,8 @@ export async function processRunnerSelectedRecord({
|
|
|
4128
4176
|
...safeObject(buildRunnerExecutionDeps()),
|
|
4129
4177
|
...safeObject(deps),
|
|
4130
4178
|
};
|
|
4179
|
+
const normalizedPrecomputedHumanIntentContext = safeObject(precomputedHumanIntentContext);
|
|
4180
|
+
const normalizedPrecomputedHumanIntent = safeObject(normalizedPrecomputedHumanIntentContext.humanIntent);
|
|
4131
4181
|
const validateWorkspaceArtifacts = typeof executionDeps.validateWorkspaceArtifacts === "function"
|
|
4132
4182
|
? executionDeps.validateWorkspaceArtifacts
|
|
4133
4183
|
: null;
|
|
@@ -4172,6 +4222,7 @@ export async function processRunnerSelectedRecord({
|
|
|
4172
4222
|
bot,
|
|
4173
4223
|
executionPlan,
|
|
4174
4224
|
deps: executionDeps,
|
|
4225
|
+
precomputedHumanIntent: normalizedPrecomputedHumanIntent,
|
|
4175
4226
|
});
|
|
4176
4227
|
const selectedResponderSelectors = ensureArray(responderAdjudication.selected_bot_usernames)
|
|
4177
4228
|
.map((value) => normalizeMentionSelector(value))
|
|
@@ -4201,14 +4252,16 @@ export async function processRunnerSelectedRecord({
|
|
|
4201
4252
|
};
|
|
4202
4253
|
}
|
|
4203
4254
|
|
|
4204
|
-
const humanIntentContext =
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4255
|
+
const humanIntentContext = Object.keys(normalizedPrecomputedHumanIntentContext).length > 0
|
|
4256
|
+
? normalizedPrecomputedHumanIntentContext
|
|
4257
|
+
: await resolveHumanIntentContext({
|
|
4258
|
+
selectedRecord,
|
|
4259
|
+
normalizedRoute,
|
|
4260
|
+
bot,
|
|
4261
|
+
executionPlan,
|
|
4262
|
+
deps: executionDeps,
|
|
4263
|
+
persistedRequest: persistedHumanIntentRequest,
|
|
4264
|
+
});
|
|
4212
4265
|
const precomputedIntentType = normalizeHumanIntentType(
|
|
4213
4266
|
safeObject(safeObject(humanIntentContext).humanIntent).intentType,
|
|
4214
4267
|
);
|
|
@@ -118,6 +118,7 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
118
118
|
const safeObject = requireDependency(deps, "safeObject");
|
|
119
119
|
const normalizeRunnerTriggerPolicy = requireDependency(deps, "normalizeRunnerTriggerPolicy");
|
|
120
120
|
const evaluateTelegramRunnerTrigger = requireDependency(deps, "evaluateTelegramRunnerTrigger");
|
|
121
|
+
const resolveRunnerResponderAdjudication = requireDependency(deps, "resolveRunnerResponderAdjudication");
|
|
121
122
|
const selectPendingArchiveComments = requireDependency(deps, "selectPendingArchiveComments");
|
|
122
123
|
const selectRunnerPendingWork = requireDependency(deps, "selectRunnerPendingWork");
|
|
123
124
|
const processRunnerSelectedRecord = requireDependency(deps, "processRunnerSelectedRecord");
|
|
@@ -2228,6 +2229,134 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
2228
2229
|
}
|
|
2229
2230
|
}
|
|
2230
2231
|
|
|
2232
|
+
const originalStateMergeHome = process.env.HOME;
|
|
2233
|
+
const originalStateMergeUserProfile = process.env.USERPROFILE;
|
|
2234
|
+
let runnerStateMergeTempRoot = "";
|
|
2235
|
+
try {
|
|
2236
|
+
runnerStateMergeTempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-state-merge-selftest-"));
|
|
2237
|
+
const runnerStateMergeHome = path.join(runnerStateMergeTempRoot, "home");
|
|
2238
|
+
fs.mkdirSync(path.join(runnerStateMergeHome, ".metheus"), { recursive: true });
|
|
2239
|
+
process.env.HOME = runnerStateMergeHome;
|
|
2240
|
+
process.env.USERPROFILE = runnerStateMergeHome;
|
|
2241
|
+
const mergeRoute = normalizeRunnerRoute({
|
|
2242
|
+
name: "telegram-monitor-state-merge",
|
|
2243
|
+
project_id: selftestProjectID,
|
|
2244
|
+
provider: "telegram",
|
|
2245
|
+
role: "monitor",
|
|
2246
|
+
destination_label: "Main Room",
|
|
2247
|
+
});
|
|
2248
|
+
const mergeRouteKey = runnerRouteKey(mergeRoute);
|
|
2249
|
+
saveBotRunnerState({
|
|
2250
|
+
routes: {
|
|
2251
|
+
[mergeRouteKey]: {
|
|
2252
|
+
updated_at: "2026-03-24T06:29:05.000Z",
|
|
2253
|
+
last_request_key: "request-key-597",
|
|
2254
|
+
last_action: "running",
|
|
2255
|
+
conversation_sessions: {
|
|
2256
|
+
"conversation-597": {
|
|
2257
|
+
conversation_id: "conversation-597",
|
|
2258
|
+
status: "open",
|
|
2259
|
+
updated_at: "2026-03-24T06:29:05.000Z",
|
|
2260
|
+
last_activity_at: "2026-03-24T06:29:05.000Z",
|
|
2261
|
+
last_execution_contract_type: "delegation",
|
|
2262
|
+
next_expected_responders: ["ryoai2_bot", "ryoai3_bot"],
|
|
2263
|
+
},
|
|
2264
|
+
},
|
|
2265
|
+
},
|
|
2266
|
+
},
|
|
2267
|
+
sharedInboxes: {},
|
|
2268
|
+
excludedComments: {},
|
|
2269
|
+
requests: {
|
|
2270
|
+
"request-key-597": {
|
|
2271
|
+
request_key: "request-key-597",
|
|
2272
|
+
project_id: selftestProjectID,
|
|
2273
|
+
provider: "telegram",
|
|
2274
|
+
chat_id: "-100123",
|
|
2275
|
+
source_message_id: 597,
|
|
2276
|
+
conversation_id: "conversation-597",
|
|
2277
|
+
normalized_intent: "explanation_query",
|
|
2278
|
+
execution_contract_type: "delegation",
|
|
2279
|
+
execution_contract_actionable: true,
|
|
2280
|
+
next_expected_responders: ["ryoai2_bot", "ryoai3_bot"],
|
|
2281
|
+
status: "running",
|
|
2282
|
+
claimed_by_route: mergeRouteKey,
|
|
2283
|
+
root_work_item_id: "root-work-item-597",
|
|
2284
|
+
root_thread_id: "root-thread-597",
|
|
2285
|
+
updated_at: "2026-03-24T06:29:05.000Z",
|
|
2286
|
+
},
|
|
2287
|
+
},
|
|
2288
|
+
consumedComments: {},
|
|
2289
|
+
});
|
|
2290
|
+
saveBotRunnerState({
|
|
2291
|
+
routes: {
|
|
2292
|
+
[mergeRouteKey]: {
|
|
2293
|
+
updated_at: "2026-03-24T06:29:06.000Z",
|
|
2294
|
+
last_request_key: "request-key-597",
|
|
2295
|
+
last_action: "replied",
|
|
2296
|
+
conversation_sessions: {
|
|
2297
|
+
"conversation-597": {
|
|
2298
|
+
conversation_id: "conversation-597",
|
|
2299
|
+
status: "open",
|
|
2300
|
+
updated_at: "2026-03-24T06:29:06.000Z",
|
|
2301
|
+
},
|
|
2302
|
+
},
|
|
2303
|
+
},
|
|
2304
|
+
},
|
|
2305
|
+
sharedInboxes: {},
|
|
2306
|
+
excludedComments: {},
|
|
2307
|
+
requests: {
|
|
2308
|
+
"request-key-597": {
|
|
2309
|
+
request_key: "request-key-597",
|
|
2310
|
+
project_id: selftestProjectID,
|
|
2311
|
+
provider: "telegram",
|
|
2312
|
+
chat_id: "-100123",
|
|
2313
|
+
source_message_id: 597,
|
|
2314
|
+
conversation_id: "conversation-597",
|
|
2315
|
+
normalized_intent: "general_execution",
|
|
2316
|
+
status: "claimed",
|
|
2317
|
+
claimed_by_route: mergeRouteKey,
|
|
2318
|
+
root_work_item_id: "",
|
|
2319
|
+
root_thread_id: "",
|
|
2320
|
+
next_expected_responders: [],
|
|
2321
|
+
updated_at: "2026-03-24T06:28:30.000Z",
|
|
2322
|
+
},
|
|
2323
|
+
},
|
|
2324
|
+
consumedComments: {},
|
|
2325
|
+
});
|
|
2326
|
+
const mergedState = loadBotRunnerState();
|
|
2327
|
+
const mergedRouteState = safeObject(safeObject(mergedState.routes)[mergeRouteKey]);
|
|
2328
|
+
const mergedSession = safeObject(safeObject(mergedRouteState.conversation_sessions)["conversation-597"]);
|
|
2329
|
+
const mergedRequest = safeObject(safeObject(mergedState.requests)["request-key-597"]);
|
|
2330
|
+
push(
|
|
2331
|
+
"runner_state_save_preserves_newer_request_and_conversation_session",
|
|
2332
|
+
String(mergedRequest.status || "") === "running"
|
|
2333
|
+
&& String(mergedRequest.root_work_item_id || "") === "root-work-item-597"
|
|
2334
|
+
&& ensureArray(mergedRequest.next_expected_responders).includes("ryoai2_bot")
|
|
2335
|
+
&& String(mergedSession.status || "") === "open"
|
|
2336
|
+
&& String(mergedSession.last_execution_contract_type || "") === "delegation"
|
|
2337
|
+
&& ensureArray(mergedSession.next_expected_responders).includes("ryoai3_bot"),
|
|
2338
|
+
`request_status=${String(mergedRequest.status || "(none)")} root_work_item=${String(mergedRequest.root_work_item_id || "(none)")} session_contract=${String(mergedSession.last_execution_contract_type || "(none)")} next=${ensureArray(mergedSession.next_expected_responders).join(",")}`,
|
|
2339
|
+
);
|
|
2340
|
+
} catch (err) {
|
|
2341
|
+
push("runner_state_save_preserves_newer_request_and_conversation_session", false, String(err?.message || err));
|
|
2342
|
+
} finally {
|
|
2343
|
+
if (typeof originalStateMergeHome === "string") {
|
|
2344
|
+
process.env.HOME = originalStateMergeHome;
|
|
2345
|
+
} else {
|
|
2346
|
+
delete process.env.HOME;
|
|
2347
|
+
}
|
|
2348
|
+
if (typeof originalStateMergeUserProfile === "string") {
|
|
2349
|
+
process.env.USERPROFILE = originalStateMergeUserProfile;
|
|
2350
|
+
} else {
|
|
2351
|
+
delete process.env.USERPROFILE;
|
|
2352
|
+
}
|
|
2353
|
+
if (runnerStateMergeTempRoot) {
|
|
2354
|
+
try {
|
|
2355
|
+
fs.rmSync(runnerStateMergeTempRoot, { recursive: true, force: true });
|
|
2356
|
+
} catch {}
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2231
2360
|
const defaultMonitorTriggerPolicy = normalizeRunnerTriggerPolicy({}, { role: "monitor" });
|
|
2232
2361
|
push(
|
|
2233
2362
|
"bot_runner_default_monitor_trigger_policy",
|
|
@@ -2935,6 +3064,108 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
2935
3064
|
push("public_multi_bot_human_intent_is_computed_once_per_message", false, String(err?.message || err));
|
|
2936
3065
|
}
|
|
2937
3066
|
|
|
3067
|
+
try {
|
|
3068
|
+
let adjudicatorCalls = 0;
|
|
3069
|
+
const adjudication = await resolveRunnerResponderAdjudication({
|
|
3070
|
+
selectedRecord: {
|
|
3071
|
+
id: "comment-precomputed-human-intent-adjudication",
|
|
3072
|
+
parsedArchive: {
|
|
3073
|
+
kind: "telegram_message",
|
|
3074
|
+
chatID: "-100123",
|
|
3075
|
+
chatType: "supergroup",
|
|
3076
|
+
senderIsBot: false,
|
|
3077
|
+
body: "@RyoAI_bot @RyoAI2_bot @RyoAI3_bot discuss this project together.",
|
|
3078
|
+
mentionUsernames: ["RyoAI_bot", "RyoAI2_bot", "RyoAI3_bot"],
|
|
3079
|
+
messageID: 1193,
|
|
3080
|
+
},
|
|
3081
|
+
},
|
|
3082
|
+
pendingOrdered: [],
|
|
3083
|
+
normalizedRoute: normalizeRunnerRoute({
|
|
3084
|
+
name: "telegram-monitor-precomputed-human-intent-adjudication",
|
|
3085
|
+
project_id: selftestProjectID,
|
|
3086
|
+
provider: "telegram",
|
|
3087
|
+
role: "monitor",
|
|
3088
|
+
role_profile: "monitor",
|
|
3089
|
+
destination_id: "dest-1",
|
|
3090
|
+
destination_label: "Main Room",
|
|
3091
|
+
server_bot_name: "RyoAI_bot",
|
|
3092
|
+
server_bot_id: "bot-lead-1",
|
|
3093
|
+
trigger_policy: {
|
|
3094
|
+
mentions_only: true,
|
|
3095
|
+
direct_messages: true,
|
|
3096
|
+
reply_to_bot_messages: true,
|
|
3097
|
+
},
|
|
3098
|
+
archive_policy: {
|
|
3099
|
+
mirror_replies: true,
|
|
3100
|
+
dedupe_inbound: true,
|
|
3101
|
+
dedupe_outbound: true,
|
|
3102
|
+
skip_bot_messages: true,
|
|
3103
|
+
},
|
|
3104
|
+
dry_run_delivery: true,
|
|
3105
|
+
}),
|
|
3106
|
+
bot: {
|
|
3107
|
+
id: "bot-lead-1",
|
|
3108
|
+
name: "RyoAI_bot",
|
|
3109
|
+
username: "RyoAI_bot",
|
|
3110
|
+
role: "monitor",
|
|
3111
|
+
provider: "telegram",
|
|
3112
|
+
},
|
|
3113
|
+
executionPlan: {
|
|
3114
|
+
mode: "role_profile",
|
|
3115
|
+
roleProfileName: "monitor",
|
|
3116
|
+
roleProfile: {
|
|
3117
|
+
client: "sample",
|
|
3118
|
+
model: "",
|
|
3119
|
+
permissionMode: "read_only",
|
|
3120
|
+
reasoningEffort: "low",
|
|
3121
|
+
},
|
|
3122
|
+
workspaceDir: path.join(os.tmpdir(), "metheus-runner-selftest-precomputed-human-intent-adjudication"),
|
|
3123
|
+
workspaceSource: "selftest",
|
|
3124
|
+
usedCommandFallback: false,
|
|
3125
|
+
},
|
|
3126
|
+
precomputedHumanIntent: {
|
|
3127
|
+
intentMode: "delegated_single_lead",
|
|
3128
|
+
intentType: "explanation_query",
|
|
3129
|
+
participantSelectors: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
3130
|
+
initialResponderSelectors: ["ryoai_bot"],
|
|
3131
|
+
allowedResponderSelectors: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
3132
|
+
leadBotSelector: "ryoai_bot",
|
|
3133
|
+
summaryBotSelector: "ryoai_bot",
|
|
3134
|
+
allowBotToBot: true,
|
|
3135
|
+
replyExpectation: "informational",
|
|
3136
|
+
},
|
|
3137
|
+
deps: {
|
|
3138
|
+
adjudicateRunnerRespondersWithAI: async () => {
|
|
3139
|
+
adjudicatorCalls += 1;
|
|
3140
|
+
return {
|
|
3141
|
+
decision: "multiple_responders",
|
|
3142
|
+
selected_bot_usernames: ["ryoai2_bot", "ryoai3_bot"],
|
|
3143
|
+
};
|
|
3144
|
+
},
|
|
3145
|
+
managedConversationBots: [
|
|
3146
|
+
{ route: { serverBotName: "RyoAI_bot" }, bot: { username: "RyoAI_bot", name: "RyoAI_bot" } },
|
|
3147
|
+
{ route: { serverBotName: "RyoAI2_bot" }, bot: { username: "RyoAI2_bot", name: "RyoAI2_bot" } },
|
|
3148
|
+
{ route: { serverBotName: "RyoAI3_bot" }, bot: { username: "RyoAI3_bot", name: "RyoAI3_bot" } },
|
|
3149
|
+
],
|
|
3150
|
+
resolveConversationPeerBots: () => [
|
|
3151
|
+
{ id: "bot-lead-1", name: "RyoAI_bot" },
|
|
3152
|
+
{ id: "bot-peer-1", name: "RyoAI2_bot" },
|
|
3153
|
+
{ id: "bot-peer-2", name: "RyoAI3_bot" },
|
|
3154
|
+
],
|
|
3155
|
+
},
|
|
3156
|
+
});
|
|
3157
|
+
push(
|
|
3158
|
+
"precomputed_human_intent_contract_drives_responder_selection",
|
|
3159
|
+
adjudicatorCalls === 0
|
|
3160
|
+
&& ensureArray(adjudication.selected_bot_usernames).length === 1
|
|
3161
|
+
&& String(ensureArray(adjudication.selected_bot_usernames)[0] || "") === "ryoai_bot"
|
|
3162
|
+
&& String(adjudication.reason_code || "") === "precomputed_human_intent_contract",
|
|
3163
|
+
`adjudicator_calls=${adjudicatorCalls} selected=${JSON.stringify(ensureArray(adjudication.selected_bot_usernames))} reason=${String(adjudication.reason_code || "(none)")}`,
|
|
3164
|
+
);
|
|
3165
|
+
} catch (err) {
|
|
3166
|
+
push("precomputed_human_intent_contract_drives_responder_selection", false, String(err?.message || err));
|
|
3167
|
+
}
|
|
3168
|
+
|
|
2938
3169
|
try {
|
|
2939
3170
|
let humanIntentCalls = 0;
|
|
2940
3171
|
const processed = await processRunnerSelectedRecord({
|