metheus-governance-mcp-cli 0.2.285 → 0.2.287
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/README.md +9 -5
- package/cli.mjs +239 -94
- package/lib/runner-recovery.mjs +83 -17
- package/lib/selftest-runner-scenarios.mjs +271 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -379,7 +379,7 @@ Behavior:
|
|
|
379
379
|
- `bot room-audit` now suggests executable runner routes for the managed server+local intersection. Room visibility is still reported, but a bot no longer has to appear in the admin list before route automation can prepare it.
|
|
380
380
|
- `bot room-audit` defaults to `monitor` only unless you pass `--role` or `--roles`.
|
|
381
381
|
- `bot room-audit --apply true` writes missing suggested routes into `~/.metheus/bot-runner.json` and disables overlapping enabled routes in the same project/provider/destination/bot scope that are outside the selected role set.
|
|
382
|
-
- `runner project up` is the
|
|
382
|
+
- `runner project up` is the route preparation path for Telegram project operations: it runs the same room audit, applies the selected role routes, and starts polling only when you explicitly pass `--start true` or `--start-detached true`.
|
|
383
383
|
- `runner project up` can be narrowed with `--bot-name`, `--bot-id`, `--role`, or `--roles <csv>` when you do not want every suggested role route for that room.
|
|
384
384
|
- `bot remove` without flags starts a guided numbered flow: provider -> bot entry -> confirm removal.
|
|
385
385
|
- Telegram stores one bot file per entry under `~/.metheus/telegram-bots/<ServerBotName>.env` with generic fields:
|
|
@@ -421,7 +421,8 @@ metheus-governance-mcp-cli bot remove --provider telegram --bot-name <server_bot
|
|
|
421
421
|
metheus-governance-mcp-cli bot verify --provider telegram --bot-name <server_bot_name> --json true
|
|
422
422
|
metheus-governance-mcp-cli bot room-audit --provider telegram --project-id <project_uuid> --destination-label <room_label> --json true
|
|
423
423
|
metheus-governance-mcp-cli bot room-audit --provider telegram --project-id <project_uuid> --destination-label <room_label> --apply true --json true
|
|
424
|
-
metheus-governance-mcp-cli runner project up --project-id <project_uuid> --provider telegram --destination-label <room_label>
|
|
424
|
+
metheus-governance-mcp-cli runner project up --project-id <project_uuid> --provider telegram --destination-label <room_label> --start false
|
|
425
|
+
metheus-governance-mcp-cli runner start-detached --project-id <project_uuid> --provider telegram --destination-label <room_label>
|
|
425
426
|
metheus-governance-mcp-cli runner project up --project-id <project_uuid> --provider telegram --destination-label <room_label> --bot-name <server_bot_name> --roles monitor,review --start false
|
|
426
427
|
```
|
|
427
428
|
|
|
@@ -540,7 +541,8 @@ Commands:
|
|
|
540
541
|
|
|
541
542
|
```bash
|
|
542
543
|
metheus-governance-mcp-cli runner list
|
|
543
|
-
metheus-governance-mcp-cli runner project up --project-id <project_uuid> --provider telegram --destination-label <room_label>
|
|
544
|
+
metheus-governance-mcp-cli runner project up --project-id <project_uuid> --provider telegram --destination-label <room_label> --start false
|
|
545
|
+
metheus-governance-mcp-cli runner start-detached --project-id <project_uuid> --provider telegram --destination-label <room_label>
|
|
544
546
|
metheus-governance-mcp-cli runner route add
|
|
545
547
|
metheus-governance-mcp-cli runner route edit --route-name telegram-monitor
|
|
546
548
|
metheus-governance-mcp-cli runner route remove --route-name telegram-monitor
|
|
@@ -553,7 +555,7 @@ metheus-governance-mcp-cli runner start --route-name telegram-monitor --concurre
|
|
|
553
555
|
Route management:
|
|
554
556
|
- `runner route add` creates one executable route by selecting the project, provider, role, server bot, and project chat destination in order.
|
|
555
557
|
- `runner route add` now auto-uses the suggested route name, `5000` ms poll interval, and `enabled=true` unless you pass explicit flags or edit the route later.
|
|
556
|
-
- `runner project up` is the shortest practical route bootstrap path for Telegram: it audits one project destination, writes any missing suggested routes,
|
|
558
|
+
- `runner project up` is the shortest practical route bootstrap path for Telegram: it audits one project destination, writes any missing suggested routes, and prepares that destination for later launch. It starts polling only when you explicitly pass `--start true` or `--start-detached true`.
|
|
557
559
|
- if room visibility probe fails but one or more enabled routes already exist for that project destination, `runner project up` now treats the probe failure as a warning and can still start those existing routes.
|
|
558
560
|
- `runner project up --dry-run-delivery true` lets the started runner validate route execution without sending a real provider message.
|
|
559
561
|
- In public Telegram bot conversations, the stored route role is treated as a hint only. Live room context, the current human request, and recent bot replies take priority over the stored route role hint when the local AI client decides how to answer.
|
|
@@ -566,8 +568,10 @@ Route management:
|
|
|
566
568
|
Recommended operational path:
|
|
567
569
|
|
|
568
570
|
```bash
|
|
569
|
-
metheus-governance-mcp-cli runner project up --project-id <project_uuid> --provider telegram --destination-label <room_label>
|
|
571
|
+
metheus-governance-mcp-cli runner project up --project-id <project_uuid> --provider telegram --destination-label <room_label> --start false
|
|
572
|
+
metheus-governance-mcp-cli runner start-detached --project-id <project_uuid> --provider telegram --destination-label <room_label>
|
|
570
573
|
metheus-governance-mcp-cli runner project up --project-id <project_uuid> --provider telegram --destination-label <room_label> --bot-name <server_bot_name> --roles monitor,review --start false
|
|
574
|
+
metheus-governance-mcp-cli runner start-detached --project-id <project_uuid> --provider telegram --destination-label <room_label> --bot-name <server_bot_name> --role monitor
|
|
571
575
|
```
|
|
572
576
|
|
|
573
577
|
If you want the older step-by-step path instead:
|
package/cli.mjs
CHANGED
|
@@ -3562,7 +3562,9 @@ function buildRunnerRequestKey({
|
|
|
3562
3562
|
const intent = canonicalHumanKey
|
|
3563
3563
|
? "-"
|
|
3564
3564
|
: String(normalizedIntent || "").trim().toLowerCase() || "-";
|
|
3565
|
-
const resolvedConversationID =
|
|
3565
|
+
const resolvedConversationID = canonicalHumanKey
|
|
3566
|
+
? "-"
|
|
3567
|
+
: String(conversationID || parsed.conversationID || "").trim() || "-";
|
|
3566
3568
|
return [
|
|
3567
3569
|
String(normalizedRoute?.projectID || "").trim() || "-",
|
|
3568
3570
|
String(normalizedRoute?.provider || "").trim() || "-",
|
|
@@ -3994,10 +3996,10 @@ function runnerRequestPreferredNextExpectedResponders(entryRaw) {
|
|
|
3994
3996
|
);
|
|
3995
3997
|
}
|
|
3996
3998
|
|
|
3997
|
-
function runnerRequestPreferredAuthoritySelectedBotUsernames(entryRaw) {
|
|
3998
|
-
const entry = safeObject(entryRaw);
|
|
3999
|
-
const normalizedSummaryBot = normalizeTelegramMentionUsername(entry.conversation_summary_bot);
|
|
4000
|
-
return uniqueOrderedStrings(
|
|
3999
|
+
function runnerRequestPreferredAuthoritySelectedBotUsernames(entryRaw) {
|
|
4000
|
+
const entry = safeObject(entryRaw);
|
|
4001
|
+
const normalizedSummaryBot = normalizeTelegramMentionUsername(entry.conversation_summary_bot);
|
|
4002
|
+
return uniqueOrderedStrings(
|
|
4001
4003
|
runnerRequestPreferredNextExpectedResponders(entry).length
|
|
4002
4004
|
? runnerRequestPreferredNextExpectedResponders(entry)
|
|
4003
4005
|
: runnerRequestPreferredExecutionContractTargets(entry).length
|
|
@@ -4025,6 +4027,35 @@ function extractRunnerExplicitSelectedBotUsernamesFromParsed(parsedArchiveRaw) {
|
|
|
4025
4027
|
normalizeTelegramMentionUsername,
|
|
4026
4028
|
);
|
|
4027
4029
|
}
|
|
4030
|
+
|
|
4031
|
+
function runnerRequestAllowsSharedHumanClaimTakeover({
|
|
4032
|
+
entryRaw,
|
|
4033
|
+
currentBotUsername = "",
|
|
4034
|
+
canonicalHumanMessageKey = "",
|
|
4035
|
+
}) {
|
|
4036
|
+
const entry = safeObject(entryRaw);
|
|
4037
|
+
const normalizedCurrentBotUsername = normalizeTelegramMentionUsername(currentBotUsername);
|
|
4038
|
+
const normalizedCanonicalHumanMessageKey = String(canonicalHumanMessageKey || "").trim();
|
|
4039
|
+
if (!normalizedCurrentBotUsername || !normalizedCanonicalHumanMessageKey) {
|
|
4040
|
+
return false;
|
|
4041
|
+
}
|
|
4042
|
+
if (String(entry.canonical_human_message_key || "").trim() !== normalizedCanonicalHumanMessageKey) {
|
|
4043
|
+
return false;
|
|
4044
|
+
}
|
|
4045
|
+
const pendingResponders = runnerRequestPreferredNextExpectedResponders(entry);
|
|
4046
|
+
if (pendingResponders.length > 0) {
|
|
4047
|
+
return pendingResponders.includes(normalizedCurrentBotUsername);
|
|
4048
|
+
}
|
|
4049
|
+
const directHumanResponders = uniqueOrderedStrings(
|
|
4050
|
+
[
|
|
4051
|
+
...ensureArray(entry.selected_bot_usernames),
|
|
4052
|
+
...ensureArray(entry.conversation_initial_responders),
|
|
4053
|
+
...ensureArray(entry.conversation_allowed_responders),
|
|
4054
|
+
],
|
|
4055
|
+
normalizeTelegramMentionUsername,
|
|
4056
|
+
);
|
|
4057
|
+
return directHumanResponders.includes(normalizedCurrentBotUsername);
|
|
4058
|
+
}
|
|
4028
4059
|
|
|
4029
4060
|
function runnerRequestPreferredAIReplyPreview(entryRaw) {
|
|
4030
4061
|
const entry = safeObject(entryRaw);
|
|
@@ -5067,8 +5098,8 @@ async function claimRunnerRequestForHumanComment({
|
|
|
5067
5098
|
reason: "non_human_comment_cannot_create_request",
|
|
5068
5099
|
};
|
|
5069
5100
|
}
|
|
5070
|
-
const currentState = loadBotRunnerState();
|
|
5071
|
-
const currentRouteState = safeObject(safeObject(currentState.routes)[String(routeKey || "").trim()]);
|
|
5101
|
+
const currentState = loadBotRunnerState();
|
|
5102
|
+
const currentRouteState = safeObject(safeObject(currentState.routes)[String(routeKey || "").trim()]);
|
|
5072
5103
|
const replyChainResolution = await resolveRunnerReplyChainConversationContextWithServerFallback({
|
|
5073
5104
|
state: currentState,
|
|
5074
5105
|
normalizedRoute,
|
|
@@ -5168,6 +5199,16 @@ async function claimRunnerRequestForHumanComment({
|
|
|
5168
5199
|
const authoritativeDecisionBundle = decisionBundleValidation.ok === true
|
|
5169
5200
|
? safeObject(decisionBundleValidation.bundle)
|
|
5170
5201
|
: {};
|
|
5202
|
+
const normalizedAuthoritativeSourceMessageEnvelope = normalizeRunnerTelegramMessageEnvelope(
|
|
5203
|
+
authoritativeSourceMessageEnvelope,
|
|
5204
|
+
);
|
|
5205
|
+
const currentClaimBotUsername = normalizeTelegramMentionUsername(
|
|
5206
|
+
normalizedAuthoritativeSourceMessageEnvelope.source_bot_username
|
|
5207
|
+
|| normalizedAuthoritativeSourceMessageEnvelope.sender_username
|
|
5208
|
+
|| currentRouteState.last_source_bot_username
|
|
5209
|
+
|| currentRouteState.source_message_bot_username
|
|
5210
|
+
|| currentRouteState.last_speaker_bot_username,
|
|
5211
|
+
);
|
|
5171
5212
|
const authorityReplyChainContext = await buildRunnerAuthorityReplyChainContext({
|
|
5172
5213
|
selectedRecord,
|
|
5173
5214
|
replyChainContext,
|
|
@@ -5200,24 +5241,35 @@ async function claimRunnerRequestForHumanComment({
|
|
|
5200
5241
|
requestKey,
|
|
5201
5242
|
};
|
|
5202
5243
|
}
|
|
5203
|
-
if (
|
|
5204
|
-
isActiveRunnerRequestStatus(existing.status)
|
|
5205
|
-
&& String(existing.claimed_by_route || "").trim()
|
|
5206
|
-
&& String(existing.claimed_by_route || "").trim() !== String(routeKey || "").trim()
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5244
|
+
if (
|
|
5245
|
+
isActiveRunnerRequestStatus(existing.status)
|
|
5246
|
+
&& String(existing.claimed_by_route || "").trim()
|
|
5247
|
+
&& String(existing.claimed_by_route || "").trim() !== String(routeKey || "").trim()
|
|
5248
|
+
&& !runnerRequestAllowsSharedHumanClaimTakeover({
|
|
5249
|
+
entryRaw: existing,
|
|
5250
|
+
currentBotUsername: currentClaimBotUsername,
|
|
5251
|
+
canonicalHumanMessageKey,
|
|
5252
|
+
})
|
|
5253
|
+
) {
|
|
5254
|
+
return {
|
|
5255
|
+
ok: false,
|
|
5256
|
+
reason: "request_already_claimed",
|
|
5257
|
+
requestKey,
|
|
5258
|
+
};
|
|
5259
|
+
}
|
|
5260
|
+
const nowISO = new Date().toISOString();
|
|
5261
|
+
const existingSourceMessageEnvelope = normalizeRunnerTelegramMessageEnvelope(existing.source_message_envelope);
|
|
5262
|
+
const preserveExistingCanonicalSourceEnvelope = Boolean(
|
|
5263
|
+
canonicalHumanMessageKey
|
|
5264
|
+
&& String(existing.canonical_human_message_key || "").trim() === canonicalHumanMessageKey
|
|
5265
|
+
&& intFromRawAllowZero(existing.source_message_id, 0) === currentMessageID
|
|
5266
|
+
&& Object.keys(existingSourceMessageEnvelope).length > 0
|
|
5217
5267
|
);
|
|
5218
|
-
const sourceMessageEnvelope =
|
|
5219
|
-
?
|
|
5220
|
-
:
|
|
5268
|
+
const sourceMessageEnvelope = preserveExistingCanonicalSourceEnvelope
|
|
5269
|
+
? existingSourceMessageEnvelope
|
|
5270
|
+
: Object.keys(normalizedAuthoritativeSourceMessageEnvelope).length
|
|
5271
|
+
? normalizedAuthoritativeSourceMessageEnvelope
|
|
5272
|
+
: existingSourceMessageEnvelope;
|
|
5221
5273
|
const decisionConversationParticipants = uniqueOrderedStrings(
|
|
5222
5274
|
ensureArray(authoritativeDecisionBundle.participants),
|
|
5223
5275
|
normalizeTelegramMentionUsername,
|
|
@@ -12206,11 +12258,11 @@ async function runRunnerProjectUp(flags) {
|
|
|
12206
12258
|
if (!matchingRoutes.length) {
|
|
12207
12259
|
throw new Error("runner project up did not find any enabled routes for the selected project destination");
|
|
12208
12260
|
}
|
|
12209
|
-
if (!boolFromRaw(flags.json, false)) {
|
|
12210
|
-
process.stdout.write(
|
|
12211
|
-
`${startDetachedRequested ? "Starting detached runner" : "Starting runner"} for project ${summaryPayload.project_id} destination ${summaryPayload.destination_label || summaryPayload.destination_id} using ${matchingRoutes.length} enabled route(s).\n`,
|
|
12212
|
-
);
|
|
12213
|
-
}
|
|
12261
|
+
if (!boolFromRaw(flags.json, false)) {
|
|
12262
|
+
process.stdout.write(
|
|
12263
|
+
`${startDetachedRequested ? "Starting detached runner" : "Starting foreground runner (stops when this session ends)"} for project ${summaryPayload.project_id} destination ${summaryPayload.destination_label || summaryPayload.destination_id} using ${matchingRoutes.length} enabled route(s).\n`,
|
|
12264
|
+
);
|
|
12265
|
+
}
|
|
12214
12266
|
if (startDetachedRequested) {
|
|
12215
12267
|
await runRunnerStartDetachedResolvedRoutes(matchingRoutes, startFlags, "runner project up");
|
|
12216
12268
|
return;
|
|
@@ -12225,39 +12277,89 @@ async function runRunnerProjectUp(flags) {
|
|
|
12225
12277
|
});
|
|
12226
12278
|
}
|
|
12227
12279
|
|
|
12228
|
-
function canStartRunnerDespiteProjectUpApplyFailure({ applyRequested, applyResult, matchingRoutes }) {
|
|
12229
|
-
if (!applyRequested) return false;
|
|
12230
|
-
const normalizedApplyResult = safeObject(applyResult);
|
|
12231
|
-
if (normalizedApplyResult.ok !== false) return false;
|
|
12280
|
+
function canStartRunnerDespiteProjectUpApplyFailure({ applyRequested, applyResult, matchingRoutes }) {
|
|
12281
|
+
if (!applyRequested) return false;
|
|
12282
|
+
const normalizedApplyResult = safeObject(applyResult);
|
|
12283
|
+
if (normalizedApplyResult.ok !== false) return false;
|
|
12232
12284
|
const errorText = String(normalizedApplyResult.error || "").trim();
|
|
12233
12285
|
if (errorText !== "route apply skipped because the Telegram room visibility probe failed") {
|
|
12234
12286
|
return false;
|
|
12235
12287
|
}
|
|
12236
|
-
return ensureArray(matchingRoutes).length > 0;
|
|
12237
|
-
}
|
|
12238
|
-
|
|
12239
|
-
|
|
12240
|
-
const
|
|
12241
|
-
|
|
12242
|
-
|
|
12243
|
-
|
|
12244
|
-
|
|
12245
|
-
|
|
12246
|
-
|
|
12247
|
-
|
|
12248
|
-
|
|
12249
|
-
|
|
12250
|
-
|
|
12251
|
-
|
|
12252
|
-
|
|
12253
|
-
|
|
12254
|
-
|
|
12255
|
-
|
|
12256
|
-
|
|
12257
|
-
|
|
12258
|
-
|
|
12259
|
-
|
|
12260
|
-
|
|
12288
|
+
return ensureArray(matchingRoutes).length > 0;
|
|
12289
|
+
}
|
|
12290
|
+
|
|
12291
|
+
function resolveRunnerProjectUpExecutionPolicy(flags = {}) {
|
|
12292
|
+
const applyRequested = Object.prototype.hasOwnProperty.call(flags, "apply")
|
|
12293
|
+
? boolFromRaw(flags.apply, true)
|
|
12294
|
+
: true;
|
|
12295
|
+
const startDetachedRequested = Object.prototype.hasOwnProperty.call(flags, "start-detached")
|
|
12296
|
+
? boolFromRaw(flags["start-detached"], true)
|
|
12297
|
+
: boolFromRaw(flags.detached, false);
|
|
12298
|
+
const startRequested = Object.prototype.hasOwnProperty.call(flags, "start")
|
|
12299
|
+
? boolFromRaw(flags.start, true)
|
|
12300
|
+
: false;
|
|
12301
|
+
const shouldStartRunner = startRequested || startDetachedRequested;
|
|
12302
|
+
return {
|
|
12303
|
+
applyRequested,
|
|
12304
|
+
startDetachedRequested,
|
|
12305
|
+
startRequested,
|
|
12306
|
+
shouldStartRunner,
|
|
12307
|
+
foregroundStartRequested: startRequested && !startDetachedRequested,
|
|
12308
|
+
};
|
|
12309
|
+
}
|
|
12310
|
+
|
|
12311
|
+
function buildRunnerStartDetachedCommand(flags = {}) {
|
|
12312
|
+
return [CLI_NAME, "runner", "start-detached", ...serializeCLIFlags(flags, {
|
|
12313
|
+
omit: ["json", "start", "start-detached", "detached"],
|
|
12314
|
+
})].join(" ");
|
|
12315
|
+
}
|
|
12316
|
+
|
|
12317
|
+
function buildRunnerProjectUpNextSteps({
|
|
12318
|
+
applyRequested,
|
|
12319
|
+
shouldStartRunner,
|
|
12320
|
+
projectID,
|
|
12321
|
+
provider,
|
|
12322
|
+
destinationID,
|
|
12323
|
+
matchingRoutes,
|
|
12324
|
+
startFlags,
|
|
12325
|
+
}) {
|
|
12326
|
+
const nextSteps = [];
|
|
12327
|
+
const routeNames = ensureArray(matchingRoutes).map((route) => normalizeRunnerRoute(route).name).filter(Boolean);
|
|
12328
|
+
if (!applyRequested) {
|
|
12329
|
+
nextSteps.push(`${CLI_NAME} runner project up --project-id ${projectID} --provider ${provider} --destination-id ${destinationID} --apply true --start false`);
|
|
12330
|
+
}
|
|
12331
|
+
if (!shouldStartRunner) {
|
|
12332
|
+
if (routeNames.length > 0) {
|
|
12333
|
+
nextSteps.push(`${CLI_NAME} runner show --route-name ${routeNames[0]}`);
|
|
12334
|
+
nextSteps.push(buildRunnerStartDetachedCommand(startFlags));
|
|
12335
|
+
} else if (applyRequested) {
|
|
12336
|
+
nextSteps.push(`${CLI_NAME} runner project up --project-id ${projectID} --provider ${provider} --destination-id ${destinationID} --apply true --start false`);
|
|
12337
|
+
}
|
|
12338
|
+
return nextSteps;
|
|
12339
|
+
}
|
|
12340
|
+
nextSteps.push(`${CLI_NAME} runner show --route-name ${routeNames[0] || "<route_name>"}`);
|
|
12341
|
+
return nextSteps;
|
|
12342
|
+
}
|
|
12343
|
+
|
|
12344
|
+
async function buildRunnerProjectUpResult(flags = {}) {
|
|
12345
|
+
const ui = createPrompter();
|
|
12346
|
+
try {
|
|
12347
|
+
if (shouldRenderPromptChrome(flags)) {
|
|
12348
|
+
ui.setFlow("RUNNER PROJECT UP", "Audit one project destination, create missing runner routes, and optionally start polling when explicitly requested");
|
|
12349
|
+
}
|
|
12350
|
+
const provider = String(flags.provider || "").trim()
|
|
12351
|
+
? normalizeBotProvider(flags.provider)
|
|
12352
|
+
: "telegram";
|
|
12353
|
+
if (provider !== "telegram") {
|
|
12354
|
+
throw new Error("runner project up currently supports only --provider telegram");
|
|
12355
|
+
}
|
|
12356
|
+
const {
|
|
12357
|
+
applyRequested,
|
|
12358
|
+
startDetachedRequested,
|
|
12359
|
+
startRequested,
|
|
12360
|
+
shouldStartRunner,
|
|
12361
|
+
foregroundStartRequested,
|
|
12362
|
+
} = resolveRunnerProjectUpExecutionPolicy(flags);
|
|
12261
12363
|
const roleFilter = parseRunnerProjectUpRoles(flags);
|
|
12262
12364
|
const botNameFilter = String(flags["bot-name"] || "").trim();
|
|
12263
12365
|
const botIDFilter = String(flags["bot-id"] || "").trim();
|
|
@@ -12340,30 +12442,35 @@ async function buildRunnerProjectUpResult(flags = {}) {
|
|
|
12340
12442
|
route_apply_requested: applyRequested,
|
|
12341
12443
|
route_apply_changed: Boolean(applyResult.changed),
|
|
12342
12444
|
route_config_file: String(applyResult.filePath || auditPayload.routeSuggestionConfigFilePath || "-").trim() || "-",
|
|
12343
|
-
enabled_routes_for_selection: matchingRoutes.map((route) => normalizeRunnerRoute(route).name).filter(Boolean),
|
|
12344
|
-
start_requested: shouldStartRunner,
|
|
12345
|
-
start_detached_requested: startDetachedRequested,
|
|
12346
|
-
next_steps: [],
|
|
12347
|
-
applied_routes: ensureArray(applyResult.appliedRoutes).map((item) => safeObject(item)),
|
|
12348
|
-
disabled_routes: ensureArray(applyResult.disabledRoutes).map((item) => safeObject(item)),
|
|
12349
|
-
};
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
|
|
12355
|
-
|
|
12356
|
-
|
|
12357
|
-
|
|
12358
|
-
|
|
12359
|
-
|
|
12360
|
-
|
|
12361
|
-
summaryPayload.
|
|
12362
|
-
}
|
|
12363
|
-
|
|
12364
|
-
|
|
12365
|
-
|
|
12366
|
-
|
|
12445
|
+
enabled_routes_for_selection: matchingRoutes.map((route) => normalizeRunnerRoute(route).name).filter(Boolean),
|
|
12446
|
+
start_requested: shouldStartRunner,
|
|
12447
|
+
start_detached_requested: startDetachedRequested,
|
|
12448
|
+
next_steps: [],
|
|
12449
|
+
applied_routes: ensureArray(applyResult.appliedRoutes).map((item) => safeObject(item)),
|
|
12450
|
+
disabled_routes: ensureArray(applyResult.disabledRoutes).map((item) => safeObject(item)),
|
|
12451
|
+
};
|
|
12452
|
+
summaryPayload.next_steps.push(...buildRunnerProjectUpNextSteps({
|
|
12453
|
+
applyRequested,
|
|
12454
|
+
shouldStartRunner,
|
|
12455
|
+
projectID: summaryPayload.project_id,
|
|
12456
|
+
provider,
|
|
12457
|
+
destinationID: summaryPayload.destination_id,
|
|
12458
|
+
matchingRoutes,
|
|
12459
|
+
startFlags,
|
|
12460
|
+
}));
|
|
12461
|
+
if (applyRequested && applyResult.ok === false && applyResult.error) {
|
|
12462
|
+
if (applyFailureWarningOnly) {
|
|
12463
|
+
summaryPayload.warning = String(applyResult.error || "").trim();
|
|
12464
|
+
} else {
|
|
12465
|
+
summaryPayload.error = String(applyResult.error || "").trim();
|
|
12466
|
+
}
|
|
12467
|
+
}
|
|
12468
|
+
if (foregroundStartRequested && !summaryPayload.warning && !summaryPayload.error) {
|
|
12469
|
+
summaryPayload.warning = "foreground runner start was explicitly requested; the runner stops when this terminal session ends. Use runner start-detached for persistent polling.";
|
|
12470
|
+
}
|
|
12471
|
+
return {
|
|
12472
|
+
summaryPayload,
|
|
12473
|
+
applyRequested,
|
|
12367
12474
|
applyResult,
|
|
12368
12475
|
shouldStartRunner,
|
|
12369
12476
|
startDetachedRequested,
|
|
@@ -16855,12 +16962,12 @@ function buildLocalToolSpecs() {
|
|
|
16855
16962
|
"Delete one project context item permanently.",
|
|
16856
16963
|
inputSchema: buildProjectContextGetInputSchema(),
|
|
16857
16964
|
},
|
|
16858
|
-
{
|
|
16859
|
-
name: "runner.project_up",
|
|
16860
|
-
description:
|
|
16861
|
-
"Audit one project destination, create missing runner routes, and optionally start polling. Use this before runner.start_detached when the route is not ready yet.",
|
|
16862
|
-
inputSchema: buildRunnerProjectUpInputSchema(),
|
|
16863
|
-
},
|
|
16965
|
+
{
|
|
16966
|
+
name: "runner.project_up",
|
|
16967
|
+
description:
|
|
16968
|
+
"Audit one project destination, create missing runner routes, and optionally start polling only when explicitly requested. Use this before runner.start_detached when the route is not ready yet.",
|
|
16969
|
+
inputSchema: buildRunnerProjectUpInputSchema(),
|
|
16970
|
+
},
|
|
16864
16971
|
{
|
|
16865
16972
|
name: "runner.show",
|
|
16866
16973
|
description:
|
|
@@ -19215,12 +19322,50 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
19215
19322
|
push("detached_runner_linux_launcher_selects_supported_terminal", false, String(err?.message || err));
|
|
19216
19323
|
}
|
|
19217
19324
|
|
|
19218
|
-
try {
|
|
19219
|
-
|
|
19220
|
-
|
|
19221
|
-
|
|
19222
|
-
|
|
19223
|
-
|
|
19325
|
+
try {
|
|
19326
|
+
const projectUpPolicy = resolveRunnerProjectUpExecutionPolicy({});
|
|
19327
|
+
push(
|
|
19328
|
+
"runner_project_up_default_policy_is_prepare_only",
|
|
19329
|
+
projectUpPolicy.applyRequested === true
|
|
19330
|
+
&& projectUpPolicy.startRequested === false
|
|
19331
|
+
&& projectUpPolicy.startDetachedRequested === false
|
|
19332
|
+
&& projectUpPolicy.shouldStartRunner === false,
|
|
19333
|
+
`apply=${projectUpPolicy.applyRequested} start=${projectUpPolicy.startRequested} detached=${projectUpPolicy.startDetachedRequested} shouldStart=${projectUpPolicy.shouldStartRunner}`,
|
|
19334
|
+
);
|
|
19335
|
+
} catch (err) {
|
|
19336
|
+
push("runner_project_up_default_policy_is_prepare_only", false, String(err?.message || err));
|
|
19337
|
+
}
|
|
19338
|
+
|
|
19339
|
+
try {
|
|
19340
|
+
const nextSteps = buildRunnerProjectUpNextSteps({
|
|
19341
|
+
applyRequested: true,
|
|
19342
|
+
shouldStartRunner: false,
|
|
19343
|
+
projectID: selftestProjectID,
|
|
19344
|
+
provider: "telegram",
|
|
19345
|
+
destinationID: "dest-1",
|
|
19346
|
+
matchingRoutes: [{ name: "telegram-monitor-selftest", enabled: true }],
|
|
19347
|
+
startFlags: {
|
|
19348
|
+
"project-id": selftestProjectID,
|
|
19349
|
+
provider: "telegram",
|
|
19350
|
+
"destination-id": "dest-1",
|
|
19351
|
+
},
|
|
19352
|
+
});
|
|
19353
|
+
push(
|
|
19354
|
+
"runner_project_up_next_steps_prefer_start_detached",
|
|
19355
|
+
nextSteps.includes(`${CLI_NAME} runner show --route-name telegram-monitor-selftest`)
|
|
19356
|
+
&& nextSteps.includes(`${CLI_NAME} runner start-detached --project-id ${selftestProjectID} --provider telegram --destination-id dest-1`),
|
|
19357
|
+
nextSteps.join(" | "),
|
|
19358
|
+
);
|
|
19359
|
+
} catch (err) {
|
|
19360
|
+
push("runner_project_up_next_steps_prefer_start_detached", false, String(err?.message || err));
|
|
19361
|
+
}
|
|
19362
|
+
|
|
19363
|
+
try {
|
|
19364
|
+
push(
|
|
19365
|
+
"runner_project_up_probe_failure_allows_existing_route_start",
|
|
19366
|
+
canStartRunnerDespiteProjectUpApplyFailure({
|
|
19367
|
+
applyRequested: true,
|
|
19368
|
+
applyResult: { ok: false, error: "route apply skipped because the Telegram room visibility probe failed" },
|
|
19224
19369
|
matchingRoutes: [{ name: "telegram-monitor-selftest" }],
|
|
19225
19370
|
}) === true,
|
|
19226
19371
|
"existing_routes=1 probe_failure=warning_only",
|
package/lib/runner-recovery.mjs
CHANGED
|
@@ -46,6 +46,58 @@ function uniqueOrderedStrings(values, normalizer = (value) => String(value || ""
|
|
|
46
46
|
return output;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function buildRunnerRequestRecoveryAuthoritativeDecisionBundle(requestRaw) {
|
|
50
|
+
return safeObject(safeObject(requestRaw).authoritative_decision_bundle);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function buildRunnerRequestRecoveryPreferredExecutionContractTargets(requestRaw) {
|
|
54
|
+
const request = safeObject(requestRaw);
|
|
55
|
+
const decisionBundle = buildRunnerRequestRecoveryAuthoritativeDecisionBundle(request);
|
|
56
|
+
return uniqueOrderedStrings(
|
|
57
|
+
ensureArray(decisionBundle.execution_contract_targets).length
|
|
58
|
+
? decisionBundle.execution_contract_targets
|
|
59
|
+
: ensureArray(request.execution_contract_targets).length
|
|
60
|
+
? request.execution_contract_targets
|
|
61
|
+
: ensureArray(request.root_execution_contract_targets),
|
|
62
|
+
normalizeTelegramMentionUsername,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function buildRunnerRequestRecoveryPreferredNextExpectedResponders(requestRaw) {
|
|
67
|
+
const request = safeObject(requestRaw);
|
|
68
|
+
const decisionBundle = buildRunnerRequestRecoveryAuthoritativeDecisionBundle(request);
|
|
69
|
+
return uniqueOrderedStrings(
|
|
70
|
+
ensureArray(decisionBundle.next_expected_responders).length
|
|
71
|
+
? decisionBundle.next_expected_responders
|
|
72
|
+
: ensureArray(request.next_expected_responders).length
|
|
73
|
+
? request.next_expected_responders
|
|
74
|
+
: ensureArray(request.root_next_expected_responders),
|
|
75
|
+
normalizeTelegramMentionUsername,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function runnerRequestShouldRemainRunningDuringRecovery(requestRaw) {
|
|
80
|
+
const request = safeObject(requestRaw);
|
|
81
|
+
const decisionBundle = buildRunnerRequestRecoveryAuthoritativeDecisionBundle(request);
|
|
82
|
+
const preferredExecutionContractType = String(
|
|
83
|
+
decisionBundle.execution_contract_type
|
|
84
|
+
|| request.execution_contract_type
|
|
85
|
+
|| request.root_execution_contract_type
|
|
86
|
+
|| "",
|
|
87
|
+
).trim().toLowerCase();
|
|
88
|
+
const preferredExecutionContractTargets = buildRunnerRequestRecoveryPreferredExecutionContractTargets(request);
|
|
89
|
+
const preferredNextExpectedResponders = buildRunnerRequestRecoveryPreferredNextExpectedResponders(request);
|
|
90
|
+
if (decisionBundle.should_close_after_reply === true) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
if (decisionBundle.should_close_after_reply === false) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
return preferredExecutionContractType === "delegation"
|
|
97
|
+
|| preferredExecutionContractTargets.length > 0
|
|
98
|
+
|| preferredNextExpectedResponders.length > 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
49
101
|
function normalizeRunnerRequestStatus(rawStatus) {
|
|
50
102
|
const status = String(rawStatus || "").trim().toLowerCase();
|
|
51
103
|
return [
|
|
@@ -144,25 +196,39 @@ export function buildRunnerRequestRecoveryPatchFromRouteState(currentStateRaw, r
|
|
|
144
196
|
const requestStatus = normalizeRunnerRequestStatus(request.status);
|
|
145
197
|
if (!isFinalRunnerRequestStatus(requestStatus)) {
|
|
146
198
|
const routeAction = String(routeState.last_action || "").trim().toLowerCase();
|
|
199
|
+
const shouldRemainRunning = runnerRequestShouldRemainRunningDuringRecovery(request);
|
|
147
200
|
if (routeAction === "replied") {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
201
|
+
if (shouldRemainRunning) {
|
|
202
|
+
patch.status = "running";
|
|
203
|
+
patch.completed_at = "";
|
|
204
|
+
patch.closed_at = "";
|
|
205
|
+
patch.closed_reason = "";
|
|
206
|
+
} else {
|
|
207
|
+
patch.status = "completed";
|
|
208
|
+
patch.completed_at = firstNonEmptyString([
|
|
209
|
+
request.completed_at,
|
|
210
|
+
request.updated_at,
|
|
211
|
+
new Date().toISOString(),
|
|
212
|
+
]);
|
|
213
|
+
}
|
|
154
214
|
} else if (routeAction === "error" || routeAction === "skipped" || routeAction === "closed") {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
215
|
+
if (shouldRemainRunning) {
|
|
216
|
+
patch.status = "running";
|
|
217
|
+
patch.closed_at = "";
|
|
218
|
+
patch.closed_reason = "";
|
|
219
|
+
} else {
|
|
220
|
+
patch.status = "closed";
|
|
221
|
+
patch.closed_at = firstNonEmptyString([
|
|
222
|
+
request.closed_at,
|
|
223
|
+
request.updated_at,
|
|
224
|
+
new Date().toISOString(),
|
|
225
|
+
]);
|
|
226
|
+
patch.closed_reason = firstNonEmptyString([
|
|
227
|
+
request.closed_reason,
|
|
228
|
+
routeState.last_reason,
|
|
229
|
+
routeAction,
|
|
230
|
+
]);
|
|
231
|
+
}
|
|
166
232
|
}
|
|
167
233
|
}
|
|
168
234
|
if (Object.keys(patch).length) {
|
|
@@ -2334,8 +2334,238 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
2334
2334
|
`primary=${String(sharedHumanClaimPrimary.requestKey || "(none)")} peer=${String(sharedHumanClaimPeer.requestKey || "(none)")} peer_reason=${String(sharedHumanClaimPeer.reason || "(none)")} count=${sharedHumanRequestCount} canonical=${String(safeObject(sharedHumanRequests[0]).canonical_human_message_key || "(none)")}`,
|
|
2335
2335
|
);
|
|
2336
2336
|
|
|
2337
|
+
const canonicalConversationVariantBody = "@RyoAI_bot @RyoAI2_bot @RyoAI3_bot 같은 사람 메시지";
|
|
2338
|
+
const canonicalConversationVariantPrimary = {
|
|
2339
|
+
id: "comment-request-shared-human-conversation-1",
|
|
2340
|
+
createdAt: "2026-04-01T06:10:00.000Z",
|
|
2341
|
+
updatedAt: "2026-04-01T06:10:00.000Z",
|
|
2342
|
+
parsedArchive: {
|
|
2343
|
+
kind: "telegram_message",
|
|
2344
|
+
chatID: "-100124",
|
|
2345
|
+
chatType: "supergroup",
|
|
2346
|
+
body: canonicalConversationVariantBody,
|
|
2347
|
+
messageID: 1290,
|
|
2348
|
+
senderID: "7002",
|
|
2349
|
+
senderIsBot: false,
|
|
2350
|
+
occurredAt: "2026-04-01T06:10:00.000Z",
|
|
2351
|
+
mentionUsernames: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
2352
|
+
},
|
|
2353
|
+
};
|
|
2354
|
+
const canonicalConversationVariantPeer = {
|
|
2355
|
+
id: "comment-request-shared-human-conversation-2",
|
|
2356
|
+
createdAt: "2026-04-01T06:10:00.000Z",
|
|
2357
|
+
updatedAt: "2026-04-01T06:10:00.000Z",
|
|
2358
|
+
parsedArchive: {
|
|
2359
|
+
kind: "telegram_message",
|
|
2360
|
+
chatID: "-100124",
|
|
2361
|
+
chatType: "supergroup",
|
|
2362
|
+
body: canonicalConversationVariantBody,
|
|
2363
|
+
messageID: 1290,
|
|
2364
|
+
senderID: "7002",
|
|
2365
|
+
senderIsBot: false,
|
|
2366
|
+
occurredAt: "2026-04-01T06:10:00.000Z",
|
|
2367
|
+
mentionUsernames: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
2368
|
+
conversationID: "comment-request-shared-human-conversation-1",
|
|
2369
|
+
},
|
|
2370
|
+
};
|
|
2371
|
+
const canonicalConversationVariantPrimaryClaim = await claimRunnerRequestForHumanComment({
|
|
2372
|
+
normalizedRoute: sharedHumanRouteRyoai1,
|
|
2373
|
+
routeKey: sharedHumanRouteRyoai1Key,
|
|
2374
|
+
selectedRecord: canonicalConversationVariantPrimary,
|
|
2375
|
+
selectedBotUsernames: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
2376
|
+
normalizedIntent: "discussion_request",
|
|
2377
|
+
authoritativeSourceMessageEnvelope: {
|
|
2378
|
+
chat_id: "-100124",
|
|
2379
|
+
message_id: 1290,
|
|
2380
|
+
sender_id: "7002",
|
|
2381
|
+
sender_is_bot: false,
|
|
2382
|
+
occurred_at: "2026-04-01T06:10:00.000Z",
|
|
2383
|
+
body: canonicalConversationVariantBody,
|
|
2384
|
+
source_origin: "local_telegram_inbound",
|
|
2385
|
+
source_route_key: sharedHumanRouteRyoai1Key,
|
|
2386
|
+
source_bot_username: "ryoai_bot",
|
|
2387
|
+
},
|
|
2388
|
+
});
|
|
2389
|
+
const canonicalConversationVariantPeerClaim = await claimRunnerRequestForHumanComment({
|
|
2390
|
+
normalizedRoute: sharedHumanRouteRyoai3,
|
|
2391
|
+
routeKey: sharedHumanRouteRyoai3Key,
|
|
2392
|
+
selectedRecord: canonicalConversationVariantPeer,
|
|
2393
|
+
selectedBotUsernames: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
2394
|
+
normalizedIntent: "discussion_request",
|
|
2395
|
+
authoritativeSourceMessageEnvelope: {
|
|
2396
|
+
chat_id: "-100124",
|
|
2397
|
+
message_id: 1290,
|
|
2398
|
+
sender_id: "7002",
|
|
2399
|
+
sender_is_bot: false,
|
|
2400
|
+
occurred_at: "2026-04-01T06:10:00.000Z",
|
|
2401
|
+
body: canonicalConversationVariantBody,
|
|
2402
|
+
source_origin: "local_telegram_inbound",
|
|
2403
|
+
source_route_key: sharedHumanRouteRyoai3Key,
|
|
2404
|
+
source_bot_username: "ryoai3_bot",
|
|
2405
|
+
},
|
|
2406
|
+
});
|
|
2407
|
+
const canonicalConversationVariantState = loadBotRunnerState();
|
|
2408
|
+
const canonicalConversationVariantRequests = Object.values(safeObject(canonicalConversationVariantState.requests))
|
|
2409
|
+
.filter((entryRaw) => {
|
|
2410
|
+
const entry = safeObject(entryRaw);
|
|
2411
|
+
return String(entry.chat_id || "") === "-100124"
|
|
2412
|
+
&& intFromRawAllowZero(entry.source_message_id, 0) === 1290;
|
|
2413
|
+
});
|
|
2414
|
+
push(
|
|
2415
|
+
"runner_human_opening_request_key_ignores_conversation_id_for_canonical_inbound",
|
|
2416
|
+
canonicalConversationVariantPrimaryClaim.ok === true
|
|
2417
|
+
&& (canonicalConversationVariantPeerClaim.ok === true || String(canonicalConversationVariantPeerClaim.reason || "") === "request_already_claimed")
|
|
2418
|
+
&& String(canonicalConversationVariantPrimaryClaim.requestKey || "") === String(canonicalConversationVariantPeerClaim.requestKey || "")
|
|
2419
|
+
&& canonicalConversationVariantRequests.length === 1,
|
|
2420
|
+
`primary=${String(canonicalConversationVariantPrimaryClaim.requestKey || "(none)")} peer=${String(canonicalConversationVariantPeerClaim.requestKey || "(none)")} peer_reason=${String(canonicalConversationVariantPeerClaim.reason || "(none)")} count=${canonicalConversationVariantRequests.length}`,
|
|
2421
|
+
);
|
|
2422
|
+
|
|
2423
|
+
const sharedDirectTakeoverBody = "@RyoAI_bot @RyoAI2_bot @RyoAI3_bot 같이 답해";
|
|
2424
|
+
const sharedDirectTakeoverRecordLead = {
|
|
2425
|
+
id: "comment-request-shared-direct-1",
|
|
2426
|
+
createdAt: "2026-04-01T06:20:00.000Z",
|
|
2427
|
+
updatedAt: "2026-04-01T06:20:00.000Z",
|
|
2428
|
+
parsedArchive: {
|
|
2429
|
+
kind: "telegram_message",
|
|
2430
|
+
chatID: "-100125",
|
|
2431
|
+
chatType: "supergroup",
|
|
2432
|
+
body: sharedDirectTakeoverBody,
|
|
2433
|
+
messageID: 1291,
|
|
2434
|
+
senderID: "7003",
|
|
2435
|
+
senderIsBot: false,
|
|
2436
|
+
occurredAt: "2026-04-01T06:20:00.000Z",
|
|
2437
|
+
mentionUsernames: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
2438
|
+
},
|
|
2439
|
+
};
|
|
2440
|
+
const sharedDirectTakeoverLeadClaim = await claimRunnerRequestForHumanComment({
|
|
2441
|
+
normalizedRoute: sharedHumanRouteRyoai1,
|
|
2442
|
+
routeKey: sharedHumanRouteRyoai1Key,
|
|
2443
|
+
selectedRecord: sharedDirectTakeoverRecordLead,
|
|
2444
|
+
selectedBotUsernames: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
2445
|
+
normalizedIntent: "discussion_request",
|
|
2446
|
+
authoritativeSourceMessageEnvelope: {
|
|
2447
|
+
chat_id: "-100125",
|
|
2448
|
+
message_id: 1291,
|
|
2449
|
+
sender_id: "7003",
|
|
2450
|
+
sender_is_bot: false,
|
|
2451
|
+
occurred_at: "2026-04-01T06:20:00.000Z",
|
|
2452
|
+
body: sharedDirectTakeoverBody,
|
|
2453
|
+
source_origin: "local_telegram_inbound",
|
|
2454
|
+
source_route_key: sharedHumanRouteRyoai1Key,
|
|
2455
|
+
source_bot_username: "ryoai_bot",
|
|
2456
|
+
},
|
|
2457
|
+
});
|
|
2458
|
+
const sharedDirectTakeoverCanonicalKey = buildArchivedInboundMessageKey({
|
|
2459
|
+
chatID: "-100125",
|
|
2460
|
+
messageID: 1291,
|
|
2461
|
+
messageThreadID: 0,
|
|
2462
|
+
kind: "telegram_message",
|
|
2463
|
+
senderID: "7003",
|
|
2464
|
+
senderIsBot: false,
|
|
2465
|
+
occurredAt: "2026-04-01T06:20:00.000Z",
|
|
2466
|
+
body: sharedDirectTakeoverBody,
|
|
2467
|
+
}).replace(/^human:/, "");
|
|
2468
|
+
const sharedDirectTakeoverState = loadBotRunnerState();
|
|
2469
|
+
saveBotRunnerState({
|
|
2470
|
+
...sharedDirectTakeoverState,
|
|
2471
|
+
requests: {
|
|
2472
|
+
...safeObject(sharedDirectTakeoverState.requests),
|
|
2473
|
+
[sharedDirectTakeoverLeadClaim.requestKey]: {
|
|
2474
|
+
...safeObject(safeObject(sharedDirectTakeoverState.requests)[sharedDirectTakeoverLeadClaim.requestKey]),
|
|
2475
|
+
canonical_human_message_key: sharedDirectTakeoverCanonicalKey,
|
|
2476
|
+
source_message_id: 1291,
|
|
2477
|
+
source_message_origin: "local_telegram_inbound",
|
|
2478
|
+
source_message_route_key: sharedHumanRouteRyoai1Key,
|
|
2479
|
+
source_message_bot_username: "ryoai_bot",
|
|
2480
|
+
source_message_envelope: {
|
|
2481
|
+
chat_id: "-100125",
|
|
2482
|
+
message_id: 1291,
|
|
2483
|
+
body: sharedDirectTakeoverBody,
|
|
2484
|
+
source_origin: "local_telegram_inbound",
|
|
2485
|
+
source_route_key: sharedHumanRouteRyoai1Key,
|
|
2486
|
+
source_bot_username: "ryoai_bot",
|
|
2487
|
+
},
|
|
2488
|
+
selected_bot_usernames: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
2489
|
+
conversation_initial_responders: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
2490
|
+
conversation_allowed_responders: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
2491
|
+
conversation_intent_mode: "multi_bot_direct",
|
|
2492
|
+
execution_contract_type: "direct_result",
|
|
2493
|
+
execution_contract_targets: ["ryoai_bot"],
|
|
2494
|
+
next_expected_responders: ["ryoai2_bot", "ryoai3_bot"],
|
|
2495
|
+
status: "running",
|
|
2496
|
+
claimed_by_route: sharedHumanRouteRyoai1Key,
|
|
2497
|
+
authoritative_decision_bundle: {
|
|
2498
|
+
schema_version: "runner_conversation_decision.v1",
|
|
2499
|
+
decision_type: "reply_outcome",
|
|
2500
|
+
conversation_intent_mode: "multi_bot_direct",
|
|
2501
|
+
allowed_responders: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
2502
|
+
initial_responders: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
2503
|
+
selected_bot_usernames: ["ryoai_bot"],
|
|
2504
|
+
allow_bot_to_bot: false,
|
|
2505
|
+
execution_contract_type: "direct_result",
|
|
2506
|
+
execution_contract_targets: ["ryoai_bot"],
|
|
2507
|
+
next_expected_responders: ["ryoai2_bot", "ryoai3_bot"],
|
|
2508
|
+
should_close_after_reply: false,
|
|
2509
|
+
turn_kind: "direct_reply",
|
|
2510
|
+
actionable_for_current_route: true,
|
|
2511
|
+
visible_handoff_required: false,
|
|
2512
|
+
visible_handoff_targets: [],
|
|
2513
|
+
reasoning_summary: "shared direct reply remains open for the remaining directly mentioned bots",
|
|
2514
|
+
},
|
|
2515
|
+
},
|
|
2516
|
+
},
|
|
2517
|
+
});
|
|
2518
|
+
const sharedDirectTakeoverRecordPeer = {
|
|
2519
|
+
id: "comment-request-shared-direct-2",
|
|
2520
|
+
createdAt: "2026-04-01T06:20:00.000Z",
|
|
2521
|
+
updatedAt: "2026-04-01T06:20:00.000Z",
|
|
2522
|
+
parsedArchive: {
|
|
2523
|
+
kind: "telegram_message",
|
|
2524
|
+
chatID: "-100125",
|
|
2525
|
+
chatType: "supergroup",
|
|
2526
|
+
body: sharedDirectTakeoverBody,
|
|
2527
|
+
messageID: 1291,
|
|
2528
|
+
senderID: "7003",
|
|
2529
|
+
senderIsBot: false,
|
|
2530
|
+
occurredAt: "2026-04-01T06:20:00.000Z",
|
|
2531
|
+
mentionUsernames: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
2532
|
+
conversationID: "comment-request-shared-direct-1",
|
|
2533
|
+
},
|
|
2534
|
+
};
|
|
2535
|
+
const sharedDirectTakeoverPeerClaim = await claimRunnerRequestForHumanComment({
|
|
2536
|
+
normalizedRoute: sharedHumanRouteRyoai3,
|
|
2537
|
+
routeKey: sharedHumanRouteRyoai3Key,
|
|
2538
|
+
selectedRecord: sharedDirectTakeoverRecordPeer,
|
|
2539
|
+
selectedBotUsernames: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
2540
|
+
normalizedIntent: "discussion_request",
|
|
2541
|
+
authoritativeSourceMessageEnvelope: {
|
|
2542
|
+
chat_id: "-100125",
|
|
2543
|
+
message_id: 1291,
|
|
2544
|
+
sender_id: "7003",
|
|
2545
|
+
sender_is_bot: false,
|
|
2546
|
+
occurred_at: "2026-04-01T06:20:00.000Z",
|
|
2547
|
+
body: sharedDirectTakeoverBody,
|
|
2548
|
+
source_origin: "local_telegram_inbound",
|
|
2549
|
+
source_route_key: sharedHumanRouteRyoai3Key,
|
|
2550
|
+
source_bot_username: "ryoai3_bot",
|
|
2551
|
+
},
|
|
2552
|
+
});
|
|
2553
|
+
const sharedDirectTakeoverRequest = safeObject(
|
|
2554
|
+
safeObject(loadBotRunnerState().requests)[sharedDirectTakeoverLeadClaim.requestKey],
|
|
2555
|
+
);
|
|
2556
|
+
push(
|
|
2557
|
+
"runner_shared_human_direct_reply_allows_pending_route_takeover_without_rekeying",
|
|
2558
|
+
sharedDirectTakeoverLeadClaim.ok === true
|
|
2559
|
+
&& sharedDirectTakeoverPeerClaim.ok === true
|
|
2560
|
+
&& String(sharedDirectTakeoverPeerClaim.requestKey || "") === String(sharedDirectTakeoverLeadClaim.requestKey || "")
|
|
2561
|
+
&& String(sharedDirectTakeoverRequest.claimed_by_route || "") === sharedHumanRouteRyoai3Key
|
|
2562
|
+
&& String(sharedDirectTakeoverRequest.source_message_route_key || "") === sharedHumanRouteRyoai1Key
|
|
2563
|
+
&& String(safeObject(sharedDirectTakeoverRequest.source_message_envelope).source_route_key || "") === sharedHumanRouteRyoai1Key,
|
|
2564
|
+
`lead=${String(sharedDirectTakeoverLeadClaim.requestKey || "(none)")} peer=${String(sharedDirectTakeoverPeerClaim.requestKey || "(none)")} claimed_by=${String(sharedDirectTakeoverRequest.claimed_by_route || "(none)")} source_route=${String(sharedDirectTakeoverRequest.source_message_route_key || "(none)")}`,
|
|
2565
|
+
);
|
|
2566
|
+
|
|
2337
2567
|
const rootTaskRecord = {
|
|
2338
|
-
id: "comment-request-root-task-1",
|
|
2568
|
+
id: "comment-request-root-task-1",
|
|
2339
2569
|
createdAt: "2026-03-22T00:05:00.000Z",
|
|
2340
2570
|
updatedAt: "2026-03-22T00:05:00.000Z",
|
|
2341
2571
|
parsedArchive: {
|
|
@@ -17529,10 +17759,46 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
17529
17759
|
&& !Number(recoveredSourceEnvelope.message_id || 0),
|
|
17530
17760
|
`origin=${String(recoveryPatch.source_message_origin || "(none)")} route=${String(recoveryPatch.source_message_route_key || "(none)")} bot=${String(recoveryPatch.source_message_bot_username || "(none)")} message=${String(recoveredSourceEnvelope.message_id || "(none)")} thread=${String(recoveryPatch.source_message_thread_id || "(none)")}`,
|
|
17531
17761
|
);
|
|
17532
|
-
|
|
17533
|
-
|
|
17534
|
-
|
|
17535
|
-
|
|
17762
|
+
|
|
17763
|
+
const runningRecoveryPatch = buildRunnerRequestRecoveryPatchFromRouteState(
|
|
17764
|
+
{
|
|
17765
|
+
routes: {
|
|
17766
|
+
[provenanceRouteKey]: {
|
|
17767
|
+
last_source_message_id: 780,
|
|
17768
|
+
last_action: "replied",
|
|
17769
|
+
},
|
|
17770
|
+
},
|
|
17771
|
+
},
|
|
17772
|
+
{
|
|
17773
|
+
request_key: "request-provenance-running-recovery",
|
|
17774
|
+
claimed_by_route: provenanceRouteKey,
|
|
17775
|
+
chat_id: "-100123",
|
|
17776
|
+
source_message_id: 780,
|
|
17777
|
+
status: "running",
|
|
17778
|
+
execution_contract_type: "direct_result",
|
|
17779
|
+
next_expected_responders: ["ryoai2_bot"],
|
|
17780
|
+
authoritative_decision_bundle: {
|
|
17781
|
+
schema_version: "runner_conversation_decision.v1",
|
|
17782
|
+
decision_type: "reply_outcome",
|
|
17783
|
+
conversation_intent_mode: "multi_bot_direct",
|
|
17784
|
+
execution_contract_type: "direct_result",
|
|
17785
|
+
next_expected_responders: ["ryoai2_bot"],
|
|
17786
|
+
should_close_after_reply: false,
|
|
17787
|
+
},
|
|
17788
|
+
},
|
|
17789
|
+
provenanceRouteKey,
|
|
17790
|
+
);
|
|
17791
|
+
push(
|
|
17792
|
+
"runner_request_recovery_keeps_running_requests_with_pending_responders",
|
|
17793
|
+
String(runningRecoveryPatch.status || "") === "running"
|
|
17794
|
+
&& !String(runningRecoveryPatch.completed_at || "").trim()
|
|
17795
|
+
&& !String(runningRecoveryPatch.closed_at || "").trim(),
|
|
17796
|
+
`status=${String(runningRecoveryPatch.status || "(none)")} completed_at=${String(runningRecoveryPatch.completed_at || "(none)")} closed_at=${String(runningRecoveryPatch.closed_at || "(none)")}`,
|
|
17797
|
+
);
|
|
17798
|
+
|
|
17799
|
+
saveBotRunnerState({
|
|
17800
|
+
routes: {
|
|
17801
|
+
[provenanceRouteKey]: {
|
|
17536
17802
|
active_request_key: "request-provenance-continuation",
|
|
17537
17803
|
last_source_message_id: 779,
|
|
17538
17804
|
recent_local_inbound_envelopes: {
|