metheus-governance-mcp-cli 0.2.205 → 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 +330 -9
- package/lib/selftest-runner-scenarios.mjs +128 -0
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -2065,14 +2065,176 @@ function saveBotRunnerState(nextState) {
|
|
|
2065
2065
|
try {
|
|
2066
2066
|
current = safeObject(tryJsonParse(fs.readFileSync(filePath, "utf8")));
|
|
2067
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
|
+
};
|
|
2068
2215
|
const payload = {
|
|
2069
2216
|
version: 1,
|
|
2070
2217
|
updated_at: new Date().toISOString(),
|
|
2071
|
-
routes:
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
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
|
+
),
|
|
2076
2238
|
};
|
|
2077
2239
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
2078
2240
|
fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
@@ -2507,6 +2669,89 @@ function findRunnerRequestsForScope(state, normalizedRoute, selectors = {}) {
|
|
|
2507
2669
|
});
|
|
2508
2670
|
}
|
|
2509
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
|
+
|
|
2510
2755
|
function findRunnerRequestsForMessageID(state, normalizedRoute, selectors = {}) {
|
|
2511
2756
|
const chatID = String(selectors.chatID || "").trim();
|
|
2512
2757
|
const messageID = intFromRawAllowZero(selectors.messageID, 0);
|
|
@@ -3895,11 +4140,80 @@ function resolveRunnerContinuationRequestForBotReply({
|
|
|
3895
4140
|
};
|
|
3896
4141
|
}
|
|
3897
4142
|
const currentState = loadBotRunnerState();
|
|
3898
|
-
|
|
4143
|
+
let requests = findRunnerRequestsForScope(currentState, normalizedRoute, {
|
|
3899
4144
|
conversationID,
|
|
3900
4145
|
chatID: String(parsed.chatID || parsed.chatId || "").trim(),
|
|
3901
4146
|
}).filter((entry) => isActiveRunnerRequestStatus(entry.status));
|
|
3902
|
-
|
|
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
|
+
}
|
|
3903
4217
|
if (!Object.keys(request).length) {
|
|
3904
4218
|
return {
|
|
3905
4219
|
ok: false,
|
|
@@ -4149,7 +4463,10 @@ function cleanupBotRunnerRequestState({
|
|
|
4149
4463
|
&& String(entry.conversation_id || "").trim() === String(conversationID || "").trim()
|
|
4150
4464
|
&& isActiveRunnerRequestStatus(entry.status)
|
|
4151
4465
|
));
|
|
4152
|
-
|
|
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)) {
|
|
4153
4470
|
continue;
|
|
4154
4471
|
}
|
|
4155
4472
|
const closedReason = expired ? "expired_session" : "orphaned_open_session";
|
|
@@ -7487,7 +7804,11 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
7487
7804
|
&& String(entry.conversation_id || "").trim() === conversationID
|
|
7488
7805
|
&& isActiveRunnerRequestStatus(entry.status)
|
|
7489
7806
|
));
|
|
7490
|
-
|
|
7807
|
+
if (activeRequests.length > 0) {
|
|
7808
|
+
return true;
|
|
7809
|
+
}
|
|
7810
|
+
const sessionMatch = findScopedConversationSessionState(latestRunnerState, normalizedRoute, conversationID);
|
|
7811
|
+
return sessionAllowsConversationResponder(sessionMatch.session, currentBotSelector);
|
|
7491
7812
|
}
|
|
7492
7813
|
return true;
|
|
7493
7814
|
});
|
|
@@ -2229,6 +2229,134 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
2229
2229
|
}
|
|
2230
2230
|
}
|
|
2231
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
|
+
|
|
2232
2360
|
const defaultMonitorTriggerPolicy = normalizeRunnerTriggerPolicy({}, { role: "monitor" });
|
|
2233
2361
|
push(
|
|
2234
2362
|
"bot_runner_default_monitor_trigger_policy",
|