metheus-governance-mcp-cli 0.2.282 → 0.2.284
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 +65 -39
- package/lib/local-ai-adapters.mjs +34 -3
- package/lib/runner-delivery-archive-handoff.mjs +42 -4
- package/lib/runner-helpers.mjs +21 -0
- package/lib/runner-orchestration-failure.mjs +14 -2
- package/lib/runner-orchestration-selected-record-preparation.mjs +17 -2
- package/lib/runner-orchestration-selected-record-reply-outcome.mjs +22 -6
- package/lib/runner-recorder-failure-delivery-handoff.mjs +2 -8
- package/lib/runner-recorder-failure-delivery-outcome-handoff.mjs +36 -8
- package/lib/runner-recorder-lifecycle-handoff.mjs +5 -0
- package/lib/selftest-bot-commands.mjs +14 -1
- package/lib/selftest-runner-scenarios.mjs +644 -2
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -25,9 +25,10 @@ import {
|
|
|
25
25
|
resolveRolePlannerAuditorModelDisplayName,
|
|
26
26
|
resolveRolePlannerModelDisplayName,
|
|
27
27
|
resolveRolePlannerRepairModelDisplayName,
|
|
28
|
-
resolveResponderAdjudicatorModelDisplayName,
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
resolveResponderAdjudicatorModelDisplayName,
|
|
29
|
+
resolveGeminiHeadlessExecutionModel,
|
|
30
|
+
resolveGeminiReasoningConfig,
|
|
31
|
+
suggestLocalAIModelDisplayName,
|
|
31
32
|
SUPPORTED_LOCAL_AI_CLIENTS,
|
|
32
33
|
normalizeLocalAIClientName,
|
|
33
34
|
normalizeLocalAIPermissionMode,
|
|
@@ -144,10 +145,10 @@ import {
|
|
|
144
145
|
findEarlierProcessableArchiveDuplicate,
|
|
145
146
|
findRecentTelegramMessageEnvelope,
|
|
146
147
|
isTelegramLocalInboundEnvelopeForRoute,
|
|
147
|
-
isInboundArchiveKind,
|
|
148
|
-
normalizeTelegramMessageEnvelope as normalizeRunnerTelegramMessageEnvelope,
|
|
149
|
-
normalizeArchiveCommentRecord,
|
|
150
|
-
selectPendingArchiveComments,
|
|
148
|
+
isInboundArchiveKind,
|
|
149
|
+
normalizeTelegramMessageEnvelope as normalizeRunnerTelegramMessageEnvelope,
|
|
150
|
+
normalizeArchiveCommentRecord,
|
|
151
|
+
selectPendingArchiveComments,
|
|
151
152
|
printRunnerResult,
|
|
152
153
|
} from "./lib/runner-helpers.mjs";
|
|
153
154
|
import {
|
|
@@ -4144,12 +4145,14 @@ function buildRunnerValidationAndDeliverySummary({
|
|
|
4144
4145
|
responseContractValidationStatus = "",
|
|
4145
4146
|
responseContractValidationReason = "",
|
|
4146
4147
|
responseContractValidationTargets = [],
|
|
4147
|
-
assignmentValidationStatus = "",
|
|
4148
|
-
assignmentValidationReason = "",
|
|
4149
|
-
assignmentValidationModes = [],
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4148
|
+
assignmentValidationStatus = "",
|
|
4149
|
+
assignmentValidationReason = "",
|
|
4150
|
+
assignmentValidationModes = [],
|
|
4151
|
+
failureReplyClassification = "",
|
|
4152
|
+
failureFacts = {},
|
|
4153
|
+
deliveryStatus = "",
|
|
4154
|
+
archiveStatus = "",
|
|
4155
|
+
transportError = "",
|
|
4153
4156
|
archiveError = "",
|
|
4154
4157
|
sourceMessageEnvelope = {},
|
|
4155
4158
|
lastReplyMessageEnvelope = {},
|
|
@@ -6381,12 +6384,14 @@ function markRunnerRequestLifecycle({
|
|
|
6381
6384
|
responseContractValidationStatus = "",
|
|
6382
6385
|
responseContractValidationReason = "",
|
|
6383
6386
|
responseContractValidationTargets = [],
|
|
6384
|
-
assignmentValidationStatus = "",
|
|
6385
|
-
assignmentValidationReason = "",
|
|
6386
|
-
assignmentValidationModes = [],
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6387
|
+
assignmentValidationStatus = "",
|
|
6388
|
+
assignmentValidationReason = "",
|
|
6389
|
+
assignmentValidationModes = [],
|
|
6390
|
+
failureReplyClassification = "",
|
|
6391
|
+
failureFacts = {},
|
|
6392
|
+
deliveryStatus = "",
|
|
6393
|
+
archiveStatus = "",
|
|
6394
|
+
transportError = "",
|
|
6390
6395
|
archiveError = "",
|
|
6391
6396
|
lastReplyMessageID = 0,
|
|
6392
6397
|
lastReplyMessageThreadID = 0,
|
|
@@ -6425,10 +6430,7 @@ function markRunnerRequestLifecycle({
|
|
|
6425
6430
|
const authoritativeDecisionBundle = resolvedDecisionBundleValidation.ok === true
|
|
6426
6431
|
? safeObject(resolvedDecisionBundleValidation.bundle)
|
|
6427
6432
|
: runnerRequestAuthoritativeDecisionBundle(existing);
|
|
6428
|
-
const effectiveReplyToMessageID = intFromRawAllowZero(
|
|
6429
|
-
replyToMessageID,
|
|
6430
|
-
intFromRawAllowZero(existing.last_reply_to_message_id, 0),
|
|
6431
|
-
);
|
|
6433
|
+
const effectiveReplyToMessageID = intFromRawAllowZero(replyToMessageID, 0);
|
|
6432
6434
|
const lastReplyMessageEnvelope = buildTelegramBotReplyEnvelope({
|
|
6433
6435
|
sourceEnvelope: sourceMessageEnvelope,
|
|
6434
6436
|
chatID: existing.chat_id,
|
|
@@ -6449,14 +6451,14 @@ function markRunnerRequestLifecycle({
|
|
|
6449
6451
|
senderUsername: normalizedCurrentBotSelector,
|
|
6450
6452
|
body: aiReplyPreview,
|
|
6451
6453
|
});
|
|
6452
|
-
const shouldRefreshAttemptedDeliveryEnvelope = (
|
|
6453
|
-
aiReplyGenerated === true
|
|
6454
|
-
|| String(aiReplyPreview || "").trim().length > 0
|
|
6455
|
-
|| String(deliveryStatus || "").trim().length > 0
|
|
6456
|
-
|| String(transportError || "").trim().length > 0
|
|
6457
|
-
|| intFromRawAllowZero(replyToMessageID, 0) > 0
|
|
6458
|
-
|| intFromRawAllowZero(lastReplyMessageThreadID, 0) > 0
|
|
6459
|
-
);
|
|
6454
|
+
const shouldRefreshAttemptedDeliveryEnvelope = (
|
|
6455
|
+
aiReplyGenerated === true
|
|
6456
|
+
|| String(aiReplyPreview || "").trim().length > 0
|
|
6457
|
+
|| String(deliveryStatus || "").trim().length > 0
|
|
6458
|
+
|| String(transportError || "").trim().length > 0
|
|
6459
|
+
|| intFromRawAllowZero(replyToMessageID, 0) > 0
|
|
6460
|
+
|| intFromRawAllowZero(lastReplyMessageThreadID, 0) > 0
|
|
6461
|
+
);
|
|
6460
6462
|
const rootEffectiveExecutionContractTargets = uniqueOrderedStrings(
|
|
6461
6463
|
[
|
|
6462
6464
|
...ensureArray(authoritativeDecisionBundle.execution_contract_targets),
|
|
@@ -6497,6 +6499,15 @@ function markRunnerRequestLifecycle({
|
|
|
6497
6499
|
|| "",
|
|
6498
6500
|
).trim().toLowerCase();
|
|
6499
6501
|
const normalizedOutcome = String(outcome || "").trim().toLowerCase();
|
|
6502
|
+
const normalizedFailureReplyClassification = String(failureReplyClassification || "").trim().toLowerCase();
|
|
6503
|
+
const normalizedFailureFacts = safeObject(failureFacts);
|
|
6504
|
+
const shouldPersistReplyAnchor = (
|
|
6505
|
+
aiReplyGenerated === true
|
|
6506
|
+
|| intFromRawAllowZero(lastReplyMessageID, 0) > 0
|
|
6507
|
+
|| ["delivered", "dry_run", "archive_error", "failed_transport"].includes(normalizedDeliveryStatus)
|
|
6508
|
+
|| String(transportError || "").trim().length > 0
|
|
6509
|
+
|| ["replied", "delivery_failed_after_generation"].includes(normalizedOutcome)
|
|
6510
|
+
);
|
|
6500
6511
|
const shouldRemainRunningAfterReply = authoritativeDecisionBundle.should_close_after_reply === true
|
|
6501
6512
|
? false
|
|
6502
6513
|
: authoritativeDecisionBundle.should_close_after_reply === false
|
|
@@ -6511,6 +6522,18 @@ function markRunnerRequestLifecycle({
|
|
|
6511
6522
|
|| rootEffectiveNextExpectedResponders.length > 0
|
|
6512
6523
|
|| continuationSelectors.length > 0
|
|
6513
6524
|
);
|
|
6525
|
+
const shouldRemainRunningAfterError = ["error", "execution_failed"].includes(normalizedOutcome)
|
|
6526
|
+
&& (
|
|
6527
|
+
normalizedFailureFacts.retryable === true
|
|
6528
|
+
|| normalizedFailureReplyClassification === "retryable_failure"
|
|
6529
|
+
)
|
|
6530
|
+
&& authoritativeDecisionBundle.should_close_after_reply !== true
|
|
6531
|
+
&& (
|
|
6532
|
+
nextExecutionContractType === "delegation"
|
|
6533
|
+
|| rootEffectiveExecutionContractTargets.length > 0
|
|
6534
|
+
|| rootEffectiveNextExpectedResponders.length > 0
|
|
6535
|
+
|| continuationSelectors.length > 0
|
|
6536
|
+
);
|
|
6514
6537
|
const nextConversationIntentMode = String(
|
|
6515
6538
|
authoritativeDecisionBundle.conversation_intent_mode
|
|
6516
6539
|
|| conversationIntentMode
|
|
@@ -6547,7 +6570,7 @@ function markRunnerRequestLifecycle({
|
|
|
6547
6570
|
|| normalizedOutcome === "execution_failed"
|
|
6548
6571
|
|| normalizedOutcome === "policy_violation"
|
|
6549
6572
|
) {
|
|
6550
|
-
return "closed";
|
|
6573
|
+
return shouldRemainRunningAfterError ? "running" : "closed";
|
|
6551
6574
|
}
|
|
6552
6575
|
return normalizeRunnerRequestStatus(existing.status);
|
|
6553
6576
|
})();
|
|
@@ -6888,7 +6911,9 @@ function markRunnerRequestLifecycle({
|
|
|
6888
6911
|
last_source_message_thread_id: intFromRawAllowZero(parsed.messageThreadID, 0) || existing.last_source_message_thread_id,
|
|
6889
6912
|
last_reply_message_id: intFromRawAllowZero(lastReplyMessageID, 0) || existing.last_reply_message_id,
|
|
6890
6913
|
last_reply_message_thread_id: intFromRawAllowZero(lastReplyMessageThreadID, 0) || existing.last_reply_message_thread_id,
|
|
6891
|
-
last_reply_to_message_id:
|
|
6914
|
+
last_reply_to_message_id: shouldPersistReplyAnchor
|
|
6915
|
+
? effectiveReplyToMessageID
|
|
6916
|
+
: existing.last_reply_to_message_id,
|
|
6892
6917
|
last_reply_message_envelope: persistSuccessfulReplyEnvelope
|
|
6893
6918
|
? lastReplyMessageEnvelope
|
|
6894
6919
|
: safeObject(existing.last_reply_message_envelope),
|
|
@@ -19554,12 +19579,13 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
19554
19579
|
push("runner_tui_frame_renders_route_statuses", false, String(err?.message || err));
|
|
19555
19580
|
}
|
|
19556
19581
|
|
|
19557
|
-
await runSelftestBotCommands(push, {
|
|
19558
|
-
cliPath: fileURLToPath(import.meta.url),
|
|
19559
|
-
parseSimpleEnvText,
|
|
19560
|
-
resolveLocalAIExecutionModel,
|
|
19561
|
-
|
|
19562
|
-
|
|
19582
|
+
await runSelftestBotCommands(push, {
|
|
19583
|
+
cliPath: fileURLToPath(import.meta.url),
|
|
19584
|
+
parseSimpleEnvText,
|
|
19585
|
+
resolveLocalAIExecutionModel,
|
|
19586
|
+
resolveGeminiHeadlessExecutionModel,
|
|
19587
|
+
suggestLocalAIModelDisplayName,
|
|
19588
|
+
resolveGeminiReasoningConfig,
|
|
19563
19589
|
stripLocalOnlyToolArgs: (requestObj, toolName) =>
|
|
19564
19590
|
stripLocalOnlyToolArgs(requestObj, toolName),
|
|
19565
19591
|
applyProxyResponsePatches: (params, deps = buildProxyResponsePipelineDeps()) =>
|
|
@@ -19,6 +19,7 @@ const GEMINI_HOME_SYNC_FILES = [
|
|
|
19
19
|
];
|
|
20
20
|
const GEMINI_STDIN_BRIDGE_PROMPT = "Use the full task provided on standard input as the authoritative prompt. Follow it exactly and output only the final answer.";
|
|
21
21
|
const GEMINI_CLI_TIMEOUT_MS = 90 * 1000;
|
|
22
|
+
const GEMINI_RUNNER_STABLE_EXECUTION_MODEL = "gemini-3-flash-preview";
|
|
22
23
|
const LOCAL_AI_MODEL_MAPPINGS = {
|
|
23
24
|
gpt: [
|
|
24
25
|
{
|
|
@@ -880,7 +881,12 @@ function runLocalAIPromptRawText({
|
|
|
880
881
|
const normalizedClient = normalizeLocalAIClientName(client);
|
|
881
882
|
const normalizedPermissionMode = normalizeLocalAIPermissionMode(permissionMode);
|
|
882
883
|
const normalizedReasoningEffort = normalizeLocalAIReasoningEffort(reasoningEffort, "low");
|
|
883
|
-
const resolvedExecutionModel =
|
|
884
|
+
const resolvedExecutionModel = normalizedClient === "gemini"
|
|
885
|
+
? resolveGeminiHeadlessExecutionModel(model, {
|
|
886
|
+
permissionMode: normalizedPermissionMode,
|
|
887
|
+
reasoningEffort: normalizedReasoningEffort,
|
|
888
|
+
})
|
|
889
|
+
: resolveLocalAIExecutionModel(normalizedClient, model);
|
|
884
890
|
const resolvedWorkspaceDir = ensureWorkspaceDir(workspaceDir);
|
|
885
891
|
const nextEnv = {
|
|
886
892
|
...process.env,
|
|
@@ -1386,6 +1392,23 @@ export function resolveLocalAIExecutionModel(clientName, rawModelValue = "") {
|
|
|
1386
1392
|
return match ? String(match.execution || "").trim() : modelValue;
|
|
1387
1393
|
}
|
|
1388
1394
|
|
|
1395
|
+
export function resolveGeminiHeadlessExecutionModel(
|
|
1396
|
+
rawModelValue = "",
|
|
1397
|
+
{ permissionMode = "read_only", reasoningEffort = "low" } = {},
|
|
1398
|
+
) {
|
|
1399
|
+
const resolvedExecutionModel = resolveLocalAIExecutionModel("gemini", rawModelValue);
|
|
1400
|
+
const normalizedExecutionModel = normalizeModelAliasText(resolvedExecutionModel);
|
|
1401
|
+
void normalizeLocalAIPermissionMode(permissionMode);
|
|
1402
|
+
void normalizeLocalAIReasoningEffort(reasoningEffort, "low");
|
|
1403
|
+
if (normalizedExecutionModel !== "auto-gemini-3") {
|
|
1404
|
+
return resolvedExecutionModel;
|
|
1405
|
+
}
|
|
1406
|
+
// Headless runner turns should not depend on Gemini CLI's internal auto-router.
|
|
1407
|
+
// Under heavier prompts it can escalate to capacity-constrained preview models,
|
|
1408
|
+
// which makes one bot path look flaky even though the routing logic is correct.
|
|
1409
|
+
return GEMINI_RUNNER_STABLE_EXECUTION_MODEL;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1389
1412
|
function buildCodexArgs({ workspaceDir, model, permissionMode, reasoningEffort, outputPath }) {
|
|
1390
1413
|
const args = ["exec"];
|
|
1391
1414
|
if (model) {
|
|
@@ -1513,7 +1536,10 @@ function buildGeminiThinkingConfig(model, reasoningEffort) {
|
|
|
1513
1536
|
}
|
|
1514
1537
|
|
|
1515
1538
|
export function resolveGeminiReasoningConfig(rawModelValue = "", reasoningEffort = "medium") {
|
|
1516
|
-
const executionModel =
|
|
1539
|
+
const executionModel = resolveGeminiHeadlessExecutionModel(rawModelValue, {
|
|
1540
|
+
permissionMode: "read_only",
|
|
1541
|
+
reasoningEffort,
|
|
1542
|
+
});
|
|
1517
1543
|
if (!executionModel) {
|
|
1518
1544
|
return null;
|
|
1519
1545
|
}
|
|
@@ -3256,7 +3282,12 @@ export function runLocalAIClient({
|
|
|
3256
3282
|
const normalizedClient = normalizeLocalAIClientName(client);
|
|
3257
3283
|
const normalizedPermissionMode = normalizeLocalAIPermissionMode(permissionMode);
|
|
3258
3284
|
const normalizedReasoningEffort = normalizeLocalAIReasoningEffort(reasoningEffort);
|
|
3259
|
-
const resolvedExecutionModel =
|
|
3285
|
+
const resolvedExecutionModel = normalizedClient === "gemini"
|
|
3286
|
+
? resolveGeminiHeadlessExecutionModel(model, {
|
|
3287
|
+
permissionMode: normalizedPermissionMode,
|
|
3288
|
+
reasoningEffort: normalizedReasoningEffort,
|
|
3289
|
+
})
|
|
3290
|
+
: resolveLocalAIExecutionModel(normalizedClient, model);
|
|
3260
3291
|
const resolvedWorkspaceDir = ensureWorkspaceDir(workspaceDir);
|
|
3261
3292
|
const promptText = buildLocalBotPrompt(inputPayload);
|
|
3262
3293
|
if (normalizedClient === "sample") {
|
|
@@ -13,6 +13,14 @@ function intFromRawAllowZero(value, fallback = 0) {
|
|
|
13
13
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function buildReplyAnchorMismatchError(expectedReplyToMessageID, observedReplyToMessageID) {
|
|
17
|
+
return `reply anchor mismatch: expected ${String(expectedReplyToMessageID || 0)}, observed ${String(observedReplyToMessageID || 0)}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildMessageThreadMismatchError(expectedMessageThreadID, observedMessageThreadID) {
|
|
21
|
+
return `message thread mismatch: expected ${String(expectedMessageThreadID || 0)}, observed ${String(observedMessageThreadID || 0)}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
16
24
|
function requireArchiveDependency(deps, key) {
|
|
17
25
|
const candidate = deps?.[key];
|
|
18
26
|
if (typeof candidate !== "function") {
|
|
@@ -83,11 +91,41 @@ export async function finalizeLocalBotDeliveryArchive({
|
|
|
83
91
|
deliveredResult.message_id ?? deliveredBody.message_id ?? deliveredBody.ts,
|
|
84
92
|
0,
|
|
85
93
|
);
|
|
86
|
-
const
|
|
94
|
+
const observedMessageThreadID = intFromRawAllowZero(
|
|
87
95
|
deliveredResult.message_thread_id ?? deliveredBody.message_thread_id ?? delivery.effectiveMessageThreadID,
|
|
88
96
|
intFromRawAllowZero(messageThreadID, 0),
|
|
89
97
|
);
|
|
90
|
-
const
|
|
98
|
+
const expectedMessageThreadID = intFromRawAllowZero(messageThreadID, 0);
|
|
99
|
+
const expectedReplyToMessageID = intFromRawAllowZero(replyToMessageID, 0);
|
|
100
|
+
const observedReplyToMessageID = intFromRawAllowZero(delivery.effectiveReplyToMessageID, 0);
|
|
101
|
+
if (
|
|
102
|
+
(expectedReplyToMessageID > 0 || observedReplyToMessageID > 0)
|
|
103
|
+
&& observedReplyToMessageID !== expectedReplyToMessageID
|
|
104
|
+
) {
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
error: buildReplyAnchorMismatchError(expectedReplyToMessageID, observedReplyToMessageID),
|
|
108
|
+
reply_anchor_mismatch: true,
|
|
109
|
+
expected_reply_to_message_id: expectedReplyToMessageID,
|
|
110
|
+
observed_reply_to_message_id: observedReplyToMessageID,
|
|
111
|
+
thread_id: thread.threadID,
|
|
112
|
+
work_item_id: thread.workItemID,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (
|
|
116
|
+
(expectedMessageThreadID > 0 || observedMessageThreadID > 0)
|
|
117
|
+
&& observedMessageThreadID !== expectedMessageThreadID
|
|
118
|
+
) {
|
|
119
|
+
return {
|
|
120
|
+
ok: false,
|
|
121
|
+
error: buildMessageThreadMismatchError(expectedMessageThreadID, observedMessageThreadID),
|
|
122
|
+
message_thread_mismatch: true,
|
|
123
|
+
expected_message_thread_id: expectedMessageThreadID,
|
|
124
|
+
observed_message_thread_id: observedMessageThreadID,
|
|
125
|
+
thread_id: thread.threadID,
|
|
126
|
+
work_item_id: thread.workItemID,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
91
129
|
if (archiveDedupeOutbound && deliveredMessageID > 0) {
|
|
92
130
|
const existingComments = await listThreadCommentsTail({
|
|
93
131
|
siteBaseURL,
|
|
@@ -119,8 +157,8 @@ export async function finalizeLocalBotDeliveryArchive({
|
|
|
119
157
|
destination,
|
|
120
158
|
replyText: text,
|
|
121
159
|
messageID: deliveredMessageID,
|
|
122
|
-
messageThreadID:
|
|
123
|
-
replyToMessageID:
|
|
160
|
+
messageThreadID: expectedMessageThreadID,
|
|
161
|
+
replyToMessageID: expectedReplyToMessageID,
|
|
124
162
|
conversation: archiveConversation,
|
|
125
163
|
});
|
|
126
164
|
const createdComment = await createThreadComment({
|
package/lib/runner-helpers.mjs
CHANGED
|
@@ -314,6 +314,27 @@ export function buildTelegramMessageEnvelopeFromParsedArchive(parsedArchiveRaw,
|
|
|
314
314
|
});
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
+
export function resolveTelegramReplyAnchorMessageID({
|
|
318
|
+
replyToMessageID = 0,
|
|
319
|
+
sourceEnvelope: sourceEnvelopeRaw = {},
|
|
320
|
+
fallbackReplyToMessageID = 0,
|
|
321
|
+
} = {}) {
|
|
322
|
+
const explicitReplyToMessageID = intFromRawAllowZero(replyToMessageID, 0);
|
|
323
|
+
if (explicitReplyToMessageID > 0) {
|
|
324
|
+
return explicitReplyToMessageID;
|
|
325
|
+
}
|
|
326
|
+
const sourceEnvelope = normalizeTelegramMessageEnvelope(sourceEnvelopeRaw);
|
|
327
|
+
const sourceMessageID = intFromRawAllowZero(sourceEnvelope.message_id, 0);
|
|
328
|
+
if (sourceMessageID > 0) {
|
|
329
|
+
return sourceMessageID;
|
|
330
|
+
}
|
|
331
|
+
const sourceReplyToMessageID = intFromRawAllowZero(sourceEnvelope.reply_to_message_id, 0);
|
|
332
|
+
if (sourceReplyToMessageID > 0) {
|
|
333
|
+
return sourceReplyToMessageID;
|
|
334
|
+
}
|
|
335
|
+
return intFromRawAllowZero(fallbackReplyToMessageID, 0);
|
|
336
|
+
}
|
|
337
|
+
|
|
317
338
|
export function buildTelegramBotReplyEnvelope({
|
|
318
339
|
sourceEnvelope: sourceEnvelopeRaw = {},
|
|
319
340
|
chatID = "",
|
|
@@ -26,11 +26,14 @@ export function classifyExecutionFailureFacts(detail) {
|
|
|
26
26
|
const normalizedDetail = String(detail || "").trim();
|
|
27
27
|
const networkReset = /ECONNRESET|socket hang up|read ECONNRESET/i.test(normalizedDetail);
|
|
28
28
|
const networkTimeout = /ETIMEDOUT|http timeout|ECONNABORTED|aborted/i.test(normalizedDetail);
|
|
29
|
-
const
|
|
29
|
+
const providerCapacityExhausted = /MODEL_CAPACITY_EXHAUSTED|RESOURCE_EXHAUSTED|No capacity available for model|rateLimitExceeded/i.test(normalizedDetail);
|
|
30
|
+
const retryable = networkReset || networkTimeout || providerCapacityExhausted;
|
|
30
31
|
const base = {
|
|
31
32
|
stage: "execution",
|
|
32
33
|
operation: "runner_execution",
|
|
33
|
-
errorType:
|
|
34
|
+
errorType: providerCapacityExhausted
|
|
35
|
+
? "provider_capacity_exhausted"
|
|
36
|
+
: retryable
|
|
34
37
|
? (networkTimeout ? "network_timeout" : "network_reset")
|
|
35
38
|
: "execution_failed",
|
|
36
39
|
retryable,
|
|
@@ -42,6 +45,15 @@ export function classifyExecutionFailureFacts(detail) {
|
|
|
42
45
|
if (!normalizedDetail) {
|
|
43
46
|
return base;
|
|
44
47
|
}
|
|
48
|
+
if (providerCapacityExhausted) {
|
|
49
|
+
return {
|
|
50
|
+
...base,
|
|
51
|
+
stage: "provider_call",
|
|
52
|
+
operation: "local_ai_model_request",
|
|
53
|
+
errorType: "provider_capacity_exhausted",
|
|
54
|
+
retryable: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
45
57
|
if (/permission_mode=read_only|read[_ -]?only/i.test(normalizedDetail)) {
|
|
46
58
|
return {
|
|
47
59
|
...base,
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveTelegramReplyAnchorMessageID,
|
|
3
|
+
} from "./runner-helpers.mjs";
|
|
4
|
+
|
|
1
5
|
function safeObject(value) {
|
|
2
6
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3
7
|
return {};
|
|
@@ -154,9 +158,20 @@ export function prepareRunnerSelectedRecordIngress({
|
|
|
154
158
|
currentBotSelector,
|
|
155
159
|
});
|
|
156
160
|
const replyMessageThreadID = intFromRawAllowZero(sourceMessageEnvelope.message_thread_id, 0);
|
|
157
|
-
const
|
|
161
|
+
const sourceMessageID = intFromRawAllowZero(sourceMessageEnvelope.message_id, 0);
|
|
162
|
+
const sourceReplyToMessageID = intFromRawAllowZero(sourceMessageEnvelope.reply_to_message_id, 0);
|
|
163
|
+
const replyToMessageID = resolveTelegramReplyAnchorMessageID({
|
|
164
|
+
replyToMessageID: sourceMessageID,
|
|
165
|
+
sourceEnvelope: sourceMessageEnvelope,
|
|
166
|
+
});
|
|
158
167
|
const replyAnchorSource = String(sourceMessageEnvelope.source_origin || "").trim()
|
|
159
|
-
|| (replyToMessageID > 0
|
|
168
|
+
|| (replyToMessageID > 0
|
|
169
|
+
? sourceMessageID > 0
|
|
170
|
+
? "source_message_envelope"
|
|
171
|
+
: sourceReplyToMessageID > 0
|
|
172
|
+
? "source_message_envelope_reply_to"
|
|
173
|
+
: ""
|
|
174
|
+
: "");
|
|
160
175
|
|
|
161
176
|
return {
|
|
162
177
|
handledResult: null,
|
|
@@ -29,6 +29,10 @@ function buildConversationSummaryDetail({
|
|
|
29
29
|
].filter(Boolean).join(" | ");
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
function buildMessageThreadMismatchError(expectedMessageThreadID, observedMessageThreadID) {
|
|
33
|
+
return `message thread mismatch: expected ${String(expectedMessageThreadID || 0)}, observed ${String(observedMessageThreadID || 0)}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
32
36
|
function buildSelectedRecordReplyOutcomeBase({
|
|
33
37
|
routeKey,
|
|
34
38
|
normalizedRoute,
|
|
@@ -354,11 +358,19 @@ export async function finalizeRunnerSelectedRecordReplyOutcome({
|
|
|
354
358
|
deliveryBody.result?.message_id ?? deliveryBody.message_id,
|
|
355
359
|
0,
|
|
356
360
|
);
|
|
357
|
-
const
|
|
361
|
+
const expectedReplyMessageThreadID = intFromRawAllowZero(replyMessageThreadID, 0);
|
|
362
|
+
const observedReplyMessageThreadID = intFromRawAllowZero(
|
|
358
363
|
deliveryResult?.delivery?.effectiveMessageThreadID,
|
|
359
|
-
|
|
364
|
+
0,
|
|
365
|
+
);
|
|
366
|
+
const messageThreadMismatch = (
|
|
367
|
+
(expectedReplyMessageThreadID > 0 || observedReplyMessageThreadID > 0)
|
|
368
|
+
&& observedReplyMessageThreadID !== expectedReplyMessageThreadID
|
|
360
369
|
);
|
|
361
|
-
const
|
|
370
|
+
const messageThreadError = messageThreadMismatch
|
|
371
|
+
? buildMessageThreadMismatchError(expectedReplyMessageThreadID, observedReplyMessageThreadID)
|
|
372
|
+
: "";
|
|
373
|
+
const effectiveReplyToMessageID = intFromRawAllowZero(replyToMessageID, 0);
|
|
362
374
|
const conversationDetail = buildConversationSummaryDetail({
|
|
363
375
|
effectiveConversationContext,
|
|
364
376
|
executionContract,
|
|
@@ -371,7 +383,7 @@ export async function finalizeRunnerSelectedRecordReplyOutcome({
|
|
|
371
383
|
...buildRunnerRouteStateFromComment(selectedRecord, {
|
|
372
384
|
last_action: "replied",
|
|
373
385
|
last_reply_message_id: replyMessageID,
|
|
374
|
-
last_reply_message_thread_id:
|
|
386
|
+
last_reply_message_thread_id: expectedReplyMessageThreadID,
|
|
375
387
|
last_reply_anchor_source: replyAnchorSource,
|
|
376
388
|
last_contract_validation_status: String(responseContractValidation?.status || "").trim(),
|
|
377
389
|
last_contract_validation_reason: String(responseContractValidation?.reason || "").trim(),
|
|
@@ -445,17 +457,21 @@ export async function finalizeRunnerSelectedRecordReplyOutcome({
|
|
|
445
457
|
evidence_ids: ensureArray(aiResult?.evidenceItems).map((item) => String(safeObject(item).id || "").trim()).filter(Boolean),
|
|
446
458
|
evidence_paths: ensureArray(aiResult?.evidenceItems).map((item) => String(safeObject(item).path || "").trim()).filter(Boolean),
|
|
447
459
|
last_reply_message_id: replyMessageID,
|
|
448
|
-
last_reply_message_thread_id:
|
|
460
|
+
last_reply_message_thread_id: expectedReplyMessageThreadID,
|
|
449
461
|
reply_to_message_id: effectiveReplyToMessageID,
|
|
450
462
|
last_reply_message_envelope: buildTelegramBotReplyEnvelope({
|
|
451
463
|
sourceEnvelope: sourceMessageEnvelope,
|
|
452
464
|
messageID: replyMessageID,
|
|
453
|
-
messageThreadID:
|
|
465
|
+
messageThreadID: expectedReplyMessageThreadID,
|
|
454
466
|
replyToMessageID: effectiveReplyToMessageID,
|
|
455
467
|
sender: bot?.username ? `@${String(bot.username || "").trim().replace(/^@+/, "")}` : String(bot?.name || "bot").trim(),
|
|
456
468
|
senderUsername: normalizeMentionSelector(bot?.username || bot?.name),
|
|
457
469
|
body: sanitizedReplyText,
|
|
458
470
|
}),
|
|
471
|
+
message_thread_mismatch: messageThreadMismatch,
|
|
472
|
+
expected_message_thread_id: expectedReplyMessageThreadID || undefined,
|
|
473
|
+
observed_message_thread_id: observedReplyMessageThreadID || undefined,
|
|
474
|
+
message_thread_error: messageThreadError,
|
|
459
475
|
reply_anchor_source: replyAnchorSource,
|
|
460
476
|
reply_fallback_used: deliveryResult?.delivery?.replyFallbackUsed === true,
|
|
461
477
|
delivery_status: deliveryResult?.delivery?.dryRun ? "dry_run" : "delivered",
|
|
@@ -36,14 +36,8 @@ export function prepareRunnerFailureReplyDeliveryHandoff({
|
|
|
36
36
|
const sourceMessageEnvelope = Object.keys(safeObject(authoritativeSourceMessageEnvelope)).length > 0
|
|
37
37
|
? safeObject(authoritativeSourceMessageEnvelope)
|
|
38
38
|
: safeObject(result.source_message_envelope);
|
|
39
|
-
const replyToMessageID = intFromRawAllowZero(
|
|
40
|
-
|
|
41
|
-
intFromRawAllowZero(sourceMessageEnvelope.message_id, 0),
|
|
42
|
-
);
|
|
43
|
-
const messageThreadID = intFromRawAllowZero(
|
|
44
|
-
result.reply_message_thread_id,
|
|
45
|
-
intFromRawAllowZero(sourceMessageEnvelope.message_thread_id, 0),
|
|
46
|
-
);
|
|
39
|
+
const replyToMessageID = intFromRawAllowZero(result.reply_to_message_id, 0);
|
|
40
|
+
const messageThreadID = intFromRawAllowZero(result.reply_message_thread_id, 0);
|
|
47
41
|
return {
|
|
48
42
|
siteBaseURL: normalizedRuntime.baseURL,
|
|
49
43
|
token: normalizedRuntime.token,
|
|
@@ -11,6 +11,14 @@ function intFromRawAllowZero(value, fallback = 0) {
|
|
|
11
11
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
function buildReplyAnchorMismatchError(expectedReplyToMessageID, observedReplyToMessageID) {
|
|
15
|
+
return `reply anchor mismatch: expected ${String(expectedReplyToMessageID || 0)}, observed ${String(observedReplyToMessageID || 0)}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function buildMessageThreadMismatchError(expectedMessageThreadID, observedMessageThreadID) {
|
|
19
|
+
return `message thread mismatch: expected ${String(expectedMessageThreadID || 0)}, observed ${String(observedMessageThreadID || 0)}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
14
22
|
export function finalizeRunnerFailureReplyDeliveryOutcome({
|
|
15
23
|
processed = {},
|
|
16
24
|
routeKey = "",
|
|
@@ -50,6 +58,24 @@ export function finalizeRunnerFailureReplyDeliveryOutcome({
|
|
|
50
58
|
const delivery = safeObject(normalizedDeliveryResult.delivery);
|
|
51
59
|
const archive = safeObject(normalizedDeliveryResult.archive);
|
|
52
60
|
const handoff = safeObject(deliveryHandoff);
|
|
61
|
+
const expectedReplyToMessageID = intFromRawAllowZero(handoff.replyToMessageID, 0);
|
|
62
|
+
const observedReplyToMessageID = intFromRawAllowZero(delivery.effectiveReplyToMessageID, 0);
|
|
63
|
+
const expectedMessageThreadID = intFromRawAllowZero(handoff.messageThreadID, 0);
|
|
64
|
+
const observedMessageThreadID = intFromRawAllowZero(delivery.effectiveMessageThreadID, 0);
|
|
65
|
+
const replyAnchorMismatch = (
|
|
66
|
+
(expectedReplyToMessageID > 0 || observedReplyToMessageID > 0)
|
|
67
|
+
&& observedReplyToMessageID !== expectedReplyToMessageID
|
|
68
|
+
);
|
|
69
|
+
const replyAnchorError = replyAnchorMismatch
|
|
70
|
+
? buildReplyAnchorMismatchError(expectedReplyToMessageID, observedReplyToMessageID)
|
|
71
|
+
: "";
|
|
72
|
+
const messageThreadMismatch = (
|
|
73
|
+
(expectedMessageThreadID > 0 || observedMessageThreadID > 0)
|
|
74
|
+
&& observedMessageThreadID !== expectedMessageThreadID
|
|
75
|
+
);
|
|
76
|
+
const messageThreadError = messageThreadMismatch
|
|
77
|
+
? buildMessageThreadMismatchError(expectedMessageThreadID, observedMessageThreadID)
|
|
78
|
+
: "";
|
|
53
79
|
const mergedResult = {
|
|
54
80
|
...result,
|
|
55
81
|
failure_reply_sent: true,
|
|
@@ -69,14 +95,16 @@ export function finalizeRunnerFailureReplyDeliveryOutcome({
|
|
|
69
95
|
safeObject(delivery.body).result?.message_id ?? safeObject(delivery.body).message_id,
|
|
70
96
|
intFromRawAllowZero(result.last_reply_message_id, 0),
|
|
71
97
|
),
|
|
72
|
-
last_reply_message_thread_id:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
98
|
+
last_reply_message_thread_id: expectedMessageThreadID,
|
|
99
|
+
reply_to_message_id: expectedReplyToMessageID,
|
|
100
|
+
reply_anchor_mismatch: replyAnchorMismatch,
|
|
101
|
+
expected_reply_to_message_id: expectedReplyToMessageID || undefined,
|
|
102
|
+
observed_reply_to_message_id: observedReplyToMessageID || undefined,
|
|
103
|
+
reply_anchor_error: replyAnchorError,
|
|
104
|
+
message_thread_mismatch: messageThreadMismatch,
|
|
105
|
+
expected_message_thread_id: expectedMessageThreadID || undefined,
|
|
106
|
+
observed_message_thread_id: observedMessageThreadID || undefined,
|
|
107
|
+
message_thread_error: messageThreadError,
|
|
80
108
|
};
|
|
81
109
|
if (normalizedRouteKey && typeof saveRunnerRouteState === "function") {
|
|
82
110
|
const currentRouteState = typeof loadRouteState === "function"
|
|
@@ -88,6 +88,7 @@ export function buildRunnerProcessedLifecycleInput({
|
|
|
88
88
|
const processed = safeObject(processedRaw);
|
|
89
89
|
const result = safeObject(processed.result);
|
|
90
90
|
const normalizedOutcome = normalizeRunnerProcessedLifecycleOutcome(processed);
|
|
91
|
+
const normalizedFailureFacts = safeObject(result.failure_facts);
|
|
91
92
|
return {
|
|
92
93
|
requestKey,
|
|
93
94
|
selectedRecord,
|
|
@@ -95,6 +96,8 @@ export function buildRunnerProcessedLifecycleInput({
|
|
|
95
96
|
outcome: normalizedOutcome,
|
|
96
97
|
closedReason: normalizedOutcome === "skipped"
|
|
97
98
|
? String(processed.skippedRecord?.reason || result.detail || "skipped").trim() || "skipped"
|
|
99
|
+
: ["error", "execution_failed", "policy_violation"].includes(normalizedOutcome)
|
|
100
|
+
? String(result.detail || "execution_error").trim() || "execution_error"
|
|
98
101
|
: "",
|
|
99
102
|
conversationIDRaw: String(result.conversation_id || "").trim(),
|
|
100
103
|
conversationParticipants: ensureArray(result.conversation_participants),
|
|
@@ -126,6 +129,8 @@ export function buildRunnerProcessedLifecycleInput({
|
|
|
126
129
|
assignmentValidationStatus: String(result.assignment_validation_status || "").trim(),
|
|
127
130
|
assignmentValidationReason: String(result.assignment_validation_reason || "").trim(),
|
|
128
131
|
assignmentValidationModes: ensureArray(result.assignment_validation_modes),
|
|
132
|
+
failureReplyClassification: String(result.failure_reply_classification || "").trim(),
|
|
133
|
+
failureFacts: normalizedFailureFacts,
|
|
129
134
|
deliveryStatus: String(result.delivery_status || "").trim(),
|
|
130
135
|
archiveStatus: String(result.archive_status || "").trim(),
|
|
131
136
|
transportError: String(result.transport_error || "").trim(),
|
|
@@ -361,6 +361,7 @@ export async function runSelftestBotCommands(push, deps) {
|
|
|
361
361
|
const cliPath = String(requireDependency(deps, "cliPath") || "").trim();
|
|
362
362
|
const parseSimpleEnvText = requireDependency(deps, "parseSimpleEnvText");
|
|
363
363
|
const resolveLocalAIExecutionModel = requireDependency(deps, "resolveLocalAIExecutionModel");
|
|
364
|
+
const resolveGeminiHeadlessExecutionModel = requireDependency(deps, "resolveGeminiHeadlessExecutionModel");
|
|
364
365
|
const suggestLocalAIModelDisplayName = requireDependency(deps, "suggestLocalAIModelDisplayName");
|
|
365
366
|
const resolveGeminiReasoningConfig = requireDependency(deps, "resolveGeminiReasoningConfig");
|
|
366
367
|
const stripLocalOnlyToolArgs = requireDependency(deps, "stripLocalOnlyToolArgs");
|
|
@@ -392,6 +393,18 @@ export async function runSelftestBotCommands(push, deps) {
|
|
|
392
393
|
].join(" "),
|
|
393
394
|
);
|
|
394
395
|
|
|
396
|
+
push(
|
|
397
|
+
"gemini_headless_runner_uses_explicit_stable_execution_model",
|
|
398
|
+
resolveGeminiHeadlessExecutionModel("gemini-3.1-pro", {
|
|
399
|
+
permissionMode: "read_only",
|
|
400
|
+
reasoningEffort: "low",
|
|
401
|
+
}) === "gemini-3-flash-preview",
|
|
402
|
+
`gemini_headless=${resolveGeminiHeadlessExecutionModel("gemini-3.1-pro", {
|
|
403
|
+
permissionMode: "read_only",
|
|
404
|
+
reasoningEffort: "low",
|
|
405
|
+
})}`,
|
|
406
|
+
);
|
|
407
|
+
|
|
395
408
|
push(
|
|
396
409
|
"blank_model_defaults_to_first_display_model_for_each_client",
|
|
397
410
|
suggestLocalAIModelDisplayName("gpt", "") === "gpt-5.4"
|
|
@@ -409,7 +422,7 @@ export async function runSelftestBotCommands(push, deps) {
|
|
|
409
422
|
const geminiHighReasoning = resolveGeminiReasoningConfig("gemini-3.1-pro", "high");
|
|
410
423
|
push(
|
|
411
424
|
"gemini_reasoning_effort_maps_to_runtime_settings_override",
|
|
412
|
-
String(geminiLowReasoning?.model || "") === "
|
|
425
|
+
String(geminiLowReasoning?.model || "") === "gemini-3-flash-preview"
|
|
413
426
|
&& String(safeObject(geminiLowReasoning?.thinkingConfig).thinkingLevel || "") === "LOW"
|
|
414
427
|
&& String(safeObject(geminiMediumReasoning?.thinkingConfig).thinkingLevel || "") === "THINKING_LEVEL_UNSPECIFIED"
|
|
415
428
|
&& String(safeObject(geminiHighReasoning?.thinkingConfig).thinkingLevel || "") === "HIGH",
|