metheus-governance-mcp-cli 0.2.196 → 0.2.198
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 +1323 -96
- package/lib/runner-data.mjs +129 -46
- package/lib/selftest-runner-scenarios.mjs +242 -2
- 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,16 @@ function buildRunnerRequestKey({
|
|
|
2363
2386
|
].join("::");
|
|
2364
2387
|
}
|
|
2365
2388
|
|
|
2389
|
+
function buildSyntheticReplyChainConversationID(normalizedRoute, chatID, anchorMessageID) {
|
|
2390
|
+
const provider = String(normalizedRoute?.provider || "").trim() || "unknown";
|
|
2391
|
+
const normalizedChatID = String(chatID || "").trim() || "-";
|
|
2392
|
+
const normalizedAnchorMessageID = intFromRawAllowZero(anchorMessageID, 0);
|
|
2393
|
+
if (normalizedAnchorMessageID <= 0) {
|
|
2394
|
+
return "";
|
|
2395
|
+
}
|
|
2396
|
+
return `reply_chain:${provider}:${normalizedChatID}:${normalizedAnchorMessageID}`;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2366
2399
|
function findRunnerRequestsForScope(state, normalizedRoute, selectors = {}) {
|
|
2367
2400
|
const requests = normalizeBotRunnerRequests(state?.requests);
|
|
2368
2401
|
const projectID = String(normalizedRoute?.projectID || "").trim();
|
|
@@ -2386,6 +2419,190 @@ function findRunnerRequestsForScope(state, normalizedRoute, selectors = {}) {
|
|
|
2386
2419
|
});
|
|
2387
2420
|
}
|
|
2388
2421
|
|
|
2422
|
+
function findRunnerRequestsForMessageID(state, normalizedRoute, selectors = {}) {
|
|
2423
|
+
const chatID = String(selectors.chatID || "").trim();
|
|
2424
|
+
const messageID = intFromRawAllowZero(selectors.messageID, 0);
|
|
2425
|
+
if (!chatID || messageID <= 0) {
|
|
2426
|
+
return [];
|
|
2427
|
+
}
|
|
2428
|
+
return findRunnerRequestsForScope(state, normalizedRoute, { chatID })
|
|
2429
|
+
.filter((entry) => (
|
|
2430
|
+
intFromRawAllowZero(entry.source_message_id, 0) === messageID
|
|
2431
|
+
|| intFromRawAllowZero(entry.last_source_message_id, 0) === messageID
|
|
2432
|
+
));
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
function sortRunnerRequestEntriesNewestFirst(entries = []) {
|
|
2436
|
+
return ensureArray(entries).slice().sort((leftRaw, rightRaw) => {
|
|
2437
|
+
const left = safeObject(leftRaw);
|
|
2438
|
+
const right = safeObject(rightRaw);
|
|
2439
|
+
const leftTime = firstNonEmptyString([left.updated_at, left.completed_at, left.closed_at, left.claimed_at]);
|
|
2440
|
+
const rightTime = firstNonEmptyString([right.updated_at, right.completed_at, right.closed_at, right.claimed_at]);
|
|
2441
|
+
if (leftTime && rightTime && leftTime !== rightTime) {
|
|
2442
|
+
return leftTime < rightTime ? 1 : -1;
|
|
2443
|
+
}
|
|
2444
|
+
return String(left.request_key || "").localeCompare(String(right.request_key || ""));
|
|
2445
|
+
});
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
async function findServerRunnerRequestForMessageID({
|
|
2449
|
+
normalizedRoute,
|
|
2450
|
+
runtime,
|
|
2451
|
+
chatID,
|
|
2452
|
+
messageID,
|
|
2453
|
+
}) {
|
|
2454
|
+
const projectID = String(normalizedRoute?.projectID || "").trim();
|
|
2455
|
+
const provider = String(normalizedRoute?.provider || "").trim();
|
|
2456
|
+
const normalizedChatID = String(chatID || "").trim();
|
|
2457
|
+
const normalizedMessageID = intFromRawAllowZero(messageID, 0);
|
|
2458
|
+
if (
|
|
2459
|
+
!projectID
|
|
2460
|
+
|| !provider
|
|
2461
|
+
|| !normalizedChatID
|
|
2462
|
+
|| normalizedMessageID <= 0
|
|
2463
|
+
|| !runtime?.baseURL
|
|
2464
|
+
|| !runtime?.token
|
|
2465
|
+
) {
|
|
2466
|
+
return null;
|
|
2467
|
+
}
|
|
2468
|
+
try {
|
|
2469
|
+
const serverRequests = await listProjectRunnerRequests({
|
|
2470
|
+
siteBaseURL: runtime.baseURL,
|
|
2471
|
+
projectID,
|
|
2472
|
+
token: runtime.token,
|
|
2473
|
+
timeoutSeconds: runtime.timeoutSeconds,
|
|
2474
|
+
actorUserID: runtime.actor?.user_id,
|
|
2475
|
+
limit: 500,
|
|
2476
|
+
offset: 0,
|
|
2477
|
+
});
|
|
2478
|
+
const matched = sortRunnerRequestEntriesNewestFirst(serverRequests.filter((entryRaw) => {
|
|
2479
|
+
const entry = safeObject(entryRaw);
|
|
2480
|
+
return (
|
|
2481
|
+
String(entry.project_id || "").trim() === projectID
|
|
2482
|
+
&& String(entry.provider || "").trim() === provider
|
|
2483
|
+
&& String(entry.chat_id || "").trim() === normalizedChatID
|
|
2484
|
+
&& (
|
|
2485
|
+
intFromRawAllowZero(entry.source_message_id, 0) === normalizedMessageID
|
|
2486
|
+
|| intFromRawAllowZero(entry.last_source_message_id, 0) === normalizedMessageID
|
|
2487
|
+
)
|
|
2488
|
+
);
|
|
2489
|
+
}));
|
|
2490
|
+
return safeObject(matched[0]);
|
|
2491
|
+
} catch {
|
|
2492
|
+
return null;
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
function resolveRunnerReplyChainConversationContext(state, normalizedRoute, selectedRecord) {
|
|
2497
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2498
|
+
const explicitConversationID = String(parsed.conversationID || "").trim();
|
|
2499
|
+
if (explicitConversationID) {
|
|
2500
|
+
return {
|
|
2501
|
+
conversationID: explicitConversationID,
|
|
2502
|
+
replyToMessageID: intFromRawAllowZero(parsed.replyToMessageID, 0),
|
|
2503
|
+
anchorMessageID: 0,
|
|
2504
|
+
reason: "archive_conversation",
|
|
2505
|
+
referencedRequest: null,
|
|
2506
|
+
};
|
|
2507
|
+
}
|
|
2508
|
+
const chatID = String(parsed.chatID || parsed.chatId || "").trim();
|
|
2509
|
+
const replyToMessageID = intFromRawAllowZero(parsed.replyToMessageID, 0);
|
|
2510
|
+
if (!chatID || replyToMessageID <= 0) {
|
|
2511
|
+
return {
|
|
2512
|
+
conversationID: "",
|
|
2513
|
+
replyToMessageID,
|
|
2514
|
+
anchorMessageID: 0,
|
|
2515
|
+
reason: "",
|
|
2516
|
+
referencedRequest: null,
|
|
2517
|
+
};
|
|
2518
|
+
}
|
|
2519
|
+
const referencedRequest = safeObject(findRunnerRequestsForMessageID(state, normalizedRoute, {
|
|
2520
|
+
chatID,
|
|
2521
|
+
messageID: replyToMessageID,
|
|
2522
|
+
})[0]);
|
|
2523
|
+
const referencedConversationID = String(referencedRequest.conversation_id || "").trim();
|
|
2524
|
+
const anchorMessageID = intFromRawAllowZero(referencedRequest.source_message_id, 0) || replyToMessageID;
|
|
2525
|
+
return {
|
|
2526
|
+
conversationID: referencedConversationID || buildSyntheticReplyChainConversationID(normalizedRoute, chatID, anchorMessageID),
|
|
2527
|
+
replyToMessageID,
|
|
2528
|
+
anchorMessageID,
|
|
2529
|
+
reason: referencedConversationID
|
|
2530
|
+
? "reply_request_conversation"
|
|
2531
|
+
: Object.keys(referencedRequest).length > 0
|
|
2532
|
+
? "reply_request_synthetic"
|
|
2533
|
+
: "reply_message_synthetic",
|
|
2534
|
+
referencedRequest: Object.keys(referencedRequest).length > 0 ? referencedRequest : null,
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
async function resolveRunnerReplyChainConversationContextWithServerFallback({
|
|
2539
|
+
state,
|
|
2540
|
+
normalizedRoute,
|
|
2541
|
+
selectedRecord,
|
|
2542
|
+
runtime,
|
|
2543
|
+
}) {
|
|
2544
|
+
const initialState = safeObject(state);
|
|
2545
|
+
const initialContext = resolveRunnerReplyChainConversationContext(initialState, normalizedRoute, selectedRecord);
|
|
2546
|
+
if (safeObject(initialContext.referencedRequest).request_key) {
|
|
2547
|
+
return {
|
|
2548
|
+
state: initialState,
|
|
2549
|
+
replyChainContext: initialContext,
|
|
2550
|
+
hydrated: false,
|
|
2551
|
+
};
|
|
2552
|
+
}
|
|
2553
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2554
|
+
const replyToMessageID = intFromRawAllowZero(
|
|
2555
|
+
parsed.replyToMessageID || safeObject(initialContext).replyToMessageID,
|
|
2556
|
+
0,
|
|
2557
|
+
);
|
|
2558
|
+
if (replyToMessageID <= 0 || !runtime?.baseURL || !runtime?.token) {
|
|
2559
|
+
return {
|
|
2560
|
+
state: initialState,
|
|
2561
|
+
replyChainContext: initialContext,
|
|
2562
|
+
hydrated: false,
|
|
2563
|
+
};
|
|
2564
|
+
}
|
|
2565
|
+
const chatID = String(parsed.chatID || parsed.chatId || "").trim();
|
|
2566
|
+
const serverReferencedRequest = await findServerRunnerRequestForMessageID({
|
|
2567
|
+
normalizedRoute,
|
|
2568
|
+
runtime,
|
|
2569
|
+
chatID,
|
|
2570
|
+
messageID: replyToMessageID,
|
|
2571
|
+
});
|
|
2572
|
+
if (serverReferencedRequest.request_key) {
|
|
2573
|
+
const requestIndex = normalizeBotRunnerRequests(initialState.requests);
|
|
2574
|
+
requestIndex[String(serverReferencedRequest.request_key || "").trim()] = serverReferencedRequest;
|
|
2575
|
+
const anchorMessageID = intFromRawAllowZero(serverReferencedRequest.source_message_id, 0) || replyToMessageID;
|
|
2576
|
+
return {
|
|
2577
|
+
state: {
|
|
2578
|
+
...initialState,
|
|
2579
|
+
requests: requestIndex,
|
|
2580
|
+
},
|
|
2581
|
+
replyChainContext: {
|
|
2582
|
+
conversationID: String(serverReferencedRequest.conversation_id || "").trim()
|
|
2583
|
+
|| buildSyntheticReplyChainConversationID(normalizedRoute, chatID, anchorMessageID),
|
|
2584
|
+
replyToMessageID,
|
|
2585
|
+
anchorMessageID,
|
|
2586
|
+
reason: String(serverReferencedRequest.conversation_id || "").trim()
|
|
2587
|
+
? "reply_request_conversation_server"
|
|
2588
|
+
: "reply_request_synthetic_server",
|
|
2589
|
+
referencedRequest: serverReferencedRequest,
|
|
2590
|
+
},
|
|
2591
|
+
hydrated: false,
|
|
2592
|
+
};
|
|
2593
|
+
}
|
|
2594
|
+
const hydratedState = await hydrateRunnerRequestLedgerFromServer({
|
|
2595
|
+
normalizedRoute,
|
|
2596
|
+
runtime,
|
|
2597
|
+
});
|
|
2598
|
+
const hydratedContext = resolveRunnerReplyChainConversationContext(hydratedState, normalizedRoute, selectedRecord);
|
|
2599
|
+
return {
|
|
2600
|
+
state: hydratedState,
|
|
2601
|
+
replyChainContext: hydratedContext,
|
|
2602
|
+
hydrated: true,
|
|
2603
|
+
};
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2389
2606
|
function upsertRunnerRequest(state, requestKey, patch = {}) {
|
|
2390
2607
|
const currentState = safeObject(state);
|
|
2391
2608
|
const requests = normalizeBotRunnerRequests(currentState.requests);
|
|
@@ -2425,87 +2642,679 @@ function upsertRunnerConsumedComment(state, commentIDRaw, patch = {}) {
|
|
|
2425
2642
|
};
|
|
2426
2643
|
}
|
|
2427
2644
|
|
|
2428
|
-
function claimRunnerRequestForHumanComment({
|
|
2645
|
+
async function claimRunnerRequestForHumanComment({
|
|
2646
|
+
normalizedRoute,
|
|
2647
|
+
routeKey,
|
|
2648
|
+
selectedRecord,
|
|
2649
|
+
selectedBotUsernames = [],
|
|
2650
|
+
normalizedIntent = "",
|
|
2651
|
+
runtime = null,
|
|
2652
|
+
}) {
|
|
2653
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2654
|
+
const commentKind = String(parsed.kind || "").trim().toLowerCase();
|
|
2655
|
+
if (!isInboundArchiveKind(commentKind)) {
|
|
2656
|
+
return {
|
|
2657
|
+
ok: false,
|
|
2658
|
+
reason: "non_human_comment_cannot_create_request",
|
|
2659
|
+
};
|
|
2660
|
+
}
|
|
2661
|
+
const requestKey = buildRunnerRequestKey({
|
|
2662
|
+
normalizedRoute,
|
|
2663
|
+
selectedRecord,
|
|
2664
|
+
selectedBotUsernames,
|
|
2665
|
+
normalizedIntent,
|
|
2666
|
+
});
|
|
2667
|
+
const currentState = loadBotRunnerState();
|
|
2668
|
+
const replyChainResolution = await resolveRunnerReplyChainConversationContextWithServerFallback({
|
|
2669
|
+
state: currentState,
|
|
2670
|
+
normalizedRoute,
|
|
2671
|
+
selectedRecord,
|
|
2672
|
+
runtime,
|
|
2673
|
+
});
|
|
2674
|
+
const replyChainContext = safeObject(replyChainResolution.replyChainContext);
|
|
2675
|
+
const referencedRequest = safeObject(replyChainContext.referencedRequest);
|
|
2676
|
+
const conversationID = String(parsed.conversationID || replyChainContext.conversationID || "").trim();
|
|
2677
|
+
let stateForClaim = safeObject(replyChainResolution.state);
|
|
2678
|
+
if (
|
|
2679
|
+
Object.keys(referencedRequest).length > 0
|
|
2680
|
+
&& conversationID
|
|
2681
|
+
&& !String(referencedRequest.conversation_id || "").trim()
|
|
2682
|
+
&& String(referencedRequest.request_key || "").trim()
|
|
2683
|
+
) {
|
|
2684
|
+
const backfilled = upsertRunnerRequest(stateForClaim, referencedRequest.request_key, {
|
|
2685
|
+
conversation_id: conversationID,
|
|
2686
|
+
});
|
|
2687
|
+
stateForClaim = {
|
|
2688
|
+
...stateForClaim,
|
|
2689
|
+
requests: backfilled.requests,
|
|
2690
|
+
};
|
|
2691
|
+
}
|
|
2692
|
+
const requests = normalizeBotRunnerRequests(stateForClaim.requests);
|
|
2693
|
+
const existing = safeObject(requests[requestKey]);
|
|
2694
|
+
if (isFinalRunnerRequestStatus(existing.status)) {
|
|
2695
|
+
return {
|
|
2696
|
+
ok: false,
|
|
2697
|
+
reason: "request_already_finalized",
|
|
2698
|
+
requestKey,
|
|
2699
|
+
};
|
|
2700
|
+
}
|
|
2701
|
+
if (
|
|
2702
|
+
isActiveRunnerRequestStatus(existing.status)
|
|
2703
|
+
&& String(existing.claimed_by_route || "").trim()
|
|
2704
|
+
&& String(existing.claimed_by_route || "").trim() !== String(routeKey || "").trim()
|
|
2705
|
+
) {
|
|
2706
|
+
return {
|
|
2707
|
+
ok: false,
|
|
2708
|
+
reason: "request_already_claimed",
|
|
2709
|
+
requestKey,
|
|
2710
|
+
};
|
|
2711
|
+
}
|
|
2712
|
+
const nowISO = new Date().toISOString();
|
|
2713
|
+
const { requests: nextRequests, request } = upsertRunnerRequest(stateForClaim, requestKey, {
|
|
2714
|
+
project_id: String(normalizedRoute?.projectID || "").trim(),
|
|
2715
|
+
provider: String(normalizedRoute?.provider || "").trim(),
|
|
2716
|
+
chat_id: String(parsed.chatID || parsed.chatId || "").trim(),
|
|
2717
|
+
source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
2718
|
+
root_comment_id: String(selectedRecord?.id || "").trim(),
|
|
2719
|
+
root_comment_kind: commentKind,
|
|
2720
|
+
conversation_id: conversationID,
|
|
2721
|
+
selected_bot_usernames: uniqueOrderedStrings(selectedBotUsernames, normalizeTelegramMentionUsername),
|
|
2722
|
+
normalized_intent: String(normalizedIntent || "").trim().toLowerCase(),
|
|
2723
|
+
status: "claimed",
|
|
2724
|
+
claimed_by_route: String(routeKey || "").trim(),
|
|
2725
|
+
claimed_at: firstNonEmptyString([existing.claimed_at, nowISO]) || nowISO,
|
|
2726
|
+
root_work_item_id: String(existing.root_work_item_id || referencedRequest.root_work_item_id || "").trim(),
|
|
2727
|
+
root_work_item_title: String(existing.root_work_item_title || referencedRequest.root_work_item_title || "").trim(),
|
|
2728
|
+
root_work_item_status: normalizeRunnerWorkItemStatus(
|
|
2729
|
+
existing.root_work_item_status || referencedRequest.root_work_item_status,
|
|
2730
|
+
),
|
|
2731
|
+
root_thread_id: String(existing.root_thread_id || referencedRequest.root_thread_id || "").trim(),
|
|
2732
|
+
root_work_item_created_at: firstNonEmptyString([
|
|
2733
|
+
existing.root_work_item_created_at,
|
|
2734
|
+
referencedRequest.root_work_item_created_at,
|
|
2735
|
+
]),
|
|
2736
|
+
root_work_item_last_error: String(existing.root_work_item_last_error || "").trim(),
|
|
2737
|
+
last_comment_id: String(selectedRecord?.id || "").trim(),
|
|
2738
|
+
last_comment_kind: commentKind,
|
|
2739
|
+
last_source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
2740
|
+
});
|
|
2741
|
+
const { consumedComments: nextConsumedComments } = upsertRunnerConsumedComment(stateForClaim, selectedRecord?.id, {
|
|
2742
|
+
project_id: String(normalizedRoute?.projectID || "").trim(),
|
|
2743
|
+
provider: String(normalizedRoute?.provider || "").trim(),
|
|
2744
|
+
request_key: requestKey,
|
|
2745
|
+
route_key: String(routeKey || "").trim(),
|
|
2746
|
+
conversation_id: conversationID,
|
|
2747
|
+
source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
2748
|
+
comment_kind: commentKind,
|
|
2749
|
+
request_status: "claimed",
|
|
2750
|
+
});
|
|
2751
|
+
saveBotRunnerState({
|
|
2752
|
+
routes: stateForClaim.routes,
|
|
2753
|
+
sharedInboxes: stateForClaim.sharedInboxes || stateForClaim.shared_inboxes,
|
|
2754
|
+
excludedComments: stateForClaim.excludedComments || stateForClaim.excluded_comments,
|
|
2755
|
+
requests: nextRequests,
|
|
2756
|
+
consumedComments: nextConsumedComments,
|
|
2757
|
+
});
|
|
2758
|
+
return {
|
|
2759
|
+
ok: true,
|
|
2760
|
+
requestKey,
|
|
2761
|
+
request,
|
|
2762
|
+
};
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2765
|
+
function isActionableRunnerRequestIntent(rawIntent) {
|
|
2766
|
+
const normalizedIntent = String(rawIntent || "").trim().toLowerCase();
|
|
2767
|
+
return Boolean(normalizedIntent) && !isInformationalRunnerRequestIntent(normalizedIntent);
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
function truncateRunnerWorkItemTitleText(rawText, maxLength = 96) {
|
|
2771
|
+
const text = String(rawText || "").replace(/\s+/g, " ").trim();
|
|
2772
|
+
if (!text) {
|
|
2773
|
+
return "";
|
|
2774
|
+
}
|
|
2775
|
+
if (text.length <= maxLength) {
|
|
2776
|
+
return text;
|
|
2777
|
+
}
|
|
2778
|
+
return `${text.slice(0, Math.max(1, maxLength - 3)).trim()}...`;
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
function buildRunnerRootWorkItemTitle({ selectedRecord, request }) {
|
|
2782
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2783
|
+
const intent = String(safeObject(request).normalized_intent || "").trim().toLowerCase();
|
|
2784
|
+
const requestBody = truncateRunnerWorkItemTitleText(parsed.body || "", 84);
|
|
2785
|
+
const prefix = intent === "ctxpack_mutation"
|
|
2786
|
+
? "Ctxpack request"
|
|
2787
|
+
: intent === "workitem_mutation"
|
|
2788
|
+
? "Work item request"
|
|
2789
|
+
: "Runner request";
|
|
2790
|
+
if (requestBody) {
|
|
2791
|
+
return `${prefix}: ${requestBody}`;
|
|
2792
|
+
}
|
|
2793
|
+
const messageID = intFromRawAllowZero(parsed.messageID, 0);
|
|
2794
|
+
return messageID > 0 ? `${prefix} #${messageID}` : prefix;
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
function buildRunnerRootWorkItemDescription({
|
|
2798
|
+
normalizedRoute,
|
|
2799
|
+
routeKey,
|
|
2800
|
+
selectedRecord,
|
|
2801
|
+
request,
|
|
2802
|
+
}) {
|
|
2803
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2804
|
+
const lines = [
|
|
2805
|
+
`Source request: ${String(parsed.body || "").trim() || "(empty)"}`,
|
|
2806
|
+
`Intent: ${String(safeObject(request).normalized_intent || "").trim() || "unknown"}`,
|
|
2807
|
+
`Route: ${String(normalizedRoute?.name || routeKey || "").trim() || "-"}`,
|
|
2808
|
+
`Provider: ${String(normalizedRoute?.provider || "").trim() || "-"}`,
|
|
2809
|
+
`Chat ID: ${String(parsed.chatID || parsed.chatId || "").trim() || "-"}`,
|
|
2810
|
+
`Message ID: ${intFromRawAllowZero(parsed.messageID, 0) || "-"}`,
|
|
2811
|
+
`Request key: ${String(safeObject(request).request_key || "").trim() || "-"}`,
|
|
2812
|
+
];
|
|
2813
|
+
const conversationID = String(safeObject(request).conversation_id || parsed.conversationID || "").trim();
|
|
2814
|
+
if (conversationID) {
|
|
2815
|
+
lines.push(`Conversation ID: ${conversationID}`);
|
|
2816
|
+
}
|
|
2817
|
+
const selectedBots = ensureArray(safeObject(request).selected_bot_usernames)
|
|
2818
|
+
.map((item) => normalizeTelegramMentionUsername(item))
|
|
2819
|
+
.filter(Boolean);
|
|
2820
|
+
if (selectedBots.length > 0) {
|
|
2821
|
+
lines.push(`Selected bots: ${selectedBots.join(", ")}`);
|
|
2822
|
+
}
|
|
2823
|
+
return lines.join("\n").trim();
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
function buildRunnerRootWorkItemThreadTitle({ selectedRecord }) {
|
|
2827
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2828
|
+
const requestBody = truncateRunnerWorkItemTitleText(parsed.body || "", 72);
|
|
2829
|
+
if (requestBody) {
|
|
2830
|
+
return `Request Context: ${requestBody}`;
|
|
2831
|
+
}
|
|
2832
|
+
const messageID = intFromRawAllowZero(parsed.messageID, 0);
|
|
2833
|
+
return messageID > 0 ? `Request Context #${messageID}` : "Request Context";
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
function buildRunnerRootWorkItemThreadBody({
|
|
2837
|
+
normalizedRoute,
|
|
2838
|
+
routeKey,
|
|
2839
|
+
selectedRecord,
|
|
2840
|
+
request,
|
|
2841
|
+
}) {
|
|
2842
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2843
|
+
const lines = [
|
|
2844
|
+
"Runner root request context thread.",
|
|
2845
|
+
buildRunnerRootWorkItemDescription({
|
|
2846
|
+
normalizedRoute,
|
|
2847
|
+
routeKey,
|
|
2848
|
+
selectedRecord,
|
|
2849
|
+
request,
|
|
2850
|
+
}),
|
|
2851
|
+
];
|
|
2852
|
+
if (String(selectedRecord?.id || "").trim()) {
|
|
2853
|
+
lines.push(`Archive comment ID: ${String(selectedRecord.id || "").trim()}`);
|
|
2854
|
+
}
|
|
2855
|
+
const occurredAt = String(parsed.occurredAt || parsed.occurred_at || "").trim();
|
|
2856
|
+
if (occurredAt) {
|
|
2857
|
+
lines.push(`Occurred at: ${occurredAt}`);
|
|
2858
|
+
}
|
|
2859
|
+
return lines.filter(Boolean).join("\n").trim();
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
async function ensureRunnerRootThreadForRequest({
|
|
2863
|
+
normalizedRoute,
|
|
2864
|
+
routeKey,
|
|
2865
|
+
selectedRecord,
|
|
2866
|
+
runtime,
|
|
2867
|
+
requestKey,
|
|
2868
|
+
}) {
|
|
2869
|
+
const key = String(requestKey || "").trim();
|
|
2870
|
+
if (!key) {
|
|
2871
|
+
return {
|
|
2872
|
+
ok: false,
|
|
2873
|
+
reason: "request_key_missing",
|
|
2874
|
+
};
|
|
2875
|
+
}
|
|
2876
|
+
const currentState = loadBotRunnerState();
|
|
2877
|
+
const request = safeObject(normalizeBotRunnerRequests(currentState.requests)[key]);
|
|
2878
|
+
const rootWorkItemID = String(request.root_work_item_id || "").trim();
|
|
2879
|
+
if (!Object.keys(request).length) {
|
|
2880
|
+
return {
|
|
2881
|
+
ok: false,
|
|
2882
|
+
reason: "request_not_found",
|
|
2883
|
+
requestKey: key,
|
|
2884
|
+
};
|
|
2885
|
+
}
|
|
2886
|
+
if (!rootWorkItemID) {
|
|
2887
|
+
return {
|
|
2888
|
+
ok: true,
|
|
2889
|
+
requestKey: key,
|
|
2890
|
+
request,
|
|
2891
|
+
skipped: true,
|
|
2892
|
+
reason: "root_work_item_missing",
|
|
2893
|
+
};
|
|
2894
|
+
}
|
|
2895
|
+
if (String(request.root_thread_id || "").trim()) {
|
|
2896
|
+
return {
|
|
2897
|
+
ok: true,
|
|
2898
|
+
requestKey: key,
|
|
2899
|
+
request,
|
|
2900
|
+
reused: true,
|
|
2901
|
+
};
|
|
2902
|
+
}
|
|
2903
|
+
if (!runtime?.baseURL || !runtime?.token || !runtime?.actor?.user_id) {
|
|
2904
|
+
return {
|
|
2905
|
+
ok: false,
|
|
2906
|
+
reason: "governance_runtime_unavailable",
|
|
2907
|
+
requestKey: key,
|
|
2908
|
+
request,
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2911
|
+
try {
|
|
2912
|
+
let rootThreadID = "";
|
|
2913
|
+
const existingThreads = ensureArray(await listWorkItemThreads({
|
|
2914
|
+
siteBaseURL: runtime.baseURL,
|
|
2915
|
+
token: runtime.token,
|
|
2916
|
+
timeoutSeconds: runtime.timeoutSeconds,
|
|
2917
|
+
workItemID: rootWorkItemID,
|
|
2918
|
+
status: "",
|
|
2919
|
+
}));
|
|
2920
|
+
rootThreadID = String(
|
|
2921
|
+
safeObject(existingThreads[0]).id
|
|
2922
|
+
|| safeObject(existingThreads[0]).thread_id
|
|
2923
|
+
|| safeObject(existingThreads[0]).threadID
|
|
2924
|
+
|| "",
|
|
2925
|
+
).trim();
|
|
2926
|
+
if (!rootThreadID) {
|
|
2927
|
+
const createdThread = safeObject(await createWorkItemThread({
|
|
2928
|
+
siteBaseURL: runtime.baseURL,
|
|
2929
|
+
token: runtime.token,
|
|
2930
|
+
timeoutSeconds: runtime.timeoutSeconds,
|
|
2931
|
+
actorUserID: runtime.actor.user_id,
|
|
2932
|
+
workItemID: rootWorkItemID,
|
|
2933
|
+
title: buildRunnerRootWorkItemThreadTitle({
|
|
2934
|
+
selectedRecord,
|
|
2935
|
+
}),
|
|
2936
|
+
body: buildRunnerRootWorkItemThreadBody({
|
|
2937
|
+
normalizedRoute,
|
|
2938
|
+
routeKey,
|
|
2939
|
+
selectedRecord,
|
|
2940
|
+
request,
|
|
2941
|
+
}),
|
|
2942
|
+
}));
|
|
2943
|
+
rootThreadID = String(createdThread.thread_id || createdThread.threadID || createdThread.id || "").trim();
|
|
2944
|
+
}
|
|
2945
|
+
if (!rootThreadID) {
|
|
2946
|
+
throw new Error("root thread creation returned no id");
|
|
2947
|
+
}
|
|
2948
|
+
const { requests: nextRequests, request: nextRequest } = upsertRunnerRequest(currentState, key, {
|
|
2949
|
+
root_thread_id: rootThreadID,
|
|
2950
|
+
});
|
|
2951
|
+
saveBotRunnerState({
|
|
2952
|
+
routes: currentState.routes,
|
|
2953
|
+
sharedInboxes: currentState.sharedInboxes || currentState.shared_inboxes,
|
|
2954
|
+
excludedComments: currentState.excludedComments || currentState.excluded_comments,
|
|
2955
|
+
requests: nextRequests,
|
|
2956
|
+
consumedComments: currentState.consumedComments || currentState.consumed_comments,
|
|
2957
|
+
});
|
|
2958
|
+
return {
|
|
2959
|
+
ok: true,
|
|
2960
|
+
requestKey: key,
|
|
2961
|
+
request: nextRequest,
|
|
2962
|
+
created: true,
|
|
2963
|
+
};
|
|
2964
|
+
} catch (err) {
|
|
2965
|
+
return {
|
|
2966
|
+
ok: false,
|
|
2967
|
+
reason: "root_thread_create_failed",
|
|
2968
|
+
requestKey: key,
|
|
2969
|
+
request,
|
|
2970
|
+
error: String(err?.message || err).trim() || "failed to create root thread",
|
|
2971
|
+
};
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
async function inheritRunnerReferenceRootWorkItemForRequest({
|
|
2976
|
+
normalizedRoute,
|
|
2977
|
+
selectedRecord,
|
|
2978
|
+
runtime,
|
|
2979
|
+
requestKey,
|
|
2980
|
+
}) {
|
|
2981
|
+
const key = String(requestKey || "").trim();
|
|
2982
|
+
if (!key) {
|
|
2983
|
+
return {
|
|
2984
|
+
ok: false,
|
|
2985
|
+
reason: "request_key_missing",
|
|
2986
|
+
};
|
|
2987
|
+
}
|
|
2988
|
+
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2989
|
+
const chatID = String(parsed.chatID || parsed.chatId || "").trim();
|
|
2990
|
+
const replyToMessageID = intFromRawAllowZero(parsed.replyToMessageID, 0);
|
|
2991
|
+
if (!chatID || replyToMessageID <= 0) {
|
|
2992
|
+
return {
|
|
2993
|
+
ok: true,
|
|
2994
|
+
requestKey: key,
|
|
2995
|
+
skipped: true,
|
|
2996
|
+
reason: "reply_reference_missing",
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
2999
|
+
const currentState = loadBotRunnerState();
|
|
3000
|
+
const currentRequest = safeObject(normalizeBotRunnerRequests(currentState.requests)[key]);
|
|
3001
|
+
if (!Object.keys(currentRequest).length) {
|
|
3002
|
+
return {
|
|
3003
|
+
ok: false,
|
|
3004
|
+
requestKey: key,
|
|
3005
|
+
reason: "request_not_found",
|
|
3006
|
+
};
|
|
3007
|
+
}
|
|
3008
|
+
if (String(currentRequest.root_work_item_id || "").trim()) {
|
|
3009
|
+
return {
|
|
3010
|
+
ok: true,
|
|
3011
|
+
requestKey: key,
|
|
3012
|
+
request: currentRequest,
|
|
3013
|
+
reused: true,
|
|
3014
|
+
};
|
|
3015
|
+
}
|
|
3016
|
+
const serverReferencedRequest = await findServerRunnerRequestForMessageID({
|
|
3017
|
+
normalizedRoute,
|
|
3018
|
+
runtime,
|
|
3019
|
+
chatID,
|
|
3020
|
+
messageID: replyToMessageID,
|
|
3021
|
+
});
|
|
3022
|
+
if (!String(serverReferencedRequest.root_work_item_id || "").trim()) {
|
|
3023
|
+
return {
|
|
3024
|
+
ok: true,
|
|
3025
|
+
requestKey: key,
|
|
3026
|
+
request: currentRequest,
|
|
3027
|
+
skipped: true,
|
|
3028
|
+
reason: "referenced_root_work_item_missing",
|
|
3029
|
+
};
|
|
3030
|
+
}
|
|
3031
|
+
const { requests: nextRequests, request } = upsertRunnerRequest(currentState, key, {
|
|
3032
|
+
root_work_item_id: String(serverReferencedRequest.root_work_item_id || "").trim(),
|
|
3033
|
+
root_work_item_title: String(serverReferencedRequest.root_work_item_title || "").trim(),
|
|
3034
|
+
root_work_item_status: normalizeRunnerWorkItemStatus(serverReferencedRequest.root_work_item_status),
|
|
3035
|
+
root_thread_id: String(serverReferencedRequest.root_thread_id || "").trim(),
|
|
3036
|
+
root_work_item_created_at: firstNonEmptyString([serverReferencedRequest.root_work_item_created_at]),
|
|
3037
|
+
root_work_item_last_error: String(serverReferencedRequest.root_work_item_last_error || "").trim(),
|
|
3038
|
+
});
|
|
3039
|
+
saveBotRunnerState({
|
|
3040
|
+
routes: currentState.routes,
|
|
3041
|
+
sharedInboxes: currentState.sharedInboxes || currentState.shared_inboxes,
|
|
3042
|
+
excludedComments: currentState.excludedComments || currentState.excluded_comments,
|
|
3043
|
+
requests: nextRequests,
|
|
3044
|
+
consumedComments: currentState.consumedComments || currentState.consumed_comments,
|
|
3045
|
+
});
|
|
3046
|
+
return {
|
|
3047
|
+
ok: true,
|
|
3048
|
+
requestKey: key,
|
|
3049
|
+
request,
|
|
3050
|
+
inherited: true,
|
|
3051
|
+
};
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
async function ensureRunnerRootWorkItemForRequest({
|
|
3055
|
+
normalizedRoute,
|
|
3056
|
+
routeKey,
|
|
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 currentState = loadBotRunnerState();
|
|
3069
|
+
const existing = safeObject(normalizeBotRunnerRequests(currentState.requests)[key]);
|
|
3070
|
+
if (!Object.keys(existing).length) {
|
|
3071
|
+
return {
|
|
3072
|
+
ok: false,
|
|
3073
|
+
reason: "request_not_found",
|
|
3074
|
+
requestKey: key,
|
|
3075
|
+
};
|
|
3076
|
+
}
|
|
3077
|
+
if (!isActionableRunnerRequestIntent(existing.normalized_intent)) {
|
|
3078
|
+
return {
|
|
3079
|
+
ok: true,
|
|
3080
|
+
requestKey: key,
|
|
3081
|
+
request: existing,
|
|
3082
|
+
skipped: true,
|
|
3083
|
+
reason: "informational_request",
|
|
3084
|
+
};
|
|
3085
|
+
}
|
|
3086
|
+
if (String(existing.root_work_item_id || "").trim()) {
|
|
3087
|
+
const rootThreadClaim = await ensureRunnerRootThreadForRequest({
|
|
3088
|
+
normalizedRoute,
|
|
3089
|
+
routeKey,
|
|
3090
|
+
selectedRecord,
|
|
3091
|
+
runtime,
|
|
3092
|
+
requestKey: key,
|
|
3093
|
+
});
|
|
3094
|
+
return {
|
|
3095
|
+
ok: true,
|
|
3096
|
+
requestKey: key,
|
|
3097
|
+
request: safeObject(rootThreadClaim.request || existing),
|
|
3098
|
+
reused: true,
|
|
3099
|
+
root_thread_created: rootThreadClaim.created === true,
|
|
3100
|
+
root_thread_error: String(rootThreadClaim.error || "").trim(),
|
|
3101
|
+
};
|
|
3102
|
+
}
|
|
3103
|
+
if (!runtime?.baseURL || !runtime?.token || !runtime?.actor?.user_id) {
|
|
3104
|
+
return {
|
|
3105
|
+
ok: false,
|
|
3106
|
+
reason: "governance_runtime_unavailable",
|
|
3107
|
+
requestKey: key,
|
|
3108
|
+
};
|
|
3109
|
+
}
|
|
3110
|
+
try {
|
|
3111
|
+
const actorBotID = firstNonEmptyString([
|
|
3112
|
+
normalizedRoute?.botID,
|
|
3113
|
+
normalizedRoute?.botId,
|
|
3114
|
+
normalizedRoute?.serverBotID,
|
|
3115
|
+
normalizedRoute?.server_bot_id,
|
|
3116
|
+
]);
|
|
3117
|
+
const actorBotName = firstNonEmptyString([
|
|
3118
|
+
normalizedRoute?.botName,
|
|
3119
|
+
normalizedRoute?.bot_name,
|
|
3120
|
+
normalizedRoute?.serverBotName,
|
|
3121
|
+
normalizedRoute?.server_bot_name,
|
|
3122
|
+
]);
|
|
3123
|
+
const title = buildRunnerRootWorkItemTitle({
|
|
3124
|
+
selectedRecord,
|
|
3125
|
+
request: existing,
|
|
3126
|
+
});
|
|
3127
|
+
const description = buildRunnerRootWorkItemDescription({
|
|
3128
|
+
normalizedRoute,
|
|
3129
|
+
routeKey,
|
|
3130
|
+
selectedRecord,
|
|
3131
|
+
request: existing,
|
|
3132
|
+
});
|
|
3133
|
+
const created = safeObject(await createProjectWorkItem({
|
|
3134
|
+
siteBaseURL: runtime.baseURL,
|
|
3135
|
+
token: runtime.token,
|
|
3136
|
+
timeoutSeconds: runtime.timeoutSeconds,
|
|
3137
|
+
actorUserID: runtime.actor.user_id,
|
|
3138
|
+
actorBotID: String(actorBotID || "").trim(),
|
|
3139
|
+
actorBotName: String(actorBotName || "").trim(),
|
|
3140
|
+
projectID: String(normalizedRoute?.projectID || "").trim(),
|
|
3141
|
+
title,
|
|
3142
|
+
description,
|
|
3143
|
+
}));
|
|
3144
|
+
const rootWorkItemID = String(created.id || created.work_item_id || created.workItemID || "").trim();
|
|
3145
|
+
if (!rootWorkItemID) {
|
|
3146
|
+
throw new Error("work item creation returned no id");
|
|
3147
|
+
}
|
|
3148
|
+
const rootWorkItemStatus = normalizeRunnerWorkItemStatus(created.status || "backlog") || "backlog";
|
|
3149
|
+
const { requests: nextRequests, request } = upsertRunnerRequest(currentState, key, {
|
|
3150
|
+
root_work_item_id: rootWorkItemID,
|
|
3151
|
+
root_work_item_title: String(created.title || title).trim() || title,
|
|
3152
|
+
root_work_item_status: rootWorkItemStatus,
|
|
3153
|
+
root_work_item_created_at: new Date().toISOString(),
|
|
3154
|
+
root_work_item_last_error: "",
|
|
3155
|
+
});
|
|
3156
|
+
saveBotRunnerState({
|
|
3157
|
+
routes: currentState.routes,
|
|
3158
|
+
sharedInboxes: currentState.sharedInboxes || currentState.shared_inboxes,
|
|
3159
|
+
excludedComments: currentState.excludedComments || currentState.excluded_comments,
|
|
3160
|
+
requests: nextRequests,
|
|
3161
|
+
consumedComments: currentState.consumedComments || currentState.consumed_comments,
|
|
3162
|
+
});
|
|
3163
|
+
const rootThreadClaim = await ensureRunnerRootThreadForRequest({
|
|
3164
|
+
normalizedRoute,
|
|
3165
|
+
routeKey,
|
|
3166
|
+
selectedRecord,
|
|
3167
|
+
runtime,
|
|
3168
|
+
requestKey: key,
|
|
3169
|
+
});
|
|
3170
|
+
return {
|
|
3171
|
+
ok: true,
|
|
3172
|
+
requestKey: key,
|
|
3173
|
+
request: safeObject(rootThreadClaim.request || request),
|
|
3174
|
+
created: true,
|
|
3175
|
+
root_thread_created: rootThreadClaim.created === true,
|
|
3176
|
+
root_thread_error: String(rootThreadClaim.error || "").trim(),
|
|
3177
|
+
};
|
|
3178
|
+
} catch (err) {
|
|
3179
|
+
const errorText = String(err?.message || err).trim() || "failed to create root work item";
|
|
3180
|
+
const { requests: nextRequests, request } = upsertRunnerRequest(currentState, key, {
|
|
3181
|
+
root_work_item_last_error: errorText,
|
|
3182
|
+
});
|
|
3183
|
+
saveBotRunnerState({
|
|
3184
|
+
routes: currentState.routes,
|
|
3185
|
+
sharedInboxes: currentState.sharedInboxes || currentState.shared_inboxes,
|
|
3186
|
+
excludedComments: currentState.excludedComments || currentState.excluded_comments,
|
|
3187
|
+
requests: nextRequests,
|
|
3188
|
+
consumedComments: currentState.consumedComments || currentState.consumed_comments,
|
|
3189
|
+
});
|
|
3190
|
+
return {
|
|
3191
|
+
ok: false,
|
|
3192
|
+
reason: "root_work_item_create_failed",
|
|
3193
|
+
requestKey: key,
|
|
3194
|
+
request,
|
|
3195
|
+
error: errorText,
|
|
3196
|
+
};
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
|
|
3200
|
+
function deriveRunnerRootWorkItemTargetStatus(rawRequestStatus) {
|
|
3201
|
+
const status = normalizeRunnerRequestStatus(rawRequestStatus);
|
|
3202
|
+
if (status === "running") {
|
|
3203
|
+
return "doing";
|
|
3204
|
+
}
|
|
3205
|
+
if (status === "completed" || status === "loop_closed") {
|
|
3206
|
+
return "done";
|
|
3207
|
+
}
|
|
3208
|
+
if (status === "closed" || status === "expired") {
|
|
3209
|
+
return "canceled";
|
|
3210
|
+
}
|
|
3211
|
+
return "";
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
function buildRunnerRootWorkItemTransitionPath(currentStatusRaw, targetStatusRaw) {
|
|
3215
|
+
const currentStatus = normalizeRunnerWorkItemStatus(currentStatusRaw) || "backlog";
|
|
3216
|
+
const targetStatus = normalizeRunnerWorkItemStatus(targetStatusRaw);
|
|
3217
|
+
if (!targetStatus || currentStatus === targetStatus) {
|
|
3218
|
+
return [];
|
|
3219
|
+
}
|
|
3220
|
+
if (targetStatus === "doing") {
|
|
3221
|
+
return currentStatus === "backlog" ? ["doing"] : [];
|
|
3222
|
+
}
|
|
3223
|
+
if (targetStatus === "done") {
|
|
3224
|
+
if (currentStatus === "backlog") return ["doing", "review", "done"];
|
|
3225
|
+
if (currentStatus === "doing") return ["review", "done"];
|
|
3226
|
+
if (currentStatus === "review") return ["done"];
|
|
3227
|
+
return [];
|
|
3228
|
+
}
|
|
3229
|
+
if (targetStatus === "canceled") {
|
|
3230
|
+
if (currentStatus === "backlog" || currentStatus === "doing" || currentStatus === "review") {
|
|
3231
|
+
return ["canceled"];
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
return [];
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
async function syncRunnerRequestRootWorkItemForOutcome({
|
|
2429
3238
|
normalizedRoute,
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
selectedBotUsernames = [],
|
|
2433
|
-
normalizedIntent = "",
|
|
3239
|
+
runtime,
|
|
3240
|
+
requestKey,
|
|
2434
3241
|
}) {
|
|
2435
|
-
const
|
|
2436
|
-
|
|
2437
|
-
if (!isInboundArchiveKind(commentKind)) {
|
|
3242
|
+
const key = String(requestKey || "").trim();
|
|
3243
|
+
if (!key) {
|
|
2438
3244
|
return {
|
|
2439
3245
|
ok: false,
|
|
2440
|
-
reason: "
|
|
3246
|
+
reason: "request_key_missing",
|
|
2441
3247
|
};
|
|
2442
3248
|
}
|
|
2443
|
-
const requestKey = buildRunnerRequestKey({
|
|
2444
|
-
normalizedRoute,
|
|
2445
|
-
selectedRecord,
|
|
2446
|
-
selectedBotUsernames,
|
|
2447
|
-
normalizedIntent,
|
|
2448
|
-
});
|
|
2449
3249
|
const currentState = loadBotRunnerState();
|
|
2450
|
-
const
|
|
2451
|
-
const
|
|
2452
|
-
if (
|
|
3250
|
+
const request = safeObject(normalizeBotRunnerRequests(currentState.requests)[key]);
|
|
3251
|
+
const rootWorkItemID = String(request.root_work_item_id || "").trim();
|
|
3252
|
+
if (!rootWorkItemID) {
|
|
2453
3253
|
return {
|
|
2454
|
-
ok:
|
|
2455
|
-
|
|
2456
|
-
|
|
3254
|
+
ok: true,
|
|
3255
|
+
skipped: true,
|
|
3256
|
+
reason: "root_work_item_missing",
|
|
3257
|
+
request,
|
|
2457
3258
|
};
|
|
2458
3259
|
}
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
&& String(existing.claimed_by_route || "").trim()
|
|
2462
|
-
&& String(existing.claimed_by_route || "").trim() !== String(routeKey || "").trim()
|
|
2463
|
-
) {
|
|
3260
|
+
const targetStatus = deriveRunnerRootWorkItemTargetStatus(request.status);
|
|
3261
|
+
if (!targetStatus || !runtime?.baseURL || !runtime?.token || !runtime?.actor?.user_id) {
|
|
2464
3262
|
return {
|
|
2465
|
-
ok:
|
|
2466
|
-
|
|
2467
|
-
|
|
3263
|
+
ok: true,
|
|
3264
|
+
skipped: true,
|
|
3265
|
+
reason: !targetStatus ? "no_target_status" : "governance_runtime_unavailable",
|
|
3266
|
+
request,
|
|
2468
3267
|
};
|
|
2469
3268
|
}
|
|
2470
|
-
|
|
2471
|
-
const
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
3269
|
+
let currentStatus = normalizeRunnerWorkItemStatus(request.root_work_item_status) || "backlog";
|
|
3270
|
+
const transitions = buildRunnerRootWorkItemTransitionPath(currentStatus, targetStatus);
|
|
3271
|
+
let lastError = "";
|
|
3272
|
+
const actorBotID = firstNonEmptyString([
|
|
3273
|
+
normalizedRoute?.botID,
|
|
3274
|
+
normalizedRoute?.botId,
|
|
3275
|
+
normalizedRoute?.serverBotID,
|
|
3276
|
+
normalizedRoute?.server_bot_id,
|
|
3277
|
+
]);
|
|
3278
|
+
const actorBotName = firstNonEmptyString([
|
|
3279
|
+
normalizedRoute?.botName,
|
|
3280
|
+
normalizedRoute?.bot_name,
|
|
3281
|
+
normalizedRoute?.serverBotName,
|
|
3282
|
+
normalizedRoute?.server_bot_name,
|
|
3283
|
+
]);
|
|
3284
|
+
for (const nextStatus of transitions) {
|
|
3285
|
+
try {
|
|
3286
|
+
await transitionProjectWorkItem({
|
|
3287
|
+
siteBaseURL: runtime.baseURL,
|
|
3288
|
+
token: runtime.token,
|
|
3289
|
+
timeoutSeconds: runtime.timeoutSeconds,
|
|
3290
|
+
actorUserID: runtime.actor.user_id,
|
|
3291
|
+
actorBotID: String(actorBotID || "").trim(),
|
|
3292
|
+
actorBotName: String(actorBotName || "").trim(),
|
|
3293
|
+
workItemID: rootWorkItemID,
|
|
3294
|
+
status: nextStatus,
|
|
3295
|
+
});
|
|
3296
|
+
currentStatus = nextStatus;
|
|
3297
|
+
} catch (err) {
|
|
3298
|
+
lastError = String(err?.message || err).trim() || `failed to transition work item to ${nextStatus}`;
|
|
3299
|
+
break;
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
const { requests: nextRequests, request: nextRequest } = upsertRunnerRequest(currentState, key, {
|
|
3303
|
+
root_work_item_status: currentStatus,
|
|
3304
|
+
root_work_item_last_error: lastError,
|
|
2497
3305
|
});
|
|
2498
3306
|
saveBotRunnerState({
|
|
2499
3307
|
routes: currentState.routes,
|
|
2500
3308
|
sharedInboxes: currentState.sharedInboxes || currentState.shared_inboxes,
|
|
2501
3309
|
excludedComments: currentState.excludedComments || currentState.excluded_comments,
|
|
2502
3310
|
requests: nextRequests,
|
|
2503
|
-
consumedComments:
|
|
3311
|
+
consumedComments: currentState.consumedComments || currentState.consumed_comments,
|
|
2504
3312
|
});
|
|
2505
3313
|
return {
|
|
2506
|
-
ok:
|
|
2507
|
-
|
|
2508
|
-
|
|
3314
|
+
ok: lastError === "",
|
|
3315
|
+
request: nextRequest,
|
|
3316
|
+
transitioned: transitions.length > 0 && lastError === "",
|
|
3317
|
+
error: lastError,
|
|
2509
3318
|
};
|
|
2510
3319
|
}
|
|
2511
3320
|
|
|
@@ -2836,6 +3645,30 @@ function runnerLedgerEntryMatchesProject(entryRaw, normalizedRoute, requestIndex
|
|
|
2836
3645
|
);
|
|
2837
3646
|
}
|
|
2838
3647
|
|
|
3648
|
+
function mergeRunnerRequestForServerHydration(localEntryRaw, serverEntryRaw) {
|
|
3649
|
+
const localEntry = safeObject(localEntryRaw);
|
|
3650
|
+
const serverEntry = safeObject(serverEntryRaw);
|
|
3651
|
+
const merged = {
|
|
3652
|
+
...localEntry,
|
|
3653
|
+
...serverEntry,
|
|
3654
|
+
};
|
|
3655
|
+
const preserveLocalStringWhenServerBlank = (fieldName) => {
|
|
3656
|
+
const serverValue = String(serverEntry[fieldName] || "").trim();
|
|
3657
|
+
const localValue = String(localEntry[fieldName] || "").trim();
|
|
3658
|
+
if (!serverValue && localValue) {
|
|
3659
|
+
merged[fieldName] = localValue;
|
|
3660
|
+
}
|
|
3661
|
+
};
|
|
3662
|
+
preserveLocalStringWhenServerBlank("conversation_id");
|
|
3663
|
+
preserveLocalStringWhenServerBlank("root_work_item_id");
|
|
3664
|
+
preserveLocalStringWhenServerBlank("root_work_item_title");
|
|
3665
|
+
preserveLocalStringWhenServerBlank("root_work_item_status");
|
|
3666
|
+
preserveLocalStringWhenServerBlank("root_thread_id");
|
|
3667
|
+
preserveLocalStringWhenServerBlank("root_work_item_created_at");
|
|
3668
|
+
preserveLocalStringWhenServerBlank("root_work_item_last_error");
|
|
3669
|
+
return merged;
|
|
3670
|
+
}
|
|
3671
|
+
|
|
2839
3672
|
function mergeServerRunnerRequestLedgerIntoLocalState(currentState, normalizedRoute, serverRequests = [], serverCommentStates = []) {
|
|
2840
3673
|
const state = safeObject(currentState);
|
|
2841
3674
|
const normalizedRequests = normalizeBotRunnerRequests(state.requests);
|
|
@@ -2859,7 +3692,12 @@ function mergeServerRunnerRequestLedgerIntoLocalState(currentState, normalizedRo
|
|
|
2859
3692
|
const request = safeObject(requestRaw);
|
|
2860
3693
|
const requestKey = String(request.request_key || request.requestKey || "").trim();
|
|
2861
3694
|
if (!requestKey) continue;
|
|
2862
|
-
|
|
3695
|
+
const localRequest = safeObject(normalizedRequests[requestKey]);
|
|
3696
|
+
nextRequests[requestKey] = normalizeBotRunnerRequests({
|
|
3697
|
+
[requestKey]: {
|
|
3698
|
+
...mergeRunnerRequestForServerHydration(localRequest, request),
|
|
3699
|
+
},
|
|
3700
|
+
})[requestKey];
|
|
2863
3701
|
}
|
|
2864
3702
|
|
|
2865
3703
|
const requestIndex = normalizeBotRunnerRequests(nextRequests);
|
|
@@ -4843,6 +5681,10 @@ async function createProjectWorkItem(params) {
|
|
|
4843
5681
|
return createProjectWorkItemImpl(params, buildRunnerDataDeps());
|
|
4844
5682
|
}
|
|
4845
5683
|
|
|
5684
|
+
async function transitionProjectWorkItem(params) {
|
|
5685
|
+
return transitionProjectWorkItemImpl(params, buildRunnerDataDeps());
|
|
5686
|
+
}
|
|
5687
|
+
|
|
4846
5688
|
async function createProjectEvidence(params) {
|
|
4847
5689
|
return createProjectEvidenceImpl(params, buildRunnerDataDeps());
|
|
4848
5690
|
}
|
|
@@ -4851,6 +5693,10 @@ async function createWorkItemThread(params) {
|
|
|
4851
5693
|
return createWorkItemThreadImpl(params, buildRunnerDataDeps());
|
|
4852
5694
|
}
|
|
4853
5695
|
|
|
5696
|
+
async function listWorkItemThreads(params) {
|
|
5697
|
+
return listWorkItemThreadsImpl(params, buildRunnerDataDeps());
|
|
5698
|
+
}
|
|
5699
|
+
|
|
4854
5700
|
async function linkWorkItemEvidence(params) {
|
|
4855
5701
|
return linkWorkItemEvidenceImpl(params, buildRunnerDataDeps());
|
|
4856
5702
|
}
|
|
@@ -5149,20 +5995,85 @@ function summarizeRunnerRequestForStatusLookup(entryRaw) {
|
|
|
5149
5995
|
return {
|
|
5150
5996
|
status: String(entry.status || "").trim(),
|
|
5151
5997
|
normalized_intent: String(entry.normalized_intent || "").trim(),
|
|
5998
|
+
conversation_id: String(entry.conversation_id || "").trim(),
|
|
5999
|
+
closed_reason: String(entry.closed_reason || "").trim(),
|
|
5152
6000
|
claimed_at: firstNonEmptyString([entry.claimed_at, entry.started_at]),
|
|
5153
6001
|
started_at: firstNonEmptyString([entry.started_at, entry.claimed_at]),
|
|
5154
6002
|
updated_at: String(entry.updated_at || "").trim(),
|
|
5155
6003
|
source_message_id: intFromRawAllowZero(entry.source_message_id, 0) || undefined,
|
|
6004
|
+
last_source_message_id: intFromRawAllowZero(entry.last_source_message_id, 0) || undefined,
|
|
5156
6005
|
selected_bot_usernames: ensureArray(entry.selected_bot_usernames)
|
|
5157
6006
|
.map((value) => normalizeTelegramMentionUsername(value))
|
|
5158
6007
|
.filter(Boolean),
|
|
6008
|
+
root_work_item: String(entry.root_work_item_id || "").trim()
|
|
6009
|
+
? {
|
|
6010
|
+
id: String(entry.root_work_item_id || "").trim(),
|
|
6011
|
+
title: String(entry.root_work_item_title || "").trim(),
|
|
6012
|
+
status: normalizeRunnerWorkItemStatus(entry.root_work_item_status),
|
|
6013
|
+
thread_id: String(entry.root_thread_id || "").trim(),
|
|
6014
|
+
}
|
|
6015
|
+
: null,
|
|
5159
6016
|
};
|
|
5160
6017
|
}
|
|
5161
6018
|
|
|
5162
|
-
function
|
|
6019
|
+
function isInformationalRunnerRequestIntent(intentType) {
|
|
6020
|
+
return new Set([
|
|
6021
|
+
"status_query",
|
|
6022
|
+
"bot_role_query",
|
|
6023
|
+
"workspace_query",
|
|
6024
|
+
"explanation_query",
|
|
6025
|
+
"small_talk",
|
|
6026
|
+
]).has(String(intentType || "").trim().toLowerCase());
|
|
6027
|
+
}
|
|
6028
|
+
|
|
6029
|
+
function requestEligibleForStatusLookup(entryRaw, routeKey, selfBotUsername, currentMessageID) {
|
|
6030
|
+
const entry = safeObject(entryRaw);
|
|
6031
|
+
const sourceMessageID = intFromRawAllowZero(entry.source_message_id, 0);
|
|
6032
|
+
const lastSourceMessageID = intFromRawAllowZero(entry.last_source_message_id, 0);
|
|
6033
|
+
if (
|
|
6034
|
+
(currentMessageID > 0 && sourceMessageID === currentMessageID)
|
|
6035
|
+
|| (currentMessageID > 0 && lastSourceMessageID === currentMessageID)
|
|
6036
|
+
) {
|
|
6037
|
+
return false;
|
|
6038
|
+
}
|
|
6039
|
+
if (String(entry.claimed_by_route || "").trim() === routeKey) {
|
|
6040
|
+
return true;
|
|
6041
|
+
}
|
|
6042
|
+
if (!selfBotUsername) {
|
|
6043
|
+
return true;
|
|
6044
|
+
}
|
|
6045
|
+
return ensureArray(entry.selected_bot_usernames)
|
|
6046
|
+
.map((value) => normalizeTelegramMentionUsername(value))
|
|
6047
|
+
.filter(Boolean)
|
|
6048
|
+
.includes(selfBotUsername);
|
|
6049
|
+
}
|
|
6050
|
+
|
|
6051
|
+
function pickPreferredStatusLookupRequest(entries = []) {
|
|
6052
|
+
const candidates = ensureArray(entries).map((entry) => safeObject(entry)).filter((entry) => Object.keys(entry).length > 0);
|
|
6053
|
+
if (!candidates.length) {
|
|
6054
|
+
return null;
|
|
6055
|
+
}
|
|
6056
|
+
const activeNonInformational = candidates.filter((entry) => (
|
|
6057
|
+
isActiveRunnerRequestStatus(entry.status)
|
|
6058
|
+
&& !isInformationalRunnerRequestIntent(entry.normalized_intent)
|
|
6059
|
+
));
|
|
6060
|
+
if (activeNonInformational.length) {
|
|
6061
|
+
return activeNonInformational[0];
|
|
6062
|
+
}
|
|
6063
|
+
const nonInformational = candidates.filter((entry) => !isInformationalRunnerRequestIntent(entry.normalized_intent));
|
|
6064
|
+
if (nonInformational.length) {
|
|
6065
|
+
return nonInformational[0];
|
|
6066
|
+
}
|
|
6067
|
+
const activeAny = candidates.filter((entry) => isActiveRunnerRequestStatus(entry.status));
|
|
6068
|
+
if (activeAny.length) {
|
|
6069
|
+
return activeAny[0];
|
|
6070
|
+
}
|
|
6071
|
+
return candidates[0];
|
|
6072
|
+
}
|
|
6073
|
+
|
|
6074
|
+
function buildRunnerStatusQueryLookup({ route, routeState, selectedRecord, runnerStateOverride = null }) {
|
|
5163
6075
|
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
5164
6076
|
const currentMessageID = intFromRawAllowZero(parsed.messageID, 0);
|
|
5165
|
-
const currentConversationID = String(parsed.conversationID || "").trim();
|
|
5166
6077
|
const currentChatID = String(parsed.chatID || parsed.chatId || "").trim();
|
|
5167
6078
|
const routeKey = runnerRouteKey(route);
|
|
5168
6079
|
const selfBotUsername = normalizeTelegramMentionUsername(firstNonEmptyString([
|
|
@@ -5179,34 +6090,80 @@ function buildRunnerStatusQueryLookup({ route, routeState, selectedRecord }) {
|
|
|
5179
6090
|
&& activeSourceMessageID > 0
|
|
5180
6091
|
&& currentMessageID === activeSourceMessageID
|
|
5181
6092
|
);
|
|
6093
|
+
let runnerState = safeObject(runnerStateOverride);
|
|
6094
|
+
if (!Object.keys(runnerState).length) {
|
|
6095
|
+
runnerState = { requests: {} };
|
|
6096
|
+
try {
|
|
6097
|
+
runnerState = loadBotRunnerState();
|
|
6098
|
+
} catch {}
|
|
6099
|
+
}
|
|
6100
|
+
const replyChainContext = resolveRunnerReplyChainConversationContext(runnerState, route, selectedRecord);
|
|
6101
|
+
const currentConversationID = String(parsed.conversationID || replyChainContext.conversationID || "").trim();
|
|
6102
|
+
const activeRequestKey = String(safeObject(routeState).active_request_key || "").trim();
|
|
6103
|
+
const requestMatchesCurrentRoute = (entry) => requestEligibleForStatusLookup(
|
|
6104
|
+
entry,
|
|
6105
|
+
routeKey,
|
|
6106
|
+
selfBotUsername,
|
|
6107
|
+
currentMessageID,
|
|
6108
|
+
);
|
|
6109
|
+
const referencedRequestCandidate = safeObject(replyChainContext.referencedRequest);
|
|
5182
6110
|
let relatedActiveRequest = null;
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
6111
|
+
let relatedRequest = null;
|
|
6112
|
+
const selectors = currentConversationID
|
|
6113
|
+
? { conversationID: currentConversationID, chatID: currentChatID }
|
|
6114
|
+
: { chatID: currentChatID };
|
|
6115
|
+
let scopedRequests = findRunnerRequestsForScope(runnerState, route, selectors);
|
|
6116
|
+
if (!scopedRequests.length && currentConversationID) {
|
|
6117
|
+
scopedRequests = findRunnerRequestsForScope(runnerState, route, { chatID: currentChatID });
|
|
6118
|
+
}
|
|
6119
|
+
if (!scopedRequests.length && activeRequestKey) {
|
|
6120
|
+
scopedRequests = findRunnerRequestsForScope(runnerState, route, { requestKey: activeRequestKey });
|
|
6121
|
+
}
|
|
6122
|
+
const eligibleScopedRequests = scopedRequests.filter(requestMatchesCurrentRoute);
|
|
6123
|
+
const statusLookupCandidates = [...eligibleScopedRequests];
|
|
6124
|
+
if (
|
|
6125
|
+
Object.keys(referencedRequestCandidate).length > 0
|
|
6126
|
+
&& requestMatchesCurrentRoute(referencedRequestCandidate)
|
|
6127
|
+
&& !statusLookupCandidates.some(
|
|
6128
|
+
(entry) => String(safeObject(entry).request_key || "").trim() === String(referencedRequestCandidate.request_key || "").trim(),
|
|
6129
|
+
)
|
|
6130
|
+
) {
|
|
6131
|
+
statusLookupCandidates.push(referencedRequestCandidate);
|
|
6132
|
+
}
|
|
6133
|
+
relatedActiveRequest = statusLookupCandidates
|
|
6134
|
+
.filter((entry) => isActiveRunnerRequestStatus(entry.status))[0] || null;
|
|
6135
|
+
relatedRequest = pickPreferredStatusLookupRequest(statusLookupCandidates);
|
|
5204
6136
|
const lastAction = String(safeObject(routeState).last_action || "").trim();
|
|
5205
6137
|
const lastReason = String(safeObject(routeState).last_reason || "").trim();
|
|
5206
6138
|
const lastIntentType = String(safeObject(routeState).last_intent_type || "").trim();
|
|
6139
|
+
const routeConversationID = String(safeObject(routeState).last_conversation_id || "").trim();
|
|
6140
|
+
const activeRootWorkItemID = String(safeObject(routeState).active_root_work_item_id || "").trim();
|
|
6141
|
+
const activeRootWorkItemTitle = String(safeObject(routeState).active_root_work_item_title || "").trim();
|
|
6142
|
+
const activeRootWorkItemStatus = normalizeRunnerWorkItemStatus(safeObject(routeState).active_root_work_item_status);
|
|
6143
|
+
const routeRootWorkItemID = String(safeObject(routeState).last_root_work_item_id || "").trim();
|
|
6144
|
+
const routeRootWorkItemTitle = String(safeObject(routeState).last_root_work_item_title || "").trim();
|
|
6145
|
+
const routeRootWorkItemStatus = normalizeRunnerWorkItemStatus(safeObject(routeState).last_root_work_item_status);
|
|
6146
|
+
const routeWorkItemIDs = ensureArray(safeObject(routeState).last_work_item_ids).map((item) => String(item || "").trim()).filter(Boolean);
|
|
6147
|
+
const routeWorkItemTitles = ensureArray(safeObject(routeState).last_work_item_titles).map((item) => String(item || "").trim()).filter(Boolean);
|
|
6148
|
+
const requestRootWorkItem = String(safeObject(relatedRequest).root_work_item_id || "").trim()
|
|
6149
|
+
? {
|
|
6150
|
+
id: String(safeObject(relatedRequest).root_work_item_id || "").trim(),
|
|
6151
|
+
title: String(safeObject(relatedRequest).root_work_item_title || "").trim(),
|
|
6152
|
+
status: normalizeRunnerWorkItemStatus(safeObject(relatedRequest).root_work_item_status),
|
|
6153
|
+
thread_id: String(safeObject(relatedRequest).root_thread_id || "").trim(),
|
|
6154
|
+
}
|
|
6155
|
+
: null;
|
|
5207
6156
|
return {
|
|
5208
6157
|
kind: "runner_status",
|
|
5209
|
-
status: (!selfBusyFiltered && activeExecution.active) || relatedActiveRequest
|
|
6158
|
+
status: (!selfBusyFiltered && activeExecution.active) || relatedActiveRequest
|
|
6159
|
+
? "running"
|
|
6160
|
+
: String(safeObject(relatedRequest).status || "").trim() || "idle",
|
|
6161
|
+
resolved_conversation_id: currentConversationID,
|
|
6162
|
+
reply_chain_resolution: {
|
|
6163
|
+
reason: String(replyChainContext.reason || "").trim(),
|
|
6164
|
+
reply_to_message_id: intFromRawAllowZero(replyChainContext.replyToMessageID, 0) || undefined,
|
|
6165
|
+
anchor_message_id: intFromRawAllowZero(replyChainContext.anchorMessageID, 0) || undefined,
|
|
6166
|
+
},
|
|
5210
6167
|
self_busy_filtered: selfBusyFiltered,
|
|
5211
6168
|
active_execution: activeExecution.active && !selfBusyFiltered
|
|
5212
6169
|
? {
|
|
@@ -5218,6 +6175,28 @@ function buildRunnerStatusQueryLookup({ route, routeState, selectedRecord }) {
|
|
|
5218
6175
|
}
|
|
5219
6176
|
: null,
|
|
5220
6177
|
related_active_request: relatedActiveRequest ? summarizeRunnerRequestForStatusLookup(relatedActiveRequest) : null,
|
|
6178
|
+
related_request: relatedRequest ? summarizeRunnerRequestForStatusLookup(relatedRequest) : null,
|
|
6179
|
+
root_work_item: String(safeObject(requestRootWorkItem).id || "").trim()
|
|
6180
|
+
? requestRootWorkItem
|
|
6181
|
+
: activeRootWorkItemID
|
|
6182
|
+
? {
|
|
6183
|
+
id: activeRootWorkItemID,
|
|
6184
|
+
title: activeRootWorkItemTitle,
|
|
6185
|
+
status: activeRootWorkItemStatus,
|
|
6186
|
+
}
|
|
6187
|
+
: currentConversationID && routeConversationID === currentConversationID && routeRootWorkItemID
|
|
6188
|
+
? {
|
|
6189
|
+
id: routeRootWorkItemID,
|
|
6190
|
+
title: routeRootWorkItemTitle,
|
|
6191
|
+
status: routeRootWorkItemStatus,
|
|
6192
|
+
}
|
|
6193
|
+
: null,
|
|
6194
|
+
route_work_items: currentConversationID && routeConversationID === currentConversationID && (routeWorkItemIDs.length > 0 || routeWorkItemTitles.length > 0)
|
|
6195
|
+
? {
|
|
6196
|
+
ids: routeWorkItemIDs,
|
|
6197
|
+
titles: routeWorkItemTitles,
|
|
6198
|
+
}
|
|
6199
|
+
: null,
|
|
5221
6200
|
last_route_result: {
|
|
5222
6201
|
action: lastAction,
|
|
5223
6202
|
reason: lastReason,
|
|
@@ -5278,6 +6257,13 @@ async function resolveInformationalQueryReply({
|
|
|
5278
6257
|
};
|
|
5279
6258
|
}
|
|
5280
6259
|
if (normalizedIntentType === "status_query") {
|
|
6260
|
+
let hydratedRunnerState = null;
|
|
6261
|
+
try {
|
|
6262
|
+
hydratedRunnerState = await hydrateRunnerRequestLedgerFromServer({
|
|
6263
|
+
normalizedRoute: route,
|
|
6264
|
+
runtime,
|
|
6265
|
+
});
|
|
6266
|
+
} catch {}
|
|
5281
6267
|
return {
|
|
5282
6268
|
handled: true,
|
|
5283
6269
|
source: "runner.status",
|
|
@@ -5287,6 +6273,7 @@ async function resolveInformationalQueryReply({
|
|
|
5287
6273
|
route,
|
|
5288
6274
|
routeState,
|
|
5289
6275
|
selectedRecord,
|
|
6276
|
+
runnerStateOverride: hydratedRunnerState,
|
|
5290
6277
|
}),
|
|
5291
6278
|
};
|
|
5292
6279
|
const activeExecution = resolveRunnerActiveExecutionState(routeState);
|
|
@@ -5427,14 +6414,18 @@ function emptyRunnerActiveExecutionPatch() {
|
|
|
5427
6414
|
active_request_key: "",
|
|
5428
6415
|
active_started_at: "",
|
|
5429
6416
|
active_heartbeat_at: "",
|
|
6417
|
+
active_root_work_item_id: "",
|
|
6418
|
+
active_root_work_item_title: "",
|
|
6419
|
+
active_root_work_item_status: "",
|
|
5430
6420
|
active_runner_pid: undefined,
|
|
5431
6421
|
active_execution_token: "",
|
|
5432
6422
|
};
|
|
5433
6423
|
}
|
|
5434
6424
|
|
|
5435
|
-
function buildRunnerActiveExecutionPatch(selectedRecord, requestKey = "") {
|
|
6425
|
+
function buildRunnerActiveExecutionPatch(selectedRecord, requestKey = "", rootWorkItem = {}) {
|
|
5436
6426
|
const nowISO = new Date().toISOString();
|
|
5437
6427
|
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
6428
|
+
const root = safeObject(rootWorkItem);
|
|
5438
6429
|
return {
|
|
5439
6430
|
active_comment_id: String(selectedRecord?.id || "").trim(),
|
|
5440
6431
|
active_comment_created_at: firstNonEmptyString([selectedRecord?.createdAt, selectedRecord?.updatedAt]),
|
|
@@ -5442,6 +6433,9 @@ function buildRunnerActiveExecutionPatch(selectedRecord, requestKey = "") {
|
|
|
5442
6433
|
active_request_key: String(requestKey || "").trim(),
|
|
5443
6434
|
active_started_at: nowISO,
|
|
5444
6435
|
active_heartbeat_at: nowISO,
|
|
6436
|
+
active_root_work_item_id: String(root.id || root.root_work_item_id || "").trim(),
|
|
6437
|
+
active_root_work_item_title: String(root.title || root.root_work_item_title || "").trim(),
|
|
6438
|
+
active_root_work_item_status: normalizeRunnerWorkItemStatus(root.status || root.root_work_item_status),
|
|
5445
6439
|
active_runner_pid: process.pid,
|
|
5446
6440
|
active_execution_token: `${Date.now()}-${process.pid}-${String(selectedRecord?.id || "").trim()}`,
|
|
5447
6441
|
};
|
|
@@ -5921,7 +6915,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
5921
6915
|
}
|
|
5922
6916
|
return null;
|
|
5923
6917
|
};
|
|
5924
|
-
const prepareRunnerRequestClaim = (selectedRecord, selectedResponderSelectors = []) => {
|
|
6918
|
+
const prepareRunnerRequestClaim = async (selectedRecord, selectedResponderSelectors = []) => {
|
|
5925
6919
|
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
5926
6920
|
const kind = String(parsed.kind || "").trim().toLowerCase();
|
|
5927
6921
|
if (kind === "bot_reply") {
|
|
@@ -5951,6 +6945,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
5951
6945
|
routeKey,
|
|
5952
6946
|
selectedRecord,
|
|
5953
6947
|
selectedBotUsernames: selectedResponderSelectors,
|
|
6948
|
+
runtime,
|
|
5954
6949
|
});
|
|
5955
6950
|
};
|
|
5956
6951
|
if (deferExecution) {
|
|
@@ -6037,7 +7032,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6037
7032
|
});
|
|
6038
7033
|
continue;
|
|
6039
7034
|
}
|
|
6040
|
-
const requestClaim = prepareRunnerRequestClaim(selectedRecord, adjudication.selected_bot_usernames);
|
|
7035
|
+
const requestClaim = await prepareRunnerRequestClaim(selectedRecord, adjudication.selected_bot_usernames);
|
|
6041
7036
|
if (!requestClaim.ok) {
|
|
6042
7037
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
6043
7038
|
normalizedRoute,
|
|
@@ -6062,9 +7057,64 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6062
7057
|
});
|
|
6063
7058
|
continue;
|
|
6064
7059
|
}
|
|
7060
|
+
const inheritedRootReference = await inheritRunnerReferenceRootWorkItemForRequest({
|
|
7061
|
+
normalizedRoute,
|
|
7062
|
+
selectedRecord,
|
|
7063
|
+
runtime,
|
|
7064
|
+
requestKey: requestClaim.requestKey,
|
|
7065
|
+
});
|
|
7066
|
+
const rootWorkItemClaim = await ensureRunnerRootWorkItemForRequest({
|
|
7067
|
+
normalizedRoute,
|
|
7068
|
+
routeKey,
|
|
7069
|
+
selectedRecord,
|
|
7070
|
+
runtime,
|
|
7071
|
+
requestKey: requestClaim.requestKey,
|
|
7072
|
+
});
|
|
7073
|
+
if (!rootWorkItemClaim.ok) {
|
|
7074
|
+
if (String(requestClaim.requestKey || "").trim()) {
|
|
7075
|
+
markRunnerRequestLifecycle({
|
|
7076
|
+
normalizedRoute,
|
|
7077
|
+
requestKey: requestClaim.requestKey,
|
|
7078
|
+
selectedRecord,
|
|
7079
|
+
routeKey,
|
|
7080
|
+
outcome: "closed",
|
|
7081
|
+
closedReason: String(rootWorkItemClaim.error || rootWorkItemClaim.reason || "root_work_item_create_failed").trim(),
|
|
7082
|
+
});
|
|
7083
|
+
await syncRunnerRequestLedgerForProjectToServer({
|
|
7084
|
+
normalizedRoute,
|
|
7085
|
+
runtime,
|
|
7086
|
+
});
|
|
7087
|
+
}
|
|
7088
|
+
saveRunnerRouteState(
|
|
7089
|
+
routeKey,
|
|
7090
|
+
buildRunnerRouteStateFromComment(selectedRecord, {
|
|
7091
|
+
last_action: "request_skipped",
|
|
7092
|
+
last_reason: String(rootWorkItemClaim.error || rootWorkItemClaim.reason || "root_work_item_create_failed").trim() || "root_work_item_create_failed",
|
|
7093
|
+
last_trigger: "work_item_root",
|
|
7094
|
+
last_request_key: String(requestClaim.requestKey || "").trim(),
|
|
7095
|
+
}),
|
|
7096
|
+
);
|
|
7097
|
+
skippedRecords.push({
|
|
7098
|
+
id: selectedRecord.id,
|
|
7099
|
+
reason: String(rootWorkItemClaim.error || rootWorkItemClaim.reason || "root_work_item_create_failed").trim() || "root_work_item_create_failed",
|
|
7100
|
+
messageID: intFromRawAllowZero(selectedRecord?.parsedArchive?.messageID, 0),
|
|
7101
|
+
diagnosticType: "skip",
|
|
7102
|
+
action: "skip_missing_root_work_item",
|
|
7103
|
+
closedReason: String(rootWorkItemClaim.reason || "").trim(),
|
|
7104
|
+
});
|
|
7105
|
+
continue;
|
|
7106
|
+
}
|
|
7107
|
+
const claimedRequest = safeObject(rootWorkItemClaim.request || inheritedRootReference.request || requestClaim.request);
|
|
6065
7108
|
saveRunnerRouteState(routeKey, {
|
|
6066
|
-
...buildRunnerActiveExecutionPatch(selectedRecord, requestClaim.requestKey
|
|
7109
|
+
...buildRunnerActiveExecutionPatch(selectedRecord, requestClaim.requestKey, {
|
|
7110
|
+
id: String(claimedRequest.root_work_item_id || "").trim(),
|
|
7111
|
+
title: String(claimedRequest.root_work_item_title || "").trim(),
|
|
7112
|
+
status: String(claimedRequest.root_work_item_status || "").trim(),
|
|
7113
|
+
}),
|
|
6067
7114
|
last_request_key: String(requestClaim.requestKey || "").trim(),
|
|
7115
|
+
last_root_work_item_id: String(claimedRequest.root_work_item_id || "").trim(),
|
|
7116
|
+
last_root_work_item_title: String(claimedRequest.root_work_item_title || "").trim(),
|
|
7117
|
+
last_root_work_item_status: String(claimedRequest.root_work_item_status || "").trim(),
|
|
6068
7118
|
});
|
|
6069
7119
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
6070
7120
|
normalizedRoute,
|
|
@@ -6211,7 +7261,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6211
7261
|
continue;
|
|
6212
7262
|
}
|
|
6213
7263
|
const currentRouteState = safeObject(loadBotRunnerState().routes[routeKey]);
|
|
6214
|
-
const requestClaim = prepareRunnerRequestClaim(selectedRecord, inlineAdjudication.selected_bot_usernames);
|
|
7264
|
+
const requestClaim = await prepareRunnerRequestClaim(selectedRecord, inlineAdjudication.selected_bot_usernames);
|
|
6215
7265
|
if (!requestClaim.ok) {
|
|
6216
7266
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
6217
7267
|
normalizedRoute,
|
|
@@ -6236,11 +7286,67 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6236
7286
|
});
|
|
6237
7287
|
continue;
|
|
6238
7288
|
}
|
|
7289
|
+
const inheritedRootReference = await inheritRunnerReferenceRootWorkItemForRequest({
|
|
7290
|
+
normalizedRoute,
|
|
7291
|
+
selectedRecord,
|
|
7292
|
+
runtime,
|
|
7293
|
+
requestKey: requestClaim.requestKey,
|
|
7294
|
+
});
|
|
7295
|
+
const rootWorkItemClaim = await ensureRunnerRootWorkItemForRequest({
|
|
7296
|
+
normalizedRoute,
|
|
7297
|
+
routeKey,
|
|
7298
|
+
selectedRecord,
|
|
7299
|
+
runtime,
|
|
7300
|
+
requestKey: requestClaim.requestKey,
|
|
7301
|
+
});
|
|
7302
|
+
if (!rootWorkItemClaim.ok) {
|
|
7303
|
+
if (String(requestClaim.requestKey || "").trim()) {
|
|
7304
|
+
markRunnerRequestLifecycle({
|
|
7305
|
+
normalizedRoute,
|
|
7306
|
+
requestKey: requestClaim.requestKey,
|
|
7307
|
+
selectedRecord,
|
|
7308
|
+
routeKey,
|
|
7309
|
+
outcome: "closed",
|
|
7310
|
+
closedReason: String(rootWorkItemClaim.error || rootWorkItemClaim.reason || "root_work_item_create_failed").trim(),
|
|
7311
|
+
});
|
|
7312
|
+
await syncRunnerRequestLedgerForProjectToServer({
|
|
7313
|
+
normalizedRoute,
|
|
7314
|
+
runtime,
|
|
7315
|
+
});
|
|
7316
|
+
}
|
|
7317
|
+
saveRunnerRouteState(
|
|
7318
|
+
routeKey,
|
|
7319
|
+
buildRunnerRouteStateFromComment(selectedRecord, {
|
|
7320
|
+
last_action: "request_skipped",
|
|
7321
|
+
last_reason: String(rootWorkItemClaim.error || rootWorkItemClaim.reason || "root_work_item_create_failed").trim() || "root_work_item_create_failed",
|
|
7322
|
+
last_trigger: "work_item_root",
|
|
7323
|
+
last_request_key: String(requestClaim.requestKey || "").trim(),
|
|
7324
|
+
}),
|
|
7325
|
+
);
|
|
7326
|
+
skippedRecords.push({
|
|
7327
|
+
id: selectedRecord.id,
|
|
7328
|
+
reason: String(rootWorkItemClaim.error || rootWorkItemClaim.reason || "root_work_item_create_failed").trim() || "root_work_item_create_failed",
|
|
7329
|
+
messageID: intFromRawAllowZero(selectedRecord?.parsedArchive?.messageID, 0),
|
|
7330
|
+
diagnosticType: "skip",
|
|
7331
|
+
action: "skip_missing_root_work_item",
|
|
7332
|
+
closedReason: String(rootWorkItemClaim.reason || "").trim(),
|
|
7333
|
+
});
|
|
7334
|
+
continue;
|
|
7335
|
+
}
|
|
7336
|
+
const claimedRequest = safeObject(rootWorkItemClaim.request || inheritedRootReference.request || requestClaim.request);
|
|
6239
7337
|
saveRunnerRouteState(routeKey, {
|
|
6240
|
-
|
|
7338
|
+
...buildRunnerActiveExecutionPatch(selectedRecord, requestClaim.requestKey, {
|
|
7339
|
+
id: String(claimedRequest.root_work_item_id || "").trim(),
|
|
7340
|
+
title: String(claimedRequest.root_work_item_title || "").trim(),
|
|
7341
|
+
status: String(claimedRequest.root_work_item_status || "").trim(),
|
|
7342
|
+
}),
|
|
6241
7343
|
last_request_key: String(requestClaim.requestKey || "").trim(),
|
|
7344
|
+
last_root_work_item_id: String(claimedRequest.root_work_item_id || "").trim(),
|
|
7345
|
+
last_root_work_item_title: String(claimedRequest.root_work_item_title || "").trim(),
|
|
7346
|
+
last_root_work_item_status: String(claimedRequest.root_work_item_status || "").trim(),
|
|
6242
7347
|
});
|
|
6243
7348
|
if (String(requestClaim.requestKey || "").trim()) {
|
|
7349
|
+
const resolvedIntentType = String(safeObject(loadBotRunnerState().routes[routeKey]).last_intent_type || "").trim();
|
|
6244
7350
|
markRunnerRequestLifecycle({
|
|
6245
7351
|
normalizedRoute,
|
|
6246
7352
|
requestKey: requestClaim.requestKey,
|
|
@@ -6248,6 +7354,22 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6248
7354
|
routeKey,
|
|
6249
7355
|
outcome: "running",
|
|
6250
7356
|
});
|
|
7357
|
+
const rootWorkItemSync = await syncRunnerRequestRootWorkItemForOutcome({
|
|
7358
|
+
normalizedRoute,
|
|
7359
|
+
runtime,
|
|
7360
|
+
requestKey: requestClaim.requestKey,
|
|
7361
|
+
});
|
|
7362
|
+
const syncedRequest = safeObject(rootWorkItemSync.request);
|
|
7363
|
+
if (String(syncedRequest.root_work_item_id || "").trim()) {
|
|
7364
|
+
saveRunnerRouteState(routeKey, {
|
|
7365
|
+
active_root_work_item_id: String(syncedRequest.root_work_item_id || "").trim(),
|
|
7366
|
+
active_root_work_item_title: String(syncedRequest.root_work_item_title || "").trim(),
|
|
7367
|
+
active_root_work_item_status: String(syncedRequest.root_work_item_status || "").trim(),
|
|
7368
|
+
last_root_work_item_id: String(syncedRequest.root_work_item_id || "").trim(),
|
|
7369
|
+
last_root_work_item_title: String(syncedRequest.root_work_item_title || "").trim(),
|
|
7370
|
+
last_root_work_item_status: String(syncedRequest.root_work_item_status || "").trim(),
|
|
7371
|
+
});
|
|
7372
|
+
}
|
|
6251
7373
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
6252
7374
|
normalizedRoute,
|
|
6253
7375
|
runtime,
|
|
@@ -6258,7 +7380,11 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6258
7380
|
normalizedRoute,
|
|
6259
7381
|
routeState: {
|
|
6260
7382
|
...currentRouteState,
|
|
6261
|
-
|
|
7383
|
+
...buildRunnerActiveExecutionPatch(selectedRecord, requestClaim.requestKey, {
|
|
7384
|
+
id: String(claimedRequest.root_work_item_id || "").trim(),
|
|
7385
|
+
title: String(claimedRequest.root_work_item_title || "").trim(),
|
|
7386
|
+
status: String(claimedRequest.root_work_item_status || "").trim(),
|
|
7387
|
+
}),
|
|
6262
7388
|
},
|
|
6263
7389
|
selectedRecord,
|
|
6264
7390
|
pendingOrdered: pending.ordered,
|
|
@@ -6293,6 +7419,19 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6293
7419
|
outcome: "skipped",
|
|
6294
7420
|
closedReason: String(processed.skippedRecord?.reason || "skipped").trim() || "skipped",
|
|
6295
7421
|
});
|
|
7422
|
+
const rootWorkItemSync = await syncRunnerRequestRootWorkItemForOutcome({
|
|
7423
|
+
normalizedRoute,
|
|
7424
|
+
runtime,
|
|
7425
|
+
requestKey: requestClaim.requestKey,
|
|
7426
|
+
});
|
|
7427
|
+
const syncedRequest = safeObject(rootWorkItemSync.request);
|
|
7428
|
+
if (String(syncedRequest.root_work_item_id || "").trim()) {
|
|
7429
|
+
saveRunnerRouteState(routeKey, {
|
|
7430
|
+
last_root_work_item_id: String(syncedRequest.root_work_item_id || "").trim(),
|
|
7431
|
+
last_root_work_item_title: String(syncedRequest.root_work_item_title || "").trim(),
|
|
7432
|
+
last_root_work_item_status: String(syncedRequest.root_work_item_status || "").trim(),
|
|
7433
|
+
});
|
|
7434
|
+
}
|
|
6296
7435
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
6297
7436
|
normalizedRoute,
|
|
6298
7437
|
runtime,
|
|
@@ -6302,6 +7441,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6302
7441
|
continue;
|
|
6303
7442
|
}
|
|
6304
7443
|
if (String(requestClaim.requestKey || "").trim()) {
|
|
7444
|
+
const resolvedIntentType = String(safeObject(loadBotRunnerState().routes[routeKey]).last_intent_type || "").trim();
|
|
6305
7445
|
markRunnerRequestLifecycle({
|
|
6306
7446
|
normalizedRoute,
|
|
6307
7447
|
requestKey: requestClaim.requestKey,
|
|
@@ -6315,8 +7455,28 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6315
7455
|
nextExpectedResponders: ensureArray(processed.result?.next_expected_responders),
|
|
6316
7456
|
currentBotSelector,
|
|
6317
7457
|
conversationIntentMode: String(processed.result?.conversation_intent_mode || "").trim(),
|
|
6318
|
-
normalizedIntent:
|
|
7458
|
+
normalizedIntent: resolvedIntentType,
|
|
7459
|
+
});
|
|
7460
|
+
await ensureRunnerRootWorkItemForRequest({
|
|
7461
|
+
normalizedRoute,
|
|
7462
|
+
routeKey,
|
|
7463
|
+
selectedRecord,
|
|
7464
|
+
runtime,
|
|
7465
|
+
requestKey: requestClaim.requestKey,
|
|
7466
|
+
});
|
|
7467
|
+
const rootWorkItemSync = await syncRunnerRequestRootWorkItemForOutcome({
|
|
7468
|
+
normalizedRoute,
|
|
7469
|
+
runtime,
|
|
7470
|
+
requestKey: requestClaim.requestKey,
|
|
6319
7471
|
});
|
|
7472
|
+
const syncedRequest = safeObject(rootWorkItemSync.request);
|
|
7473
|
+
if (String(syncedRequest.root_work_item_id || "").trim()) {
|
|
7474
|
+
saveRunnerRouteState(routeKey, {
|
|
7475
|
+
last_root_work_item_id: String(syncedRequest.root_work_item_id || "").trim(),
|
|
7476
|
+
last_root_work_item_title: String(syncedRequest.root_work_item_title || "").trim(),
|
|
7477
|
+
last_root_work_item_status: String(syncedRequest.root_work_item_status || "").trim(),
|
|
7478
|
+
});
|
|
7479
|
+
}
|
|
6320
7480
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
6321
7481
|
normalizedRoute,
|
|
6322
7482
|
runtime,
|
|
@@ -8008,7 +9168,7 @@ async function runRunnerStop(flags) {
|
|
|
8008
9168
|
}
|
|
8009
9169
|
process.stdout.write("Detached runner stop: OK\n");
|
|
8010
9170
|
process.stdout.write(`registry_file: ${payload.registry_file}\n`);
|
|
8011
|
-
for (const entry of stopped) {
|
|
9171
|
+
for (const entry of ensureArray(payload.stopped)) {
|
|
8012
9172
|
process.stdout.write(`stopped: ${entry.launch_id} pid=${entry.pid} routes=${entry.route_names.join(", ") || "-"}\n`);
|
|
8013
9173
|
}
|
|
8014
9174
|
}
|
|
@@ -8033,7 +9193,7 @@ async function runRunnerStartDetachedResolvedRoutes(routes, flags, sourceCommand
|
|
|
8033
9193
|
process.stdout.write(`Detached runner already running: launch_id=${existing.launch_id} pid=${existing.pid}${existing.log_file ? ` log_file=${existing.log_file}` : ""}\n`);
|
|
8034
9194
|
return payload;
|
|
8035
9195
|
}
|
|
8036
|
-
const launch = launchDetachedRunnerProcess(flags, routes, sourceCommand);
|
|
9196
|
+
const launch = await launchDetachedRunnerProcess(flags, routes, sourceCommand);
|
|
8037
9197
|
const nextLaunches = {
|
|
8038
9198
|
...safeObject(registry).launches,
|
|
8039
9199
|
[launch.launch_id]: launch,
|
|
@@ -8560,6 +9720,22 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
8560
9720
|
routeKey: deferredExecution.routeKey,
|
|
8561
9721
|
outcome: "running",
|
|
8562
9722
|
});
|
|
9723
|
+
const rootWorkItemSync = await syncRunnerRequestRootWorkItemForOutcome({
|
|
9724
|
+
normalizedRoute: deferredExecution.normalizedRoute,
|
|
9725
|
+
runtime: deferredExecution.runtime,
|
|
9726
|
+
requestKey: deferredExecution.requestKey,
|
|
9727
|
+
});
|
|
9728
|
+
const syncedRequest = safeObject(rootWorkItemSync.request);
|
|
9729
|
+
if (String(syncedRequest.root_work_item_id || "").trim()) {
|
|
9730
|
+
saveRunnerRouteState(deferredExecution.routeKey, {
|
|
9731
|
+
active_root_work_item_id: String(syncedRequest.root_work_item_id || "").trim(),
|
|
9732
|
+
active_root_work_item_title: String(syncedRequest.root_work_item_title || "").trim(),
|
|
9733
|
+
active_root_work_item_status: String(syncedRequest.root_work_item_status || "").trim(),
|
|
9734
|
+
last_root_work_item_id: String(syncedRequest.root_work_item_id || "").trim(),
|
|
9735
|
+
last_root_work_item_title: String(syncedRequest.root_work_item_title || "").trim(),
|
|
9736
|
+
last_root_work_item_status: String(syncedRequest.root_work_item_status || "").trim(),
|
|
9737
|
+
});
|
|
9738
|
+
}
|
|
8563
9739
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
8564
9740
|
normalizedRoute: deferredExecution.normalizedRoute,
|
|
8565
9741
|
runtime: deferredExecution.runtime,
|
|
@@ -8622,6 +9798,19 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
8622
9798
|
outcome: "skipped",
|
|
8623
9799
|
closedReason: String(processed.skippedRecord?.reason || "skipped").trim() || "skipped",
|
|
8624
9800
|
});
|
|
9801
|
+
const rootWorkItemSync = await syncRunnerRequestRootWorkItemForOutcome({
|
|
9802
|
+
normalizedRoute: deferredExecution.normalizedRoute,
|
|
9803
|
+
runtime: deferredExecution.runtime,
|
|
9804
|
+
requestKey: deferredExecution.requestKey,
|
|
9805
|
+
});
|
|
9806
|
+
const syncedRequest = safeObject(rootWorkItemSync.request);
|
|
9807
|
+
if (String(syncedRequest.root_work_item_id || "").trim()) {
|
|
9808
|
+
saveRunnerRouteState(deferredExecution.routeKey, {
|
|
9809
|
+
last_root_work_item_id: String(syncedRequest.root_work_item_id || "").trim(),
|
|
9810
|
+
last_root_work_item_title: String(syncedRequest.root_work_item_title || "").trim(),
|
|
9811
|
+
last_root_work_item_status: String(syncedRequest.root_work_item_status || "").trim(),
|
|
9812
|
+
});
|
|
9813
|
+
}
|
|
8625
9814
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
8626
9815
|
normalizedRoute: deferredExecution.normalizedRoute,
|
|
8627
9816
|
runtime: deferredExecution.runtime,
|
|
@@ -8642,6 +9831,9 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
8642
9831
|
};
|
|
8643
9832
|
}
|
|
8644
9833
|
if (String(deferredExecution.requestKey || "").trim()) {
|
|
9834
|
+
const resolvedIntentType = String(
|
|
9835
|
+
safeObject(loadBotRunnerState().routes[deferredExecution.routeKey]).last_intent_type || "",
|
|
9836
|
+
).trim();
|
|
8645
9837
|
markRunnerRequestLifecycle({
|
|
8646
9838
|
normalizedRoute: deferredExecution.normalizedRoute,
|
|
8647
9839
|
requestKey: deferredExecution.requestKey,
|
|
@@ -8657,8 +9849,28 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
8657
9849
|
deferredExecution.bot?.username || deferredExecution.bot?.name,
|
|
8658
9850
|
),
|
|
8659
9851
|
conversationIntentMode: String(processed.result?.conversation_intent_mode || "").trim(),
|
|
8660
|
-
normalizedIntent:
|
|
9852
|
+
normalizedIntent: resolvedIntentType,
|
|
9853
|
+
});
|
|
9854
|
+
await ensureRunnerRootWorkItemForRequest({
|
|
9855
|
+
normalizedRoute: deferredExecution.normalizedRoute,
|
|
9856
|
+
routeKey: deferredExecution.routeKey,
|
|
9857
|
+
selectedRecord: deferredExecution.selectedRecord,
|
|
9858
|
+
runtime: deferredExecution.runtime,
|
|
9859
|
+
requestKey: deferredExecution.requestKey,
|
|
9860
|
+
});
|
|
9861
|
+
const rootWorkItemSync = await syncRunnerRequestRootWorkItemForOutcome({
|
|
9862
|
+
normalizedRoute: deferredExecution.normalizedRoute,
|
|
9863
|
+
runtime: deferredExecution.runtime,
|
|
9864
|
+
requestKey: deferredExecution.requestKey,
|
|
8661
9865
|
});
|
|
9866
|
+
const syncedRequest = safeObject(rootWorkItemSync.request);
|
|
9867
|
+
if (String(syncedRequest.root_work_item_id || "").trim()) {
|
|
9868
|
+
saveRunnerRouteState(deferredExecution.routeKey, {
|
|
9869
|
+
last_root_work_item_id: String(syncedRequest.root_work_item_id || "").trim(),
|
|
9870
|
+
last_root_work_item_title: String(syncedRequest.root_work_item_title || "").trim(),
|
|
9871
|
+
last_root_work_item_status: String(syncedRequest.root_work_item_status || "").trim(),
|
|
9872
|
+
});
|
|
9873
|
+
}
|
|
8662
9874
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
8663
9875
|
normalizedRoute: deferredExecution.normalizedRoute,
|
|
8664
9876
|
runtime: deferredExecution.runtime,
|
|
@@ -8687,6 +9899,19 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
8687
9899
|
outcome: "error",
|
|
8688
9900
|
closedReason: errorText || "execution_error",
|
|
8689
9901
|
});
|
|
9902
|
+
const rootWorkItemSync = await syncRunnerRequestRootWorkItemForOutcome({
|
|
9903
|
+
normalizedRoute: deferredExecution.normalizedRoute,
|
|
9904
|
+
runtime: deferredExecution.runtime,
|
|
9905
|
+
requestKey: deferredExecution.requestKey,
|
|
9906
|
+
});
|
|
9907
|
+
const syncedRequest = safeObject(rootWorkItemSync.request);
|
|
9908
|
+
if (String(syncedRequest.root_work_item_id || "").trim()) {
|
|
9909
|
+
saveRunnerRouteState(deferredExecution.routeKey, {
|
|
9910
|
+
last_root_work_item_id: String(syncedRequest.root_work_item_id || "").trim(),
|
|
9911
|
+
last_root_work_item_title: String(syncedRequest.root_work_item_title || "").trim(),
|
|
9912
|
+
last_root_work_item_status: String(syncedRequest.root_work_item_status || "").trim(),
|
|
9913
|
+
});
|
|
9914
|
+
}
|
|
8690
9915
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
8691
9916
|
normalizedRoute: deferredExecution.normalizedRoute,
|
|
8692
9917
|
runtime: deferredExecution.runtime,
|
|
@@ -12966,6 +14191,8 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
12966
14191
|
runnerRouteLogicalSignature,
|
|
12967
14192
|
loadBotRunnerState,
|
|
12968
14193
|
saveBotRunnerState,
|
|
14194
|
+
mergeServerRunnerRequestLedgerIntoLocalState,
|
|
14195
|
+
buildRunnerStatusQueryLookup,
|
|
12969
14196
|
tryJsonParse,
|
|
12970
14197
|
safeObject,
|
|
12971
14198
|
normalizeRunnerTriggerPolicy,
|