metheus-governance-mcp-cli 0.2.197 → 0.2.199
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 +1283 -99
- package/lib/runner-data.mjs +130 -46
- package/lib/selftest-runner-scenarios.mjs +203 -5
- package/lib/selftest-telegram-e2e.mjs +105 -14
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -158,11 +158,13 @@ import {
|
|
|
158
158
|
listProjectCtxpackFiles as listProjectCtxpackFilesImpl,
|
|
159
159
|
listProjectRunnerRequestCommentStates as listProjectRunnerRequestCommentStatesImpl,
|
|
160
160
|
listProjectRunnerRequests as listProjectRunnerRequestsImpl,
|
|
161
|
+
listWorkItemThreads as listWorkItemThreadsImpl,
|
|
161
162
|
listThreadComments as listThreadCommentsImpl,
|
|
162
163
|
listThreadCommentsTail as listThreadCommentsTailImpl,
|
|
163
164
|
listUserBotsForRunner as listUserBotsForRunnerImpl,
|
|
164
165
|
selectProjectChatDestination as selectProjectChatDestinationImpl,
|
|
165
166
|
selectRunnerBot as selectRunnerBotImpl,
|
|
167
|
+
transitionProjectWorkItem as transitionProjectWorkItemImpl,
|
|
166
168
|
upsertProjectRunnerRequest as upsertProjectRunnerRequestImpl,
|
|
167
169
|
upsertProjectRunnerRequestCommentState as upsertProjectRunnerRequestCommentStateImpl,
|
|
168
170
|
updateProjectContextItem as updateProjectContextItemImpl,
|
|
@@ -1914,9 +1916,17 @@ function mergeRunnerStateRecords(preferred, fallback) {
|
|
|
1914
1916
|
active_comment_id: pickString(primary.active_comment_id, secondary.active_comment_id),
|
|
1915
1917
|
active_comment_created_at: pickString(primary.active_comment_created_at, secondary.active_comment_created_at),
|
|
1916
1918
|
active_source_message_id: pickNumber(primary.active_source_message_id, secondary.active_source_message_id) || undefined,
|
|
1919
|
+
active_request_key: pickString(primary.active_request_key, secondary.active_request_key),
|
|
1917
1920
|
active_started_at: pickString(primary.active_started_at, secondary.active_started_at),
|
|
1921
|
+
active_root_work_item_id: pickString(primary.active_root_work_item_id, secondary.active_root_work_item_id),
|
|
1922
|
+
active_root_work_item_title: pickString(primary.active_root_work_item_title, secondary.active_root_work_item_title),
|
|
1923
|
+
active_root_work_item_status: pickString(primary.active_root_work_item_status, secondary.active_root_work_item_status),
|
|
1918
1924
|
active_runner_pid: pickNumber(primary.active_runner_pid, secondary.active_runner_pid) || undefined,
|
|
1919
1925
|
active_execution_token: pickString(primary.active_execution_token, secondary.active_execution_token),
|
|
1926
|
+
last_request_key: pickString(primary.last_request_key, secondary.last_request_key),
|
|
1927
|
+
last_root_work_item_id: pickString(primary.last_root_work_item_id, secondary.last_root_work_item_id),
|
|
1928
|
+
last_root_work_item_title: pickString(primary.last_root_work_item_title, secondary.last_root_work_item_title),
|
|
1929
|
+
last_root_work_item_status: pickString(primary.last_root_work_item_status, secondary.last_root_work_item_status),
|
|
1920
1930
|
conversation_sessions: {
|
|
1921
1931
|
...safeObject(secondary.conversation_sessions),
|
|
1922
1932
|
...safeObject(primary.conversation_sessions),
|
|
@@ -2123,6 +2133,13 @@ function isActiveRunnerRequestStatus(rawStatus) {
|
|
|
2123
2133
|
return status === "planned" || status === "claimed" || status === "running";
|
|
2124
2134
|
}
|
|
2125
2135
|
|
|
2136
|
+
function normalizeRunnerWorkItemStatus(rawStatus) {
|
|
2137
|
+
const status = String(rawStatus || "").trim().toLowerCase();
|
|
2138
|
+
return ["backlog", "doing", "review", "done", "canceled"].includes(status)
|
|
2139
|
+
? status
|
|
2140
|
+
: "";
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2126
2143
|
function normalizeBotRunnerRequests(rawRequests, nowMs = Date.now()) {
|
|
2127
2144
|
const normalized = {};
|
|
2128
2145
|
for (const [requestKeyRaw, entryRaw] of Object.entries(safeObject(rawRequests))) {
|
|
@@ -2158,6 +2175,12 @@ function normalizeBotRunnerRequests(rawRequests, nowMs = Date.now()) {
|
|
|
2158
2175
|
completed_at: firstNonEmptyString([entry.completed_at, entry.completedAt]),
|
|
2159
2176
|
closed_at: firstNonEmptyString([entry.closed_at, entry.closedAt]),
|
|
2160
2177
|
closed_reason: String(entry.closed_reason || entry.closedReason || "").trim(),
|
|
2178
|
+
root_work_item_id: String(entry.root_work_item_id || entry.rootWorkItemID || "").trim(),
|
|
2179
|
+
root_work_item_title: String(entry.root_work_item_title || entry.rootWorkItemTitle || "").trim(),
|
|
2180
|
+
root_work_item_status: normalizeRunnerWorkItemStatus(entry.root_work_item_status || entry.rootWorkItemStatus),
|
|
2181
|
+
root_thread_id: String(entry.root_thread_id || entry.rootThreadID || "").trim(),
|
|
2182
|
+
root_work_item_created_at: firstNonEmptyString([entry.root_work_item_created_at, entry.rootWorkItemCreatedAt]),
|
|
2183
|
+
root_work_item_last_error: String(entry.root_work_item_last_error || entry.rootWorkItemLastError || "").trim(),
|
|
2161
2184
|
last_comment_id: String(entry.last_comment_id || entry.lastCommentID || "").trim(),
|
|
2162
2185
|
last_comment_kind: String(entry.last_comment_kind || entry.lastCommentKind || "").trim().toLowerCase(),
|
|
2163
2186
|
last_source_message_id: intFromRawAllowZero(entry.last_source_message_id || entry.lastSourceMessageID, 0) || undefined,
|
|
@@ -2363,6 +2386,66 @@ function buildRunnerRequestKey({
|
|
|
2363
2386
|
].join("::");
|
|
2364
2387
|
}
|
|
2365
2388
|
|
|
2389
|
+
function looksLikeRunnerClaimQuestion(rawText) {
|
|
2390
|
+
return /[??]/.test(String(rawText || ""));
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
function inferRunnerRequestClaimIntent(selectedRecord) {
|
|
2394
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2395
|
+
const commentKind = String(parsed.kind || "").trim().toLowerCase();
|
|
2396
|
+
if (!isInboundArchiveKind(commentKind)) {
|
|
2397
|
+
return "";
|
|
2398
|
+
}
|
|
2399
|
+
const rawText = String(parsed.body || "").trim();
|
|
2400
|
+
const normalizedText = rawText.toLowerCase();
|
|
2401
|
+
const replyToMessageID = intFromRawAllowZero(parsed.replyToMessageID, 0);
|
|
2402
|
+
if (!rawText) {
|
|
2403
|
+
return "";
|
|
2404
|
+
}
|
|
2405
|
+
if (/^(hi|hello|hey|thanks|thank you|good morning|good afternoon|good evening)\b/.test(normalizedText)) {
|
|
2406
|
+
return "small_talk";
|
|
2407
|
+
}
|
|
2408
|
+
if (/\b(bot role|your role|what do you do|who are you|which bot|who should respond)\b/.test(normalizedText)) {
|
|
2409
|
+
return "bot_role_query";
|
|
2410
|
+
}
|
|
2411
|
+
if (/\b(workspace|working directory|workdir|project folder|local folder)\b/.test(normalizedText)) {
|
|
2412
|
+
return "workspace_query";
|
|
2413
|
+
}
|
|
2414
|
+
if (
|
|
2415
|
+
(/\b(where|path|locate|find)\b/.test(normalizedText) || /\.[a-z0-9]{1,8}\b/.test(normalizedText))
|
|
2416
|
+
&& /\b(file|folder|path|doc|guide|readme|workspace)\b/.test(normalizedText)
|
|
2417
|
+
) {
|
|
2418
|
+
return "artifact_location_query";
|
|
2419
|
+
}
|
|
2420
|
+
if (/\b(why|explain|what is|what does|how does|describe)\b/.test(normalizedText) && looksLikeRunnerClaimQuestion(rawText)) {
|
|
2421
|
+
return "explanation_query";
|
|
2422
|
+
}
|
|
2423
|
+
if (
|
|
2424
|
+
/\b(status|progress|done|finished|complete|completed|working on|current work|did you|handled|handle it|what are you working|check status)\b/
|
|
2425
|
+
.test(normalizedText)
|
|
2426
|
+
) {
|
|
2427
|
+
return "status_query";
|
|
2428
|
+
}
|
|
2429
|
+
if (replyToMessageID > 0 && looksLikeRunnerClaimQuestion(rawText)) {
|
|
2430
|
+
return "status_query";
|
|
2431
|
+
}
|
|
2432
|
+
if (looksLikeRunnerClaimQuestion(rawText) && normalizedText.split(/\s+/).filter(Boolean).length <= 8) {
|
|
2433
|
+
return "status_query";
|
|
2434
|
+
}
|
|
2435
|
+
return "general_execution";
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
function resolveRunnerRequestClaimIntent({
|
|
2439
|
+
normalizedIntent = "",
|
|
2440
|
+
selectedRecord,
|
|
2441
|
+
}) {
|
|
2442
|
+
const explicitIntent = String(normalizedIntent || "").trim().toLowerCase();
|
|
2443
|
+
if (explicitIntent) {
|
|
2444
|
+
return explicitIntent;
|
|
2445
|
+
}
|
|
2446
|
+
return inferRunnerRequestClaimIntent(selectedRecord);
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2366
2449
|
function buildSyntheticReplyChainConversationID(normalizedRoute, chatID, anchorMessageID) {
|
|
2367
2450
|
const provider = String(normalizedRoute?.provider || "").trim() || "unknown";
|
|
2368
2451
|
const normalizedChatID = String(chatID || "").trim() || "-";
|
|
@@ -2409,6 +2492,67 @@ function findRunnerRequestsForMessageID(state, normalizedRoute, selectors = {})
|
|
|
2409
2492
|
));
|
|
2410
2493
|
}
|
|
2411
2494
|
|
|
2495
|
+
function sortRunnerRequestEntriesNewestFirst(entries = []) {
|
|
2496
|
+
return ensureArray(entries).slice().sort((leftRaw, rightRaw) => {
|
|
2497
|
+
const left = safeObject(leftRaw);
|
|
2498
|
+
const right = safeObject(rightRaw);
|
|
2499
|
+
const leftTime = firstNonEmptyString([left.updated_at, left.completed_at, left.closed_at, left.claimed_at]);
|
|
2500
|
+
const rightTime = firstNonEmptyString([right.updated_at, right.completed_at, right.closed_at, right.claimed_at]);
|
|
2501
|
+
if (leftTime && rightTime && leftTime !== rightTime) {
|
|
2502
|
+
return leftTime < rightTime ? 1 : -1;
|
|
2503
|
+
}
|
|
2504
|
+
return String(left.request_key || "").localeCompare(String(right.request_key || ""));
|
|
2505
|
+
});
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
async function findServerRunnerRequestForMessageID({
|
|
2509
|
+
normalizedRoute,
|
|
2510
|
+
runtime,
|
|
2511
|
+
chatID,
|
|
2512
|
+
messageID,
|
|
2513
|
+
}) {
|
|
2514
|
+
const projectID = String(normalizedRoute?.projectID || "").trim();
|
|
2515
|
+
const provider = String(normalizedRoute?.provider || "").trim();
|
|
2516
|
+
const normalizedChatID = String(chatID || "").trim();
|
|
2517
|
+
const normalizedMessageID = intFromRawAllowZero(messageID, 0);
|
|
2518
|
+
if (
|
|
2519
|
+
!projectID
|
|
2520
|
+
|| !provider
|
|
2521
|
+
|| !normalizedChatID
|
|
2522
|
+
|| normalizedMessageID <= 0
|
|
2523
|
+
|| !runtime?.baseURL
|
|
2524
|
+
|| !runtime?.token
|
|
2525
|
+
) {
|
|
2526
|
+
return null;
|
|
2527
|
+
}
|
|
2528
|
+
try {
|
|
2529
|
+
const serverRequests = await listProjectRunnerRequests({
|
|
2530
|
+
siteBaseURL: runtime.baseURL,
|
|
2531
|
+
projectID,
|
|
2532
|
+
token: runtime.token,
|
|
2533
|
+
timeoutSeconds: runtime.timeoutSeconds,
|
|
2534
|
+
actorUserID: runtime.actor?.user_id,
|
|
2535
|
+
limit: 500,
|
|
2536
|
+
offset: 0,
|
|
2537
|
+
});
|
|
2538
|
+
const matched = sortRunnerRequestEntriesNewestFirst(serverRequests.filter((entryRaw) => {
|
|
2539
|
+
const entry = safeObject(entryRaw);
|
|
2540
|
+
return (
|
|
2541
|
+
String(entry.project_id || "").trim() === projectID
|
|
2542
|
+
&& String(entry.provider || "").trim() === provider
|
|
2543
|
+
&& String(entry.chat_id || "").trim() === normalizedChatID
|
|
2544
|
+
&& (
|
|
2545
|
+
intFromRawAllowZero(entry.source_message_id, 0) === normalizedMessageID
|
|
2546
|
+
|| intFromRawAllowZero(entry.last_source_message_id, 0) === normalizedMessageID
|
|
2547
|
+
)
|
|
2548
|
+
);
|
|
2549
|
+
}));
|
|
2550
|
+
return safeObject(matched[0]);
|
|
2551
|
+
} catch {
|
|
2552
|
+
return null;
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2412
2556
|
function resolveRunnerReplyChainConversationContext(state, normalizedRoute, selectedRecord) {
|
|
2413
2557
|
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2414
2558
|
const explicitConversationID = String(parsed.conversationID || "").trim();
|
|
@@ -2451,6 +2595,74 @@ function resolveRunnerReplyChainConversationContext(state, normalizedRoute, sele
|
|
|
2451
2595
|
};
|
|
2452
2596
|
}
|
|
2453
2597
|
|
|
2598
|
+
async function resolveRunnerReplyChainConversationContextWithServerFallback({
|
|
2599
|
+
state,
|
|
2600
|
+
normalizedRoute,
|
|
2601
|
+
selectedRecord,
|
|
2602
|
+
runtime,
|
|
2603
|
+
}) {
|
|
2604
|
+
const initialState = safeObject(state);
|
|
2605
|
+
const initialContext = resolveRunnerReplyChainConversationContext(initialState, normalizedRoute, selectedRecord);
|
|
2606
|
+
if (safeObject(initialContext.referencedRequest).request_key) {
|
|
2607
|
+
return {
|
|
2608
|
+
state: initialState,
|
|
2609
|
+
replyChainContext: initialContext,
|
|
2610
|
+
hydrated: false,
|
|
2611
|
+
};
|
|
2612
|
+
}
|
|
2613
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2614
|
+
const replyToMessageID = intFromRawAllowZero(
|
|
2615
|
+
parsed.replyToMessageID || safeObject(initialContext).replyToMessageID,
|
|
2616
|
+
0,
|
|
2617
|
+
);
|
|
2618
|
+
if (replyToMessageID <= 0 || !runtime?.baseURL || !runtime?.token) {
|
|
2619
|
+
return {
|
|
2620
|
+
state: initialState,
|
|
2621
|
+
replyChainContext: initialContext,
|
|
2622
|
+
hydrated: false,
|
|
2623
|
+
};
|
|
2624
|
+
}
|
|
2625
|
+
const chatID = String(parsed.chatID || parsed.chatId || "").trim();
|
|
2626
|
+
const serverReferencedRequest = await findServerRunnerRequestForMessageID({
|
|
2627
|
+
normalizedRoute,
|
|
2628
|
+
runtime,
|
|
2629
|
+
chatID,
|
|
2630
|
+
messageID: replyToMessageID,
|
|
2631
|
+
});
|
|
2632
|
+
if (serverReferencedRequest.request_key) {
|
|
2633
|
+
const requestIndex = normalizeBotRunnerRequests(initialState.requests);
|
|
2634
|
+
requestIndex[String(serverReferencedRequest.request_key || "").trim()] = serverReferencedRequest;
|
|
2635
|
+
const anchorMessageID = intFromRawAllowZero(serverReferencedRequest.source_message_id, 0) || replyToMessageID;
|
|
2636
|
+
return {
|
|
2637
|
+
state: {
|
|
2638
|
+
...initialState,
|
|
2639
|
+
requests: requestIndex,
|
|
2640
|
+
},
|
|
2641
|
+
replyChainContext: {
|
|
2642
|
+
conversationID: String(serverReferencedRequest.conversation_id || "").trim()
|
|
2643
|
+
|| buildSyntheticReplyChainConversationID(normalizedRoute, chatID, anchorMessageID),
|
|
2644
|
+
replyToMessageID,
|
|
2645
|
+
anchorMessageID,
|
|
2646
|
+
reason: String(serverReferencedRequest.conversation_id || "").trim()
|
|
2647
|
+
? "reply_request_conversation_server"
|
|
2648
|
+
: "reply_request_synthetic_server",
|
|
2649
|
+
referencedRequest: serverReferencedRequest,
|
|
2650
|
+
},
|
|
2651
|
+
hydrated: false,
|
|
2652
|
+
};
|
|
2653
|
+
}
|
|
2654
|
+
const hydratedState = await hydrateRunnerRequestLedgerFromServer({
|
|
2655
|
+
normalizedRoute,
|
|
2656
|
+
runtime,
|
|
2657
|
+
});
|
|
2658
|
+
const hydratedContext = resolveRunnerReplyChainConversationContext(hydratedState, normalizedRoute, selectedRecord);
|
|
2659
|
+
return {
|
|
2660
|
+
state: hydratedState,
|
|
2661
|
+
replyChainContext: hydratedContext,
|
|
2662
|
+
hydrated: true,
|
|
2663
|
+
};
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2454
2666
|
function upsertRunnerRequest(state, requestKey, patch = {}) {
|
|
2455
2667
|
const currentState = safeObject(state);
|
|
2456
2668
|
const requests = normalizeBotRunnerRequests(currentState.requests);
|
|
@@ -2490,104 +2702,699 @@ function upsertRunnerConsumedComment(state, commentIDRaw, patch = {}) {
|
|
|
2490
2702
|
};
|
|
2491
2703
|
}
|
|
2492
2704
|
|
|
2493
|
-
function claimRunnerRequestForHumanComment({
|
|
2705
|
+
async function claimRunnerRequestForHumanComment({
|
|
2494
2706
|
normalizedRoute,
|
|
2495
2707
|
routeKey,
|
|
2496
2708
|
selectedRecord,
|
|
2497
2709
|
selectedBotUsernames = [],
|
|
2498
2710
|
normalizedIntent = "",
|
|
2711
|
+
runtime = null,
|
|
2712
|
+
}) {
|
|
2713
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2714
|
+
const commentKind = String(parsed.kind || "").trim().toLowerCase();
|
|
2715
|
+
if (!isInboundArchiveKind(commentKind)) {
|
|
2716
|
+
return {
|
|
2717
|
+
ok: false,
|
|
2718
|
+
reason: "non_human_comment_cannot_create_request",
|
|
2719
|
+
};
|
|
2720
|
+
}
|
|
2721
|
+
const currentState = loadBotRunnerState();
|
|
2722
|
+
const replyChainResolution = await resolveRunnerReplyChainConversationContextWithServerFallback({
|
|
2723
|
+
state: currentState,
|
|
2724
|
+
normalizedRoute,
|
|
2725
|
+
selectedRecord,
|
|
2726
|
+
runtime,
|
|
2727
|
+
});
|
|
2728
|
+
const replyChainContext = safeObject(replyChainResolution.replyChainContext);
|
|
2729
|
+
const referencedRequest = safeObject(replyChainContext.referencedRequest);
|
|
2730
|
+
const conversationID = String(parsed.conversationID || replyChainContext.conversationID || "").trim();
|
|
2731
|
+
const resolvedNormalizedIntent = resolveRunnerRequestClaimIntent({
|
|
2732
|
+
normalizedIntent,
|
|
2733
|
+
selectedRecord,
|
|
2734
|
+
});
|
|
2735
|
+
const requestKey = buildRunnerRequestKey({
|
|
2736
|
+
normalizedRoute,
|
|
2737
|
+
selectedRecord,
|
|
2738
|
+
selectedBotUsernames,
|
|
2739
|
+
normalizedIntent: resolvedNormalizedIntent,
|
|
2740
|
+
});
|
|
2741
|
+
let stateForClaim = safeObject(replyChainResolution.state);
|
|
2742
|
+
if (
|
|
2743
|
+
Object.keys(referencedRequest).length > 0
|
|
2744
|
+
&& conversationID
|
|
2745
|
+
&& !String(referencedRequest.conversation_id || "").trim()
|
|
2746
|
+
&& String(referencedRequest.request_key || "").trim()
|
|
2747
|
+
) {
|
|
2748
|
+
const backfilled = upsertRunnerRequest(stateForClaim, referencedRequest.request_key, {
|
|
2749
|
+
conversation_id: conversationID,
|
|
2750
|
+
});
|
|
2751
|
+
stateForClaim = {
|
|
2752
|
+
...stateForClaim,
|
|
2753
|
+
requests: backfilled.requests,
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
const requests = normalizeBotRunnerRequests(stateForClaim.requests);
|
|
2757
|
+
const existing = safeObject(requests[requestKey]);
|
|
2758
|
+
if (isFinalRunnerRequestStatus(existing.status)) {
|
|
2759
|
+
return {
|
|
2760
|
+
ok: false,
|
|
2761
|
+
reason: "request_already_finalized",
|
|
2762
|
+
requestKey,
|
|
2763
|
+
};
|
|
2764
|
+
}
|
|
2765
|
+
if (
|
|
2766
|
+
isActiveRunnerRequestStatus(existing.status)
|
|
2767
|
+
&& String(existing.claimed_by_route || "").trim()
|
|
2768
|
+
&& String(existing.claimed_by_route || "").trim() !== String(routeKey || "").trim()
|
|
2769
|
+
) {
|
|
2770
|
+
return {
|
|
2771
|
+
ok: false,
|
|
2772
|
+
reason: "request_already_claimed",
|
|
2773
|
+
requestKey,
|
|
2774
|
+
};
|
|
2775
|
+
}
|
|
2776
|
+
const nowISO = new Date().toISOString();
|
|
2777
|
+
const { requests: nextRequests, request } = upsertRunnerRequest(stateForClaim, requestKey, {
|
|
2778
|
+
project_id: String(normalizedRoute?.projectID || "").trim(),
|
|
2779
|
+
provider: String(normalizedRoute?.provider || "").trim(),
|
|
2780
|
+
chat_id: String(parsed.chatID || parsed.chatId || "").trim(),
|
|
2781
|
+
source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
2782
|
+
root_comment_id: String(selectedRecord?.id || "").trim(),
|
|
2783
|
+
root_comment_kind: commentKind,
|
|
2784
|
+
conversation_id: conversationID,
|
|
2785
|
+
selected_bot_usernames: uniqueOrderedStrings(selectedBotUsernames, normalizeTelegramMentionUsername),
|
|
2786
|
+
normalized_intent: resolvedNormalizedIntent,
|
|
2787
|
+
status: "claimed",
|
|
2788
|
+
claimed_by_route: String(routeKey || "").trim(),
|
|
2789
|
+
claimed_at: firstNonEmptyString([existing.claimed_at, nowISO]) || nowISO,
|
|
2790
|
+
root_work_item_id: String(existing.root_work_item_id || referencedRequest.root_work_item_id || "").trim(),
|
|
2791
|
+
root_work_item_title: String(existing.root_work_item_title || referencedRequest.root_work_item_title || "").trim(),
|
|
2792
|
+
root_work_item_status: normalizeRunnerWorkItemStatus(
|
|
2793
|
+
existing.root_work_item_status || referencedRequest.root_work_item_status,
|
|
2794
|
+
),
|
|
2795
|
+
root_thread_id: String(existing.root_thread_id || referencedRequest.root_thread_id || "").trim(),
|
|
2796
|
+
root_work_item_created_at: firstNonEmptyString([
|
|
2797
|
+
existing.root_work_item_created_at,
|
|
2798
|
+
referencedRequest.root_work_item_created_at,
|
|
2799
|
+
]),
|
|
2800
|
+
root_work_item_last_error: String(existing.root_work_item_last_error || "").trim(),
|
|
2801
|
+
last_comment_id: String(selectedRecord?.id || "").trim(),
|
|
2802
|
+
last_comment_kind: commentKind,
|
|
2803
|
+
last_source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
2804
|
+
});
|
|
2805
|
+
const { consumedComments: nextConsumedComments } = upsertRunnerConsumedComment(stateForClaim, selectedRecord?.id, {
|
|
2806
|
+
project_id: String(normalizedRoute?.projectID || "").trim(),
|
|
2807
|
+
provider: String(normalizedRoute?.provider || "").trim(),
|
|
2808
|
+
request_key: requestKey,
|
|
2809
|
+
route_key: String(routeKey || "").trim(),
|
|
2810
|
+
conversation_id: conversationID,
|
|
2811
|
+
source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
2812
|
+
comment_kind: commentKind,
|
|
2813
|
+
request_status: "claimed",
|
|
2814
|
+
});
|
|
2815
|
+
saveBotRunnerState({
|
|
2816
|
+
routes: stateForClaim.routes,
|
|
2817
|
+
sharedInboxes: stateForClaim.sharedInboxes || stateForClaim.shared_inboxes,
|
|
2818
|
+
excludedComments: stateForClaim.excludedComments || stateForClaim.excluded_comments,
|
|
2819
|
+
requests: nextRequests,
|
|
2820
|
+
consumedComments: nextConsumedComments,
|
|
2821
|
+
});
|
|
2822
|
+
return {
|
|
2823
|
+
ok: true,
|
|
2824
|
+
requestKey,
|
|
2825
|
+
request,
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
function isActionableRunnerRequestIntent(rawIntent) {
|
|
2830
|
+
const normalizedIntent = String(rawIntent || "").trim().toLowerCase();
|
|
2831
|
+
return Boolean(normalizedIntent) && !isInformationalRunnerRequestIntent(normalizedIntent);
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
function loadRunnerRequestByKey(requestKey) {
|
|
2835
|
+
const key = String(requestKey || "").trim();
|
|
2836
|
+
if (!key) {
|
|
2837
|
+
return {};
|
|
2838
|
+
}
|
|
2839
|
+
return safeObject(normalizeBotRunnerRequests(loadBotRunnerState().requests)[key]);
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
function actionableRunnerRequestMissingRootWorkItem(requestRaw) {
|
|
2843
|
+
const request = safeObject(requestRaw);
|
|
2844
|
+
return (
|
|
2845
|
+
isActionableRunnerRequestIntent(request.normalized_intent)
|
|
2846
|
+
&& !String(request.root_work_item_id || "").trim()
|
|
2847
|
+
);
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
function truncateRunnerWorkItemTitleText(rawText, maxLength = 96) {
|
|
2851
|
+
const text = String(rawText || "").replace(/\s+/g, " ").trim();
|
|
2852
|
+
if (!text) {
|
|
2853
|
+
return "";
|
|
2854
|
+
}
|
|
2855
|
+
if (text.length <= maxLength) {
|
|
2856
|
+
return text;
|
|
2857
|
+
}
|
|
2858
|
+
return `${text.slice(0, Math.max(1, maxLength - 3)).trim()}...`;
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
function buildRunnerRootWorkItemTitle({ selectedRecord, request }) {
|
|
2862
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2863
|
+
const intent = String(safeObject(request).normalized_intent || "").trim().toLowerCase();
|
|
2864
|
+
const requestBody = truncateRunnerWorkItemTitleText(parsed.body || "", 84);
|
|
2865
|
+
const prefix = intent === "ctxpack_mutation"
|
|
2866
|
+
? "Ctxpack request"
|
|
2867
|
+
: intent === "workitem_mutation"
|
|
2868
|
+
? "Work item request"
|
|
2869
|
+
: "Runner request";
|
|
2870
|
+
if (requestBody) {
|
|
2871
|
+
return `${prefix}: ${requestBody}`;
|
|
2872
|
+
}
|
|
2873
|
+
const messageID = intFromRawAllowZero(parsed.messageID, 0);
|
|
2874
|
+
return messageID > 0 ? `${prefix} #${messageID}` : prefix;
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
function buildRunnerRootWorkItemDescription({
|
|
2878
|
+
normalizedRoute,
|
|
2879
|
+
routeKey,
|
|
2880
|
+
selectedRecord,
|
|
2881
|
+
request,
|
|
2882
|
+
}) {
|
|
2883
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2884
|
+
const lines = [
|
|
2885
|
+
`Source request: ${String(parsed.body || "").trim() || "(empty)"}`,
|
|
2886
|
+
`Intent: ${String(safeObject(request).normalized_intent || "").trim() || "unknown"}`,
|
|
2887
|
+
`Route: ${String(normalizedRoute?.name || routeKey || "").trim() || "-"}`,
|
|
2888
|
+
`Provider: ${String(normalizedRoute?.provider || "").trim() || "-"}`,
|
|
2889
|
+
`Chat ID: ${String(parsed.chatID || parsed.chatId || "").trim() || "-"}`,
|
|
2890
|
+
`Message ID: ${intFromRawAllowZero(parsed.messageID, 0) || "-"}`,
|
|
2891
|
+
`Request key: ${String(safeObject(request).request_key || "").trim() || "-"}`,
|
|
2892
|
+
];
|
|
2893
|
+
const conversationID = String(safeObject(request).conversation_id || parsed.conversationID || "").trim();
|
|
2894
|
+
if (conversationID) {
|
|
2895
|
+
lines.push(`Conversation ID: ${conversationID}`);
|
|
2896
|
+
}
|
|
2897
|
+
const selectedBots = ensureArray(safeObject(request).selected_bot_usernames)
|
|
2898
|
+
.map((item) => normalizeTelegramMentionUsername(item))
|
|
2899
|
+
.filter(Boolean);
|
|
2900
|
+
if (selectedBots.length > 0) {
|
|
2901
|
+
lines.push(`Selected bots: ${selectedBots.join(", ")}`);
|
|
2902
|
+
}
|
|
2903
|
+
return lines.join("\n").trim();
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
function buildRunnerRootWorkItemThreadTitle({ selectedRecord }) {
|
|
2907
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2908
|
+
const requestBody = truncateRunnerWorkItemTitleText(parsed.body || "", 72);
|
|
2909
|
+
if (requestBody) {
|
|
2910
|
+
return `Request Context: ${requestBody}`;
|
|
2911
|
+
}
|
|
2912
|
+
const messageID = intFromRawAllowZero(parsed.messageID, 0);
|
|
2913
|
+
return messageID > 0 ? `Request Context #${messageID}` : "Request Context";
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
function buildRunnerRootWorkItemThreadBody({
|
|
2917
|
+
normalizedRoute,
|
|
2918
|
+
routeKey,
|
|
2919
|
+
selectedRecord,
|
|
2920
|
+
request,
|
|
2921
|
+
}) {
|
|
2922
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2923
|
+
const lines = [
|
|
2924
|
+
"Runner root request context thread.",
|
|
2925
|
+
buildRunnerRootWorkItemDescription({
|
|
2926
|
+
normalizedRoute,
|
|
2927
|
+
routeKey,
|
|
2928
|
+
selectedRecord,
|
|
2929
|
+
request,
|
|
2930
|
+
}),
|
|
2931
|
+
];
|
|
2932
|
+
if (String(selectedRecord?.id || "").trim()) {
|
|
2933
|
+
lines.push(`Archive comment ID: ${String(selectedRecord.id || "").trim()}`);
|
|
2934
|
+
}
|
|
2935
|
+
const occurredAt = String(parsed.occurredAt || parsed.occurred_at || "").trim();
|
|
2936
|
+
if (occurredAt) {
|
|
2937
|
+
lines.push(`Occurred at: ${occurredAt}`);
|
|
2938
|
+
}
|
|
2939
|
+
return lines.filter(Boolean).join("\n").trim();
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2942
|
+
async function ensureRunnerRootThreadForRequest({
|
|
2943
|
+
normalizedRoute,
|
|
2944
|
+
routeKey,
|
|
2945
|
+
selectedRecord,
|
|
2946
|
+
runtime,
|
|
2947
|
+
requestKey,
|
|
2948
|
+
}) {
|
|
2949
|
+
const key = String(requestKey || "").trim();
|
|
2950
|
+
if (!key) {
|
|
2951
|
+
return {
|
|
2952
|
+
ok: false,
|
|
2953
|
+
reason: "request_key_missing",
|
|
2954
|
+
};
|
|
2955
|
+
}
|
|
2956
|
+
const currentState = loadBotRunnerState();
|
|
2957
|
+
const request = safeObject(normalizeBotRunnerRequests(currentState.requests)[key]);
|
|
2958
|
+
const rootWorkItemID = String(request.root_work_item_id || "").trim();
|
|
2959
|
+
if (!Object.keys(request).length) {
|
|
2960
|
+
return {
|
|
2961
|
+
ok: false,
|
|
2962
|
+
reason: "request_not_found",
|
|
2963
|
+
requestKey: key,
|
|
2964
|
+
};
|
|
2965
|
+
}
|
|
2966
|
+
if (!rootWorkItemID) {
|
|
2967
|
+
return {
|
|
2968
|
+
ok: true,
|
|
2969
|
+
requestKey: key,
|
|
2970
|
+
request,
|
|
2971
|
+
skipped: true,
|
|
2972
|
+
reason: "root_work_item_missing",
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
if (String(request.root_thread_id || "").trim()) {
|
|
2976
|
+
return {
|
|
2977
|
+
ok: true,
|
|
2978
|
+
requestKey: key,
|
|
2979
|
+
request,
|
|
2980
|
+
reused: true,
|
|
2981
|
+
};
|
|
2982
|
+
}
|
|
2983
|
+
if (!runtime?.baseURL || !runtime?.token || !runtime?.actor?.user_id) {
|
|
2984
|
+
return {
|
|
2985
|
+
ok: false,
|
|
2986
|
+
reason: "governance_runtime_unavailable",
|
|
2987
|
+
requestKey: key,
|
|
2988
|
+
request,
|
|
2989
|
+
};
|
|
2990
|
+
}
|
|
2991
|
+
try {
|
|
2992
|
+
let rootThreadID = "";
|
|
2993
|
+
const existingThreads = ensureArray(await listWorkItemThreads({
|
|
2994
|
+
siteBaseURL: runtime.baseURL,
|
|
2995
|
+
token: runtime.token,
|
|
2996
|
+
timeoutSeconds: runtime.timeoutSeconds,
|
|
2997
|
+
workItemID: rootWorkItemID,
|
|
2998
|
+
status: "",
|
|
2999
|
+
}));
|
|
3000
|
+
rootThreadID = String(
|
|
3001
|
+
safeObject(existingThreads[0]).id
|
|
3002
|
+
|| safeObject(existingThreads[0]).thread_id
|
|
3003
|
+
|| safeObject(existingThreads[0]).threadID
|
|
3004
|
+
|| "",
|
|
3005
|
+
).trim();
|
|
3006
|
+
if (!rootThreadID) {
|
|
3007
|
+
const createdThread = safeObject(await createWorkItemThread({
|
|
3008
|
+
siteBaseURL: runtime.baseURL,
|
|
3009
|
+
token: runtime.token,
|
|
3010
|
+
timeoutSeconds: runtime.timeoutSeconds,
|
|
3011
|
+
actorUserID: runtime.actor.user_id,
|
|
3012
|
+
workItemID: rootWorkItemID,
|
|
3013
|
+
title: buildRunnerRootWorkItemThreadTitle({
|
|
3014
|
+
selectedRecord,
|
|
3015
|
+
}),
|
|
3016
|
+
body: buildRunnerRootWorkItemThreadBody({
|
|
3017
|
+
normalizedRoute,
|
|
3018
|
+
routeKey,
|
|
3019
|
+
selectedRecord,
|
|
3020
|
+
request,
|
|
3021
|
+
}),
|
|
3022
|
+
}));
|
|
3023
|
+
rootThreadID = String(createdThread.thread_id || createdThread.threadID || createdThread.id || "").trim();
|
|
3024
|
+
}
|
|
3025
|
+
if (!rootThreadID) {
|
|
3026
|
+
throw new Error("root thread creation returned no id");
|
|
3027
|
+
}
|
|
3028
|
+
const { requests: nextRequests, request: nextRequest } = upsertRunnerRequest(currentState, key, {
|
|
3029
|
+
root_thread_id: rootThreadID,
|
|
3030
|
+
});
|
|
3031
|
+
saveBotRunnerState({
|
|
3032
|
+
routes: currentState.routes,
|
|
3033
|
+
sharedInboxes: currentState.sharedInboxes || currentState.shared_inboxes,
|
|
3034
|
+
excludedComments: currentState.excludedComments || currentState.excluded_comments,
|
|
3035
|
+
requests: nextRequests,
|
|
3036
|
+
consumedComments: currentState.consumedComments || currentState.consumed_comments,
|
|
3037
|
+
});
|
|
3038
|
+
return {
|
|
3039
|
+
ok: true,
|
|
3040
|
+
requestKey: key,
|
|
3041
|
+
request: nextRequest,
|
|
3042
|
+
created: true,
|
|
3043
|
+
};
|
|
3044
|
+
} catch (err) {
|
|
3045
|
+
return {
|
|
3046
|
+
ok: false,
|
|
3047
|
+
reason: "root_thread_create_failed",
|
|
3048
|
+
requestKey: key,
|
|
3049
|
+
request,
|
|
3050
|
+
error: String(err?.message || err).trim() || "failed to create root thread",
|
|
3051
|
+
};
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
async function inheritRunnerReferenceRootWorkItemForRequest({
|
|
3056
|
+
normalizedRoute,
|
|
3057
|
+
selectedRecord,
|
|
3058
|
+
runtime,
|
|
3059
|
+
requestKey,
|
|
3060
|
+
}) {
|
|
3061
|
+
const key = String(requestKey || "").trim();
|
|
3062
|
+
if (!key) {
|
|
3063
|
+
return {
|
|
3064
|
+
ok: false,
|
|
3065
|
+
reason: "request_key_missing",
|
|
3066
|
+
};
|
|
3067
|
+
}
|
|
3068
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
3069
|
+
const chatID = String(parsed.chatID || parsed.chatId || "").trim();
|
|
3070
|
+
const replyToMessageID = intFromRawAllowZero(parsed.replyToMessageID, 0);
|
|
3071
|
+
if (!chatID || replyToMessageID <= 0) {
|
|
3072
|
+
return {
|
|
3073
|
+
ok: true,
|
|
3074
|
+
requestKey: key,
|
|
3075
|
+
skipped: true,
|
|
3076
|
+
reason: "reply_reference_missing",
|
|
3077
|
+
};
|
|
3078
|
+
}
|
|
3079
|
+
const currentState = loadBotRunnerState();
|
|
3080
|
+
const currentRequest = safeObject(normalizeBotRunnerRequests(currentState.requests)[key]);
|
|
3081
|
+
if (!Object.keys(currentRequest).length) {
|
|
3082
|
+
return {
|
|
3083
|
+
ok: false,
|
|
3084
|
+
requestKey: key,
|
|
3085
|
+
reason: "request_not_found",
|
|
3086
|
+
};
|
|
3087
|
+
}
|
|
3088
|
+
if (String(currentRequest.root_work_item_id || "").trim()) {
|
|
3089
|
+
return {
|
|
3090
|
+
ok: true,
|
|
3091
|
+
requestKey: key,
|
|
3092
|
+
request: currentRequest,
|
|
3093
|
+
reused: true,
|
|
3094
|
+
};
|
|
3095
|
+
}
|
|
3096
|
+
const serverReferencedRequest = await findServerRunnerRequestForMessageID({
|
|
3097
|
+
normalizedRoute,
|
|
3098
|
+
runtime,
|
|
3099
|
+
chatID,
|
|
3100
|
+
messageID: replyToMessageID,
|
|
3101
|
+
});
|
|
3102
|
+
if (!String(serverReferencedRequest.root_work_item_id || "").trim()) {
|
|
3103
|
+
return {
|
|
3104
|
+
ok: true,
|
|
3105
|
+
requestKey: key,
|
|
3106
|
+
request: currentRequest,
|
|
3107
|
+
skipped: true,
|
|
3108
|
+
reason: "referenced_root_work_item_missing",
|
|
3109
|
+
};
|
|
3110
|
+
}
|
|
3111
|
+
const { requests: nextRequests, request } = upsertRunnerRequest(currentState, key, {
|
|
3112
|
+
root_work_item_id: String(serverReferencedRequest.root_work_item_id || "").trim(),
|
|
3113
|
+
root_work_item_title: String(serverReferencedRequest.root_work_item_title || "").trim(),
|
|
3114
|
+
root_work_item_status: normalizeRunnerWorkItemStatus(serverReferencedRequest.root_work_item_status),
|
|
3115
|
+
root_thread_id: String(serverReferencedRequest.root_thread_id || "").trim(),
|
|
3116
|
+
root_work_item_created_at: firstNonEmptyString([serverReferencedRequest.root_work_item_created_at]),
|
|
3117
|
+
root_work_item_last_error: String(serverReferencedRequest.root_work_item_last_error || "").trim(),
|
|
3118
|
+
});
|
|
3119
|
+
saveBotRunnerState({
|
|
3120
|
+
routes: currentState.routes,
|
|
3121
|
+
sharedInboxes: currentState.sharedInboxes || currentState.shared_inboxes,
|
|
3122
|
+
excludedComments: currentState.excludedComments || currentState.excluded_comments,
|
|
3123
|
+
requests: nextRequests,
|
|
3124
|
+
consumedComments: currentState.consumedComments || currentState.consumed_comments,
|
|
3125
|
+
});
|
|
3126
|
+
return {
|
|
3127
|
+
ok: true,
|
|
3128
|
+
requestKey: key,
|
|
3129
|
+
request,
|
|
3130
|
+
inherited: true,
|
|
3131
|
+
};
|
|
3132
|
+
}
|
|
3133
|
+
|
|
3134
|
+
async function ensureRunnerRootWorkItemForRequest({
|
|
3135
|
+
normalizedRoute,
|
|
3136
|
+
routeKey,
|
|
3137
|
+
selectedRecord,
|
|
3138
|
+
runtime,
|
|
3139
|
+
requestKey,
|
|
3140
|
+
}) {
|
|
3141
|
+
const key = String(requestKey || "").trim();
|
|
3142
|
+
if (!key) {
|
|
3143
|
+
return {
|
|
3144
|
+
ok: false,
|
|
3145
|
+
reason: "request_key_missing",
|
|
3146
|
+
};
|
|
3147
|
+
}
|
|
3148
|
+
const currentState = loadBotRunnerState();
|
|
3149
|
+
const existing = safeObject(normalizeBotRunnerRequests(currentState.requests)[key]);
|
|
3150
|
+
if (!Object.keys(existing).length) {
|
|
3151
|
+
return {
|
|
3152
|
+
ok: false,
|
|
3153
|
+
reason: "request_not_found",
|
|
3154
|
+
requestKey: key,
|
|
3155
|
+
};
|
|
3156
|
+
}
|
|
3157
|
+
if (!isActionableRunnerRequestIntent(existing.normalized_intent)) {
|
|
3158
|
+
return {
|
|
3159
|
+
ok: true,
|
|
3160
|
+
requestKey: key,
|
|
3161
|
+
request: existing,
|
|
3162
|
+
skipped: true,
|
|
3163
|
+
reason: "informational_request",
|
|
3164
|
+
};
|
|
3165
|
+
}
|
|
3166
|
+
if (String(existing.root_work_item_id || "").trim()) {
|
|
3167
|
+
const rootThreadClaim = await ensureRunnerRootThreadForRequest({
|
|
3168
|
+
normalizedRoute,
|
|
3169
|
+
routeKey,
|
|
3170
|
+
selectedRecord,
|
|
3171
|
+
runtime,
|
|
3172
|
+
requestKey: key,
|
|
3173
|
+
});
|
|
3174
|
+
return {
|
|
3175
|
+
ok: true,
|
|
3176
|
+
requestKey: key,
|
|
3177
|
+
request: safeObject(rootThreadClaim.request || existing),
|
|
3178
|
+
reused: true,
|
|
3179
|
+
root_thread_created: rootThreadClaim.created === true,
|
|
3180
|
+
root_thread_error: String(rootThreadClaim.error || "").trim(),
|
|
3181
|
+
};
|
|
3182
|
+
}
|
|
3183
|
+
if (!runtime?.baseURL || !runtime?.token || !runtime?.actor?.user_id) {
|
|
3184
|
+
return {
|
|
3185
|
+
ok: false,
|
|
3186
|
+
reason: "governance_runtime_unavailable",
|
|
3187
|
+
requestKey: key,
|
|
3188
|
+
};
|
|
3189
|
+
}
|
|
3190
|
+
try {
|
|
3191
|
+
const actorBotID = firstNonEmptyString([
|
|
3192
|
+
normalizedRoute?.botID,
|
|
3193
|
+
normalizedRoute?.botId,
|
|
3194
|
+
normalizedRoute?.serverBotID,
|
|
3195
|
+
normalizedRoute?.server_bot_id,
|
|
3196
|
+
]);
|
|
3197
|
+
const actorBotName = firstNonEmptyString([
|
|
3198
|
+
normalizedRoute?.botName,
|
|
3199
|
+
normalizedRoute?.bot_name,
|
|
3200
|
+
normalizedRoute?.serverBotName,
|
|
3201
|
+
normalizedRoute?.server_bot_name,
|
|
3202
|
+
]);
|
|
3203
|
+
const title = buildRunnerRootWorkItemTitle({
|
|
3204
|
+
selectedRecord,
|
|
3205
|
+
request: existing,
|
|
3206
|
+
});
|
|
3207
|
+
const description = buildRunnerRootWorkItemDescription({
|
|
3208
|
+
normalizedRoute,
|
|
3209
|
+
routeKey,
|
|
3210
|
+
selectedRecord,
|
|
3211
|
+
request: existing,
|
|
3212
|
+
});
|
|
3213
|
+
const created = safeObject(await createProjectWorkItem({
|
|
3214
|
+
siteBaseURL: runtime.baseURL,
|
|
3215
|
+
token: runtime.token,
|
|
3216
|
+
timeoutSeconds: runtime.timeoutSeconds,
|
|
3217
|
+
actorUserID: runtime.actor.user_id,
|
|
3218
|
+
actorBotID: String(actorBotID || "").trim(),
|
|
3219
|
+
actorBotName: String(actorBotName || "").trim(),
|
|
3220
|
+
projectID: String(normalizedRoute?.projectID || "").trim(),
|
|
3221
|
+
title,
|
|
3222
|
+
description,
|
|
3223
|
+
}));
|
|
3224
|
+
const rootWorkItemID = String(created.id || created.work_item_id || created.workItemID || "").trim();
|
|
3225
|
+
if (!rootWorkItemID) {
|
|
3226
|
+
throw new Error("work item creation returned no id");
|
|
3227
|
+
}
|
|
3228
|
+
const rootWorkItemStatus = normalizeRunnerWorkItemStatus(created.status || "backlog") || "backlog";
|
|
3229
|
+
const { requests: nextRequests, request } = upsertRunnerRequest(currentState, key, {
|
|
3230
|
+
root_work_item_id: rootWorkItemID,
|
|
3231
|
+
root_work_item_title: String(created.title || title).trim() || title,
|
|
3232
|
+
root_work_item_status: rootWorkItemStatus,
|
|
3233
|
+
root_work_item_created_at: new Date().toISOString(),
|
|
3234
|
+
root_work_item_last_error: "",
|
|
3235
|
+
});
|
|
3236
|
+
saveBotRunnerState({
|
|
3237
|
+
routes: currentState.routes,
|
|
3238
|
+
sharedInboxes: currentState.sharedInboxes || currentState.shared_inboxes,
|
|
3239
|
+
excludedComments: currentState.excludedComments || currentState.excluded_comments,
|
|
3240
|
+
requests: nextRequests,
|
|
3241
|
+
consumedComments: currentState.consumedComments || currentState.consumed_comments,
|
|
3242
|
+
});
|
|
3243
|
+
const rootThreadClaim = await ensureRunnerRootThreadForRequest({
|
|
3244
|
+
normalizedRoute,
|
|
3245
|
+
routeKey,
|
|
3246
|
+
selectedRecord,
|
|
3247
|
+
runtime,
|
|
3248
|
+
requestKey: key,
|
|
3249
|
+
});
|
|
3250
|
+
return {
|
|
3251
|
+
ok: true,
|
|
3252
|
+
requestKey: key,
|
|
3253
|
+
request: safeObject(rootThreadClaim.request || request),
|
|
3254
|
+
created: true,
|
|
3255
|
+
root_thread_created: rootThreadClaim.created === true,
|
|
3256
|
+
root_thread_error: String(rootThreadClaim.error || "").trim(),
|
|
3257
|
+
};
|
|
3258
|
+
} catch (err) {
|
|
3259
|
+
const errorText = String(err?.message || err).trim() || "failed to create root work item";
|
|
3260
|
+
const { requests: nextRequests, request } = upsertRunnerRequest(currentState, key, {
|
|
3261
|
+
root_work_item_last_error: errorText,
|
|
3262
|
+
});
|
|
3263
|
+
saveBotRunnerState({
|
|
3264
|
+
routes: currentState.routes,
|
|
3265
|
+
sharedInboxes: currentState.sharedInboxes || currentState.shared_inboxes,
|
|
3266
|
+
excludedComments: currentState.excludedComments || currentState.excluded_comments,
|
|
3267
|
+
requests: nextRequests,
|
|
3268
|
+
consumedComments: currentState.consumedComments || currentState.consumed_comments,
|
|
3269
|
+
});
|
|
3270
|
+
return {
|
|
3271
|
+
ok: false,
|
|
3272
|
+
reason: "root_work_item_create_failed",
|
|
3273
|
+
requestKey: key,
|
|
3274
|
+
request,
|
|
3275
|
+
error: errorText,
|
|
3276
|
+
};
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3280
|
+
function deriveRunnerRootWorkItemTargetStatus(rawRequestStatus) {
|
|
3281
|
+
const status = normalizeRunnerRequestStatus(rawRequestStatus);
|
|
3282
|
+
if (status === "running") {
|
|
3283
|
+
return "doing";
|
|
3284
|
+
}
|
|
3285
|
+
if (status === "completed" || status === "loop_closed") {
|
|
3286
|
+
return "done";
|
|
3287
|
+
}
|
|
3288
|
+
if (status === "closed" || status === "expired") {
|
|
3289
|
+
return "canceled";
|
|
3290
|
+
}
|
|
3291
|
+
return "";
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
function buildRunnerRootWorkItemTransitionPath(currentStatusRaw, targetStatusRaw) {
|
|
3295
|
+
const currentStatus = normalizeRunnerWorkItemStatus(currentStatusRaw) || "backlog";
|
|
3296
|
+
const targetStatus = normalizeRunnerWorkItemStatus(targetStatusRaw);
|
|
3297
|
+
if (!targetStatus || currentStatus === targetStatus) {
|
|
3298
|
+
return [];
|
|
3299
|
+
}
|
|
3300
|
+
if (targetStatus === "doing") {
|
|
3301
|
+
return currentStatus === "backlog" ? ["doing"] : [];
|
|
3302
|
+
}
|
|
3303
|
+
if (targetStatus === "done") {
|
|
3304
|
+
if (currentStatus === "backlog") return ["doing", "review", "done"];
|
|
3305
|
+
if (currentStatus === "doing") return ["review", "done"];
|
|
3306
|
+
if (currentStatus === "review") return ["done"];
|
|
3307
|
+
return [];
|
|
3308
|
+
}
|
|
3309
|
+
if (targetStatus === "canceled") {
|
|
3310
|
+
if (currentStatus === "backlog" || currentStatus === "doing" || currentStatus === "review") {
|
|
3311
|
+
return ["canceled"];
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
return [];
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
async function syncRunnerRequestRootWorkItemForOutcome({
|
|
3318
|
+
normalizedRoute,
|
|
3319
|
+
runtime,
|
|
3320
|
+
requestKey,
|
|
2499
3321
|
}) {
|
|
2500
|
-
const
|
|
2501
|
-
|
|
2502
|
-
if (!isInboundArchiveKind(commentKind)) {
|
|
3322
|
+
const key = String(requestKey || "").trim();
|
|
3323
|
+
if (!key) {
|
|
2503
3324
|
return {
|
|
2504
3325
|
ok: false,
|
|
2505
|
-
reason: "
|
|
3326
|
+
reason: "request_key_missing",
|
|
2506
3327
|
};
|
|
2507
3328
|
}
|
|
2508
|
-
const requestKey = buildRunnerRequestKey({
|
|
2509
|
-
normalizedRoute,
|
|
2510
|
-
selectedRecord,
|
|
2511
|
-
selectedBotUsernames,
|
|
2512
|
-
normalizedIntent,
|
|
2513
|
-
});
|
|
2514
3329
|
const currentState = loadBotRunnerState();
|
|
2515
|
-
const
|
|
2516
|
-
const
|
|
2517
|
-
|
|
2518
|
-
if (
|
|
2519
|
-
replyChainContext.referencedRequest
|
|
2520
|
-
&& conversationID
|
|
2521
|
-
&& !String(replyChainContext.referencedRequest.conversation_id || "").trim()
|
|
2522
|
-
&& String(replyChainContext.referencedRequest.request_key || "").trim()
|
|
2523
|
-
) {
|
|
2524
|
-
const backfilled = upsertRunnerRequest(stateForClaim, replyChainContext.referencedRequest.request_key, {
|
|
2525
|
-
conversation_id: conversationID,
|
|
2526
|
-
});
|
|
2527
|
-
stateForClaim = {
|
|
2528
|
-
...stateForClaim,
|
|
2529
|
-
requests: backfilled.requests,
|
|
2530
|
-
};
|
|
2531
|
-
}
|
|
2532
|
-
const requests = normalizeBotRunnerRequests(stateForClaim.requests);
|
|
2533
|
-
const existing = safeObject(requests[requestKey]);
|
|
2534
|
-
if (isFinalRunnerRequestStatus(existing.status)) {
|
|
3330
|
+
const request = safeObject(normalizeBotRunnerRequests(currentState.requests)[key]);
|
|
3331
|
+
const rootWorkItemID = String(request.root_work_item_id || "").trim();
|
|
3332
|
+
if (!rootWorkItemID) {
|
|
2535
3333
|
return {
|
|
2536
|
-
ok:
|
|
2537
|
-
|
|
2538
|
-
|
|
3334
|
+
ok: true,
|
|
3335
|
+
skipped: true,
|
|
3336
|
+
reason: "root_work_item_missing",
|
|
3337
|
+
request,
|
|
2539
3338
|
};
|
|
2540
3339
|
}
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
&& String(existing.claimed_by_route || "").trim()
|
|
2544
|
-
&& String(existing.claimed_by_route || "").trim() !== String(routeKey || "").trim()
|
|
2545
|
-
) {
|
|
3340
|
+
const targetStatus = deriveRunnerRootWorkItemTargetStatus(request.status);
|
|
3341
|
+
if (!targetStatus || !runtime?.baseURL || !runtime?.token || !runtime?.actor?.user_id) {
|
|
2546
3342
|
return {
|
|
2547
|
-
ok:
|
|
2548
|
-
|
|
2549
|
-
|
|
3343
|
+
ok: true,
|
|
3344
|
+
skipped: true,
|
|
3345
|
+
reason: !targetStatus ? "no_target_status" : "governance_runtime_unavailable",
|
|
3346
|
+
request,
|
|
2550
3347
|
};
|
|
2551
3348
|
}
|
|
2552
|
-
|
|
2553
|
-
const
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
3349
|
+
let currentStatus = normalizeRunnerWorkItemStatus(request.root_work_item_status) || "backlog";
|
|
3350
|
+
const transitions = buildRunnerRootWorkItemTransitionPath(currentStatus, targetStatus);
|
|
3351
|
+
let lastError = "";
|
|
3352
|
+
const actorBotID = firstNonEmptyString([
|
|
3353
|
+
normalizedRoute?.botID,
|
|
3354
|
+
normalizedRoute?.botId,
|
|
3355
|
+
normalizedRoute?.serverBotID,
|
|
3356
|
+
normalizedRoute?.server_bot_id,
|
|
3357
|
+
]);
|
|
3358
|
+
const actorBotName = firstNonEmptyString([
|
|
3359
|
+
normalizedRoute?.botName,
|
|
3360
|
+
normalizedRoute?.bot_name,
|
|
3361
|
+
normalizedRoute?.serverBotName,
|
|
3362
|
+
normalizedRoute?.server_bot_name,
|
|
3363
|
+
]);
|
|
3364
|
+
for (const nextStatus of transitions) {
|
|
3365
|
+
try {
|
|
3366
|
+
await transitionProjectWorkItem({
|
|
3367
|
+
siteBaseURL: runtime.baseURL,
|
|
3368
|
+
token: runtime.token,
|
|
3369
|
+
timeoutSeconds: runtime.timeoutSeconds,
|
|
3370
|
+
actorUserID: runtime.actor.user_id,
|
|
3371
|
+
actorBotID: String(actorBotID || "").trim(),
|
|
3372
|
+
actorBotName: String(actorBotName || "").trim(),
|
|
3373
|
+
workItemID: rootWorkItemID,
|
|
3374
|
+
status: nextStatus,
|
|
3375
|
+
});
|
|
3376
|
+
currentStatus = nextStatus;
|
|
3377
|
+
} catch (err) {
|
|
3378
|
+
lastError = String(err?.message || err).trim() || `failed to transition work item to ${nextStatus}`;
|
|
3379
|
+
break;
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
const { requests: nextRequests, request: nextRequest } = upsertRunnerRequest(currentState, key, {
|
|
3383
|
+
root_work_item_status: currentStatus,
|
|
3384
|
+
root_work_item_last_error: lastError,
|
|
2579
3385
|
});
|
|
2580
3386
|
saveBotRunnerState({
|
|
2581
|
-
routes:
|
|
2582
|
-
sharedInboxes:
|
|
2583
|
-
excludedComments:
|
|
3387
|
+
routes: currentState.routes,
|
|
3388
|
+
sharedInboxes: currentState.sharedInboxes || currentState.shared_inboxes,
|
|
3389
|
+
excludedComments: currentState.excludedComments || currentState.excluded_comments,
|
|
2584
3390
|
requests: nextRequests,
|
|
2585
|
-
consumedComments:
|
|
3391
|
+
consumedComments: currentState.consumedComments || currentState.consumed_comments,
|
|
2586
3392
|
});
|
|
2587
3393
|
return {
|
|
2588
|
-
ok:
|
|
2589
|
-
|
|
2590
|
-
|
|
3394
|
+
ok: lastError === "",
|
|
3395
|
+
request: nextRequest,
|
|
3396
|
+
transitioned: transitions.length > 0 && lastError === "",
|
|
3397
|
+
error: lastError,
|
|
2591
3398
|
};
|
|
2592
3399
|
}
|
|
2593
3400
|
|
|
@@ -2918,6 +3725,30 @@ function runnerLedgerEntryMatchesProject(entryRaw, normalizedRoute, requestIndex
|
|
|
2918
3725
|
);
|
|
2919
3726
|
}
|
|
2920
3727
|
|
|
3728
|
+
function mergeRunnerRequestForServerHydration(localEntryRaw, serverEntryRaw) {
|
|
3729
|
+
const localEntry = safeObject(localEntryRaw);
|
|
3730
|
+
const serverEntry = safeObject(serverEntryRaw);
|
|
3731
|
+
const merged = {
|
|
3732
|
+
...localEntry,
|
|
3733
|
+
...serverEntry,
|
|
3734
|
+
};
|
|
3735
|
+
const preserveLocalStringWhenServerBlank = (fieldName) => {
|
|
3736
|
+
const serverValue = String(serverEntry[fieldName] || "").trim();
|
|
3737
|
+
const localValue = String(localEntry[fieldName] || "").trim();
|
|
3738
|
+
if (!serverValue && localValue) {
|
|
3739
|
+
merged[fieldName] = localValue;
|
|
3740
|
+
}
|
|
3741
|
+
};
|
|
3742
|
+
preserveLocalStringWhenServerBlank("conversation_id");
|
|
3743
|
+
preserveLocalStringWhenServerBlank("root_work_item_id");
|
|
3744
|
+
preserveLocalStringWhenServerBlank("root_work_item_title");
|
|
3745
|
+
preserveLocalStringWhenServerBlank("root_work_item_status");
|
|
3746
|
+
preserveLocalStringWhenServerBlank("root_thread_id");
|
|
3747
|
+
preserveLocalStringWhenServerBlank("root_work_item_created_at");
|
|
3748
|
+
preserveLocalStringWhenServerBlank("root_work_item_last_error");
|
|
3749
|
+
return merged;
|
|
3750
|
+
}
|
|
3751
|
+
|
|
2921
3752
|
function mergeServerRunnerRequestLedgerIntoLocalState(currentState, normalizedRoute, serverRequests = [], serverCommentStates = []) {
|
|
2922
3753
|
const state = safeObject(currentState);
|
|
2923
3754
|
const normalizedRequests = normalizeBotRunnerRequests(state.requests);
|
|
@@ -2941,7 +3772,12 @@ function mergeServerRunnerRequestLedgerIntoLocalState(currentState, normalizedRo
|
|
|
2941
3772
|
const request = safeObject(requestRaw);
|
|
2942
3773
|
const requestKey = String(request.request_key || request.requestKey || "").trim();
|
|
2943
3774
|
if (!requestKey) continue;
|
|
2944
|
-
|
|
3775
|
+
const localRequest = safeObject(normalizedRequests[requestKey]);
|
|
3776
|
+
nextRequests[requestKey] = normalizeBotRunnerRequests({
|
|
3777
|
+
[requestKey]: {
|
|
3778
|
+
...mergeRunnerRequestForServerHydration(localRequest, request),
|
|
3779
|
+
},
|
|
3780
|
+
})[requestKey];
|
|
2945
3781
|
}
|
|
2946
3782
|
|
|
2947
3783
|
const requestIndex = normalizeBotRunnerRequests(nextRequests);
|
|
@@ -3094,6 +3930,12 @@ async function syncRunnerRequestLedgerForProjectToServer({ normalizedRoute, runt
|
|
|
3094
3930
|
const commentStates = buildProjectRunnerRequestCommentStatesForSync(state, normalizedRoute);
|
|
3095
3931
|
|
|
3096
3932
|
for (const request of requests) {
|
|
3933
|
+
if (
|
|
3934
|
+
isActionableRunnerRequestIntent(request.normalized_intent)
|
|
3935
|
+
&& !String(request.root_work_item_id || "").trim()
|
|
3936
|
+
) {
|
|
3937
|
+
continue;
|
|
3938
|
+
}
|
|
3097
3939
|
await upsertProjectRunnerRequest({
|
|
3098
3940
|
siteBaseURL: runtime.baseURL,
|
|
3099
3941
|
projectID,
|
|
@@ -4925,6 +5767,10 @@ async function createProjectWorkItem(params) {
|
|
|
4925
5767
|
return createProjectWorkItemImpl(params, buildRunnerDataDeps());
|
|
4926
5768
|
}
|
|
4927
5769
|
|
|
5770
|
+
async function transitionProjectWorkItem(params) {
|
|
5771
|
+
return transitionProjectWorkItemImpl(params, buildRunnerDataDeps());
|
|
5772
|
+
}
|
|
5773
|
+
|
|
4928
5774
|
async function createProjectEvidence(params) {
|
|
4929
5775
|
return createProjectEvidenceImpl(params, buildRunnerDataDeps());
|
|
4930
5776
|
}
|
|
@@ -4933,6 +5779,10 @@ async function createWorkItemThread(params) {
|
|
|
4933
5779
|
return createWorkItemThreadImpl(params, buildRunnerDataDeps());
|
|
4934
5780
|
}
|
|
4935
5781
|
|
|
5782
|
+
async function listWorkItemThreads(params) {
|
|
5783
|
+
return listWorkItemThreadsImpl(params, buildRunnerDataDeps());
|
|
5784
|
+
}
|
|
5785
|
+
|
|
4936
5786
|
async function linkWorkItemEvidence(params) {
|
|
4937
5787
|
return linkWorkItemEvidenceImpl(params, buildRunnerDataDeps());
|
|
4938
5788
|
}
|
|
@@ -5241,6 +6091,14 @@ function summarizeRunnerRequestForStatusLookup(entryRaw) {
|
|
|
5241
6091
|
selected_bot_usernames: ensureArray(entry.selected_bot_usernames)
|
|
5242
6092
|
.map((value) => normalizeTelegramMentionUsername(value))
|
|
5243
6093
|
.filter(Boolean),
|
|
6094
|
+
root_work_item: String(entry.root_work_item_id || "").trim()
|
|
6095
|
+
? {
|
|
6096
|
+
id: String(entry.root_work_item_id || "").trim(),
|
|
6097
|
+
title: String(entry.root_work_item_title || "").trim(),
|
|
6098
|
+
status: normalizeRunnerWorkItemStatus(entry.root_work_item_status),
|
|
6099
|
+
thread_id: String(entry.root_thread_id || "").trim(),
|
|
6100
|
+
}
|
|
6101
|
+
: null,
|
|
5244
6102
|
};
|
|
5245
6103
|
}
|
|
5246
6104
|
|
|
@@ -5299,7 +6157,7 @@ function pickPreferredStatusLookupRequest(entries = []) {
|
|
|
5299
6157
|
return candidates[0];
|
|
5300
6158
|
}
|
|
5301
6159
|
|
|
5302
|
-
function buildRunnerStatusQueryLookup({ route, routeState, selectedRecord }) {
|
|
6160
|
+
function buildRunnerStatusQueryLookup({ route, routeState, selectedRecord, runnerStateOverride = null }) {
|
|
5303
6161
|
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
5304
6162
|
const currentMessageID = intFromRawAllowZero(parsed.messageID, 0);
|
|
5305
6163
|
const currentChatID = String(parsed.chatID || parsed.chatId || "").trim();
|
|
@@ -5318,18 +6176,23 @@ function buildRunnerStatusQueryLookup({ route, routeState, selectedRecord }) {
|
|
|
5318
6176
|
&& activeSourceMessageID > 0
|
|
5319
6177
|
&& currentMessageID === activeSourceMessageID
|
|
5320
6178
|
);
|
|
5321
|
-
let runnerState =
|
|
5322
|
-
|
|
5323
|
-
runnerState =
|
|
5324
|
-
|
|
6179
|
+
let runnerState = safeObject(runnerStateOverride);
|
|
6180
|
+
if (!Object.keys(runnerState).length) {
|
|
6181
|
+
runnerState = { requests: {} };
|
|
6182
|
+
try {
|
|
6183
|
+
runnerState = loadBotRunnerState();
|
|
6184
|
+
} catch {}
|
|
6185
|
+
}
|
|
5325
6186
|
const replyChainContext = resolveRunnerReplyChainConversationContext(runnerState, route, selectedRecord);
|
|
5326
6187
|
const currentConversationID = String(parsed.conversationID || replyChainContext.conversationID || "").trim();
|
|
6188
|
+
const activeRequestKey = String(safeObject(routeState).active_request_key || "").trim();
|
|
5327
6189
|
const requestMatchesCurrentRoute = (entry) => requestEligibleForStatusLookup(
|
|
5328
6190
|
entry,
|
|
5329
6191
|
routeKey,
|
|
5330
6192
|
selfBotUsername,
|
|
5331
6193
|
currentMessageID,
|
|
5332
6194
|
);
|
|
6195
|
+
const referencedRequestCandidate = safeObject(replyChainContext.referencedRequest);
|
|
5333
6196
|
let relatedActiveRequest = null;
|
|
5334
6197
|
let relatedRequest = null;
|
|
5335
6198
|
const selectors = currentConversationID
|
|
@@ -5339,20 +6202,43 @@ function buildRunnerStatusQueryLookup({ route, routeState, selectedRecord }) {
|
|
|
5339
6202
|
if (!scopedRequests.length && currentConversationID) {
|
|
5340
6203
|
scopedRequests = findRunnerRequestsForScope(runnerState, route, { chatID: currentChatID });
|
|
5341
6204
|
}
|
|
6205
|
+
if (!scopedRequests.length && activeRequestKey) {
|
|
6206
|
+
scopedRequests = findRunnerRequestsForScope(runnerState, route, { requestKey: activeRequestKey });
|
|
6207
|
+
}
|
|
5342
6208
|
const eligibleScopedRequests = scopedRequests.filter(requestMatchesCurrentRoute);
|
|
5343
|
-
|
|
6209
|
+
const statusLookupCandidates = [...eligibleScopedRequests];
|
|
6210
|
+
if (
|
|
6211
|
+
Object.keys(referencedRequestCandidate).length > 0
|
|
6212
|
+
&& requestMatchesCurrentRoute(referencedRequestCandidate)
|
|
6213
|
+
&& !statusLookupCandidates.some(
|
|
6214
|
+
(entry) => String(safeObject(entry).request_key || "").trim() === String(referencedRequestCandidate.request_key || "").trim(),
|
|
6215
|
+
)
|
|
6216
|
+
) {
|
|
6217
|
+
statusLookupCandidates.push(referencedRequestCandidate);
|
|
6218
|
+
}
|
|
6219
|
+
relatedActiveRequest = statusLookupCandidates
|
|
5344
6220
|
.filter((entry) => isActiveRunnerRequestStatus(entry.status))[0] || null;
|
|
5345
|
-
relatedRequest = pickPreferredStatusLookupRequest(
|
|
5346
|
-
eligibleScopedRequests.length
|
|
5347
|
-
? eligibleScopedRequests
|
|
5348
|
-
: (replyChainContext.referencedRequest ? [replyChainContext.referencedRequest] : []).filter(requestMatchesCurrentRoute),
|
|
5349
|
-
);
|
|
6221
|
+
relatedRequest = pickPreferredStatusLookupRequest(statusLookupCandidates);
|
|
5350
6222
|
const lastAction = String(safeObject(routeState).last_action || "").trim();
|
|
5351
6223
|
const lastReason = String(safeObject(routeState).last_reason || "").trim();
|
|
5352
6224
|
const lastIntentType = String(safeObject(routeState).last_intent_type || "").trim();
|
|
5353
6225
|
const routeConversationID = String(safeObject(routeState).last_conversation_id || "").trim();
|
|
6226
|
+
const activeRootWorkItemID = String(safeObject(routeState).active_root_work_item_id || "").trim();
|
|
6227
|
+
const activeRootWorkItemTitle = String(safeObject(routeState).active_root_work_item_title || "").trim();
|
|
6228
|
+
const activeRootWorkItemStatus = normalizeRunnerWorkItemStatus(safeObject(routeState).active_root_work_item_status);
|
|
6229
|
+
const routeRootWorkItemID = String(safeObject(routeState).last_root_work_item_id || "").trim();
|
|
6230
|
+
const routeRootWorkItemTitle = String(safeObject(routeState).last_root_work_item_title || "").trim();
|
|
6231
|
+
const routeRootWorkItemStatus = normalizeRunnerWorkItemStatus(safeObject(routeState).last_root_work_item_status);
|
|
5354
6232
|
const routeWorkItemIDs = ensureArray(safeObject(routeState).last_work_item_ids).map((item) => String(item || "").trim()).filter(Boolean);
|
|
5355
6233
|
const routeWorkItemTitles = ensureArray(safeObject(routeState).last_work_item_titles).map((item) => String(item || "").trim()).filter(Boolean);
|
|
6234
|
+
const requestRootWorkItem = String(safeObject(relatedRequest).root_work_item_id || "").trim()
|
|
6235
|
+
? {
|
|
6236
|
+
id: String(safeObject(relatedRequest).root_work_item_id || "").trim(),
|
|
6237
|
+
title: String(safeObject(relatedRequest).root_work_item_title || "").trim(),
|
|
6238
|
+
status: normalizeRunnerWorkItemStatus(safeObject(relatedRequest).root_work_item_status),
|
|
6239
|
+
thread_id: String(safeObject(relatedRequest).root_thread_id || "").trim(),
|
|
6240
|
+
}
|
|
6241
|
+
: null;
|
|
5356
6242
|
return {
|
|
5357
6243
|
kind: "runner_status",
|
|
5358
6244
|
status: (!selfBusyFiltered && activeExecution.active) || relatedActiveRequest
|
|
@@ -5376,6 +6262,21 @@ function buildRunnerStatusQueryLookup({ route, routeState, selectedRecord }) {
|
|
|
5376
6262
|
: null,
|
|
5377
6263
|
related_active_request: relatedActiveRequest ? summarizeRunnerRequestForStatusLookup(relatedActiveRequest) : null,
|
|
5378
6264
|
related_request: relatedRequest ? summarizeRunnerRequestForStatusLookup(relatedRequest) : null,
|
|
6265
|
+
root_work_item: String(safeObject(requestRootWorkItem).id || "").trim()
|
|
6266
|
+
? requestRootWorkItem
|
|
6267
|
+
: activeRootWorkItemID
|
|
6268
|
+
? {
|
|
6269
|
+
id: activeRootWorkItemID,
|
|
6270
|
+
title: activeRootWorkItemTitle,
|
|
6271
|
+
status: activeRootWorkItemStatus,
|
|
6272
|
+
}
|
|
6273
|
+
: currentConversationID && routeConversationID === currentConversationID && routeRootWorkItemID
|
|
6274
|
+
? {
|
|
6275
|
+
id: routeRootWorkItemID,
|
|
6276
|
+
title: routeRootWorkItemTitle,
|
|
6277
|
+
status: routeRootWorkItemStatus,
|
|
6278
|
+
}
|
|
6279
|
+
: null,
|
|
5379
6280
|
route_work_items: currentConversationID && routeConversationID === currentConversationID && (routeWorkItemIDs.length > 0 || routeWorkItemTitles.length > 0)
|
|
5380
6281
|
? {
|
|
5381
6282
|
ids: routeWorkItemIDs,
|
|
@@ -5442,6 +6343,13 @@ async function resolveInformationalQueryReply({
|
|
|
5442
6343
|
};
|
|
5443
6344
|
}
|
|
5444
6345
|
if (normalizedIntentType === "status_query") {
|
|
6346
|
+
let hydratedRunnerState = null;
|
|
6347
|
+
try {
|
|
6348
|
+
hydratedRunnerState = await hydrateRunnerRequestLedgerFromServer({
|
|
6349
|
+
normalizedRoute: route,
|
|
6350
|
+
runtime,
|
|
6351
|
+
});
|
|
6352
|
+
} catch {}
|
|
5445
6353
|
return {
|
|
5446
6354
|
handled: true,
|
|
5447
6355
|
source: "runner.status",
|
|
@@ -5451,6 +6359,7 @@ async function resolveInformationalQueryReply({
|
|
|
5451
6359
|
route,
|
|
5452
6360
|
routeState,
|
|
5453
6361
|
selectedRecord,
|
|
6362
|
+
runnerStateOverride: hydratedRunnerState,
|
|
5454
6363
|
}),
|
|
5455
6364
|
};
|
|
5456
6365
|
const activeExecution = resolveRunnerActiveExecutionState(routeState);
|
|
@@ -5591,14 +6500,18 @@ function emptyRunnerActiveExecutionPatch() {
|
|
|
5591
6500
|
active_request_key: "",
|
|
5592
6501
|
active_started_at: "",
|
|
5593
6502
|
active_heartbeat_at: "",
|
|
6503
|
+
active_root_work_item_id: "",
|
|
6504
|
+
active_root_work_item_title: "",
|
|
6505
|
+
active_root_work_item_status: "",
|
|
5594
6506
|
active_runner_pid: undefined,
|
|
5595
6507
|
active_execution_token: "",
|
|
5596
6508
|
};
|
|
5597
6509
|
}
|
|
5598
6510
|
|
|
5599
|
-
function buildRunnerActiveExecutionPatch(selectedRecord, requestKey = "") {
|
|
6511
|
+
function buildRunnerActiveExecutionPatch(selectedRecord, requestKey = "", rootWorkItem = {}) {
|
|
5600
6512
|
const nowISO = new Date().toISOString();
|
|
5601
6513
|
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
6514
|
+
const root = safeObject(rootWorkItem);
|
|
5602
6515
|
return {
|
|
5603
6516
|
active_comment_id: String(selectedRecord?.id || "").trim(),
|
|
5604
6517
|
active_comment_created_at: firstNonEmptyString([selectedRecord?.createdAt, selectedRecord?.updatedAt]),
|
|
@@ -5606,6 +6519,9 @@ function buildRunnerActiveExecutionPatch(selectedRecord, requestKey = "") {
|
|
|
5606
6519
|
active_request_key: String(requestKey || "").trim(),
|
|
5607
6520
|
active_started_at: nowISO,
|
|
5608
6521
|
active_heartbeat_at: nowISO,
|
|
6522
|
+
active_root_work_item_id: String(root.id || root.root_work_item_id || "").trim(),
|
|
6523
|
+
active_root_work_item_title: String(root.title || root.root_work_item_title || "").trim(),
|
|
6524
|
+
active_root_work_item_status: normalizeRunnerWorkItemStatus(root.status || root.root_work_item_status),
|
|
5609
6525
|
active_runner_pid: process.pid,
|
|
5610
6526
|
active_execution_token: `${Date.now()}-${process.pid}-${String(selectedRecord?.id || "").trim()}`,
|
|
5611
6527
|
};
|
|
@@ -6085,7 +7001,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6085
7001
|
}
|
|
6086
7002
|
return null;
|
|
6087
7003
|
};
|
|
6088
|
-
const prepareRunnerRequestClaim = (selectedRecord, selectedResponderSelectors = []) => {
|
|
7004
|
+
const prepareRunnerRequestClaim = async (selectedRecord, selectedResponderSelectors = []) => {
|
|
6089
7005
|
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
6090
7006
|
const kind = String(parsed.kind || "").trim().toLowerCase();
|
|
6091
7007
|
if (kind === "bot_reply") {
|
|
@@ -6115,6 +7031,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6115
7031
|
routeKey,
|
|
6116
7032
|
selectedRecord,
|
|
6117
7033
|
selectedBotUsernames: selectedResponderSelectors,
|
|
7034
|
+
runtime,
|
|
6118
7035
|
});
|
|
6119
7036
|
};
|
|
6120
7037
|
if (deferExecution) {
|
|
@@ -6201,7 +7118,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6201
7118
|
});
|
|
6202
7119
|
continue;
|
|
6203
7120
|
}
|
|
6204
|
-
const requestClaim = prepareRunnerRequestClaim(selectedRecord, adjudication.selected_bot_usernames);
|
|
7121
|
+
const requestClaim = await prepareRunnerRequestClaim(selectedRecord, adjudication.selected_bot_usernames);
|
|
6205
7122
|
if (!requestClaim.ok) {
|
|
6206
7123
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
6207
7124
|
normalizedRoute,
|
|
@@ -6226,9 +7143,75 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6226
7143
|
});
|
|
6227
7144
|
continue;
|
|
6228
7145
|
}
|
|
7146
|
+
const inheritedRootReference = await inheritRunnerReferenceRootWorkItemForRequest({
|
|
7147
|
+
normalizedRoute,
|
|
7148
|
+
selectedRecord,
|
|
7149
|
+
runtime,
|
|
7150
|
+
requestKey: requestClaim.requestKey,
|
|
7151
|
+
});
|
|
7152
|
+
const rootWorkItemClaim = await ensureRunnerRootWorkItemForRequest({
|
|
7153
|
+
normalizedRoute,
|
|
7154
|
+
routeKey,
|
|
7155
|
+
selectedRecord,
|
|
7156
|
+
runtime,
|
|
7157
|
+
requestKey: requestClaim.requestKey,
|
|
7158
|
+
});
|
|
7159
|
+
const claimedRequest = safeObject(
|
|
7160
|
+
loadRunnerRequestByKey(requestClaim.requestKey)
|
|
7161
|
+
|| rootWorkItemClaim.request
|
|
7162
|
+
|| inheritedRootReference.request
|
|
7163
|
+
|| requestClaim.request,
|
|
7164
|
+
);
|
|
7165
|
+
const missingRequiredRootWorkItem = actionableRunnerRequestMissingRootWorkItem(claimedRequest);
|
|
7166
|
+
if (!rootWorkItemClaim.ok || missingRequiredRootWorkItem) {
|
|
7167
|
+
const rootWorkItemFailure = String(
|
|
7168
|
+
rootWorkItemClaim.error
|
|
7169
|
+
|| rootWorkItemClaim.reason
|
|
7170
|
+
|| (missingRequiredRootWorkItem ? "root_work_item_missing" : "root_work_item_create_failed"),
|
|
7171
|
+
).trim() || "root_work_item_create_failed";
|
|
7172
|
+
if (String(requestClaim.requestKey || "").trim()) {
|
|
7173
|
+
markRunnerRequestLifecycle({
|
|
7174
|
+
normalizedRoute,
|
|
7175
|
+
requestKey: requestClaim.requestKey,
|
|
7176
|
+
selectedRecord,
|
|
7177
|
+
routeKey,
|
|
7178
|
+
outcome: "closed",
|
|
7179
|
+
closedReason: rootWorkItemFailure,
|
|
7180
|
+
});
|
|
7181
|
+
await syncRunnerRequestLedgerForProjectToServer({
|
|
7182
|
+
normalizedRoute,
|
|
7183
|
+
runtime,
|
|
7184
|
+
});
|
|
7185
|
+
}
|
|
7186
|
+
saveRunnerRouteState(
|
|
7187
|
+
routeKey,
|
|
7188
|
+
buildRunnerRouteStateFromComment(selectedRecord, {
|
|
7189
|
+
last_action: "request_skipped",
|
|
7190
|
+
last_reason: rootWorkItemFailure,
|
|
7191
|
+
last_trigger: "work_item_root",
|
|
7192
|
+
last_request_key: String(requestClaim.requestKey || "").trim(),
|
|
7193
|
+
}),
|
|
7194
|
+
);
|
|
7195
|
+
skippedRecords.push({
|
|
7196
|
+
id: selectedRecord.id,
|
|
7197
|
+
reason: rootWorkItemFailure,
|
|
7198
|
+
messageID: intFromRawAllowZero(selectedRecord?.parsedArchive?.messageID, 0),
|
|
7199
|
+
diagnosticType: "skip",
|
|
7200
|
+
action: "skip_missing_root_work_item",
|
|
7201
|
+
closedReason: rootWorkItemFailure,
|
|
7202
|
+
});
|
|
7203
|
+
continue;
|
|
7204
|
+
}
|
|
6229
7205
|
saveRunnerRouteState(routeKey, {
|
|
6230
|
-
...buildRunnerActiveExecutionPatch(selectedRecord, requestClaim.requestKey
|
|
7206
|
+
...buildRunnerActiveExecutionPatch(selectedRecord, requestClaim.requestKey, {
|
|
7207
|
+
id: String(claimedRequest.root_work_item_id || "").trim(),
|
|
7208
|
+
title: String(claimedRequest.root_work_item_title || "").trim(),
|
|
7209
|
+
status: String(claimedRequest.root_work_item_status || "").trim(),
|
|
7210
|
+
}),
|
|
6231
7211
|
last_request_key: String(requestClaim.requestKey || "").trim(),
|
|
7212
|
+
last_root_work_item_id: String(claimedRequest.root_work_item_id || "").trim(),
|
|
7213
|
+
last_root_work_item_title: String(claimedRequest.root_work_item_title || "").trim(),
|
|
7214
|
+
last_root_work_item_status: String(claimedRequest.root_work_item_status || "").trim(),
|
|
6232
7215
|
});
|
|
6233
7216
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
6234
7217
|
normalizedRoute,
|
|
@@ -6375,7 +7358,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6375
7358
|
continue;
|
|
6376
7359
|
}
|
|
6377
7360
|
const currentRouteState = safeObject(loadBotRunnerState().routes[routeKey]);
|
|
6378
|
-
const requestClaim = prepareRunnerRequestClaim(selectedRecord, inlineAdjudication.selected_bot_usernames);
|
|
7361
|
+
const requestClaim = await prepareRunnerRequestClaim(selectedRecord, inlineAdjudication.selected_bot_usernames);
|
|
6379
7362
|
if (!requestClaim.ok) {
|
|
6380
7363
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
6381
7364
|
normalizedRoute,
|
|
@@ -6400,11 +7383,78 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6400
7383
|
});
|
|
6401
7384
|
continue;
|
|
6402
7385
|
}
|
|
7386
|
+
const inheritedRootReference = await inheritRunnerReferenceRootWorkItemForRequest({
|
|
7387
|
+
normalizedRoute,
|
|
7388
|
+
selectedRecord,
|
|
7389
|
+
runtime,
|
|
7390
|
+
requestKey: requestClaim.requestKey,
|
|
7391
|
+
});
|
|
7392
|
+
const rootWorkItemClaim = await ensureRunnerRootWorkItemForRequest({
|
|
7393
|
+
normalizedRoute,
|
|
7394
|
+
routeKey,
|
|
7395
|
+
selectedRecord,
|
|
7396
|
+
runtime,
|
|
7397
|
+
requestKey: requestClaim.requestKey,
|
|
7398
|
+
});
|
|
7399
|
+
const claimedRequest = safeObject(
|
|
7400
|
+
loadRunnerRequestByKey(requestClaim.requestKey)
|
|
7401
|
+
|| rootWorkItemClaim.request
|
|
7402
|
+
|| inheritedRootReference.request
|
|
7403
|
+
|| requestClaim.request,
|
|
7404
|
+
);
|
|
7405
|
+
const missingRequiredRootWorkItem = actionableRunnerRequestMissingRootWorkItem(claimedRequest);
|
|
7406
|
+
if (!rootWorkItemClaim.ok || missingRequiredRootWorkItem) {
|
|
7407
|
+
const rootWorkItemFailure = String(
|
|
7408
|
+
rootWorkItemClaim.error
|
|
7409
|
+
|| rootWorkItemClaim.reason
|
|
7410
|
+
|| (missingRequiredRootWorkItem ? "root_work_item_missing" : "root_work_item_create_failed"),
|
|
7411
|
+
).trim() || "root_work_item_create_failed";
|
|
7412
|
+
if (String(requestClaim.requestKey || "").trim()) {
|
|
7413
|
+
markRunnerRequestLifecycle({
|
|
7414
|
+
normalizedRoute,
|
|
7415
|
+
requestKey: requestClaim.requestKey,
|
|
7416
|
+
selectedRecord,
|
|
7417
|
+
routeKey,
|
|
7418
|
+
outcome: "closed",
|
|
7419
|
+
closedReason: rootWorkItemFailure,
|
|
7420
|
+
});
|
|
7421
|
+
await syncRunnerRequestLedgerForProjectToServer({
|
|
7422
|
+
normalizedRoute,
|
|
7423
|
+
runtime,
|
|
7424
|
+
});
|
|
7425
|
+
}
|
|
7426
|
+
saveRunnerRouteState(
|
|
7427
|
+
routeKey,
|
|
7428
|
+
buildRunnerRouteStateFromComment(selectedRecord, {
|
|
7429
|
+
last_action: "request_skipped",
|
|
7430
|
+
last_reason: rootWorkItemFailure,
|
|
7431
|
+
last_trigger: "work_item_root",
|
|
7432
|
+
last_request_key: String(requestClaim.requestKey || "").trim(),
|
|
7433
|
+
}),
|
|
7434
|
+
);
|
|
7435
|
+
skippedRecords.push({
|
|
7436
|
+
id: selectedRecord.id,
|
|
7437
|
+
reason: rootWorkItemFailure,
|
|
7438
|
+
messageID: intFromRawAllowZero(selectedRecord?.parsedArchive?.messageID, 0),
|
|
7439
|
+
diagnosticType: "skip",
|
|
7440
|
+
action: "skip_missing_root_work_item",
|
|
7441
|
+
closedReason: rootWorkItemFailure,
|
|
7442
|
+
});
|
|
7443
|
+
continue;
|
|
7444
|
+
}
|
|
6403
7445
|
saveRunnerRouteState(routeKey, {
|
|
6404
|
-
|
|
7446
|
+
...buildRunnerActiveExecutionPatch(selectedRecord, requestClaim.requestKey, {
|
|
7447
|
+
id: String(claimedRequest.root_work_item_id || "").trim(),
|
|
7448
|
+
title: String(claimedRequest.root_work_item_title || "").trim(),
|
|
7449
|
+
status: String(claimedRequest.root_work_item_status || "").trim(),
|
|
7450
|
+
}),
|
|
6405
7451
|
last_request_key: String(requestClaim.requestKey || "").trim(),
|
|
7452
|
+
last_root_work_item_id: String(claimedRequest.root_work_item_id || "").trim(),
|
|
7453
|
+
last_root_work_item_title: String(claimedRequest.root_work_item_title || "").trim(),
|
|
7454
|
+
last_root_work_item_status: String(claimedRequest.root_work_item_status || "").trim(),
|
|
6406
7455
|
});
|
|
6407
7456
|
if (String(requestClaim.requestKey || "").trim()) {
|
|
7457
|
+
const resolvedIntentType = String(safeObject(loadBotRunnerState().routes[routeKey]).last_intent_type || "").trim();
|
|
6408
7458
|
markRunnerRequestLifecycle({
|
|
6409
7459
|
normalizedRoute,
|
|
6410
7460
|
requestKey: requestClaim.requestKey,
|
|
@@ -6412,6 +7462,22 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6412
7462
|
routeKey,
|
|
6413
7463
|
outcome: "running",
|
|
6414
7464
|
});
|
|
7465
|
+
const rootWorkItemSync = await syncRunnerRequestRootWorkItemForOutcome({
|
|
7466
|
+
normalizedRoute,
|
|
7467
|
+
runtime,
|
|
7468
|
+
requestKey: requestClaim.requestKey,
|
|
7469
|
+
});
|
|
7470
|
+
const syncedRequest = safeObject(rootWorkItemSync.request);
|
|
7471
|
+
if (String(syncedRequest.root_work_item_id || "").trim()) {
|
|
7472
|
+
saveRunnerRouteState(routeKey, {
|
|
7473
|
+
active_root_work_item_id: String(syncedRequest.root_work_item_id || "").trim(),
|
|
7474
|
+
active_root_work_item_title: String(syncedRequest.root_work_item_title || "").trim(),
|
|
7475
|
+
active_root_work_item_status: String(syncedRequest.root_work_item_status || "").trim(),
|
|
7476
|
+
last_root_work_item_id: String(syncedRequest.root_work_item_id || "").trim(),
|
|
7477
|
+
last_root_work_item_title: String(syncedRequest.root_work_item_title || "").trim(),
|
|
7478
|
+
last_root_work_item_status: String(syncedRequest.root_work_item_status || "").trim(),
|
|
7479
|
+
});
|
|
7480
|
+
}
|
|
6415
7481
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
6416
7482
|
normalizedRoute,
|
|
6417
7483
|
runtime,
|
|
@@ -6422,7 +7488,11 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6422
7488
|
normalizedRoute,
|
|
6423
7489
|
routeState: {
|
|
6424
7490
|
...currentRouteState,
|
|
6425
|
-
|
|
7491
|
+
...buildRunnerActiveExecutionPatch(selectedRecord, requestClaim.requestKey, {
|
|
7492
|
+
id: String(claimedRequest.root_work_item_id || "").trim(),
|
|
7493
|
+
title: String(claimedRequest.root_work_item_title || "").trim(),
|
|
7494
|
+
status: String(claimedRequest.root_work_item_status || "").trim(),
|
|
7495
|
+
}),
|
|
6426
7496
|
},
|
|
6427
7497
|
selectedRecord,
|
|
6428
7498
|
pendingOrdered: pending.ordered,
|
|
@@ -6457,6 +7527,19 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6457
7527
|
outcome: "skipped",
|
|
6458
7528
|
closedReason: String(processed.skippedRecord?.reason || "skipped").trim() || "skipped",
|
|
6459
7529
|
});
|
|
7530
|
+
const rootWorkItemSync = await syncRunnerRequestRootWorkItemForOutcome({
|
|
7531
|
+
normalizedRoute,
|
|
7532
|
+
runtime,
|
|
7533
|
+
requestKey: requestClaim.requestKey,
|
|
7534
|
+
});
|
|
7535
|
+
const syncedRequest = safeObject(rootWorkItemSync.request);
|
|
7536
|
+
if (String(syncedRequest.root_work_item_id || "").trim()) {
|
|
7537
|
+
saveRunnerRouteState(routeKey, {
|
|
7538
|
+
last_root_work_item_id: String(syncedRequest.root_work_item_id || "").trim(),
|
|
7539
|
+
last_root_work_item_title: String(syncedRequest.root_work_item_title || "").trim(),
|
|
7540
|
+
last_root_work_item_status: String(syncedRequest.root_work_item_status || "").trim(),
|
|
7541
|
+
});
|
|
7542
|
+
}
|
|
6460
7543
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
6461
7544
|
normalizedRoute,
|
|
6462
7545
|
runtime,
|
|
@@ -6466,6 +7549,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6466
7549
|
continue;
|
|
6467
7550
|
}
|
|
6468
7551
|
if (String(requestClaim.requestKey || "").trim()) {
|
|
7552
|
+
const resolvedIntentType = String(safeObject(loadBotRunnerState().routes[routeKey]).last_intent_type || "").trim();
|
|
6469
7553
|
markRunnerRequestLifecycle({
|
|
6470
7554
|
normalizedRoute,
|
|
6471
7555
|
requestKey: requestClaim.requestKey,
|
|
@@ -6479,8 +7563,28 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6479
7563
|
nextExpectedResponders: ensureArray(processed.result?.next_expected_responders),
|
|
6480
7564
|
currentBotSelector,
|
|
6481
7565
|
conversationIntentMode: String(processed.result?.conversation_intent_mode || "").trim(),
|
|
6482
|
-
normalizedIntent:
|
|
7566
|
+
normalizedIntent: resolvedIntentType,
|
|
6483
7567
|
});
|
|
7568
|
+
await ensureRunnerRootWorkItemForRequest({
|
|
7569
|
+
normalizedRoute,
|
|
7570
|
+
routeKey,
|
|
7571
|
+
selectedRecord,
|
|
7572
|
+
runtime,
|
|
7573
|
+
requestKey: requestClaim.requestKey,
|
|
7574
|
+
});
|
|
7575
|
+
const rootWorkItemSync = await syncRunnerRequestRootWorkItemForOutcome({
|
|
7576
|
+
normalizedRoute,
|
|
7577
|
+
runtime,
|
|
7578
|
+
requestKey: requestClaim.requestKey,
|
|
7579
|
+
});
|
|
7580
|
+
const syncedRequest = safeObject(rootWorkItemSync.request);
|
|
7581
|
+
if (String(syncedRequest.root_work_item_id || "").trim()) {
|
|
7582
|
+
saveRunnerRouteState(routeKey, {
|
|
7583
|
+
last_root_work_item_id: String(syncedRequest.root_work_item_id || "").trim(),
|
|
7584
|
+
last_root_work_item_title: String(syncedRequest.root_work_item_title || "").trim(),
|
|
7585
|
+
last_root_work_item_status: String(syncedRequest.root_work_item_status || "").trim(),
|
|
7586
|
+
});
|
|
7587
|
+
}
|
|
6484
7588
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
6485
7589
|
normalizedRoute,
|
|
6486
7590
|
runtime,
|
|
@@ -8172,7 +9276,7 @@ async function runRunnerStop(flags) {
|
|
|
8172
9276
|
}
|
|
8173
9277
|
process.stdout.write("Detached runner stop: OK\n");
|
|
8174
9278
|
process.stdout.write(`registry_file: ${payload.registry_file}\n`);
|
|
8175
|
-
for (const entry of stopped) {
|
|
9279
|
+
for (const entry of ensureArray(payload.stopped)) {
|
|
8176
9280
|
process.stdout.write(`stopped: ${entry.launch_id} pid=${entry.pid} routes=${entry.route_names.join(", ") || "-"}\n`);
|
|
8177
9281
|
}
|
|
8178
9282
|
}
|
|
@@ -8197,7 +9301,7 @@ async function runRunnerStartDetachedResolvedRoutes(routes, flags, sourceCommand
|
|
|
8197
9301
|
process.stdout.write(`Detached runner already running: launch_id=${existing.launch_id} pid=${existing.pid}${existing.log_file ? ` log_file=${existing.log_file}` : ""}\n`);
|
|
8198
9302
|
return payload;
|
|
8199
9303
|
}
|
|
8200
|
-
const launch = launchDetachedRunnerProcess(flags, routes, sourceCommand);
|
|
9304
|
+
const launch = await launchDetachedRunnerProcess(flags, routes, sourceCommand);
|
|
8201
9305
|
const nextLaunches = {
|
|
8202
9306
|
...safeObject(registry).launches,
|
|
8203
9307
|
[launch.launch_id]: launch,
|
|
@@ -8724,6 +9828,22 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
8724
9828
|
routeKey: deferredExecution.routeKey,
|
|
8725
9829
|
outcome: "running",
|
|
8726
9830
|
});
|
|
9831
|
+
const rootWorkItemSync = await syncRunnerRequestRootWorkItemForOutcome({
|
|
9832
|
+
normalizedRoute: deferredExecution.normalizedRoute,
|
|
9833
|
+
runtime: deferredExecution.runtime,
|
|
9834
|
+
requestKey: deferredExecution.requestKey,
|
|
9835
|
+
});
|
|
9836
|
+
const syncedRequest = safeObject(rootWorkItemSync.request);
|
|
9837
|
+
if (String(syncedRequest.root_work_item_id || "").trim()) {
|
|
9838
|
+
saveRunnerRouteState(deferredExecution.routeKey, {
|
|
9839
|
+
active_root_work_item_id: String(syncedRequest.root_work_item_id || "").trim(),
|
|
9840
|
+
active_root_work_item_title: String(syncedRequest.root_work_item_title || "").trim(),
|
|
9841
|
+
active_root_work_item_status: String(syncedRequest.root_work_item_status || "").trim(),
|
|
9842
|
+
last_root_work_item_id: String(syncedRequest.root_work_item_id || "").trim(),
|
|
9843
|
+
last_root_work_item_title: String(syncedRequest.root_work_item_title || "").trim(),
|
|
9844
|
+
last_root_work_item_status: String(syncedRequest.root_work_item_status || "").trim(),
|
|
9845
|
+
});
|
|
9846
|
+
}
|
|
8727
9847
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
8728
9848
|
normalizedRoute: deferredExecution.normalizedRoute,
|
|
8729
9849
|
runtime: deferredExecution.runtime,
|
|
@@ -8786,6 +9906,19 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
8786
9906
|
outcome: "skipped",
|
|
8787
9907
|
closedReason: String(processed.skippedRecord?.reason || "skipped").trim() || "skipped",
|
|
8788
9908
|
});
|
|
9909
|
+
const rootWorkItemSync = await syncRunnerRequestRootWorkItemForOutcome({
|
|
9910
|
+
normalizedRoute: deferredExecution.normalizedRoute,
|
|
9911
|
+
runtime: deferredExecution.runtime,
|
|
9912
|
+
requestKey: deferredExecution.requestKey,
|
|
9913
|
+
});
|
|
9914
|
+
const syncedRequest = safeObject(rootWorkItemSync.request);
|
|
9915
|
+
if (String(syncedRequest.root_work_item_id || "").trim()) {
|
|
9916
|
+
saveRunnerRouteState(deferredExecution.routeKey, {
|
|
9917
|
+
last_root_work_item_id: String(syncedRequest.root_work_item_id || "").trim(),
|
|
9918
|
+
last_root_work_item_title: String(syncedRequest.root_work_item_title || "").trim(),
|
|
9919
|
+
last_root_work_item_status: String(syncedRequest.root_work_item_status || "").trim(),
|
|
9920
|
+
});
|
|
9921
|
+
}
|
|
8789
9922
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
8790
9923
|
normalizedRoute: deferredExecution.normalizedRoute,
|
|
8791
9924
|
runtime: deferredExecution.runtime,
|
|
@@ -8806,6 +9939,9 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
8806
9939
|
};
|
|
8807
9940
|
}
|
|
8808
9941
|
if (String(deferredExecution.requestKey || "").trim()) {
|
|
9942
|
+
const resolvedIntentType = String(
|
|
9943
|
+
safeObject(loadBotRunnerState().routes[deferredExecution.routeKey]).last_intent_type || "",
|
|
9944
|
+
).trim();
|
|
8809
9945
|
markRunnerRequestLifecycle({
|
|
8810
9946
|
normalizedRoute: deferredExecution.normalizedRoute,
|
|
8811
9947
|
requestKey: deferredExecution.requestKey,
|
|
@@ -8821,8 +9957,28 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
8821
9957
|
deferredExecution.bot?.username || deferredExecution.bot?.name,
|
|
8822
9958
|
),
|
|
8823
9959
|
conversationIntentMode: String(processed.result?.conversation_intent_mode || "").trim(),
|
|
8824
|
-
normalizedIntent:
|
|
9960
|
+
normalizedIntent: resolvedIntentType,
|
|
9961
|
+
});
|
|
9962
|
+
await ensureRunnerRootWorkItemForRequest({
|
|
9963
|
+
normalizedRoute: deferredExecution.normalizedRoute,
|
|
9964
|
+
routeKey: deferredExecution.routeKey,
|
|
9965
|
+
selectedRecord: deferredExecution.selectedRecord,
|
|
9966
|
+
runtime: deferredExecution.runtime,
|
|
9967
|
+
requestKey: deferredExecution.requestKey,
|
|
9968
|
+
});
|
|
9969
|
+
const rootWorkItemSync = await syncRunnerRequestRootWorkItemForOutcome({
|
|
9970
|
+
normalizedRoute: deferredExecution.normalizedRoute,
|
|
9971
|
+
runtime: deferredExecution.runtime,
|
|
9972
|
+
requestKey: deferredExecution.requestKey,
|
|
8825
9973
|
});
|
|
9974
|
+
const syncedRequest = safeObject(rootWorkItemSync.request);
|
|
9975
|
+
if (String(syncedRequest.root_work_item_id || "").trim()) {
|
|
9976
|
+
saveRunnerRouteState(deferredExecution.routeKey, {
|
|
9977
|
+
last_root_work_item_id: String(syncedRequest.root_work_item_id || "").trim(),
|
|
9978
|
+
last_root_work_item_title: String(syncedRequest.root_work_item_title || "").trim(),
|
|
9979
|
+
last_root_work_item_status: String(syncedRequest.root_work_item_status || "").trim(),
|
|
9980
|
+
});
|
|
9981
|
+
}
|
|
8826
9982
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
8827
9983
|
normalizedRoute: deferredExecution.normalizedRoute,
|
|
8828
9984
|
runtime: deferredExecution.runtime,
|
|
@@ -8843,6 +9999,12 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
8843
9999
|
last_error: errorText,
|
|
8844
10000
|
});
|
|
8845
10001
|
if (String(deferredExecution.requestKey || "").trim()) {
|
|
10002
|
+
const currentRequest = safeObject(loadRunnerRequestByKey(deferredExecution.requestKey));
|
|
10003
|
+
const resolvedIntentType = String(
|
|
10004
|
+
safeObject(loadBotRunnerState().routes[deferredExecution.routeKey]).last_intent_type
|
|
10005
|
+
|| currentRequest.normalized_intent
|
|
10006
|
+
|| "",
|
|
10007
|
+
).trim();
|
|
8846
10008
|
markRunnerRequestLifecycle({
|
|
8847
10009
|
normalizedRoute: deferredExecution.normalizedRoute,
|
|
8848
10010
|
requestKey: deferredExecution.requestKey,
|
|
@@ -8850,7 +10012,28 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
8850
10012
|
routeKey: deferredExecution.routeKey,
|
|
8851
10013
|
outcome: "error",
|
|
8852
10014
|
closedReason: errorText || "execution_error",
|
|
10015
|
+
normalizedIntent: resolvedIntentType,
|
|
10016
|
+
});
|
|
10017
|
+
await ensureRunnerRootWorkItemForRequest({
|
|
10018
|
+
normalizedRoute: deferredExecution.normalizedRoute,
|
|
10019
|
+
routeKey: deferredExecution.routeKey,
|
|
10020
|
+
selectedRecord: deferredExecution.selectedRecord,
|
|
10021
|
+
runtime: deferredExecution.runtime,
|
|
10022
|
+
requestKey: deferredExecution.requestKey,
|
|
10023
|
+
});
|
|
10024
|
+
const rootWorkItemSync = await syncRunnerRequestRootWorkItemForOutcome({
|
|
10025
|
+
normalizedRoute: deferredExecution.normalizedRoute,
|
|
10026
|
+
runtime: deferredExecution.runtime,
|
|
10027
|
+
requestKey: deferredExecution.requestKey,
|
|
8853
10028
|
});
|
|
10029
|
+
const syncedRequest = safeObject(rootWorkItemSync.request);
|
|
10030
|
+
if (String(syncedRequest.root_work_item_id || "").trim()) {
|
|
10031
|
+
saveRunnerRouteState(deferredExecution.routeKey, {
|
|
10032
|
+
last_root_work_item_id: String(syncedRequest.root_work_item_id || "").trim(),
|
|
10033
|
+
last_root_work_item_title: String(syncedRequest.root_work_item_title || "").trim(),
|
|
10034
|
+
last_root_work_item_status: String(syncedRequest.root_work_item_status || "").trim(),
|
|
10035
|
+
});
|
|
10036
|
+
}
|
|
8854
10037
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
8855
10038
|
normalizedRoute: deferredExecution.normalizedRoute,
|
|
8856
10039
|
runtime: deferredExecution.runtime,
|
|
@@ -13130,6 +14313,7 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
13130
14313
|
runnerRouteLogicalSignature,
|
|
13131
14314
|
loadBotRunnerState,
|
|
13132
14315
|
saveBotRunnerState,
|
|
14316
|
+
mergeServerRunnerRequestLedgerIntoLocalState,
|
|
13133
14317
|
buildRunnerStatusQueryLookup,
|
|
13134
14318
|
tryJsonParse,
|
|
13135
14319
|
safeObject,
|