metheus-governance-mcp-cli 0.2.192 → 0.2.193
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 +780 -7
- package/lib/runner-helpers.mjs +1 -0
- package/lib/selftest-runner-scenarios.mjs +135 -0
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -213,6 +213,7 @@ const BOT_RUNNER_ACTIVE_EXECUTION_HEARTBEAT_INTERVAL_MS = 5000;
|
|
|
213
213
|
const BOT_RUNNER_ACTIVE_EXECUTION_HEARTBEAT_STALE_MS = 60 * 1000;
|
|
214
214
|
const BOT_RUNNER_PENDING_COMMENT_MAX_AGE_MS = 15 * 60 * 1000;
|
|
215
215
|
const BOT_RUNNER_EXCLUDED_COMMENT_KEEP_MS = 14 * 24 * 60 * 60 * 1000;
|
|
216
|
+
const BOT_RUNNER_REQUEST_KEEP_MS = 14 * 24 * 60 * 60 * 1000;
|
|
216
217
|
const BOT_RUNNER_DEFAULT_CONCURRENCY = 2;
|
|
217
218
|
const BOT_RUNNER_MAX_CONCURRENCY = 8;
|
|
218
219
|
const TELEGRAM_ROOT_LEGACY_ENV_RELATIVE_PATH = path.join(".metheus", "telegram.env");
|
|
@@ -1998,6 +1999,8 @@ function loadBotRunnerState() {
|
|
|
1998
1999
|
routes: {},
|
|
1999
2000
|
sharedInboxes: {},
|
|
2000
2001
|
excludedComments: {},
|
|
2002
|
+
requests: {},
|
|
2003
|
+
consumedComments: {},
|
|
2001
2004
|
migrated: false,
|
|
2002
2005
|
migratedKeys: [],
|
|
2003
2006
|
remainingAnonymousKeys: [],
|
|
@@ -2011,6 +2014,8 @@ function loadBotRunnerState() {
|
|
|
2011
2014
|
routes: migratedState.routes,
|
|
2012
2015
|
sharedInboxes: safeObject(parsed?.shared_inboxes || parsed?.sharedInboxes),
|
|
2013
2016
|
excludedComments: safeObject(parsed?.excluded_comments || parsed?.excludedComments),
|
|
2017
|
+
requests: safeObject(parsed?.requests),
|
|
2018
|
+
consumedComments: safeObject(parsed?.consumed_comments || parsed?.consumedComments),
|
|
2014
2019
|
});
|
|
2015
2020
|
}
|
|
2016
2021
|
return {
|
|
@@ -2018,6 +2023,8 @@ function loadBotRunnerState() {
|
|
|
2018
2023
|
routes: migratedState.routes,
|
|
2019
2024
|
sharedInboxes: safeObject(parsed?.shared_inboxes || parsed?.sharedInboxes),
|
|
2020
2025
|
excludedComments: normalizeBotRunnerExcludedComments(parsed?.excluded_comments || parsed?.excludedComments),
|
|
2026
|
+
requests: normalizeBotRunnerRequests(parsed?.requests),
|
|
2027
|
+
consumedComments: normalizeBotRunnerConsumedComments(parsed?.consumed_comments || parsed?.consumedComments),
|
|
2021
2028
|
migrated: migratedState.changed,
|
|
2022
2029
|
migratedKeys: migratedState.migratedKeys,
|
|
2023
2030
|
remainingAnonymousKeys: migratedState.remainingAnonymousKeys,
|
|
@@ -2028,6 +2035,8 @@ function loadBotRunnerState() {
|
|
|
2028
2035
|
routes: {},
|
|
2029
2036
|
sharedInboxes: {},
|
|
2030
2037
|
excludedComments: {},
|
|
2038
|
+
requests: {},
|
|
2039
|
+
consumedComments: {},
|
|
2031
2040
|
migrated: false,
|
|
2032
2041
|
migratedKeys: [],
|
|
2033
2042
|
remainingAnonymousKeys: [],
|
|
@@ -2047,6 +2056,8 @@ function saveBotRunnerState(nextState) {
|
|
|
2047
2056
|
routes: safeObject(nextState?.routes ?? current.routes),
|
|
2048
2057
|
shared_inboxes: safeObject(nextState?.sharedInboxes ?? nextState?.shared_inboxes ?? current.shared_inboxes ?? current.sharedInboxes),
|
|
2049
2058
|
excluded_comments: normalizeBotRunnerExcludedComments(nextState?.excludedComments ?? nextState?.excluded_comments ?? current.excluded_comments ?? current.excludedComments),
|
|
2059
|
+
requests: normalizeBotRunnerRequests(nextState?.requests ?? current.requests),
|
|
2060
|
+
consumed_comments: normalizeBotRunnerConsumedComments(nextState?.consumedComments ?? nextState?.consumed_comments ?? current.consumed_comments ?? current.consumedComments),
|
|
2050
2061
|
};
|
|
2051
2062
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
2052
2063
|
fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
@@ -2079,6 +2090,100 @@ function normalizeBotRunnerExcludedComments(rawExcluded, nowMs = Date.now()) {
|
|
|
2079
2090
|
return normalized;
|
|
2080
2091
|
}
|
|
2081
2092
|
|
|
2093
|
+
function normalizeRunnerRequestStatus(rawStatus) {
|
|
2094
|
+
const status = String(rawStatus || "").trim().toLowerCase();
|
|
2095
|
+
return [
|
|
2096
|
+
"planned",
|
|
2097
|
+
"claimed",
|
|
2098
|
+
"running",
|
|
2099
|
+
"completed",
|
|
2100
|
+
"closed",
|
|
2101
|
+
"expired",
|
|
2102
|
+
"loop_closed",
|
|
2103
|
+
].includes(status)
|
|
2104
|
+
? status
|
|
2105
|
+
: "planned";
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
function isFinalRunnerRequestStatus(rawStatus) {
|
|
2109
|
+
const status = normalizeRunnerRequestStatus(rawStatus);
|
|
2110
|
+
return status === "completed" || status === "closed" || status === "expired" || status === "loop_closed";
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
function isActiveRunnerRequestStatus(rawStatus) {
|
|
2114
|
+
const status = normalizeRunnerRequestStatus(rawStatus);
|
|
2115
|
+
return status === "planned" || status === "claimed" || status === "running";
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
function normalizeBotRunnerRequests(rawRequests, nowMs = Date.now()) {
|
|
2119
|
+
const normalized = {};
|
|
2120
|
+
for (const [requestKeyRaw, entryRaw] of Object.entries(safeObject(rawRequests))) {
|
|
2121
|
+
const requestKey = String(requestKeyRaw || safeObject(entryRaw).request_key || "").trim();
|
|
2122
|
+
if (!requestKey) continue;
|
|
2123
|
+
const entry = safeObject(entryRaw);
|
|
2124
|
+
const status = normalizeRunnerRequestStatus(entry.status);
|
|
2125
|
+
const updatedAt = firstNonEmptyString([entry.updated_at, entry.updatedAt, entry.completed_at, entry.completedAt, entry.closed_at, entry.closedAt, entry.claimed_at, entry.claimedAt]);
|
|
2126
|
+
const updatedAtMs = Date.parse(updatedAt);
|
|
2127
|
+
if (isFinalRunnerRequestStatus(status) && Number.isFinite(updatedAtMs) && nowMs - updatedAtMs > BOT_RUNNER_REQUEST_KEEP_MS) {
|
|
2128
|
+
continue;
|
|
2129
|
+
}
|
|
2130
|
+
normalized[requestKey] = {
|
|
2131
|
+
request_key: requestKey,
|
|
2132
|
+
project_id: String(entry.project_id || entry.projectID || "").trim(),
|
|
2133
|
+
provider: String(entry.provider || "").trim(),
|
|
2134
|
+
chat_id: String(entry.chat_id || entry.chatID || "").trim(),
|
|
2135
|
+
source_message_id: intFromRawAllowZero(entry.source_message_id || entry.sourceMessageID, 0) || undefined,
|
|
2136
|
+
root_comment_id: String(entry.root_comment_id || entry.rootCommentID || "").trim(),
|
|
2137
|
+
root_comment_kind: String(entry.root_comment_kind || entry.rootCommentKind || "").trim().toLowerCase(),
|
|
2138
|
+
conversation_id: String(entry.conversation_id || entry.conversationId || "").trim(),
|
|
2139
|
+
selected_bot_usernames: ensureArray(entry.selected_bot_usernames || entry.selectedBotUsernames)
|
|
2140
|
+
.map((value) => normalizeTelegramMentionUsername(value))
|
|
2141
|
+
.filter(Boolean),
|
|
2142
|
+
conversation_allowed_responders: ensureArray(entry.conversation_allowed_responders || entry.conversationAllowedResponders)
|
|
2143
|
+
.map((value) => normalizeTelegramMentionUsername(value))
|
|
2144
|
+
.filter(Boolean),
|
|
2145
|
+
normalized_intent: String(entry.normalized_intent || entry.normalizedIntent || "").trim().toLowerCase(),
|
|
2146
|
+
status,
|
|
2147
|
+
claimed_by_route: String(entry.claimed_by_route || entry.claimedByRoute || "").trim(),
|
|
2148
|
+
claimed_at: firstNonEmptyString([entry.claimed_at, entry.claimedAt]),
|
|
2149
|
+
started_at: firstNonEmptyString([entry.started_at, entry.startedAt]),
|
|
2150
|
+
completed_at: firstNonEmptyString([entry.completed_at, entry.completedAt]),
|
|
2151
|
+
closed_at: firstNonEmptyString([entry.closed_at, entry.closedAt]),
|
|
2152
|
+
closed_reason: String(entry.closed_reason || entry.closedReason || "").trim(),
|
|
2153
|
+
last_comment_id: String(entry.last_comment_id || entry.lastCommentID || "").trim(),
|
|
2154
|
+
last_comment_kind: String(entry.last_comment_kind || entry.lastCommentKind || "").trim().toLowerCase(),
|
|
2155
|
+
last_source_message_id: intFromRawAllowZero(entry.last_source_message_id || entry.lastSourceMessageID, 0) || undefined,
|
|
2156
|
+
updated_at: updatedAt || new Date(nowMs).toISOString(),
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2159
|
+
return normalized;
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
function normalizeBotRunnerConsumedComments(rawConsumed, nowMs = Date.now()) {
|
|
2163
|
+
const normalized = {};
|
|
2164
|
+
for (const [commentIDRaw, entryRaw] of Object.entries(safeObject(rawConsumed))) {
|
|
2165
|
+
const commentID = String(commentIDRaw || safeObject(entryRaw).comment_id || "").trim();
|
|
2166
|
+
if (!commentID) continue;
|
|
2167
|
+
const entry = safeObject(entryRaw);
|
|
2168
|
+
const consumedAt = firstNonEmptyString([entry.consumed_at, entry.consumedAt, entry.updated_at, entry.updatedAt]);
|
|
2169
|
+
const consumedAtMs = Date.parse(consumedAt);
|
|
2170
|
+
if (Number.isFinite(consumedAtMs) && nowMs - consumedAtMs > BOT_RUNNER_REQUEST_KEEP_MS) {
|
|
2171
|
+
continue;
|
|
2172
|
+
}
|
|
2173
|
+
normalized[commentID] = {
|
|
2174
|
+
comment_id: commentID,
|
|
2175
|
+
request_key: String(entry.request_key || entry.requestKey || "").trim(),
|
|
2176
|
+
consumed_at: consumedAt || new Date(nowMs).toISOString(),
|
|
2177
|
+
route_key: String(entry.route_key || entry.routeKey || "").trim(),
|
|
2178
|
+
conversation_id: String(entry.conversation_id || entry.conversationId || "").trim(),
|
|
2179
|
+
source_message_id: intFromRawAllowZero(entry.source_message_id || entry.sourceMessageID, 0) || undefined,
|
|
2180
|
+
comment_kind: String(entry.comment_kind || entry.commentKind || "").trim().toLowerCase(),
|
|
2181
|
+
request_status: normalizeRunnerRequestStatus(entry.request_status || entry.requestStatus),
|
|
2182
|
+
};
|
|
2183
|
+
}
|
|
2184
|
+
return normalized;
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2082
2187
|
function runnerRouteMatchesProjectConversationScope(candidateRouteRaw, normalizedRouteRaw) {
|
|
2083
2188
|
const candidateRoute = normalizeRunnerRoute(candidateRouteRaw);
|
|
2084
2189
|
const normalizedRoute = normalizeRunnerRoute(normalizedRouteRaw);
|
|
@@ -2211,6 +2316,452 @@ function collectBotRunnerConversationSessionFacts(routeRaw, conversationIDRaw) {
|
|
|
2211
2316
|
};
|
|
2212
2317
|
}
|
|
2213
2318
|
|
|
2319
|
+
function uniqueOrderedStrings(values, normalizer = (value) => String(value || "").trim()) {
|
|
2320
|
+
const output = [];
|
|
2321
|
+
const seen = new Set();
|
|
2322
|
+
for (const value of ensureArray(values)) {
|
|
2323
|
+
const normalized = normalizer(value);
|
|
2324
|
+
if (!normalized || seen.has(normalized)) continue;
|
|
2325
|
+
seen.add(normalized);
|
|
2326
|
+
output.push(normalized);
|
|
2327
|
+
}
|
|
2328
|
+
return output;
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
function buildRunnerRequestKey({
|
|
2332
|
+
normalizedRoute,
|
|
2333
|
+
selectedRecord,
|
|
2334
|
+
selectedBotUsernames = [],
|
|
2335
|
+
normalizedIntent = "",
|
|
2336
|
+
}) {
|
|
2337
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2338
|
+
const chatID = String(parsed.chatID || parsed.chatId || "").trim() || "-";
|
|
2339
|
+
const messageID = intFromRawAllowZero(parsed.messageID, 0);
|
|
2340
|
+
const kind = String(parsed.kind || "").trim().toLowerCase() || "-";
|
|
2341
|
+
const responders = uniqueOrderedStrings(selectedBotUsernames, normalizeTelegramMentionUsername).join(",") || "-";
|
|
2342
|
+
const intent = String(normalizedIntent || "").trim().toLowerCase() || "-";
|
|
2343
|
+
const conversationID = String(parsed.conversationID || "").trim() || "-";
|
|
2344
|
+
return [
|
|
2345
|
+
String(normalizedRoute?.projectID || "").trim() || "-",
|
|
2346
|
+
String(normalizedRoute?.provider || "").trim() || "-",
|
|
2347
|
+
chatID,
|
|
2348
|
+
String(messageID || "-"),
|
|
2349
|
+
kind,
|
|
2350
|
+
responders,
|
|
2351
|
+
intent,
|
|
2352
|
+
conversationID,
|
|
2353
|
+
].join("::");
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
function findRunnerRequestsForScope(state, normalizedRoute, selectors = {}) {
|
|
2357
|
+
const requests = normalizeBotRunnerRequests(state?.requests);
|
|
2358
|
+
const projectID = String(normalizedRoute?.projectID || "").trim();
|
|
2359
|
+
const provider = String(normalizedRoute?.provider || "").trim();
|
|
2360
|
+
const conversationID = String(selectors.conversationID || "").trim();
|
|
2361
|
+
const chatID = String(selectors.chatID || "").trim();
|
|
2362
|
+
const requestKey = String(selectors.requestKey || "").trim();
|
|
2363
|
+
return Object.values(requests)
|
|
2364
|
+
.filter((entry) => !requestKey || entry.request_key === requestKey)
|
|
2365
|
+
.filter((entry) => !projectID || entry.project_id === projectID)
|
|
2366
|
+
.filter((entry) => !provider || entry.provider === provider)
|
|
2367
|
+
.filter((entry) => !chatID || entry.chat_id === chatID)
|
|
2368
|
+
.filter((entry) => !conversationID || entry.conversation_id === conversationID)
|
|
2369
|
+
.sort((left, right) => {
|
|
2370
|
+
const leftTime = firstNonEmptyString([left.updated_at, left.completed_at, left.closed_at, left.claimed_at]);
|
|
2371
|
+
const rightTime = firstNonEmptyString([right.updated_at, right.completed_at, right.closed_at, right.claimed_at]);
|
|
2372
|
+
if (leftTime && rightTime && leftTime !== rightTime) {
|
|
2373
|
+
return leftTime < rightTime ? 1 : -1;
|
|
2374
|
+
}
|
|
2375
|
+
return String(left.request_key || "").localeCompare(String(right.request_key || ""));
|
|
2376
|
+
});
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
function upsertRunnerRequest(state, requestKey, patch = {}) {
|
|
2380
|
+
const currentState = safeObject(state);
|
|
2381
|
+
const requests = normalizeBotRunnerRequests(currentState.requests);
|
|
2382
|
+
const existing = safeObject(requests[requestKey]);
|
|
2383
|
+
const nextEntry = {
|
|
2384
|
+
...existing,
|
|
2385
|
+
...safeObject(patch),
|
|
2386
|
+
request_key: requestKey,
|
|
2387
|
+
status: normalizeRunnerRequestStatus(patch.status || existing.status),
|
|
2388
|
+
updated_at: new Date().toISOString(),
|
|
2389
|
+
};
|
|
2390
|
+
requests[requestKey] = nextEntry;
|
|
2391
|
+
return {
|
|
2392
|
+
requests,
|
|
2393
|
+
request: nextEntry,
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
function upsertRunnerConsumedComment(state, commentIDRaw, patch = {}) {
|
|
2398
|
+
const commentID = String(commentIDRaw || "").trim();
|
|
2399
|
+
const currentState = safeObject(state);
|
|
2400
|
+
const consumedComments = normalizeBotRunnerConsumedComments(currentState.consumedComments || currentState.consumed_comments);
|
|
2401
|
+
const existing = safeObject(consumedComments[commentID]);
|
|
2402
|
+
const nextEntry = {
|
|
2403
|
+
...existing,
|
|
2404
|
+
...safeObject(patch),
|
|
2405
|
+
comment_id: commentID,
|
|
2406
|
+
consumed_at: new Date().toISOString(),
|
|
2407
|
+
request_status: normalizeRunnerRequestStatus(patch.request_status || existing.request_status),
|
|
2408
|
+
};
|
|
2409
|
+
consumedComments[commentID] = nextEntry;
|
|
2410
|
+
return {
|
|
2411
|
+
consumedComments,
|
|
2412
|
+
consumedComment: nextEntry,
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
function claimRunnerRequestForHumanComment({
|
|
2417
|
+
normalizedRoute,
|
|
2418
|
+
routeKey,
|
|
2419
|
+
selectedRecord,
|
|
2420
|
+
selectedBotUsernames = [],
|
|
2421
|
+
normalizedIntent = "",
|
|
2422
|
+
}) {
|
|
2423
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2424
|
+
const commentKind = String(parsed.kind || "").trim().toLowerCase();
|
|
2425
|
+
if (!isInboundArchiveKind(commentKind)) {
|
|
2426
|
+
return {
|
|
2427
|
+
ok: false,
|
|
2428
|
+
reason: "non_human_comment_cannot_create_request",
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
const requestKey = buildRunnerRequestKey({
|
|
2432
|
+
normalizedRoute,
|
|
2433
|
+
selectedRecord,
|
|
2434
|
+
selectedBotUsernames,
|
|
2435
|
+
normalizedIntent,
|
|
2436
|
+
});
|
|
2437
|
+
const currentState = loadBotRunnerState();
|
|
2438
|
+
const requests = normalizeBotRunnerRequests(currentState.requests);
|
|
2439
|
+
const existing = safeObject(requests[requestKey]);
|
|
2440
|
+
if (isFinalRunnerRequestStatus(existing.status)) {
|
|
2441
|
+
return {
|
|
2442
|
+
ok: false,
|
|
2443
|
+
reason: "request_already_finalized",
|
|
2444
|
+
requestKey,
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
if (
|
|
2448
|
+
isActiveRunnerRequestStatus(existing.status)
|
|
2449
|
+
&& String(existing.claimed_by_route || "").trim()
|
|
2450
|
+
&& String(existing.claimed_by_route || "").trim() !== String(routeKey || "").trim()
|
|
2451
|
+
) {
|
|
2452
|
+
return {
|
|
2453
|
+
ok: false,
|
|
2454
|
+
reason: "request_already_claimed",
|
|
2455
|
+
requestKey,
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
const nowISO = new Date().toISOString();
|
|
2459
|
+
const { requests: nextRequests, request } = upsertRunnerRequest(currentState, requestKey, {
|
|
2460
|
+
project_id: String(normalizedRoute?.projectID || "").trim(),
|
|
2461
|
+
provider: String(normalizedRoute?.provider || "").trim(),
|
|
2462
|
+
chat_id: String(parsed.chatID || parsed.chatId || "").trim(),
|
|
2463
|
+
source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
2464
|
+
root_comment_id: String(selectedRecord?.id || "").trim(),
|
|
2465
|
+
root_comment_kind: commentKind,
|
|
2466
|
+
conversation_id: String(parsed.conversationID || "").trim(),
|
|
2467
|
+
selected_bot_usernames: uniqueOrderedStrings(selectedBotUsernames, normalizeTelegramMentionUsername),
|
|
2468
|
+
normalized_intent: String(normalizedIntent || "").trim().toLowerCase(),
|
|
2469
|
+
status: "claimed",
|
|
2470
|
+
claimed_by_route: String(routeKey || "").trim(),
|
|
2471
|
+
claimed_at: firstNonEmptyString([existing.claimed_at, nowISO]) || nowISO,
|
|
2472
|
+
last_comment_id: String(selectedRecord?.id || "").trim(),
|
|
2473
|
+
last_comment_kind: commentKind,
|
|
2474
|
+
last_source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
2475
|
+
});
|
|
2476
|
+
const { consumedComments: nextConsumedComments } = upsertRunnerConsumedComment(currentState, selectedRecord?.id, {
|
|
2477
|
+
request_key: requestKey,
|
|
2478
|
+
route_key: String(routeKey || "").trim(),
|
|
2479
|
+
conversation_id: String(parsed.conversationID || "").trim(),
|
|
2480
|
+
source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
2481
|
+
comment_kind: commentKind,
|
|
2482
|
+
request_status: "claimed",
|
|
2483
|
+
});
|
|
2484
|
+
saveBotRunnerState({
|
|
2485
|
+
routes: currentState.routes,
|
|
2486
|
+
sharedInboxes: currentState.sharedInboxes || currentState.shared_inboxes,
|
|
2487
|
+
excludedComments: currentState.excludedComments || currentState.excluded_comments,
|
|
2488
|
+
requests: nextRequests,
|
|
2489
|
+
consumedComments: nextConsumedComments,
|
|
2490
|
+
});
|
|
2491
|
+
return {
|
|
2492
|
+
ok: true,
|
|
2493
|
+
requestKey,
|
|
2494
|
+
request,
|
|
2495
|
+
};
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
function resolveRunnerContinuationRequestForBotReply({
|
|
2499
|
+
normalizedRoute,
|
|
2500
|
+
routeKey,
|
|
2501
|
+
selectedRecord,
|
|
2502
|
+
}) {
|
|
2503
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2504
|
+
const commentKind = String(parsed.kind || "").trim().toLowerCase();
|
|
2505
|
+
if (commentKind !== "bot_reply") {
|
|
2506
|
+
return {
|
|
2507
|
+
ok: false,
|
|
2508
|
+
reason: "not_a_bot_reply",
|
|
2509
|
+
};
|
|
2510
|
+
}
|
|
2511
|
+
const conversationID = String(parsed.conversationID || "").trim();
|
|
2512
|
+
if (!conversationID) {
|
|
2513
|
+
return {
|
|
2514
|
+
ok: false,
|
|
2515
|
+
reason: "bot_reply_without_conversation",
|
|
2516
|
+
};
|
|
2517
|
+
}
|
|
2518
|
+
const currentState = loadBotRunnerState();
|
|
2519
|
+
const requests = findRunnerRequestsForScope(currentState, normalizedRoute, {
|
|
2520
|
+
conversationID,
|
|
2521
|
+
chatID: String(parsed.chatID || parsed.chatId || "").trim(),
|
|
2522
|
+
}).filter((entry) => isActiveRunnerRequestStatus(entry.status));
|
|
2523
|
+
const request = safeObject(requests[0]);
|
|
2524
|
+
if (!Object.keys(request).length) {
|
|
2525
|
+
return {
|
|
2526
|
+
ok: false,
|
|
2527
|
+
reason: "bot_reply_without_active_request",
|
|
2528
|
+
conversationID,
|
|
2529
|
+
};
|
|
2530
|
+
}
|
|
2531
|
+
const { consumedComments: nextConsumedComments } = upsertRunnerConsumedComment(currentState, selectedRecord?.id, {
|
|
2532
|
+
request_key: request.request_key,
|
|
2533
|
+
route_key: String(routeKey || "").trim(),
|
|
2534
|
+
conversation_id: conversationID,
|
|
2535
|
+
source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
2536
|
+
comment_kind: commentKind,
|
|
2537
|
+
request_status: request.status,
|
|
2538
|
+
});
|
|
2539
|
+
const { requests: nextRequests, request: nextRequest } = upsertRunnerRequest(currentState, request.request_key, {
|
|
2540
|
+
last_comment_id: String(selectedRecord?.id || "").trim(),
|
|
2541
|
+
last_comment_kind: commentKind,
|
|
2542
|
+
last_source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
2543
|
+
});
|
|
2544
|
+
saveBotRunnerState({
|
|
2545
|
+
routes: currentState.routes,
|
|
2546
|
+
sharedInboxes: currentState.sharedInboxes || currentState.shared_inboxes,
|
|
2547
|
+
excludedComments: currentState.excludedComments || currentState.excluded_comments,
|
|
2548
|
+
requests: nextRequests,
|
|
2549
|
+
consumedComments: nextConsumedComments,
|
|
2550
|
+
});
|
|
2551
|
+
return {
|
|
2552
|
+
ok: true,
|
|
2553
|
+
requestKey: nextRequest.request_key,
|
|
2554
|
+
request: nextRequest,
|
|
2555
|
+
};
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
function markRunnerRequestLifecycle({
|
|
2559
|
+
normalizedRoute,
|
|
2560
|
+
requestKey,
|
|
2561
|
+
selectedRecord,
|
|
2562
|
+
routeKey,
|
|
2563
|
+
outcome,
|
|
2564
|
+
conversationIDRaw = "",
|
|
2565
|
+
allowedResponders = [],
|
|
2566
|
+
normalizedIntent = "",
|
|
2567
|
+
closedReason = "",
|
|
2568
|
+
}) {
|
|
2569
|
+
const key = String(requestKey || "").trim();
|
|
2570
|
+
if (!key) return null;
|
|
2571
|
+
const currentState = loadBotRunnerState();
|
|
2572
|
+
const existing = safeObject(normalizeBotRunnerRequests(currentState.requests)[key]);
|
|
2573
|
+
if (!Object.keys(existing).length) return null;
|
|
2574
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2575
|
+
const conversationID = String(conversationIDRaw || existing.conversation_id || parsed.conversationID || "").trim();
|
|
2576
|
+
const conversationFacts = conversationID
|
|
2577
|
+
? collectBotRunnerConversationSessionFacts(normalizedRoute, conversationID)
|
|
2578
|
+
: {};
|
|
2579
|
+
const normalizedOutcome = String(outcome || "").trim().toLowerCase();
|
|
2580
|
+
const nextStatus = (() => {
|
|
2581
|
+
if (normalizedOutcome === "claimed") return "claimed";
|
|
2582
|
+
if (normalizedOutcome === "running") return "running";
|
|
2583
|
+
if (normalizedOutcome === "replied") {
|
|
2584
|
+
return conversationFacts.any_open ? "running" : "completed";
|
|
2585
|
+
}
|
|
2586
|
+
if (normalizedOutcome === "loop_closed") return "loop_closed";
|
|
2587
|
+
if (normalizedOutcome === "expired") return "expired";
|
|
2588
|
+
if (normalizedOutcome === "error" || normalizedOutcome === "skipped" || normalizedOutcome === "closed") {
|
|
2589
|
+
return conversationFacts.any_open ? "running" : "closed";
|
|
2590
|
+
}
|
|
2591
|
+
return normalizeRunnerRequestStatus(existing.status);
|
|
2592
|
+
})();
|
|
2593
|
+
const nowISO = new Date().toISOString();
|
|
2594
|
+
const patch = {
|
|
2595
|
+
conversation_id: conversationID,
|
|
2596
|
+
conversation_allowed_responders: uniqueOrderedStrings(
|
|
2597
|
+
ensureArray(allowedResponders).length ? allowedResponders : existing.conversation_allowed_responders,
|
|
2598
|
+
normalizeTelegramMentionUsername,
|
|
2599
|
+
),
|
|
2600
|
+
normalized_intent: String(normalizedIntent || existing.normalized_intent || "").trim().toLowerCase(),
|
|
2601
|
+
status: nextStatus,
|
|
2602
|
+
started_at: firstNonEmptyString([existing.started_at, nowISO]),
|
|
2603
|
+
completed_at: nextStatus === "completed" ? nowISO : String(existing.completed_at || "").trim(),
|
|
2604
|
+
closed_at: (nextStatus === "closed" || nextStatus === "expired" || nextStatus === "loop_closed")
|
|
2605
|
+
? nowISO
|
|
2606
|
+
: String(existing.closed_at || "").trim(),
|
|
2607
|
+
closed_reason: (nextStatus === "closed" || nextStatus === "expired" || nextStatus === "loop_closed")
|
|
2608
|
+
? String(closedReason || existing.closed_reason || normalizedOutcome).trim()
|
|
2609
|
+
: String(existing.closed_reason || "").trim(),
|
|
2610
|
+
last_comment_id: String(selectedRecord?.id || existing.last_comment_id || "").trim(),
|
|
2611
|
+
last_comment_kind: String(parsed.kind || existing.last_comment_kind || "").trim().toLowerCase(),
|
|
2612
|
+
last_source_message_id: intFromRawAllowZero(parsed.messageID, 0) || existing.last_source_message_id,
|
|
2613
|
+
};
|
|
2614
|
+
const { requests: nextRequests, request } = upsertRunnerRequest(currentState, key, patch);
|
|
2615
|
+
const commentID = String(selectedRecord?.id || "").trim();
|
|
2616
|
+
let nextConsumedComments = currentState.consumedComments || currentState.consumed_comments;
|
|
2617
|
+
if (commentID) {
|
|
2618
|
+
({ consumedComments: nextConsumedComments } = upsertRunnerConsumedComment(currentState, commentID, {
|
|
2619
|
+
request_key: key,
|
|
2620
|
+
route_key: String(routeKey || "").trim(),
|
|
2621
|
+
conversation_id: conversationID,
|
|
2622
|
+
source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
2623
|
+
comment_kind: String(parsed.kind || "").trim().toLowerCase(),
|
|
2624
|
+
request_status: nextStatus,
|
|
2625
|
+
}));
|
|
2626
|
+
}
|
|
2627
|
+
saveBotRunnerState({
|
|
2628
|
+
routes: currentState.routes,
|
|
2629
|
+
sharedInboxes: currentState.sharedInboxes || currentState.shared_inboxes,
|
|
2630
|
+
excludedComments: currentState.excludedComments || currentState.excluded_comments,
|
|
2631
|
+
requests: nextRequests,
|
|
2632
|
+
consumedComments: nextConsumedComments,
|
|
2633
|
+
});
|
|
2634
|
+
return request;
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
function cleanupBotRunnerRequestState({
|
|
2638
|
+
normalizedRoute,
|
|
2639
|
+
comments = [],
|
|
2640
|
+
}) {
|
|
2641
|
+
const currentState = loadBotRunnerState();
|
|
2642
|
+
const config = loadBotRunnerConfig({ persistIfNeeded: true });
|
|
2643
|
+
const nextRoutes = {
|
|
2644
|
+
...safeObject(currentState.routes),
|
|
2645
|
+
};
|
|
2646
|
+
let nextRequests = normalizeBotRunnerRequests(currentState.requests);
|
|
2647
|
+
let nextExcludedComments = normalizeBotRunnerExcludedComments(currentState.excludedComments || currentState.excluded_comments);
|
|
2648
|
+
let changed = false;
|
|
2649
|
+
const orderedComments = ensureArray(comments)
|
|
2650
|
+
.map((comment) => normalizeArchiveCommentRecord(comment, parseArchivedChatComment))
|
|
2651
|
+
.filter((record) => record.id && record.parsedArchive);
|
|
2652
|
+
const nowMs = Date.now();
|
|
2653
|
+
const nowISO = new Date(nowMs).toISOString();
|
|
2654
|
+
const candidateRoutes = [];
|
|
2655
|
+
const seenRouteKeys = new Set();
|
|
2656
|
+
const pushCandidateRoute = (routeRaw) => {
|
|
2657
|
+
const routeObject = safeObject(routeRaw);
|
|
2658
|
+
if (!Object.keys(routeObject).length) {
|
|
2659
|
+
return;
|
|
2660
|
+
}
|
|
2661
|
+
const candidateRoute = normalizeRunnerRoute(routeObject);
|
|
2662
|
+
const candidateRouteKey = runnerRouteKey(candidateRoute);
|
|
2663
|
+
if (seenRouteKeys.has(candidateRouteKey)) {
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
seenRouteKeys.add(candidateRouteKey);
|
|
2667
|
+
candidateRoutes.push(candidateRoute);
|
|
2668
|
+
};
|
|
2669
|
+
pushCandidateRoute(normalizedRoute);
|
|
2670
|
+
for (const candidateRouteRaw of ensureArray(config.routes)) {
|
|
2671
|
+
if (!runnerRouteMatchesProjectConversationScope(candidateRouteRaw, normalizedRoute)) {
|
|
2672
|
+
continue;
|
|
2673
|
+
}
|
|
2674
|
+
pushCandidateRoute(candidateRouteRaw);
|
|
2675
|
+
}
|
|
2676
|
+
for (const candidateRoute of candidateRoutes) {
|
|
2677
|
+
const routeKey = runnerRouteKey(candidateRoute);
|
|
2678
|
+
const currentRouteState = safeObject(nextRoutes[routeKey]);
|
|
2679
|
+
const conversationSessions = {
|
|
2680
|
+
...safeObject(currentRouteState.conversation_sessions),
|
|
2681
|
+
};
|
|
2682
|
+
let routeChanged = false;
|
|
2683
|
+
for (const [conversationID, sessionRaw] of Object.entries(safeObject(conversationSessions))) {
|
|
2684
|
+
const session = safeObject(sessionRaw);
|
|
2685
|
+
if (String(session.status || "").trim() !== "open") {
|
|
2686
|
+
continue;
|
|
2687
|
+
}
|
|
2688
|
+
const expiresAtMs = Date.parse(String(session.expires_at || "").trim());
|
|
2689
|
+
const expired = Number.isFinite(expiresAtMs) && expiresAtMs <= nowMs;
|
|
2690
|
+
const activeRequests = Object.values(nextRequests).filter((entry) => (
|
|
2691
|
+
String(entry.project_id || "").trim() === String(candidateRoute.projectID || "").trim()
|
|
2692
|
+
&& String(entry.provider || "").trim() === String(candidateRoute.provider || "").trim()
|
|
2693
|
+
&& String(entry.conversation_id || "").trim() === String(conversationID || "").trim()
|
|
2694
|
+
&& isActiveRunnerRequestStatus(entry.status)
|
|
2695
|
+
));
|
|
2696
|
+
if (!expired && activeRequests.length > 0) {
|
|
2697
|
+
continue;
|
|
2698
|
+
}
|
|
2699
|
+
const closedReason = expired ? "expired_session" : "orphaned_open_session";
|
|
2700
|
+
conversationSessions[conversationID] = {
|
|
2701
|
+
...session,
|
|
2702
|
+
status: "closed",
|
|
2703
|
+
closed_reason: closedReason,
|
|
2704
|
+
closed_at: nowISO,
|
|
2705
|
+
last_activity_at: nowISO,
|
|
2706
|
+
};
|
|
2707
|
+
routeChanged = true;
|
|
2708
|
+
changed = true;
|
|
2709
|
+
for (const request of activeRequests) {
|
|
2710
|
+
nextRequests[request.request_key] = {
|
|
2711
|
+
...request,
|
|
2712
|
+
status: expired ? "expired" : "closed",
|
|
2713
|
+
closed_reason: closedReason,
|
|
2714
|
+
closed_at: nowISO,
|
|
2715
|
+
updated_at: nowISO,
|
|
2716
|
+
};
|
|
2717
|
+
}
|
|
2718
|
+
for (const record of orderedComments) {
|
|
2719
|
+
const parsed = safeObject(record.parsedArchive);
|
|
2720
|
+
if (String(parsed.conversationID || "").trim() !== String(conversationID || "").trim()) {
|
|
2721
|
+
continue;
|
|
2722
|
+
}
|
|
2723
|
+
if (String(parsed.kind || "").trim().toLowerCase() !== "bot_reply") {
|
|
2724
|
+
continue;
|
|
2725
|
+
}
|
|
2726
|
+
nextExcludedComments[String(record.id || "").trim()] = {
|
|
2727
|
+
...safeObject(nextExcludedComments[String(record.id || "").trim()]),
|
|
2728
|
+
comment_id: String(record.id || "").trim(),
|
|
2729
|
+
excluded_at: nowISO,
|
|
2730
|
+
reason_code: closedReason,
|
|
2731
|
+
decision: expired ? "expired" : "closed",
|
|
2732
|
+
action: "skip_closed_request_comment",
|
|
2733
|
+
conversation_id: String(conversationID || "").trim(),
|
|
2734
|
+
source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
2735
|
+
context_excluded: true,
|
|
2736
|
+
closed_reason: closedReason,
|
|
2737
|
+
};
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
if (routeChanged) {
|
|
2741
|
+
nextRoutes[routeKey] = {
|
|
2742
|
+
...currentRouteState,
|
|
2743
|
+
conversation_sessions: conversationSessions,
|
|
2744
|
+
updated_at: nowISO,
|
|
2745
|
+
};
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
if (changed) {
|
|
2749
|
+
saveBotRunnerState({
|
|
2750
|
+
routes: nextRoutes,
|
|
2751
|
+
sharedInboxes: currentState.sharedInboxes || currentState.shared_inboxes,
|
|
2752
|
+
excludedComments: nextExcludedComments,
|
|
2753
|
+
requests: nextRequests,
|
|
2754
|
+
consumedComments: currentState.consumedComments || currentState.consumed_comments,
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
return {
|
|
2758
|
+
routes: nextRoutes,
|
|
2759
|
+
requests: nextRequests,
|
|
2760
|
+
excludedComments: nextExcludedComments,
|
|
2761
|
+
changed,
|
|
2762
|
+
};
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2214
2765
|
function normalizeRunnerProcessLaunchEntry(rawEntry) {
|
|
2215
2766
|
const entry = safeObject(rawEntry);
|
|
2216
2767
|
return cleanupRunnerStateRecord({
|
|
@@ -4435,6 +4986,7 @@ function emptyRunnerActiveExecutionPatch() {
|
|
|
4435
4986
|
active_comment_id: "",
|
|
4436
4987
|
active_comment_created_at: "",
|
|
4437
4988
|
active_source_message_id: undefined,
|
|
4989
|
+
active_request_key: "",
|
|
4438
4990
|
active_started_at: "",
|
|
4439
4991
|
active_heartbeat_at: "",
|
|
4440
4992
|
active_runner_pid: undefined,
|
|
@@ -4442,13 +4994,14 @@ function emptyRunnerActiveExecutionPatch() {
|
|
|
4442
4994
|
};
|
|
4443
4995
|
}
|
|
4444
4996
|
|
|
4445
|
-
function buildRunnerActiveExecutionPatch(selectedRecord) {
|
|
4997
|
+
function buildRunnerActiveExecutionPatch(selectedRecord, requestKey = "") {
|
|
4446
4998
|
const nowISO = new Date().toISOString();
|
|
4447
4999
|
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
4448
5000
|
return {
|
|
4449
5001
|
active_comment_id: String(selectedRecord?.id || "").trim(),
|
|
4450
5002
|
active_comment_created_at: firstNonEmptyString([selectedRecord?.createdAt, selectedRecord?.updatedAt]),
|
|
4451
5003
|
active_source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
5004
|
+
active_request_key: String(requestKey || "").trim(),
|
|
4452
5005
|
active_started_at: nowISO,
|
|
4453
5006
|
active_heartbeat_at: nowISO,
|
|
4454
5007
|
active_runner_pid: process.pid,
|
|
@@ -4762,12 +5315,43 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
4762
5315
|
actorUserID: runtime.actor.user_id,
|
|
4763
5316
|
limit: Math.max(1000, normalizedRoute.contextComments * 20),
|
|
4764
5317
|
});
|
|
5318
|
+
cleanupBotRunnerRequestState({
|
|
5319
|
+
normalizedRoute,
|
|
5320
|
+
comments,
|
|
5321
|
+
});
|
|
5322
|
+
const latestRunnerState = loadBotRunnerState();
|
|
4765
5323
|
const excludedComments = normalizeBotRunnerExcludedComments(
|
|
4766
|
-
|
|
5324
|
+
latestRunnerState.excludedComments || latestRunnerState.excluded_comments,
|
|
5325
|
+
);
|
|
5326
|
+
const consumedComments = normalizeBotRunnerConsumedComments(
|
|
5327
|
+
latestRunnerState.consumedComments || latestRunnerState.consumed_comments,
|
|
4767
5328
|
);
|
|
5329
|
+
const requests = normalizeBotRunnerRequests(latestRunnerState.requests);
|
|
4768
5330
|
const commentsForPending = ensureArray(comments).filter((comment) => {
|
|
4769
|
-
const
|
|
4770
|
-
|
|
5331
|
+
const normalizedComment = normalizeArchiveCommentRecord(comment, parseArchivedChatComment);
|
|
5332
|
+
const commentID = String(normalizedComment.id || "").trim();
|
|
5333
|
+
if (commentID && safeObject(excludedComments)[commentID]) {
|
|
5334
|
+
return false;
|
|
5335
|
+
}
|
|
5336
|
+
if (commentID && safeObject(consumedComments)[commentID]) {
|
|
5337
|
+
return false;
|
|
5338
|
+
}
|
|
5339
|
+
const parsed = safeObject(normalizedComment.parsedArchive);
|
|
5340
|
+
if (String(parsed.kind || "").trim().toLowerCase() === "bot_reply") {
|
|
5341
|
+
const conversationID = String(parsed.conversationID || "").trim();
|
|
5342
|
+
if (!conversationID) {
|
|
5343
|
+
return false;
|
|
5344
|
+
}
|
|
5345
|
+
const activeRequests = Object.values(requests).filter((entry) => (
|
|
5346
|
+
String(entry.project_id || "").trim() === String(normalizedRoute.projectID || "").trim()
|
|
5347
|
+
&& String(entry.provider || "").trim() === String(normalizedRoute.provider || "").trim()
|
|
5348
|
+
&& String(entry.chat_id || "").trim() === String(parsed.chatID || "").trim()
|
|
5349
|
+
&& String(entry.conversation_id || "").trim() === conversationID
|
|
5350
|
+
&& isActiveRunnerRequestStatus(entry.status)
|
|
5351
|
+
));
|
|
5352
|
+
return activeRequests.length > 0;
|
|
5353
|
+
}
|
|
5354
|
+
return true;
|
|
4771
5355
|
});
|
|
4772
5356
|
const pendingWork = selectRunnerPendingWork({
|
|
4773
5357
|
comments: commentsForPending,
|
|
@@ -4882,6 +5466,35 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
4882
5466
|
}
|
|
4883
5467
|
return null;
|
|
4884
5468
|
};
|
|
5469
|
+
const prepareRunnerRequestClaim = (selectedRecord, selectedResponderSelectors = []) => {
|
|
5470
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
5471
|
+
const kind = String(parsed.kind || "").trim().toLowerCase();
|
|
5472
|
+
if (kind === "bot_reply") {
|
|
5473
|
+
const continuation = resolveRunnerContinuationRequestForBotReply({
|
|
5474
|
+
normalizedRoute,
|
|
5475
|
+
routeKey,
|
|
5476
|
+
selectedRecord,
|
|
5477
|
+
});
|
|
5478
|
+
if (!continuation.ok) {
|
|
5479
|
+
markBotRunnerExcludedComment(selectedRecord?.id, {
|
|
5480
|
+
reason_code: String(continuation.reason || "bot_reply_without_active_request").trim() || "bot_reply_without_active_request",
|
|
5481
|
+
decision: "closed",
|
|
5482
|
+
action: "skip_bot_reply_without_active_request",
|
|
5483
|
+
conversation_id: String(parsed.conversationID || "").trim(),
|
|
5484
|
+
source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
5485
|
+
context_excluded: true,
|
|
5486
|
+
closed_reason: String(continuation.reason || "bot_reply_without_active_request").trim() || "bot_reply_without_active_request",
|
|
5487
|
+
});
|
|
5488
|
+
}
|
|
5489
|
+
return continuation;
|
|
5490
|
+
}
|
|
5491
|
+
return claimRunnerRequestForHumanComment({
|
|
5492
|
+
normalizedRoute,
|
|
5493
|
+
routeKey,
|
|
5494
|
+
selectedRecord,
|
|
5495
|
+
selectedBotUsernames: selectedResponderSelectors,
|
|
5496
|
+
});
|
|
5497
|
+
};
|
|
4885
5498
|
if (deferExecution) {
|
|
4886
5499
|
const skippedRecords = [];
|
|
4887
5500
|
for (const selectedRecord of pending.pending) {
|
|
@@ -4948,7 +5561,31 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
4948
5561
|
});
|
|
4949
5562
|
continue;
|
|
4950
5563
|
}
|
|
4951
|
-
|
|
5564
|
+
const requestClaim = prepareRunnerRequestClaim(selectedRecord, adjudication.selected_bot_usernames);
|
|
5565
|
+
if (!requestClaim.ok) {
|
|
5566
|
+
saveRunnerRouteState(
|
|
5567
|
+
routeKey,
|
|
5568
|
+
buildRunnerRouteStateFromComment(selectedRecord, {
|
|
5569
|
+
last_action: "request_skipped",
|
|
5570
|
+
last_reason: String(requestClaim.reason || "request_unavailable").trim() || "request_unavailable",
|
|
5571
|
+
last_trigger: "request_ledger",
|
|
5572
|
+
}),
|
|
5573
|
+
);
|
|
5574
|
+
skippedRecords.push({
|
|
5575
|
+
id: selectedRecord.id,
|
|
5576
|
+
reason: String(requestClaim.reason || "request_unavailable").trim() || "request_unavailable",
|
|
5577
|
+
messageID: intFromRawAllowZero(selectedRecord?.parsedArchive?.messageID, 0),
|
|
5578
|
+
diagnosticType: "skip",
|
|
5579
|
+
contextExcluded: String(requestClaim.reason || "").trim() === "bot_reply_without_active_request",
|
|
5580
|
+
action: "skip_invalid_request_state",
|
|
5581
|
+
closedReason: String(requestClaim.reason || "").trim(),
|
|
5582
|
+
});
|
|
5583
|
+
continue;
|
|
5584
|
+
}
|
|
5585
|
+
saveRunnerRouteState(routeKey, {
|
|
5586
|
+
...buildRunnerActiveExecutionPatch(selectedRecord, requestClaim.requestKey),
|
|
5587
|
+
last_request_key: String(requestClaim.requestKey || "").trim(),
|
|
5588
|
+
});
|
|
4952
5589
|
return {
|
|
4953
5590
|
route_key: routeKey,
|
|
4954
5591
|
route_name: normalizedRoute.name,
|
|
@@ -4964,7 +5601,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
4964
5601
|
deferred_execution: {
|
|
4965
5602
|
routeKey,
|
|
4966
5603
|
normalizedRoute,
|
|
4967
|
-
routeState:
|
|
5604
|
+
routeState: safeObject(loadBotRunnerState().routes[routeKey]),
|
|
4968
5605
|
selectedRecord,
|
|
4969
5606
|
pendingOrdered: pending.ordered,
|
|
4970
5607
|
bot,
|
|
@@ -4973,6 +5610,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
4973
5610
|
executionPlan,
|
|
4974
5611
|
runtime,
|
|
4975
5612
|
managedConversationBots,
|
|
5613
|
+
requestKey: String(requestClaim.requestKey || "").trim(),
|
|
4976
5614
|
},
|
|
4977
5615
|
};
|
|
4978
5616
|
}
|
|
@@ -5037,11 +5675,79 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
5037
5675
|
skippedRecords.push(startupLoopSkipped.skippedRecord);
|
|
5038
5676
|
continue;
|
|
5039
5677
|
}
|
|
5678
|
+
const inlineAdjudication = await resolveRunnerResponderAdjudication({
|
|
5679
|
+
selectedRecord,
|
|
5680
|
+
pendingOrdered: pending.ordered,
|
|
5681
|
+
normalizedRoute,
|
|
5682
|
+
bot,
|
|
5683
|
+
executionPlan,
|
|
5684
|
+
deps: {
|
|
5685
|
+
...buildRunnerExecutionDeps(),
|
|
5686
|
+
managedConversationBots,
|
|
5687
|
+
resolveConversationPeerBots: resolveRunnerConversationPeers,
|
|
5688
|
+
},
|
|
5689
|
+
});
|
|
5690
|
+
const inlineCurrentBotSelected = ensureArray(inlineAdjudication.selected_bot_usernames)
|
|
5691
|
+
.map((value) => String(value || "").trim().replace(/^@+/, "").toLowerCase())
|
|
5692
|
+
.includes(currentBotSelector);
|
|
5693
|
+
if (!inlineCurrentBotSelected) {
|
|
5694
|
+
saveRunnerRouteState(
|
|
5695
|
+
routeKey,
|
|
5696
|
+
buildRunnerRouteStateFromComment(selectedRecord, {
|
|
5697
|
+
last_action: "adjudication_skipped",
|
|
5698
|
+
last_reason: String(inlineAdjudication.reason_code || "").trim() || "not_selected_by_adjudicator",
|
|
5699
|
+
last_trigger: "responder_adjudication",
|
|
5700
|
+
}),
|
|
5701
|
+
);
|
|
5702
|
+
skippedRecords.push({
|
|
5703
|
+
id: selectedRecord.id,
|
|
5704
|
+
reason: String(inlineAdjudication.reason_code || "").trim() || "not_selected_by_adjudicator",
|
|
5705
|
+
messageID: intFromRawAllowZero(selectedRecord?.parsedArchive?.messageID, 0),
|
|
5706
|
+
});
|
|
5707
|
+
continue;
|
|
5708
|
+
}
|
|
5040
5709
|
const currentRouteState = safeObject(loadBotRunnerState().routes[routeKey]);
|
|
5710
|
+
const requestClaim = prepareRunnerRequestClaim(selectedRecord, inlineAdjudication.selected_bot_usernames);
|
|
5711
|
+
if (!requestClaim.ok) {
|
|
5712
|
+
saveRunnerRouteState(
|
|
5713
|
+
routeKey,
|
|
5714
|
+
buildRunnerRouteStateFromComment(selectedRecord, {
|
|
5715
|
+
last_action: "request_skipped",
|
|
5716
|
+
last_reason: String(requestClaim.reason || "request_unavailable").trim() || "request_unavailable",
|
|
5717
|
+
last_trigger: "request_ledger",
|
|
5718
|
+
}),
|
|
5719
|
+
);
|
|
5720
|
+
skippedRecords.push({
|
|
5721
|
+
id: selectedRecord.id,
|
|
5722
|
+
reason: String(requestClaim.reason || "request_unavailable").trim() || "request_unavailable",
|
|
5723
|
+
messageID: intFromRawAllowZero(selectedRecord?.parsedArchive?.messageID, 0),
|
|
5724
|
+
diagnosticType: "skip",
|
|
5725
|
+
contextExcluded: String(requestClaim.reason || "").trim() === "bot_reply_without_active_request",
|
|
5726
|
+
action: "skip_invalid_request_state",
|
|
5727
|
+
closedReason: String(requestClaim.reason || "").trim(),
|
|
5728
|
+
});
|
|
5729
|
+
continue;
|
|
5730
|
+
}
|
|
5731
|
+
saveRunnerRouteState(routeKey, {
|
|
5732
|
+
active_request_key: String(requestClaim.requestKey || "").trim(),
|
|
5733
|
+
last_request_key: String(requestClaim.requestKey || "").trim(),
|
|
5734
|
+
});
|
|
5735
|
+
if (String(requestClaim.requestKey || "").trim()) {
|
|
5736
|
+
markRunnerRequestLifecycle({
|
|
5737
|
+
normalizedRoute,
|
|
5738
|
+
requestKey: requestClaim.requestKey,
|
|
5739
|
+
selectedRecord,
|
|
5740
|
+
routeKey,
|
|
5741
|
+
outcome: "running",
|
|
5742
|
+
});
|
|
5743
|
+
}
|
|
5041
5744
|
const processed = await processRunnerSelectedRecord({
|
|
5042
5745
|
routeKey,
|
|
5043
5746
|
normalizedRoute,
|
|
5044
|
-
routeState:
|
|
5747
|
+
routeState: {
|
|
5748
|
+
...currentRouteState,
|
|
5749
|
+
active_request_key: String(requestClaim.requestKey || "").trim(),
|
|
5750
|
+
},
|
|
5045
5751
|
selectedRecord,
|
|
5046
5752
|
pendingOrdered: pending.ordered,
|
|
5047
5753
|
bot,
|
|
@@ -5064,9 +5770,31 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
5064
5770
|
},
|
|
5065
5771
|
});
|
|
5066
5772
|
if (processed.kind === "skipped") {
|
|
5773
|
+
if (String(requestClaim.requestKey || "").trim()) {
|
|
5774
|
+
markRunnerRequestLifecycle({
|
|
5775
|
+
normalizedRoute,
|
|
5776
|
+
requestKey: requestClaim.requestKey,
|
|
5777
|
+
selectedRecord,
|
|
5778
|
+
routeKey,
|
|
5779
|
+
outcome: "skipped",
|
|
5780
|
+
closedReason: String(processed.skippedRecord?.reason || "skipped").trim() || "skipped",
|
|
5781
|
+
});
|
|
5782
|
+
}
|
|
5067
5783
|
skippedRecords.push(processed.skippedRecord);
|
|
5068
5784
|
continue;
|
|
5069
5785
|
}
|
|
5786
|
+
if (String(requestClaim.requestKey || "").trim()) {
|
|
5787
|
+
markRunnerRequestLifecycle({
|
|
5788
|
+
normalizedRoute,
|
|
5789
|
+
requestKey: requestClaim.requestKey,
|
|
5790
|
+
selectedRecord,
|
|
5791
|
+
routeKey,
|
|
5792
|
+
outcome: String(processed.result?.outcome || "replied").trim().toLowerCase(),
|
|
5793
|
+
conversationIDRaw: String(processed.result?.conversation_id || "").trim(),
|
|
5794
|
+
allowedResponders: ensureArray(processed.result?.conversation_allowed_responders),
|
|
5795
|
+
normalizedIntent: String(safeObject(loadBotRunnerState().routes[routeKey]).last_intent_type || "").trim(),
|
|
5796
|
+
});
|
|
5797
|
+
}
|
|
5070
5798
|
return {
|
|
5071
5799
|
logical_signature: runnerRouteLogicalSignature(normalizedRoute),
|
|
5072
5800
|
archive_source: String(archiveThread.source || "").trim() || "-",
|
|
@@ -7297,6 +8025,15 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
7297
8025
|
deferredExecution.routeKey,
|
|
7298
8026
|
deferredExecution.selectedRecord,
|
|
7299
8027
|
);
|
|
8028
|
+
if (String(deferredExecution.requestKey || "").trim()) {
|
|
8029
|
+
markRunnerRequestLifecycle({
|
|
8030
|
+
normalizedRoute: deferredExecution.normalizedRoute,
|
|
8031
|
+
requestKey: deferredExecution.requestKey,
|
|
8032
|
+
selectedRecord: deferredExecution.selectedRecord,
|
|
8033
|
+
routeKey: deferredExecution.routeKey,
|
|
8034
|
+
outcome: "running",
|
|
8035
|
+
});
|
|
8036
|
+
}
|
|
7300
8037
|
const executionPromise = (async () => {
|
|
7301
8038
|
try {
|
|
7302
8039
|
const processed = await processRunnerSelectedRecord({
|
|
@@ -7343,6 +8080,16 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
7343
8080
|
},
|
|
7344
8081
|
});
|
|
7345
8082
|
if (processed.kind === "skipped") {
|
|
8083
|
+
if (String(deferredExecution.requestKey || "").trim()) {
|
|
8084
|
+
markRunnerRequestLifecycle({
|
|
8085
|
+
normalizedRoute: deferredExecution.normalizedRoute,
|
|
8086
|
+
requestKey: deferredExecution.requestKey,
|
|
8087
|
+
selectedRecord: deferredExecution.selectedRecord,
|
|
8088
|
+
routeKey: deferredExecution.routeKey,
|
|
8089
|
+
outcome: "skipped",
|
|
8090
|
+
closedReason: String(processed.skippedRecord?.reason || "skipped").trim() || "skipped",
|
|
8091
|
+
});
|
|
8092
|
+
}
|
|
7346
8093
|
return {
|
|
7347
8094
|
route_key: deferredExecution.routeKey,
|
|
7348
8095
|
route_name: deferredExecution.normalizedRoute.name,
|
|
@@ -7357,6 +8104,18 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
7357
8104
|
role_profile: deferredExecution.executionPlan.roleProfileName,
|
|
7358
8105
|
};
|
|
7359
8106
|
}
|
|
8107
|
+
if (String(deferredExecution.requestKey || "").trim()) {
|
|
8108
|
+
markRunnerRequestLifecycle({
|
|
8109
|
+
normalizedRoute: deferredExecution.normalizedRoute,
|
|
8110
|
+
requestKey: deferredExecution.requestKey,
|
|
8111
|
+
selectedRecord: deferredExecution.selectedRecord,
|
|
8112
|
+
routeKey: deferredExecution.routeKey,
|
|
8113
|
+
outcome: String(processed.result?.outcome || "replied").trim().toLowerCase(),
|
|
8114
|
+
conversationIDRaw: String(processed.result?.conversation_id || "").trim(),
|
|
8115
|
+
allowedResponders: ensureArray(processed.result?.conversation_allowed_responders),
|
|
8116
|
+
normalizedIntent: String(safeObject(loadBotRunnerState().routes[deferredExecution.routeKey]).last_intent_type || "").trim(),
|
|
8117
|
+
});
|
|
8118
|
+
}
|
|
7360
8119
|
return {
|
|
7361
8120
|
logical_signature: runnerRouteLogicalSignature(deferredExecution.normalizedRoute),
|
|
7362
8121
|
archive_source: String(deferredExecution.archiveThread.source || "").trim() || "-",
|
|
@@ -7371,6 +8130,16 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
7371
8130
|
...emptyRunnerActiveExecutionPatch(),
|
|
7372
8131
|
last_error: errorText,
|
|
7373
8132
|
});
|
|
8133
|
+
if (String(deferredExecution.requestKey || "").trim()) {
|
|
8134
|
+
markRunnerRequestLifecycle({
|
|
8135
|
+
normalizedRoute: deferredExecution.normalizedRoute,
|
|
8136
|
+
requestKey: deferredExecution.requestKey,
|
|
8137
|
+
selectedRecord: deferredExecution.selectedRecord,
|
|
8138
|
+
routeKey: deferredExecution.routeKey,
|
|
8139
|
+
outcome: "error",
|
|
8140
|
+
closedReason: errorText || "execution_error",
|
|
8141
|
+
});
|
|
8142
|
+
}
|
|
7374
8143
|
return {
|
|
7375
8144
|
route_key: deferredExecution.routeKey,
|
|
7376
8145
|
route_name: deferredExecution.normalizedRoute.name,
|
|
@@ -11629,6 +12398,7 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
11629
12398
|
runnerRouteKey,
|
|
11630
12399
|
runnerRouteLogicalSignature,
|
|
11631
12400
|
loadBotRunnerState,
|
|
12401
|
+
saveBotRunnerState,
|
|
11632
12402
|
tryJsonParse,
|
|
11633
12403
|
safeObject,
|
|
11634
12404
|
normalizeRunnerTriggerPolicy,
|
|
@@ -11637,6 +12407,9 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
11637
12407
|
selectRunnerPendingWork,
|
|
11638
12408
|
processRunnerSelectedRecord,
|
|
11639
12409
|
resolveRunnerStartupLoopAdjudication,
|
|
12410
|
+
claimRunnerRequestForHumanComment,
|
|
12411
|
+
resolveRunnerContinuationRequestForBotReply,
|
|
12412
|
+
cleanupBotRunnerRequestState,
|
|
11640
12413
|
runRunnerAIExecution,
|
|
11641
12414
|
buildLocalBotPrompt,
|
|
11642
12415
|
formatBotReplyArchiveComment,
|
package/lib/runner-helpers.mjs
CHANGED
|
@@ -267,6 +267,7 @@ export function buildRunnerRouteStateFromComment(record, patch = {}) {
|
|
|
267
267
|
last_processed_created_at: firstNonEmptyString([record?.createdAt, record?.updatedAt]),
|
|
268
268
|
last_source_message_id: intFromRawAllowZero(parsed.messageID, 0),
|
|
269
269
|
last_source_kind: String(parsed.kind || "").trim(),
|
|
270
|
+
active_request_key: "",
|
|
270
271
|
last_intent_type: "",
|
|
271
272
|
last_error: "",
|
|
272
273
|
active_comment_id: "",
|
|
@@ -45,6 +45,10 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
45
45
|
const runnerRouteKey = requireDependency(deps, "runnerRouteKey");
|
|
46
46
|
const runnerRouteLogicalSignature = requireDependency(deps, "runnerRouteLogicalSignature");
|
|
47
47
|
const loadBotRunnerState = requireDependency(deps, "loadBotRunnerState");
|
|
48
|
+
const saveBotRunnerState = requireDependency(deps, "saveBotRunnerState");
|
|
49
|
+
const claimRunnerRequestForHumanComment = requireDependency(deps, "claimRunnerRequestForHumanComment");
|
|
50
|
+
const resolveRunnerContinuationRequestForBotReply = requireDependency(deps, "resolveRunnerContinuationRequestForBotReply");
|
|
51
|
+
const cleanupBotRunnerRequestState = requireDependency(deps, "cleanupBotRunnerRequestState");
|
|
48
52
|
const tryJsonParse = requireDependency(deps, "tryJsonParse");
|
|
49
53
|
const safeObject = requireDependency(deps, "safeObject");
|
|
50
54
|
const normalizeRunnerTriggerPolicy = requireDependency(deps, "normalizeRunnerTriggerPolicy");
|
|
@@ -1362,6 +1366,137 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
1362
1366
|
}
|
|
1363
1367
|
}
|
|
1364
1368
|
|
|
1369
|
+
const originalRequestLedgerHome = process.env.HOME;
|
|
1370
|
+
const originalRequestLedgerUserProfile = process.env.USERPROFILE;
|
|
1371
|
+
let requestLedgerTempRoot = "";
|
|
1372
|
+
try {
|
|
1373
|
+
requestLedgerTempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-request-ledger-selftest-"));
|
|
1374
|
+
const requestLedgerHome = path.join(requestLedgerTempRoot, "home");
|
|
1375
|
+
fs.mkdirSync(path.join(requestLedgerHome, ".metheus"), { recursive: true });
|
|
1376
|
+
process.env.HOME = requestLedgerHome;
|
|
1377
|
+
process.env.USERPROFILE = requestLedgerHome;
|
|
1378
|
+
saveBotRunnerState({
|
|
1379
|
+
routes: {},
|
|
1380
|
+
sharedInboxes: {},
|
|
1381
|
+
excludedComments: {},
|
|
1382
|
+
requests: {},
|
|
1383
|
+
consumedComments: {},
|
|
1384
|
+
});
|
|
1385
|
+
const requestRoute = normalizeRunnerRoute({
|
|
1386
|
+
name: "telegram-monitor-request-ledger",
|
|
1387
|
+
project_id: selftestProjectID,
|
|
1388
|
+
provider: "telegram",
|
|
1389
|
+
role: "monitor",
|
|
1390
|
+
destination_label: "Main Room",
|
|
1391
|
+
});
|
|
1392
|
+
const requestRouteKey = runnerRouteKey(requestRoute);
|
|
1393
|
+
const humanRecord = {
|
|
1394
|
+
id: "comment-request-human-1",
|
|
1395
|
+
createdAt: "2026-03-22T00:00:00.000Z",
|
|
1396
|
+
updatedAt: "2026-03-22T00:00:00.000Z",
|
|
1397
|
+
parsedArchive: {
|
|
1398
|
+
kind: "telegram_message",
|
|
1399
|
+
chatID: "-100123",
|
|
1400
|
+
chatType: "supergroup",
|
|
1401
|
+
body: "@RyoAI_bot please coordinate this",
|
|
1402
|
+
messageID: 501,
|
|
1403
|
+
senderIsBot: false,
|
|
1404
|
+
},
|
|
1405
|
+
};
|
|
1406
|
+
const claimed = claimRunnerRequestForHumanComment({
|
|
1407
|
+
normalizedRoute: requestRoute,
|
|
1408
|
+
routeKey: requestRouteKey,
|
|
1409
|
+
selectedRecord: humanRecord,
|
|
1410
|
+
selectedBotUsernames: ["ryoai_bot"],
|
|
1411
|
+
normalizedIntent: "coordination_request",
|
|
1412
|
+
});
|
|
1413
|
+
const claimedState = loadBotRunnerState();
|
|
1414
|
+
const claimedRequest = safeObject(safeObject(claimedState.requests)[claimed.requestKey]);
|
|
1415
|
+
const claimedConsumed = safeObject(safeObject(claimedState.consumedComments)[humanRecord.id]);
|
|
1416
|
+
push(
|
|
1417
|
+
"runner_request_claim_persists_request_and_consumed_comment",
|
|
1418
|
+
claimed.ok === true
|
|
1419
|
+
&& String(claimedRequest.status || "") === "claimed"
|
|
1420
|
+
&& String(claimedRequest.source_message_id || "") === "501"
|
|
1421
|
+
&& String(claimedRequest.normalized_intent || "") === "coordination_request"
|
|
1422
|
+
&& String(claimedConsumed.request_key || "") === String(claimed.requestKey || ""),
|
|
1423
|
+
`request=${String(claimed.requestKey || "(none)")} status=${String(claimedRequest.status || "(none)")} consumed=${String(claimedConsumed.request_key || "(none)")}`,
|
|
1424
|
+
);
|
|
1425
|
+
|
|
1426
|
+
saveBotRunnerState({
|
|
1427
|
+
...claimedState,
|
|
1428
|
+
routes: {
|
|
1429
|
+
[requestRouteKey]: {
|
|
1430
|
+
conversation_sessions: {
|
|
1431
|
+
"conv-stale-1": {
|
|
1432
|
+
status: "open",
|
|
1433
|
+
expires_at: "2026-03-20T00:00:00.000Z",
|
|
1434
|
+
conversation_id: "conv-stale-1",
|
|
1435
|
+
},
|
|
1436
|
+
},
|
|
1437
|
+
},
|
|
1438
|
+
},
|
|
1439
|
+
});
|
|
1440
|
+
const botReplyComments = [
|
|
1441
|
+
{
|
|
1442
|
+
id: "comment-stale-bot-reply-1",
|
|
1443
|
+
body: [
|
|
1444
|
+
"[Bot reply]",
|
|
1445
|
+
"chat_id: -100123",
|
|
1446
|
+
"chat_type: supergroup",
|
|
1447
|
+
"message_id: 777",
|
|
1448
|
+
"bot_username: @RyoAI3_bot",
|
|
1449
|
+
"conversation_id: conv-stale-1",
|
|
1450
|
+
"",
|
|
1451
|
+
"@RyoAI3_bot stale follow-up",
|
|
1452
|
+
].join("\n"),
|
|
1453
|
+
},
|
|
1454
|
+
];
|
|
1455
|
+
cleanupBotRunnerRequestState({
|
|
1456
|
+
normalizedRoute: requestRoute,
|
|
1457
|
+
comments: botReplyComments,
|
|
1458
|
+
});
|
|
1459
|
+
const cleanedState = loadBotRunnerState();
|
|
1460
|
+
const cleanedSession = safeObject(safeObject(safeObject(cleanedState.routes)[requestRouteKey]).conversation_sessions)["conv-stale-1"];
|
|
1461
|
+
const excludedEntry = safeObject(safeObject(cleanedState.excludedComments)["comment-stale-bot-reply-1"]);
|
|
1462
|
+
const continuation = resolveRunnerContinuationRequestForBotReply({
|
|
1463
|
+
normalizedRoute: requestRoute,
|
|
1464
|
+
routeKey: requestRouteKey,
|
|
1465
|
+
selectedRecord: {
|
|
1466
|
+
id: "comment-stale-bot-reply-1",
|
|
1467
|
+
parsedArchive: parseArchivedChatComment(botReplyComments[0].body),
|
|
1468
|
+
},
|
|
1469
|
+
});
|
|
1470
|
+
push(
|
|
1471
|
+
"runner_stale_open_session_cleanup_excludes_bot_reply_replay",
|
|
1472
|
+
String(cleanedSession.status || "") === "closed"
|
|
1473
|
+
&& String(cleanedSession.closed_reason || "") === "expired_session"
|
|
1474
|
+
&& String(excludedEntry.reason_code || "") === "expired_session"
|
|
1475
|
+
&& continuation.ok === false
|
|
1476
|
+
&& String(continuation.reason || "") === "bot_reply_without_active_request",
|
|
1477
|
+
`session=${String(cleanedSession.status || "(none)")} reason=${String(cleanedSession.closed_reason || "(none)")} excluded=${String(excludedEntry.reason_code || "(none)")} continuation=${String(continuation.reason || "(none)")}`,
|
|
1478
|
+
);
|
|
1479
|
+
} catch (err) {
|
|
1480
|
+
push("runner_request_claim_persists_request_and_consumed_comment", false, String(err?.message || err));
|
|
1481
|
+
push("runner_stale_open_session_cleanup_excludes_bot_reply_replay", false, String(err?.message || err));
|
|
1482
|
+
} finally {
|
|
1483
|
+
if (typeof originalRequestLedgerHome === "string") {
|
|
1484
|
+
process.env.HOME = originalRequestLedgerHome;
|
|
1485
|
+
} else {
|
|
1486
|
+
delete process.env.HOME;
|
|
1487
|
+
}
|
|
1488
|
+
if (typeof originalRequestLedgerUserProfile === "string") {
|
|
1489
|
+
process.env.USERPROFILE = originalRequestLedgerUserProfile;
|
|
1490
|
+
} else {
|
|
1491
|
+
delete process.env.USERPROFILE;
|
|
1492
|
+
}
|
|
1493
|
+
if (requestLedgerTempRoot) {
|
|
1494
|
+
try {
|
|
1495
|
+
fs.rmSync(requestLedgerTempRoot, { recursive: true, force: true });
|
|
1496
|
+
} catch {}
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1365
1500
|
const defaultMonitorTriggerPolicy = normalizeRunnerTriggerPolicy({}, { role: "monitor" });
|
|
1366
1501
|
push(
|
|
1367
1502
|
"bot_runner_default_monitor_trigger_policy",
|