metheus-governance-mcp-cli 0.2.197 → 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 +1092 -30
- package/lib/runner-data.mjs +129 -46
- package/lib/selftest-runner-scenarios.mjs +148 -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,
|
|
@@ -2409,6 +2432,67 @@ function findRunnerRequestsForMessageID(state, normalizedRoute, selectors = {})
|
|
|
2409
2432
|
));
|
|
2410
2433
|
}
|
|
2411
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
|
+
|
|
2412
2496
|
function resolveRunnerReplyChainConversationContext(state, normalizedRoute, selectedRecord) {
|
|
2413
2497
|
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2414
2498
|
const explicitConversationID = String(parsed.conversationID || "").trim();
|
|
@@ -2451,6 +2535,74 @@ function resolveRunnerReplyChainConversationContext(state, normalizedRoute, sele
|
|
|
2451
2535
|
};
|
|
2452
2536
|
}
|
|
2453
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
|
+
|
|
2454
2606
|
function upsertRunnerRequest(state, requestKey, patch = {}) {
|
|
2455
2607
|
const currentState = safeObject(state);
|
|
2456
2608
|
const requests = normalizeBotRunnerRequests(currentState.requests);
|
|
@@ -2490,12 +2642,13 @@ function upsertRunnerConsumedComment(state, commentIDRaw, patch = {}) {
|
|
|
2490
2642
|
};
|
|
2491
2643
|
}
|
|
2492
2644
|
|
|
2493
|
-
function claimRunnerRequestForHumanComment({
|
|
2645
|
+
async function claimRunnerRequestForHumanComment({
|
|
2494
2646
|
normalizedRoute,
|
|
2495
2647
|
routeKey,
|
|
2496
2648
|
selectedRecord,
|
|
2497
2649
|
selectedBotUsernames = [],
|
|
2498
2650
|
normalizedIntent = "",
|
|
2651
|
+
runtime = null,
|
|
2499
2652
|
}) {
|
|
2500
2653
|
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2501
2654
|
const commentKind = String(parsed.kind || "").trim().toLowerCase();
|
|
@@ -2512,16 +2665,23 @@ function claimRunnerRequestForHumanComment({
|
|
|
2512
2665
|
normalizedIntent,
|
|
2513
2666
|
});
|
|
2514
2667
|
const currentState = loadBotRunnerState();
|
|
2515
|
-
const
|
|
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);
|
|
2516
2676
|
const conversationID = String(parsed.conversationID || replyChainContext.conversationID || "").trim();
|
|
2517
|
-
let stateForClaim =
|
|
2677
|
+
let stateForClaim = safeObject(replyChainResolution.state);
|
|
2518
2678
|
if (
|
|
2519
|
-
|
|
2679
|
+
Object.keys(referencedRequest).length > 0
|
|
2520
2680
|
&& conversationID
|
|
2521
|
-
&& !String(
|
|
2522
|
-
&& String(
|
|
2681
|
+
&& !String(referencedRequest.conversation_id || "").trim()
|
|
2682
|
+
&& String(referencedRequest.request_key || "").trim()
|
|
2523
2683
|
) {
|
|
2524
|
-
const backfilled = upsertRunnerRequest(stateForClaim,
|
|
2684
|
+
const backfilled = upsertRunnerRequest(stateForClaim, referencedRequest.request_key, {
|
|
2525
2685
|
conversation_id: conversationID,
|
|
2526
2686
|
});
|
|
2527
2687
|
stateForClaim = {
|
|
@@ -2563,6 +2723,17 @@ function claimRunnerRequestForHumanComment({
|
|
|
2563
2723
|
status: "claimed",
|
|
2564
2724
|
claimed_by_route: String(routeKey || "").trim(),
|
|
2565
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(),
|
|
2566
2737
|
last_comment_id: String(selectedRecord?.id || "").trim(),
|
|
2567
2738
|
last_comment_kind: commentKind,
|
|
2568
2739
|
last_source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
@@ -2591,6 +2762,562 @@ function claimRunnerRequestForHumanComment({
|
|
|
2591
2762
|
};
|
|
2592
2763
|
}
|
|
2593
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({
|
|
3238
|
+
normalizedRoute,
|
|
3239
|
+
runtime,
|
|
3240
|
+
requestKey,
|
|
3241
|
+
}) {
|
|
3242
|
+
const key = String(requestKey || "").trim();
|
|
3243
|
+
if (!key) {
|
|
3244
|
+
return {
|
|
3245
|
+
ok: false,
|
|
3246
|
+
reason: "request_key_missing",
|
|
3247
|
+
};
|
|
3248
|
+
}
|
|
3249
|
+
const currentState = loadBotRunnerState();
|
|
3250
|
+
const request = safeObject(normalizeBotRunnerRequests(currentState.requests)[key]);
|
|
3251
|
+
const rootWorkItemID = String(request.root_work_item_id || "").trim();
|
|
3252
|
+
if (!rootWorkItemID) {
|
|
3253
|
+
return {
|
|
3254
|
+
ok: true,
|
|
3255
|
+
skipped: true,
|
|
3256
|
+
reason: "root_work_item_missing",
|
|
3257
|
+
request,
|
|
3258
|
+
};
|
|
3259
|
+
}
|
|
3260
|
+
const targetStatus = deriveRunnerRootWorkItemTargetStatus(request.status);
|
|
3261
|
+
if (!targetStatus || !runtime?.baseURL || !runtime?.token || !runtime?.actor?.user_id) {
|
|
3262
|
+
return {
|
|
3263
|
+
ok: true,
|
|
3264
|
+
skipped: true,
|
|
3265
|
+
reason: !targetStatus ? "no_target_status" : "governance_runtime_unavailable",
|
|
3266
|
+
request,
|
|
3267
|
+
};
|
|
3268
|
+
}
|
|
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,
|
|
3305
|
+
});
|
|
3306
|
+
saveBotRunnerState({
|
|
3307
|
+
routes: currentState.routes,
|
|
3308
|
+
sharedInboxes: currentState.sharedInboxes || currentState.shared_inboxes,
|
|
3309
|
+
excludedComments: currentState.excludedComments || currentState.excluded_comments,
|
|
3310
|
+
requests: nextRequests,
|
|
3311
|
+
consumedComments: currentState.consumedComments || currentState.consumed_comments,
|
|
3312
|
+
});
|
|
3313
|
+
return {
|
|
3314
|
+
ok: lastError === "",
|
|
3315
|
+
request: nextRequest,
|
|
3316
|
+
transitioned: transitions.length > 0 && lastError === "",
|
|
3317
|
+
error: lastError,
|
|
3318
|
+
};
|
|
3319
|
+
}
|
|
3320
|
+
|
|
2594
3321
|
function resolveRunnerContinuationRequestForBotReply({
|
|
2595
3322
|
normalizedRoute,
|
|
2596
3323
|
routeKey,
|
|
@@ -2918,6 +3645,30 @@ function runnerLedgerEntryMatchesProject(entryRaw, normalizedRoute, requestIndex
|
|
|
2918
3645
|
);
|
|
2919
3646
|
}
|
|
2920
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
|
+
|
|
2921
3672
|
function mergeServerRunnerRequestLedgerIntoLocalState(currentState, normalizedRoute, serverRequests = [], serverCommentStates = []) {
|
|
2922
3673
|
const state = safeObject(currentState);
|
|
2923
3674
|
const normalizedRequests = normalizeBotRunnerRequests(state.requests);
|
|
@@ -2941,7 +3692,12 @@ function mergeServerRunnerRequestLedgerIntoLocalState(currentState, normalizedRo
|
|
|
2941
3692
|
const request = safeObject(requestRaw);
|
|
2942
3693
|
const requestKey = String(request.request_key || request.requestKey || "").trim();
|
|
2943
3694
|
if (!requestKey) continue;
|
|
2944
|
-
|
|
3695
|
+
const localRequest = safeObject(normalizedRequests[requestKey]);
|
|
3696
|
+
nextRequests[requestKey] = normalizeBotRunnerRequests({
|
|
3697
|
+
[requestKey]: {
|
|
3698
|
+
...mergeRunnerRequestForServerHydration(localRequest, request),
|
|
3699
|
+
},
|
|
3700
|
+
})[requestKey];
|
|
2945
3701
|
}
|
|
2946
3702
|
|
|
2947
3703
|
const requestIndex = normalizeBotRunnerRequests(nextRequests);
|
|
@@ -4925,6 +5681,10 @@ async function createProjectWorkItem(params) {
|
|
|
4925
5681
|
return createProjectWorkItemImpl(params, buildRunnerDataDeps());
|
|
4926
5682
|
}
|
|
4927
5683
|
|
|
5684
|
+
async function transitionProjectWorkItem(params) {
|
|
5685
|
+
return transitionProjectWorkItemImpl(params, buildRunnerDataDeps());
|
|
5686
|
+
}
|
|
5687
|
+
|
|
4928
5688
|
async function createProjectEvidence(params) {
|
|
4929
5689
|
return createProjectEvidenceImpl(params, buildRunnerDataDeps());
|
|
4930
5690
|
}
|
|
@@ -4933,6 +5693,10 @@ async function createWorkItemThread(params) {
|
|
|
4933
5693
|
return createWorkItemThreadImpl(params, buildRunnerDataDeps());
|
|
4934
5694
|
}
|
|
4935
5695
|
|
|
5696
|
+
async function listWorkItemThreads(params) {
|
|
5697
|
+
return listWorkItemThreadsImpl(params, buildRunnerDataDeps());
|
|
5698
|
+
}
|
|
5699
|
+
|
|
4936
5700
|
async function linkWorkItemEvidence(params) {
|
|
4937
5701
|
return linkWorkItemEvidenceImpl(params, buildRunnerDataDeps());
|
|
4938
5702
|
}
|
|
@@ -5241,6 +6005,14 @@ function summarizeRunnerRequestForStatusLookup(entryRaw) {
|
|
|
5241
6005
|
selected_bot_usernames: ensureArray(entry.selected_bot_usernames)
|
|
5242
6006
|
.map((value) => normalizeTelegramMentionUsername(value))
|
|
5243
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,
|
|
5244
6016
|
};
|
|
5245
6017
|
}
|
|
5246
6018
|
|
|
@@ -5299,7 +6071,7 @@ function pickPreferredStatusLookupRequest(entries = []) {
|
|
|
5299
6071
|
return candidates[0];
|
|
5300
6072
|
}
|
|
5301
6073
|
|
|
5302
|
-
function buildRunnerStatusQueryLookup({ route, routeState, selectedRecord }) {
|
|
6074
|
+
function buildRunnerStatusQueryLookup({ route, routeState, selectedRecord, runnerStateOverride = null }) {
|
|
5303
6075
|
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
5304
6076
|
const currentMessageID = intFromRawAllowZero(parsed.messageID, 0);
|
|
5305
6077
|
const currentChatID = String(parsed.chatID || parsed.chatId || "").trim();
|
|
@@ -5318,18 +6090,23 @@ function buildRunnerStatusQueryLookup({ route, routeState, selectedRecord }) {
|
|
|
5318
6090
|
&& activeSourceMessageID > 0
|
|
5319
6091
|
&& currentMessageID === activeSourceMessageID
|
|
5320
6092
|
);
|
|
5321
|
-
let runnerState =
|
|
5322
|
-
|
|
5323
|
-
runnerState =
|
|
5324
|
-
|
|
6093
|
+
let runnerState = safeObject(runnerStateOverride);
|
|
6094
|
+
if (!Object.keys(runnerState).length) {
|
|
6095
|
+
runnerState = { requests: {} };
|
|
6096
|
+
try {
|
|
6097
|
+
runnerState = loadBotRunnerState();
|
|
6098
|
+
} catch {}
|
|
6099
|
+
}
|
|
5325
6100
|
const replyChainContext = resolveRunnerReplyChainConversationContext(runnerState, route, selectedRecord);
|
|
5326
6101
|
const currentConversationID = String(parsed.conversationID || replyChainContext.conversationID || "").trim();
|
|
6102
|
+
const activeRequestKey = String(safeObject(routeState).active_request_key || "").trim();
|
|
5327
6103
|
const requestMatchesCurrentRoute = (entry) => requestEligibleForStatusLookup(
|
|
5328
6104
|
entry,
|
|
5329
6105
|
routeKey,
|
|
5330
6106
|
selfBotUsername,
|
|
5331
6107
|
currentMessageID,
|
|
5332
6108
|
);
|
|
6109
|
+
const referencedRequestCandidate = safeObject(replyChainContext.referencedRequest);
|
|
5333
6110
|
let relatedActiveRequest = null;
|
|
5334
6111
|
let relatedRequest = null;
|
|
5335
6112
|
const selectors = currentConversationID
|
|
@@ -5339,20 +6116,43 @@ function buildRunnerStatusQueryLookup({ route, routeState, selectedRecord }) {
|
|
|
5339
6116
|
if (!scopedRequests.length && currentConversationID) {
|
|
5340
6117
|
scopedRequests = findRunnerRequestsForScope(runnerState, route, { chatID: currentChatID });
|
|
5341
6118
|
}
|
|
6119
|
+
if (!scopedRequests.length && activeRequestKey) {
|
|
6120
|
+
scopedRequests = findRunnerRequestsForScope(runnerState, route, { requestKey: activeRequestKey });
|
|
6121
|
+
}
|
|
5342
6122
|
const eligibleScopedRequests = scopedRequests.filter(requestMatchesCurrentRoute);
|
|
5343
|
-
|
|
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
|
|
5344
6134
|
.filter((entry) => isActiveRunnerRequestStatus(entry.status))[0] || null;
|
|
5345
|
-
relatedRequest = pickPreferredStatusLookupRequest(
|
|
5346
|
-
eligibleScopedRequests.length
|
|
5347
|
-
? eligibleScopedRequests
|
|
5348
|
-
: (replyChainContext.referencedRequest ? [replyChainContext.referencedRequest] : []).filter(requestMatchesCurrentRoute),
|
|
5349
|
-
);
|
|
6135
|
+
relatedRequest = pickPreferredStatusLookupRequest(statusLookupCandidates);
|
|
5350
6136
|
const lastAction = String(safeObject(routeState).last_action || "").trim();
|
|
5351
6137
|
const lastReason = String(safeObject(routeState).last_reason || "").trim();
|
|
5352
6138
|
const lastIntentType = String(safeObject(routeState).last_intent_type || "").trim();
|
|
5353
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);
|
|
5354
6146
|
const routeWorkItemIDs = ensureArray(safeObject(routeState).last_work_item_ids).map((item) => String(item || "").trim()).filter(Boolean);
|
|
5355
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;
|
|
5356
6156
|
return {
|
|
5357
6157
|
kind: "runner_status",
|
|
5358
6158
|
status: (!selfBusyFiltered && activeExecution.active) || relatedActiveRequest
|
|
@@ -5376,6 +6176,21 @@ function buildRunnerStatusQueryLookup({ route, routeState, selectedRecord }) {
|
|
|
5376
6176
|
: null,
|
|
5377
6177
|
related_active_request: relatedActiveRequest ? summarizeRunnerRequestForStatusLookup(relatedActiveRequest) : null,
|
|
5378
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,
|
|
5379
6194
|
route_work_items: currentConversationID && routeConversationID === currentConversationID && (routeWorkItemIDs.length > 0 || routeWorkItemTitles.length > 0)
|
|
5380
6195
|
? {
|
|
5381
6196
|
ids: routeWorkItemIDs,
|
|
@@ -5442,6 +6257,13 @@ async function resolveInformationalQueryReply({
|
|
|
5442
6257
|
};
|
|
5443
6258
|
}
|
|
5444
6259
|
if (normalizedIntentType === "status_query") {
|
|
6260
|
+
let hydratedRunnerState = null;
|
|
6261
|
+
try {
|
|
6262
|
+
hydratedRunnerState = await hydrateRunnerRequestLedgerFromServer({
|
|
6263
|
+
normalizedRoute: route,
|
|
6264
|
+
runtime,
|
|
6265
|
+
});
|
|
6266
|
+
} catch {}
|
|
5445
6267
|
return {
|
|
5446
6268
|
handled: true,
|
|
5447
6269
|
source: "runner.status",
|
|
@@ -5451,6 +6273,7 @@ async function resolveInformationalQueryReply({
|
|
|
5451
6273
|
route,
|
|
5452
6274
|
routeState,
|
|
5453
6275
|
selectedRecord,
|
|
6276
|
+
runnerStateOverride: hydratedRunnerState,
|
|
5454
6277
|
}),
|
|
5455
6278
|
};
|
|
5456
6279
|
const activeExecution = resolveRunnerActiveExecutionState(routeState);
|
|
@@ -5591,14 +6414,18 @@ function emptyRunnerActiveExecutionPatch() {
|
|
|
5591
6414
|
active_request_key: "",
|
|
5592
6415
|
active_started_at: "",
|
|
5593
6416
|
active_heartbeat_at: "",
|
|
6417
|
+
active_root_work_item_id: "",
|
|
6418
|
+
active_root_work_item_title: "",
|
|
6419
|
+
active_root_work_item_status: "",
|
|
5594
6420
|
active_runner_pid: undefined,
|
|
5595
6421
|
active_execution_token: "",
|
|
5596
6422
|
};
|
|
5597
6423
|
}
|
|
5598
6424
|
|
|
5599
|
-
function buildRunnerActiveExecutionPatch(selectedRecord, requestKey = "") {
|
|
6425
|
+
function buildRunnerActiveExecutionPatch(selectedRecord, requestKey = "", rootWorkItem = {}) {
|
|
5600
6426
|
const nowISO = new Date().toISOString();
|
|
5601
6427
|
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
6428
|
+
const root = safeObject(rootWorkItem);
|
|
5602
6429
|
return {
|
|
5603
6430
|
active_comment_id: String(selectedRecord?.id || "").trim(),
|
|
5604
6431
|
active_comment_created_at: firstNonEmptyString([selectedRecord?.createdAt, selectedRecord?.updatedAt]),
|
|
@@ -5606,6 +6433,9 @@ function buildRunnerActiveExecutionPatch(selectedRecord, requestKey = "") {
|
|
|
5606
6433
|
active_request_key: String(requestKey || "").trim(),
|
|
5607
6434
|
active_started_at: nowISO,
|
|
5608
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),
|
|
5609
6439
|
active_runner_pid: process.pid,
|
|
5610
6440
|
active_execution_token: `${Date.now()}-${process.pid}-${String(selectedRecord?.id || "").trim()}`,
|
|
5611
6441
|
};
|
|
@@ -6085,7 +6915,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6085
6915
|
}
|
|
6086
6916
|
return null;
|
|
6087
6917
|
};
|
|
6088
|
-
const prepareRunnerRequestClaim = (selectedRecord, selectedResponderSelectors = []) => {
|
|
6918
|
+
const prepareRunnerRequestClaim = async (selectedRecord, selectedResponderSelectors = []) => {
|
|
6089
6919
|
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
6090
6920
|
const kind = String(parsed.kind || "").trim().toLowerCase();
|
|
6091
6921
|
if (kind === "bot_reply") {
|
|
@@ -6115,6 +6945,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6115
6945
|
routeKey,
|
|
6116
6946
|
selectedRecord,
|
|
6117
6947
|
selectedBotUsernames: selectedResponderSelectors,
|
|
6948
|
+
runtime,
|
|
6118
6949
|
});
|
|
6119
6950
|
};
|
|
6120
6951
|
if (deferExecution) {
|
|
@@ -6201,7 +7032,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6201
7032
|
});
|
|
6202
7033
|
continue;
|
|
6203
7034
|
}
|
|
6204
|
-
const requestClaim = prepareRunnerRequestClaim(selectedRecord, adjudication.selected_bot_usernames);
|
|
7035
|
+
const requestClaim = await prepareRunnerRequestClaim(selectedRecord, adjudication.selected_bot_usernames);
|
|
6205
7036
|
if (!requestClaim.ok) {
|
|
6206
7037
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
6207
7038
|
normalizedRoute,
|
|
@@ -6226,9 +7057,64 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6226
7057
|
});
|
|
6227
7058
|
continue;
|
|
6228
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);
|
|
6229
7108
|
saveRunnerRouteState(routeKey, {
|
|
6230
|
-
...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
|
+
}),
|
|
6231
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(),
|
|
6232
7118
|
});
|
|
6233
7119
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
6234
7120
|
normalizedRoute,
|
|
@@ -6375,7 +7261,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6375
7261
|
continue;
|
|
6376
7262
|
}
|
|
6377
7263
|
const currentRouteState = safeObject(loadBotRunnerState().routes[routeKey]);
|
|
6378
|
-
const requestClaim = prepareRunnerRequestClaim(selectedRecord, inlineAdjudication.selected_bot_usernames);
|
|
7264
|
+
const requestClaim = await prepareRunnerRequestClaim(selectedRecord, inlineAdjudication.selected_bot_usernames);
|
|
6379
7265
|
if (!requestClaim.ok) {
|
|
6380
7266
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
6381
7267
|
normalizedRoute,
|
|
@@ -6400,11 +7286,67 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6400
7286
|
});
|
|
6401
7287
|
continue;
|
|
6402
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);
|
|
6403
7337
|
saveRunnerRouteState(routeKey, {
|
|
6404
|
-
|
|
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
|
+
}),
|
|
6405
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(),
|
|
6406
7347
|
});
|
|
6407
7348
|
if (String(requestClaim.requestKey || "").trim()) {
|
|
7349
|
+
const resolvedIntentType = String(safeObject(loadBotRunnerState().routes[routeKey]).last_intent_type || "").trim();
|
|
6408
7350
|
markRunnerRequestLifecycle({
|
|
6409
7351
|
normalizedRoute,
|
|
6410
7352
|
requestKey: requestClaim.requestKey,
|
|
@@ -6412,6 +7354,22 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6412
7354
|
routeKey,
|
|
6413
7355
|
outcome: "running",
|
|
6414
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
|
+
}
|
|
6415
7373
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
6416
7374
|
normalizedRoute,
|
|
6417
7375
|
runtime,
|
|
@@ -6422,7 +7380,11 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6422
7380
|
normalizedRoute,
|
|
6423
7381
|
routeState: {
|
|
6424
7382
|
...currentRouteState,
|
|
6425
|
-
|
|
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
|
+
}),
|
|
6426
7388
|
},
|
|
6427
7389
|
selectedRecord,
|
|
6428
7390
|
pendingOrdered: pending.ordered,
|
|
@@ -6457,6 +7419,19 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6457
7419
|
outcome: "skipped",
|
|
6458
7420
|
closedReason: String(processed.skippedRecord?.reason || "skipped").trim() || "skipped",
|
|
6459
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
|
+
}
|
|
6460
7435
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
6461
7436
|
normalizedRoute,
|
|
6462
7437
|
runtime,
|
|
@@ -6466,6 +7441,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6466
7441
|
continue;
|
|
6467
7442
|
}
|
|
6468
7443
|
if (String(requestClaim.requestKey || "").trim()) {
|
|
7444
|
+
const resolvedIntentType = String(safeObject(loadBotRunnerState().routes[routeKey]).last_intent_type || "").trim();
|
|
6469
7445
|
markRunnerRequestLifecycle({
|
|
6470
7446
|
normalizedRoute,
|
|
6471
7447
|
requestKey: requestClaim.requestKey,
|
|
@@ -6479,8 +7455,28 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
6479
7455
|
nextExpectedResponders: ensureArray(processed.result?.next_expected_responders),
|
|
6480
7456
|
currentBotSelector,
|
|
6481
7457
|
conversationIntentMode: String(processed.result?.conversation_intent_mode || "").trim(),
|
|
6482
|
-
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,
|
|
6483
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
|
+
}
|
|
6484
7480
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
6485
7481
|
normalizedRoute,
|
|
6486
7482
|
runtime,
|
|
@@ -8172,7 +9168,7 @@ async function runRunnerStop(flags) {
|
|
|
8172
9168
|
}
|
|
8173
9169
|
process.stdout.write("Detached runner stop: OK\n");
|
|
8174
9170
|
process.stdout.write(`registry_file: ${payload.registry_file}\n`);
|
|
8175
|
-
for (const entry of stopped) {
|
|
9171
|
+
for (const entry of ensureArray(payload.stopped)) {
|
|
8176
9172
|
process.stdout.write(`stopped: ${entry.launch_id} pid=${entry.pid} routes=${entry.route_names.join(", ") || "-"}\n`);
|
|
8177
9173
|
}
|
|
8178
9174
|
}
|
|
@@ -8197,7 +9193,7 @@ async function runRunnerStartDetachedResolvedRoutes(routes, flags, sourceCommand
|
|
|
8197
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`);
|
|
8198
9194
|
return payload;
|
|
8199
9195
|
}
|
|
8200
|
-
const launch = launchDetachedRunnerProcess(flags, routes, sourceCommand);
|
|
9196
|
+
const launch = await launchDetachedRunnerProcess(flags, routes, sourceCommand);
|
|
8201
9197
|
const nextLaunches = {
|
|
8202
9198
|
...safeObject(registry).launches,
|
|
8203
9199
|
[launch.launch_id]: launch,
|
|
@@ -8724,6 +9720,22 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
8724
9720
|
routeKey: deferredExecution.routeKey,
|
|
8725
9721
|
outcome: "running",
|
|
8726
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
|
+
}
|
|
8727
9739
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
8728
9740
|
normalizedRoute: deferredExecution.normalizedRoute,
|
|
8729
9741
|
runtime: deferredExecution.runtime,
|
|
@@ -8786,6 +9798,19 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
8786
9798
|
outcome: "skipped",
|
|
8787
9799
|
closedReason: String(processed.skippedRecord?.reason || "skipped").trim() || "skipped",
|
|
8788
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
|
+
}
|
|
8789
9814
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
8790
9815
|
normalizedRoute: deferredExecution.normalizedRoute,
|
|
8791
9816
|
runtime: deferredExecution.runtime,
|
|
@@ -8806,6 +9831,9 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
8806
9831
|
};
|
|
8807
9832
|
}
|
|
8808
9833
|
if (String(deferredExecution.requestKey || "").trim()) {
|
|
9834
|
+
const resolvedIntentType = String(
|
|
9835
|
+
safeObject(loadBotRunnerState().routes[deferredExecution.routeKey]).last_intent_type || "",
|
|
9836
|
+
).trim();
|
|
8809
9837
|
markRunnerRequestLifecycle({
|
|
8810
9838
|
normalizedRoute: deferredExecution.normalizedRoute,
|
|
8811
9839
|
requestKey: deferredExecution.requestKey,
|
|
@@ -8821,8 +9849,28 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
8821
9849
|
deferredExecution.bot?.username || deferredExecution.bot?.name,
|
|
8822
9850
|
),
|
|
8823
9851
|
conversationIntentMode: String(processed.result?.conversation_intent_mode || "").trim(),
|
|
8824
|
-
normalizedIntent:
|
|
9852
|
+
normalizedIntent: resolvedIntentType,
|
|
8825
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,
|
|
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
|
+
}
|
|
8826
9874
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
8827
9875
|
normalizedRoute: deferredExecution.normalizedRoute,
|
|
8828
9876
|
runtime: deferredExecution.runtime,
|
|
@@ -8851,6 +9899,19 @@ async function runRunnerStartResolvedRoutes(routes, flags, options = {}) {
|
|
|
8851
9899
|
outcome: "error",
|
|
8852
9900
|
closedReason: errorText || "execution_error",
|
|
8853
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
|
+
}
|
|
8854
9915
|
await syncRunnerRequestLedgerForProjectToServer({
|
|
8855
9916
|
normalizedRoute: deferredExecution.normalizedRoute,
|
|
8856
9917
|
runtime: deferredExecution.runtime,
|
|
@@ -13130,6 +14191,7 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
13130
14191
|
runnerRouteLogicalSignature,
|
|
13131
14192
|
loadBotRunnerState,
|
|
13132
14193
|
saveBotRunnerState,
|
|
14194
|
+
mergeServerRunnerRequestLedgerIntoLocalState,
|
|
13133
14195
|
buildRunnerStatusQueryLookup,
|
|
13134
14196
|
tryJsonParse,
|
|
13135
14197
|
safeObject,
|