metheus-governance-mcp-cli 0.2.292 → 0.2.294
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 +503 -165
- package/lib/local-ai-adapters.mjs +5 -0
- package/lib/runner-local-inbound-receipts.mjs +212 -0
- package/lib/runner-orchestration-decision-bundle.mjs +30 -0
- package/lib/runner-orchestration-entrypoints.mjs +6 -26
- package/lib/runner-orchestration-intent-contracts.mjs +0 -30
- package/lib/runner-orchestration-visibility.mjs +4 -100
- package/lib/runner-orchestration.mjs +0 -51
- package/lib/runner-runtime.mjs +15 -119
- package/lib/selftest-runner-scenarios.mjs +137 -6
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -151,6 +151,9 @@ import {
|
|
|
151
151
|
selectPendingArchiveComments,
|
|
152
152
|
printRunnerResult,
|
|
153
153
|
} from "./lib/runner-helpers.mjs";
|
|
154
|
+
import {
|
|
155
|
+
normalizeRunnerRecentLocalInboundReceiptMap,
|
|
156
|
+
} from "./lib/runner-local-inbound-receipts.mjs";
|
|
154
157
|
import {
|
|
155
158
|
normalizeRunnerConversationDecisionBundle,
|
|
156
159
|
validateRunnerConversationDecisionBundle,
|
|
@@ -1966,88 +1969,6 @@ function prefersRunnerStateRecord(candidate, current) {
|
|
|
1966
1969
|
return false;
|
|
1967
1970
|
}
|
|
1968
1971
|
|
|
1969
|
-
function normalizeRunnerRecentLocalInboundReceiptRecord(rawReceipt, fallbackKey = "") {
|
|
1970
|
-
const receipt = safeObject(rawReceipt);
|
|
1971
|
-
const chatID = String(receipt.chat_id || receipt.chatID || "").trim();
|
|
1972
|
-
const messageID = intFromRawAllowZero(receipt.message_id ?? receipt.messageID, 0);
|
|
1973
|
-
const receiptKey = String(fallbackKey || `${chatID}:${messageID}`).trim();
|
|
1974
|
-
if (!receiptKey || !chatID || !(messageID > 0)) {
|
|
1975
|
-
return null;
|
|
1976
|
-
}
|
|
1977
|
-
const normalized = {
|
|
1978
|
-
chat_id: chatID,
|
|
1979
|
-
message_id: messageID,
|
|
1980
|
-
receipt_origin: firstNonEmptyString([
|
|
1981
|
-
receipt.receipt_origin,
|
|
1982
|
-
receipt.receiptOrigin,
|
|
1983
|
-
receipt.source_origin,
|
|
1984
|
-
receipt.sourceOrigin,
|
|
1985
|
-
"local_telegram_inbound",
|
|
1986
|
-
]),
|
|
1987
|
-
receipt_route_key: firstNonEmptyString([
|
|
1988
|
-
receipt.receipt_route_key,
|
|
1989
|
-
receipt.receiptRouteKey,
|
|
1990
|
-
receipt.source_route_key,
|
|
1991
|
-
receipt.sourceRouteKey,
|
|
1992
|
-
]),
|
|
1993
|
-
receipt_bot_username: normalizeTelegramMentionUsername(
|
|
1994
|
-
firstNonEmptyString([
|
|
1995
|
-
receipt.receipt_bot_username,
|
|
1996
|
-
receipt.receiptBotUsername,
|
|
1997
|
-
receipt.source_bot_username,
|
|
1998
|
-
receipt.sourceBotUsername,
|
|
1999
|
-
]),
|
|
2000
|
-
),
|
|
2001
|
-
kind: firstNonEmptyString([receipt.kind, "telegram_message"]),
|
|
2002
|
-
sender: firstNonEmptyString([receipt.sender, receipt.from_name, receipt.fromName]),
|
|
2003
|
-
sender_username: firstNonEmptyString([
|
|
2004
|
-
receipt.sender_username,
|
|
2005
|
-
receipt.senderUsername,
|
|
2006
|
-
receipt.from_username,
|
|
2007
|
-
receipt.fromUsername,
|
|
2008
|
-
]),
|
|
2009
|
-
sender_is_bot: Boolean(receipt.sender_is_bot ?? receipt.senderIsBot ?? false),
|
|
2010
|
-
body: firstNonEmptyString([receipt.body, receipt.text]),
|
|
2011
|
-
occurred_at: firstNonEmptyString([receipt.occurred_at, receipt.occurredAt]),
|
|
2012
|
-
received_at: firstNonEmptyString([receipt.received_at, receipt.receivedAt, new Date().toISOString()]),
|
|
2013
|
-
};
|
|
2014
|
-
const senderID = firstNonEmptyString([receipt.sender_id, receipt.senderID, receipt.from_id, receipt.fromID]);
|
|
2015
|
-
if (senderID) {
|
|
2016
|
-
normalized.sender_id = senderID;
|
|
2017
|
-
}
|
|
2018
|
-
const updateID = intFromRawAllowZero(receipt.update_id ?? receipt.updateID, 0);
|
|
2019
|
-
if (updateID > 0) {
|
|
2020
|
-
normalized.update_id = updateID;
|
|
2021
|
-
}
|
|
2022
|
-
const messageThreadID = intFromRawAllowZero(receipt.message_thread_id ?? receipt.messageThreadID, 0);
|
|
2023
|
-
if (messageThreadID > 0) {
|
|
2024
|
-
normalized.message_thread_id = messageThreadID;
|
|
2025
|
-
}
|
|
2026
|
-
const replyToMessageID = intFromRawAllowZero(receipt.reply_to_message_id ?? receipt.replyToMessageID, 0);
|
|
2027
|
-
if (replyToMessageID > 0) {
|
|
2028
|
-
normalized.reply_to_message_id = replyToMessageID;
|
|
2029
|
-
}
|
|
2030
|
-
const chatType = firstNonEmptyString([receipt.chat_type, receipt.chatType]);
|
|
2031
|
-
if (chatType) {
|
|
2032
|
-
normalized.chat_type = chatType;
|
|
2033
|
-
}
|
|
2034
|
-
const chatTitle = firstNonEmptyString([receipt.chat_title, receipt.chatTitle]);
|
|
2035
|
-
if (chatTitle) {
|
|
2036
|
-
normalized.chat_title = chatTitle;
|
|
2037
|
-
}
|
|
2038
|
-
return [receiptKey, normalized];
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
|
-
function normalizeRunnerRecentLocalInboundReceiptMap(rawReceipts) {
|
|
2042
|
-
const normalizedEntries = [];
|
|
2043
|
-
for (const [key, value] of Object.entries(safeObject(rawReceipts))) {
|
|
2044
|
-
const normalizedEntry = normalizeRunnerRecentLocalInboundReceiptRecord(value, key);
|
|
2045
|
-
if (!normalizedEntry) continue;
|
|
2046
|
-
normalizedEntries.push(normalizedEntry);
|
|
2047
|
-
}
|
|
2048
|
-
return Object.fromEntries(normalizedEntries);
|
|
2049
|
-
}
|
|
2050
|
-
|
|
2051
1972
|
function mergeRunnerStateRecords(preferred, fallback) {
|
|
2052
1973
|
const primary = safeObject(preferred);
|
|
2053
1974
|
const secondary = safeObject(fallback);
|
|
@@ -7501,26 +7422,31 @@ async function syncRunnerRequestLedgerForProjectToServer({ normalizedRoute, runt
|
|
|
7501
7422
|
}
|
|
7502
7423
|
}
|
|
7503
7424
|
|
|
7504
|
-
function normalizeRunnerProcessLaunchEntry(rawEntry) {
|
|
7505
|
-
const entry = safeObject(rawEntry);
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7425
|
+
function normalizeRunnerProcessLaunchEntry(rawEntry) {
|
|
7426
|
+
const entry = safeObject(rawEntry);
|
|
7427
|
+
const routeKeys = ensureArray(entry.route_keys || entry.routeKeys).map((item) => String(item || "").trim()).filter(Boolean);
|
|
7428
|
+
return cleanupRunnerStateRecord({
|
|
7429
|
+
launch_id: String(entry.launch_id || entry.launchID || "").trim(),
|
|
7430
|
+
pid: intFromRawAllowZero(entry.pid, 0) || undefined,
|
|
7431
|
+
started_at: String(entry.started_at || entry.startedAt || "").trim(),
|
|
7432
|
+
command: String(entry.command || "").trim(),
|
|
7433
|
+
cli_path: String(entry.cli_path || entry.cliPath || "").trim(),
|
|
7434
|
+
working_directory: sanitizeWorkspaceCandidate(entry.working_directory || entry.workingDirectory) || String(entry.working_directory || entry.workingDirectory || "").trim(),
|
|
7435
|
+
route_set_signature: String(entry.route_set_signature || entry.routeSetSignature || "").trim(),
|
|
7436
|
+
route_keys: routeKeys,
|
|
7437
|
+
route_names: ensureArray(entry.route_names || entry.routeNames).map((item) => String(item || "").trim()).filter(Boolean),
|
|
7438
|
+
project_ids: ensureArray(entry.project_ids || entry.projectIds).map((item) => String(item || "").trim()).filter(Boolean),
|
|
7439
|
+
provider: normalizeBotProvider(entry.provider),
|
|
7440
|
+
destination_labels: ensureArray(entry.destination_labels || entry.destinationLabels).map((item) => String(item || "").trim()).filter(Boolean),
|
|
7441
|
+
scheduling_target_keys: uniqueOrderedStrings([
|
|
7442
|
+
...ensureArray(entry.scheduling_target_keys || entry.schedulingTargetKeys).map((item) => String(item || "").trim()),
|
|
7443
|
+
...routeKeys.map((routeKey) => runnerSchedulingTargetKeyFromRouteKey(routeKey)).filter(Boolean),
|
|
7444
|
+
], (value) => String(value || "").trim()),
|
|
7445
|
+
log_file: String(entry.log_file || entry.logFile || "").trim(),
|
|
7446
|
+
created_by_pid: intFromRawAllowZero(entry.created_by_pid || entry.createdByPid, 0) || undefined,
|
|
7447
|
+
source_command: String(entry.source_command || entry.sourceCommand || "").trim(),
|
|
7448
|
+
});
|
|
7449
|
+
}
|
|
7524
7450
|
|
|
7525
7451
|
function normalizeBotRunnerProcessRegistryContents(rawValue) {
|
|
7526
7452
|
const launchesInput = safeObject(rawValue).launches;
|
|
@@ -12324,6 +12250,9 @@ function buildRunnerProjectTUISnapshot({
|
|
|
12324
12250
|
const summaryPayload = safeObject(normalizedAuditResult.summaryPayload);
|
|
12325
12251
|
const matchingRoutes = ensureArray(normalizedAuditResult.matchingRoutes)
|
|
12326
12252
|
.map((route) => normalizeRunnerRoute(route));
|
|
12253
|
+
const matchingRouteNames = matchingRoutes
|
|
12254
|
+
.map((route) => String(route.name || runnerRouteKey(route)).trim())
|
|
12255
|
+
.filter(Boolean);
|
|
12327
12256
|
const registry = loadBotRunnerProcessRegistry({ persistIfNeeded: true });
|
|
12328
12257
|
const existingLaunch = matchingRoutes.length
|
|
12329
12258
|
? findExistingDetachedRunnerLaunch(registry, matchingRoutes)
|
|
@@ -12334,22 +12263,60 @@ function buildRunnerProjectTUISnapshot({
|
|
|
12334
12263
|
alive: true,
|
|
12335
12264
|
}
|
|
12336
12265
|
: null;
|
|
12266
|
+
const destinationID = String(summaryPayload.destination_id || "").trim();
|
|
12267
|
+
const destinationLabel = String(summaryPayload.destination_label || "").trim();
|
|
12268
|
+
const projectID = String(summaryPayload.project_id || "").trim();
|
|
12269
|
+
const relatedLaunches = Object.values(safeObject(registry).launches || {})
|
|
12270
|
+
.map((entry) => normalizeRunnerProcessLaunchEntry(entry))
|
|
12271
|
+
.filter((entry) => isProcessAlive(entry.pid))
|
|
12272
|
+
.filter((entry) => {
|
|
12273
|
+
const sameProject = ensureArray(entry.project_ids)
|
|
12274
|
+
.map((item) => String(item || "").trim())
|
|
12275
|
+
.includes(projectID);
|
|
12276
|
+
if (!sameProject) return false;
|
|
12277
|
+
const sameDestinationLabel = destinationLabel
|
|
12278
|
+
? ensureArray(entry.destination_labels)
|
|
12279
|
+
.map((item) => String(item || "").trim().toLowerCase())
|
|
12280
|
+
.includes(destinationLabel.toLowerCase())
|
|
12281
|
+
: false;
|
|
12282
|
+
const sameDestinationID = destinationID
|
|
12283
|
+
? ensureArray(entry.route_keys)
|
|
12284
|
+
.map((item) => String(item || "").trim())
|
|
12285
|
+
.some((item) => item.includes(`::${destinationID}`))
|
|
12286
|
+
: false;
|
|
12287
|
+
return sameDestinationLabel || sameDestinationID;
|
|
12288
|
+
})
|
|
12289
|
+
.map((entry) => {
|
|
12290
|
+
const routeNames = ensureArray(entry.route_names)
|
|
12291
|
+
.map((item) => String(item || "").trim())
|
|
12292
|
+
.filter(Boolean);
|
|
12293
|
+
const sharedRouteNames = routeNames.filter((routeName) => matchingRouteNames.includes(routeName));
|
|
12294
|
+
return {
|
|
12295
|
+
launch_id: String(entry.launch_id || "").trim(),
|
|
12296
|
+
pid: intFromRawAllowZero(entry.pid, 0),
|
|
12297
|
+
route_names: routeNames,
|
|
12298
|
+
shared_route_names: sharedRouteNames,
|
|
12299
|
+
source_command: String(entry.source_command || "").trim(),
|
|
12300
|
+
same_selection: Boolean(launch && String(launch.launch_id || "").trim() === String(entry.launch_id || "").trim()),
|
|
12301
|
+
};
|
|
12302
|
+
});
|
|
12337
12303
|
const logFilePath = String(launch?.log_file || "").trim();
|
|
12338
12304
|
const logLines = readTextFileTailLines(logFilePath, 12);
|
|
12339
12305
|
return {
|
|
12340
|
-
project_id:
|
|
12341
|
-
destination_label:
|
|
12342
|
-
destination_id:
|
|
12306
|
+
project_id: projectID,
|
|
12307
|
+
destination_label: destinationLabel,
|
|
12308
|
+
destination_id: destinationID,
|
|
12343
12309
|
room_probe_ok: Boolean(summaryPayload.room_probe_ok),
|
|
12344
12310
|
route_apply_requested: Boolean(summaryPayload.route_apply_requested),
|
|
12345
12311
|
route_apply_changed: Boolean(summaryPayload.route_apply_changed),
|
|
12346
12312
|
route_config_file: String(summaryPayload.route_config_file || "").trim(),
|
|
12347
12313
|
enabled_routes: ensureArray(summaryPayload.enabled_routes_for_selection).map((item) => String(item || "").trim()).filter(Boolean),
|
|
12348
|
-
matching_routes:
|
|
12314
|
+
matching_routes: matchingRouteNames,
|
|
12349
12315
|
next_steps: ensureArray(summaryPayload.next_steps).map((item) => String(item || "").trim()).filter(Boolean),
|
|
12350
12316
|
warning: String(summaryPayload.warning || "").trim(),
|
|
12351
12317
|
error: String(summaryPayload.error || errorMessage || "").trim(),
|
|
12352
12318
|
launch,
|
|
12319
|
+
related_launches: relatedLaunches,
|
|
12353
12320
|
auto_start: autoStart,
|
|
12354
12321
|
status_message: String(statusMessage || "").trim(),
|
|
12355
12322
|
last_audit_at: String(lastAuditAt || "").trim() || new Date().toISOString(),
|
|
@@ -12360,6 +12327,25 @@ function buildRunnerProjectTUISnapshot({
|
|
|
12360
12327
|
};
|
|
12361
12328
|
}
|
|
12362
12329
|
|
|
12330
|
+
function buildRunnerProjectTUIKeyLine(label, value, labelWidth = 14) {
|
|
12331
|
+
return `${bootstrapPadRight(`${label}:`, labelWidth)} ${String(value || "-").trim() || "-"}`;
|
|
12332
|
+
}
|
|
12333
|
+
|
|
12334
|
+
function buildRunnerProjectTUIList(items = [], {
|
|
12335
|
+
emptyText = "-",
|
|
12336
|
+
bullet = "- ",
|
|
12337
|
+
indent = " ",
|
|
12338
|
+
width = 140,
|
|
12339
|
+
} = {}) {
|
|
12340
|
+
const normalizedItems = ensureArray(items)
|
|
12341
|
+
.map((item) => String(item || "").trim())
|
|
12342
|
+
.filter(Boolean);
|
|
12343
|
+
if (!normalizedItems.length) {
|
|
12344
|
+
return [`${indent}${emptyText}`];
|
|
12345
|
+
}
|
|
12346
|
+
return normalizedItems.map((item) => `${indent}${bullet}${truncateRunnerTUIText(item, width)}`);
|
|
12347
|
+
}
|
|
12348
|
+
|
|
12363
12349
|
function buildRunnerProjectTUIFrame({
|
|
12364
12350
|
snapshot,
|
|
12365
12351
|
now = Date.now(),
|
|
@@ -12368,41 +12354,105 @@ function buildRunnerProjectTUIFrame({
|
|
|
12368
12354
|
}) {
|
|
12369
12355
|
const normalizedSnapshot = safeObject(snapshot);
|
|
12370
12356
|
const alive = Boolean(safeObject(normalizedSnapshot.launch).alive);
|
|
12371
|
-
const statusLabel = alive ? "RUNNING" : "STOPPED";
|
|
12357
|
+
const statusLabel = alive ? "[ RUNNING ]" : "[ STOPPED ]";
|
|
12372
12358
|
const statusDetail = alive
|
|
12373
12359
|
? `launch=${String(safeObject(normalizedSnapshot.launch).launch_id || "").trim() || "-"} pid=${String(safeObject(normalizedSnapshot.launch).pid || "").trim() || "-"}`
|
|
12374
12360
|
: "no detached runner is active for this selection";
|
|
12361
|
+
const enabledRoutes = ensureArray(normalizedSnapshot.enabled_routes);
|
|
12362
|
+
const matchingRoutes = ensureArray(normalizedSnapshot.matching_routes);
|
|
12363
|
+
const relatedLaunches = ensureArray(normalizedSnapshot.related_launches);
|
|
12364
|
+
const selectedLaunch = safeObject(normalizedSnapshot.launch);
|
|
12365
|
+
const otherLaunches = relatedLaunches.filter((launch) => !safeObject(launch).same_selection);
|
|
12366
|
+
const overlappingLaunches = otherLaunches.filter((launch) => ensureArray(safeObject(launch).shared_route_names).length > 0);
|
|
12367
|
+
const alertLines = [];
|
|
12368
|
+
if (String(normalizedSnapshot.warning || "").trim()) {
|
|
12369
|
+
alertLines.push(`warning: ${String(normalizedSnapshot.warning || "").trim()}`);
|
|
12370
|
+
}
|
|
12371
|
+
if (String(normalizedSnapshot.error || "").trim()) {
|
|
12372
|
+
alertLines.push(`error: ${String(normalizedSnapshot.error || "").trim()}`);
|
|
12373
|
+
}
|
|
12374
|
+
if (overlappingLaunches.length) {
|
|
12375
|
+
alertLines.push(`overlap: ${overlappingLaunches.length} other detached launch(es) share at least one route in this room`);
|
|
12376
|
+
} else if (otherLaunches.length) {
|
|
12377
|
+
alertLines.push(`notice: ${otherLaunches.length} other detached launch(es) are active for this project/destination`);
|
|
12378
|
+
}
|
|
12379
|
+
if (!alive) {
|
|
12380
|
+
alertLines.push("runner is not active; press s to start or reuse a detached runner");
|
|
12381
|
+
}
|
|
12382
|
+
const stateHint = alive
|
|
12383
|
+
? "This screen can be closed with q. The detached runner keeps running."
|
|
12384
|
+
: "No detached runner is attached to this selection yet.";
|
|
12375
12385
|
const lines = [
|
|
12376
|
-
"
|
|
12377
|
-
"|
|
|
12378
|
-
"|
|
|
12379
|
-
"
|
|
12380
|
-
|
|
12381
|
-
|
|
12382
|
-
`
|
|
12383
|
-
`
|
|
12384
|
-
`
|
|
12385
|
-
|
|
12386
|
-
|
|
12387
|
-
`
|
|
12388
|
-
`
|
|
12389
|
-
`
|
|
12390
|
-
`
|
|
12391
|
-
|
|
12392
|
-
|
|
12393
|
-
`
|
|
12386
|
+
"+======================================================================================+",
|
|
12387
|
+
"| METHEUS PROJECT RUNNER |",
|
|
12388
|
+
"| Detached runner dashboard |",
|
|
12389
|
+
"+======================================================================================+",
|
|
12390
|
+
"",
|
|
12391
|
+
"[ STATUS ]",
|
|
12392
|
+
` ${statusLabel}${stopping ? " (closing TUI)" : ""}`,
|
|
12393
|
+
` ${statusDetail}`,
|
|
12394
|
+
` ${stateHint}`,
|
|
12395
|
+
"",
|
|
12396
|
+
"[ PROJECT ]",
|
|
12397
|
+
` ${buildRunnerProjectTUIKeyLine("Project", normalizedSnapshot.project_id)}`,
|
|
12398
|
+
` ${buildRunnerProjectTUIKeyLine("Destination", String(normalizedSnapshot.destination_label || normalizedSnapshot.destination_id || "").trim() || "-")}`,
|
|
12399
|
+
` ${buildRunnerProjectTUIKeyLine("Updated", new Date(now).toLocaleString("sv-SE", { hour12: false }))}`,
|
|
12400
|
+
` ${buildRunnerProjectTUIKeyLine("Last Audit", String(normalizedSnapshot.last_audit_at || "").trim() || "-")}`,
|
|
12401
|
+
"",
|
|
12402
|
+
"[ RUNNER ]",
|
|
12403
|
+
` ${buildRunnerProjectTUIKeyLine("Auto Start", normalizedSnapshot.auto_start ? "enabled" : "prepare-only")}`,
|
|
12404
|
+
` ${buildRunnerProjectTUIKeyLine("Room Probe", normalizedSnapshot.room_probe_ok ? "ok" : "failed")}`,
|
|
12405
|
+
` ${buildRunnerProjectTUIKeyLine("Apply Result", normalizedSnapshot.route_apply_changed ? "route file changed" : "no route file change")}`,
|
|
12406
|
+
` ${buildRunnerProjectTUIKeyLine("Active Launches", `${relatedLaunches.length}`)}`,
|
|
12407
|
+
` ${buildRunnerProjectTUIKeyLine("This Launch", alive ? String(selectedLaunch.launch_id || "-").trim() || "-" : "-")}`,
|
|
12408
|
+
` ${buildRunnerProjectTUIKeyLine("PID", alive ? String(selectedLaunch.pid || "-").trim() || "-" : "-")}`,
|
|
12409
|
+
` ${buildRunnerProjectTUIKeyLine("Log File", String(normalizedSnapshot.log_file || "").trim() || "-")}`,
|
|
12410
|
+
"",
|
|
12411
|
+
"[ ROUTES ]",
|
|
12412
|
+
` ${buildRunnerProjectTUIKeyLine("Enabled", `${enabledRoutes.length}`)}`,
|
|
12413
|
+
` ${buildRunnerProjectTUIKeyLine("Matched", `${matchingRoutes.length}`)}`,
|
|
12414
|
+
...buildRunnerProjectTUIList(matchingRoutes.length ? matchingRoutes : enabledRoutes, {
|
|
12415
|
+
emptyText: "no routes selected",
|
|
12416
|
+
bullet: "- ",
|
|
12417
|
+
indent: " ",
|
|
12418
|
+
width: 96,
|
|
12419
|
+
}),
|
|
12394
12420
|
"",
|
|
12395
|
-
"
|
|
12421
|
+
"[ ALERTS ]",
|
|
12422
|
+
...buildRunnerProjectTUIList(alertLines, {
|
|
12423
|
+
emptyText: "none",
|
|
12424
|
+
bullet: "- ",
|
|
12425
|
+
indent: " ",
|
|
12426
|
+
width: 96,
|
|
12427
|
+
}),
|
|
12428
|
+
"",
|
|
12429
|
+
"[ CONTROLS ]",
|
|
12430
|
+
" s = start/reuse detached runner",
|
|
12431
|
+
" x = stop detached runner",
|
|
12432
|
+
" r = refresh status",
|
|
12433
|
+
" a = re-audit route selection",
|
|
12434
|
+
" q = quit this dashboard",
|
|
12396
12435
|
"",
|
|
12397
|
-
"
|
|
12398
|
-
...ensureArray(normalizedSnapshot.log_lines).map((line) => ` ${truncateRunnerTUIText(line,
|
|
12436
|
+
"[ RECENT LOG TAIL ]",
|
|
12437
|
+
...ensureArray(normalizedSnapshot.log_lines).map((line) => ` ${truncateRunnerTUIText(line, 108)}`),
|
|
12399
12438
|
];
|
|
12439
|
+
if (otherLaunches.length) {
|
|
12440
|
+
lines.push("");
|
|
12441
|
+
lines.push("[ OTHER ACTIVE LAUNCHES ]");
|
|
12442
|
+
for (const launch of otherLaunches) {
|
|
12443
|
+
const currentLaunch = safeObject(launch);
|
|
12444
|
+
const sharedRoutes = ensureArray(currentLaunch.shared_route_names).join(", ") || "-";
|
|
12445
|
+
lines.push(` - ${String(currentLaunch.launch_id || "-").trim() || "-"} pid=${String(currentLaunch.pid || "-").trim() || "-"} source=${String(currentLaunch.source_command || "-").trim() || "-"}`);
|
|
12446
|
+
lines.push(` shared routes: ${truncateRunnerTUIText(sharedRoutes, 96)}`);
|
|
12447
|
+
lines.push(` all routes: ${truncateRunnerTUIText(ensureArray(currentLaunch.route_names).join(", ") || "-", 96)}`);
|
|
12448
|
+
}
|
|
12449
|
+
}
|
|
12400
12450
|
const frame = lines.join("\n");
|
|
12401
12451
|
if (!useColor) {
|
|
12402
12452
|
return frame;
|
|
12403
12453
|
}
|
|
12404
12454
|
const statusColor = alive ? "\u001b[32m" : "\u001b[33m";
|
|
12405
|
-
return frame.replace(
|
|
12455
|
+
return frame.replace(statusLabel, `${statusColor}${statusLabel}\u001b[0m`);
|
|
12406
12456
|
}
|
|
12407
12457
|
|
|
12408
12458
|
async function runRunnerProjectTUI(flags) {
|
|
@@ -13083,13 +13133,121 @@ function serializeCLIFlags(flags, options = {}) {
|
|
|
13083
13133
|
return args;
|
|
13084
13134
|
}
|
|
13085
13135
|
|
|
13086
|
-
function runnerDetachedRouteSetSignature(routes) {
|
|
13087
|
-
return ensureArray(routes)
|
|
13088
|
-
.map((route) => runnerRouteKey(normalizeRunnerRoute(route)))
|
|
13089
|
-
.filter(Boolean)
|
|
13090
|
-
.sort()
|
|
13091
|
-
.join("||");
|
|
13092
|
-
}
|
|
13136
|
+
function runnerDetachedRouteSetSignature(routes) {
|
|
13137
|
+
return ensureArray(routes)
|
|
13138
|
+
.map((route) => runnerRouteKey(normalizeRunnerRoute(route)))
|
|
13139
|
+
.filter(Boolean)
|
|
13140
|
+
.sort()
|
|
13141
|
+
.join("||");
|
|
13142
|
+
}
|
|
13143
|
+
|
|
13144
|
+
function runnerSchedulingTargetKeyFromRouteKey(routeKey) {
|
|
13145
|
+
const parts = String(routeKey || "").trim().split("::").filter(Boolean);
|
|
13146
|
+
if (parts.length < 6) {
|
|
13147
|
+
return "";
|
|
13148
|
+
}
|
|
13149
|
+
return [
|
|
13150
|
+
String(parts[parts.length - 5] || "").trim(),
|
|
13151
|
+
String(parts[parts.length - 4] || "").trim(),
|
|
13152
|
+
String(parts[parts.length - 1] || "").trim(),
|
|
13153
|
+
].join("::");
|
|
13154
|
+
}
|
|
13155
|
+
|
|
13156
|
+
function collectRunnerDetachedLaunchSchedulingTargetKeys(entryRaw) {
|
|
13157
|
+
const entry = normalizeRunnerProcessLaunchEntry(entryRaw);
|
|
13158
|
+
const explicitKeys = uniqueOrderedStrings(
|
|
13159
|
+
ensureArray(entry.scheduling_target_keys || entry.schedulingTargetKeys),
|
|
13160
|
+
(value) => String(value || "").trim(),
|
|
13161
|
+
).filter(Boolean);
|
|
13162
|
+
if (explicitKeys.length > 0) {
|
|
13163
|
+
return explicitKeys;
|
|
13164
|
+
}
|
|
13165
|
+
const routeKeyDerived = uniqueOrderedStrings(
|
|
13166
|
+
ensureArray(entry.route_keys).map((value) => runnerSchedulingTargetKeyFromRouteKey(value)).filter(Boolean),
|
|
13167
|
+
(value) => String(value || "").trim(),
|
|
13168
|
+
).filter(Boolean);
|
|
13169
|
+
if (routeKeyDerived.length > 0) {
|
|
13170
|
+
return routeKeyDerived;
|
|
13171
|
+
}
|
|
13172
|
+
const provider = normalizeBotProvider(entry.provider);
|
|
13173
|
+
const projectIDs = ensureArray(entry.project_ids).map((value) => String(value || "").trim()).filter(Boolean);
|
|
13174
|
+
const destinations = ensureArray(entry.destination_labels).map((value) => String(value || "").trim()).filter(Boolean);
|
|
13175
|
+
return uniqueOrderedStrings(
|
|
13176
|
+
projectIDs.flatMap((projectID) => destinations.map((destination) => [projectID, provider, destination].join("::"))),
|
|
13177
|
+
(value) => String(value || "").trim(),
|
|
13178
|
+
).filter(Boolean);
|
|
13179
|
+
}
|
|
13180
|
+
|
|
13181
|
+
function describeDetachedRunnerLaunch(entryRaw) {
|
|
13182
|
+
const entry = normalizeRunnerProcessLaunchEntry(entryRaw);
|
|
13183
|
+
return `launch_id=${entry.launch_id || "-"} pid=${entry.pid || "-"} routes=${ensureArray(entry.route_names).join(", ") || "-"}`;
|
|
13184
|
+
}
|
|
13185
|
+
|
|
13186
|
+
function classifyDetachedRunnerLaunchReuse(registry, routes) {
|
|
13187
|
+
const normalizedRoutes = ensureArray(routes).map((route) => normalizeRunnerRoute(route));
|
|
13188
|
+
const targetSignature = runnerDetachedRouteSetSignature(normalizedRoutes);
|
|
13189
|
+
const targetRouteKeys = normalizedRoutes.map((route) => runnerRouteKey(route)).filter(Boolean);
|
|
13190
|
+
const targetRouteKeySet = new Set(targetRouteKeys);
|
|
13191
|
+
const targetSchedulingTargetKeys = uniqueOrderedStrings(
|
|
13192
|
+
normalizedRoutes.map((route) => runnerRouteSchedulingGroupKey(route)).filter(Boolean),
|
|
13193
|
+
(value) => String(value || "").trim(),
|
|
13194
|
+
).filter(Boolean);
|
|
13195
|
+
const aliveLaunches = Object.values(safeObject(registry).launches || {})
|
|
13196
|
+
.map((entryRaw) => normalizeRunnerProcessLaunchEntry(entryRaw))
|
|
13197
|
+
.filter((entry) => entry.launch_id && entry.pid && isProcessAlive(entry.pid));
|
|
13198
|
+
const exact = aliveLaunches.find((entry) => entry.route_set_signature === targetSignature) || null;
|
|
13199
|
+
if (exact) {
|
|
13200
|
+
return {
|
|
13201
|
+
kind: "reuse",
|
|
13202
|
+
reason: "exact",
|
|
13203
|
+
launch: exact,
|
|
13204
|
+
};
|
|
13205
|
+
}
|
|
13206
|
+
if (!targetSchedulingTargetKeys.length) {
|
|
13207
|
+
return {
|
|
13208
|
+
kind: "none",
|
|
13209
|
+
};
|
|
13210
|
+
}
|
|
13211
|
+
const sameTargetLaunches = aliveLaunches.filter((entry) => {
|
|
13212
|
+
const entryTargetKeys = collectRunnerDetachedLaunchSchedulingTargetKeys(entry);
|
|
13213
|
+
return entryTargetKeys.some((key) => targetSchedulingTargetKeys.includes(key));
|
|
13214
|
+
});
|
|
13215
|
+
if (!sameTargetLaunches.length) {
|
|
13216
|
+
return {
|
|
13217
|
+
kind: "none",
|
|
13218
|
+
};
|
|
13219
|
+
}
|
|
13220
|
+
const supersets = sameTargetLaunches.filter((entry) => targetRouteKeys.every((routeKey) => ensureArray(entry.route_keys).includes(routeKey)));
|
|
13221
|
+
if (supersets.length === 1) {
|
|
13222
|
+
return {
|
|
13223
|
+
kind: "reuse",
|
|
13224
|
+
reason: "superset",
|
|
13225
|
+
launch: supersets[0],
|
|
13226
|
+
};
|
|
13227
|
+
}
|
|
13228
|
+
const conflictingLaunches = supersets.length > 1 ? supersets : sameTargetLaunches;
|
|
13229
|
+
const overlapRouteNames = Array.from(
|
|
13230
|
+
new Set(
|
|
13231
|
+
conflictingLaunches.flatMap((entry) => ensureArray(entry.route_keys))
|
|
13232
|
+
.filter((routeKey) => targetRouteKeySet.has(routeKey))
|
|
13233
|
+
.map((routeKey) => {
|
|
13234
|
+
const matchedRoute = normalizedRoutes.find((route) => runnerRouteKey(route) === routeKey);
|
|
13235
|
+
return matchedRoute?.name || routeKey;
|
|
13236
|
+
})
|
|
13237
|
+
.filter(Boolean),
|
|
13238
|
+
),
|
|
13239
|
+
);
|
|
13240
|
+
const detail = supersets.length > 1
|
|
13241
|
+
? `multiple detached runners already cover the requested route set for the same project/provider/destination target (${conflictingLaunches.map((entry) => describeDetachedRunnerLaunch(entry)).join(" | ")})`
|
|
13242
|
+
: `another detached runner already owns the same project/provider/destination target (${conflictingLaunches.map((entry) => describeDetachedRunnerLaunch(entry)).join(" | ")}). Stop it first or use runner project tui so one detached runner owns the full route set for that destination`;
|
|
13243
|
+
return {
|
|
13244
|
+
kind: "conflict",
|
|
13245
|
+
detail,
|
|
13246
|
+
launch: conflictingLaunches.length === 1 ? conflictingLaunches[0] : null,
|
|
13247
|
+
conflicting_launches: conflictingLaunches,
|
|
13248
|
+
overlapping_route_names: overlapRouteNames,
|
|
13249
|
+
};
|
|
13250
|
+
}
|
|
13093
13251
|
|
|
13094
13252
|
function buildRunnerDetachedPosixLaunchScript({
|
|
13095
13253
|
scriptPath,
|
|
@@ -13149,25 +13307,28 @@ function buildRunnerDetachedLaunchRecord(childPID, routes, flags, sourceCommand,
|
|
|
13149
13307
|
cli_path: cliPath,
|
|
13150
13308
|
working_directory: path.dirname(cliPath),
|
|
13151
13309
|
route_set_signature: runnerDetachedRouteSetSignature(normalizedRoutes),
|
|
13152
|
-
route_keys: normalizedRoutes.map((route) => runnerRouteKey(route)),
|
|
13153
|
-
route_names: normalizedRoutes.map((route) => route.name || runnerRouteKey(route)),
|
|
13154
|
-
project_ids: Array.from(new Set(normalizedRoutes.map((route) => String(route.projectID || "").trim()).filter(Boolean))),
|
|
13155
|
-
provider: firstNonEmptyString(normalizedRoutes.map((route) => route.provider)),
|
|
13156
|
-
destination_labels: Array.from(new Set(normalizedRoutes.map((route) => String(route.destinationLabel || route.destinationID || "").trim()).filter(Boolean))),
|
|
13157
|
-
|
|
13158
|
-
|
|
13159
|
-
|
|
13160
|
-
|
|
13161
|
-
|
|
13310
|
+
route_keys: normalizedRoutes.map((route) => runnerRouteKey(route)),
|
|
13311
|
+
route_names: normalizedRoutes.map((route) => route.name || runnerRouteKey(route)),
|
|
13312
|
+
project_ids: Array.from(new Set(normalizedRoutes.map((route) => String(route.projectID || "").trim()).filter(Boolean))),
|
|
13313
|
+
provider: firstNonEmptyString(normalizedRoutes.map((route) => route.provider)),
|
|
13314
|
+
destination_labels: Array.from(new Set(normalizedRoutes.map((route) => String(route.destinationLabel || route.destinationID || "").trim()).filter(Boolean))),
|
|
13315
|
+
scheduling_target_keys: uniqueOrderedStrings(
|
|
13316
|
+
normalizedRoutes.map((route) => runnerRouteSchedulingGroupKey(route)).filter(Boolean),
|
|
13317
|
+
(value) => String(value || "").trim(),
|
|
13318
|
+
),
|
|
13319
|
+
log_file: String(logFilePath || "").trim(),
|
|
13320
|
+
created_by_pid: process.pid,
|
|
13321
|
+
source_command: sourceCommand,
|
|
13322
|
+
});
|
|
13323
|
+
}
|
|
13162
13324
|
|
|
13163
|
-
function findExistingDetachedRunnerLaunch(registry, routes) {
|
|
13164
|
-
const
|
|
13165
|
-
if (
|
|
13166
|
-
|
|
13167
|
-
|
|
13168
|
-
|
|
13169
|
-
|
|
13170
|
-
}
|
|
13325
|
+
function findExistingDetachedRunnerLaunch(registry, routes) {
|
|
13326
|
+
const decision = classifyDetachedRunnerLaunchReuse(registry, routes);
|
|
13327
|
+
if (decision.kind === "reuse" && decision.launch) {
|
|
13328
|
+
return decision.launch;
|
|
13329
|
+
}
|
|
13330
|
+
return null;
|
|
13331
|
+
}
|
|
13171
13332
|
|
|
13172
13333
|
async function launchDetachedRunnerProcess(flags, routes, sourceCommand) {
|
|
13173
13334
|
const cliPath = fileURLToPath(import.meta.url);
|
|
@@ -13359,13 +13520,15 @@ async function runRunnerStop(flags) {
|
|
|
13359
13520
|
async function runRunnerStartDetachedResolvedRoutes(routes, flags, sourceCommand = "runner start-detached", options = {}) {
|
|
13360
13521
|
const silent = boolFromRaw(safeObject(options).silent, false);
|
|
13361
13522
|
const registry = loadBotRunnerProcessRegistry({ persistIfNeeded: true });
|
|
13362
|
-
const
|
|
13363
|
-
if (
|
|
13523
|
+
const reuseDecision = classifyDetachedRunnerLaunchReuse(registry, routes);
|
|
13524
|
+
if (reuseDecision.kind === "reuse" && reuseDecision.launch) {
|
|
13525
|
+
const existing = reuseDecision.launch;
|
|
13364
13526
|
const payload = {
|
|
13365
13527
|
ok: true,
|
|
13366
|
-
already_running: true,
|
|
13367
|
-
|
|
13368
|
-
|
|
13528
|
+
already_running: true,
|
|
13529
|
+
reuse_reason: reuseDecision.reason,
|
|
13530
|
+
registry_file: registry.filePath,
|
|
13531
|
+
launch: {
|
|
13369
13532
|
...existing,
|
|
13370
13533
|
alive: true,
|
|
13371
13534
|
},
|
|
@@ -13375,15 +13538,24 @@ async function runRunnerStartDetachedResolvedRoutes(routes, flags, sourceCommand
|
|
|
13375
13538
|
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
13376
13539
|
return payload;
|
|
13377
13540
|
}
|
|
13378
|
-
process.stdout.write(`Detached runner already running: launch_id=${existing.launch_id} pid=${existing.pid}${existing.log_file ? ` log_file=${existing.log_file}` : ""}\n`);
|
|
13541
|
+
process.stdout.write(`Detached runner already running: launch_id=${existing.launch_id} pid=${existing.pid}${existing.log_file ? ` log_file=${existing.log_file}` : ""}${reuseDecision.reason ? ` reuse=${reuseDecision.reason}` : ""}\n`);
|
|
13379
13542
|
}
|
|
13380
13543
|
return payload;
|
|
13381
13544
|
}
|
|
13545
|
+
if (reuseDecision.kind === "conflict") {
|
|
13546
|
+
const overlappingRouteNames = ensureArray(reuseDecision.overlapping_route_names)
|
|
13547
|
+
.map((item) => String(item || "").trim())
|
|
13548
|
+
.filter(Boolean);
|
|
13549
|
+
const overlapSuffix = overlappingRouteNames.length
|
|
13550
|
+
? ` overlapping_routes=${overlappingRouteNames.join(", ")}`
|
|
13551
|
+
: "";
|
|
13552
|
+
throw new Error(`${String(reuseDecision.detail || "detached runner conflict").trim()}${overlapSuffix}`);
|
|
13553
|
+
}
|
|
13382
13554
|
const launch = await launchDetachedRunnerProcess(flags, routes, sourceCommand);
|
|
13383
13555
|
const nextLaunches = {
|
|
13384
13556
|
...safeObject(registry).launches,
|
|
13385
13557
|
[launch.launch_id]: launch,
|
|
13386
|
-
};
|
|
13558
|
+
};
|
|
13387
13559
|
saveBotRunnerProcessRegistry({ launches: nextLaunches });
|
|
13388
13560
|
const payload = {
|
|
13389
13561
|
ok: true,
|
|
@@ -19658,10 +19830,176 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
19658
19830
|
} catch (err) {
|
|
19659
19831
|
push("detached_runner_launch_record_persists_log_file", false, String(err?.message || err));
|
|
19660
19832
|
}
|
|
19661
|
-
|
|
19662
|
-
|
|
19663
|
-
|
|
19664
|
-
|
|
19833
|
+
|
|
19834
|
+
try {
|
|
19835
|
+
const routeBot1 = normalizeRunnerRoute({
|
|
19836
|
+
name: "telegram-monitor-selftest-bot-1",
|
|
19837
|
+
projectID: selftestProjectID,
|
|
19838
|
+
provider: "telegram",
|
|
19839
|
+
role: "monitor",
|
|
19840
|
+
botID: "bot-1",
|
|
19841
|
+
botName: "SelftestBot1",
|
|
19842
|
+
destinationID: "dest-1",
|
|
19843
|
+
destinationLabel: "Selftest Room",
|
|
19844
|
+
});
|
|
19845
|
+
const routeBot2 = normalizeRunnerRoute({
|
|
19846
|
+
name: "telegram-monitor-selftest-bot-2",
|
|
19847
|
+
projectID: selftestProjectID,
|
|
19848
|
+
provider: "telegram",
|
|
19849
|
+
role: "monitor",
|
|
19850
|
+
botID: "bot-2",
|
|
19851
|
+
botName: "SelftestBot2",
|
|
19852
|
+
destinationID: "dest-1",
|
|
19853
|
+
destinationLabel: "Selftest Room",
|
|
19854
|
+
});
|
|
19855
|
+
const supersetLaunch = buildRunnerDetachedLaunchRecord(
|
|
19856
|
+
process.pid,
|
|
19857
|
+
[routeBot1, routeBot2],
|
|
19858
|
+
{ tui: false },
|
|
19859
|
+
"runner start-detached",
|
|
19860
|
+
);
|
|
19861
|
+
const decision = classifyDetachedRunnerLaunchReuse(
|
|
19862
|
+
{ launches: { [supersetLaunch.launch_id]: supersetLaunch } },
|
|
19863
|
+
[routeBot2],
|
|
19864
|
+
);
|
|
19865
|
+
push(
|
|
19866
|
+
"detached_runner_reuses_existing_superset_launch_for_same_target",
|
|
19867
|
+
decision.kind === "reuse"
|
|
19868
|
+
&& decision.reason === "superset"
|
|
19869
|
+
&& String(safeObject(decision.launch).launch_id || "").trim() === supersetLaunch.launch_id,
|
|
19870
|
+
`kind=${String(decision.kind || "")} reason=${String(decision.reason || "")} launch=${String(safeObject(decision.launch).launch_id || "")}`,
|
|
19871
|
+
);
|
|
19872
|
+
} catch (err) {
|
|
19873
|
+
push("detached_runner_reuses_existing_superset_launch_for_same_target", false, String(err?.message || err));
|
|
19874
|
+
}
|
|
19875
|
+
|
|
19876
|
+
try {
|
|
19877
|
+
const routeBot1 = normalizeRunnerRoute({
|
|
19878
|
+
name: "telegram-monitor-selftest-bot-1",
|
|
19879
|
+
projectID: selftestProjectID,
|
|
19880
|
+
provider: "telegram",
|
|
19881
|
+
role: "monitor",
|
|
19882
|
+
botID: "bot-1",
|
|
19883
|
+
botName: "SelftestBot1",
|
|
19884
|
+
destinationID: "dest-1",
|
|
19885
|
+
destinationLabel: "Selftest Room",
|
|
19886
|
+
});
|
|
19887
|
+
const routeBot2 = normalizeRunnerRoute({
|
|
19888
|
+
name: "telegram-monitor-selftest-bot-2",
|
|
19889
|
+
projectID: selftestProjectID,
|
|
19890
|
+
provider: "telegram",
|
|
19891
|
+
role: "monitor",
|
|
19892
|
+
botID: "bot-2",
|
|
19893
|
+
botName: "SelftestBot2",
|
|
19894
|
+
destinationID: "dest-1",
|
|
19895
|
+
destinationLabel: "Selftest Room",
|
|
19896
|
+
});
|
|
19897
|
+
const supersetLaunch = buildRunnerDetachedLaunchRecord(
|
|
19898
|
+
process.pid,
|
|
19899
|
+
[routeBot1, routeBot2],
|
|
19900
|
+
{ tui: false },
|
|
19901
|
+
"runner project tui",
|
|
19902
|
+
);
|
|
19903
|
+
const existing = findExistingDetachedRunnerLaunch(
|
|
19904
|
+
{ launches: { [supersetLaunch.launch_id]: supersetLaunch } },
|
|
19905
|
+
[routeBot2],
|
|
19906
|
+
);
|
|
19907
|
+
push(
|
|
19908
|
+
"detached_runner_existing_launch_lookup_reuses_superset_for_tui_snapshot",
|
|
19909
|
+
String(safeObject(existing).launch_id || "").trim() === supersetLaunch.launch_id,
|
|
19910
|
+
`launch=${String(safeObject(existing).launch_id || "").trim() || "(none)"}`,
|
|
19911
|
+
);
|
|
19912
|
+
} catch (err) {
|
|
19913
|
+
push("detached_runner_existing_launch_lookup_reuses_superset_for_tui_snapshot", false, String(err?.message || err));
|
|
19914
|
+
}
|
|
19915
|
+
|
|
19916
|
+
try {
|
|
19917
|
+
const routeBot1 = normalizeRunnerRoute({
|
|
19918
|
+
name: "telegram-monitor-selftest-bot-1",
|
|
19919
|
+
projectID: selftestProjectID,
|
|
19920
|
+
provider: "telegram",
|
|
19921
|
+
role: "monitor",
|
|
19922
|
+
botID: "bot-1",
|
|
19923
|
+
botName: "SelftestBot1",
|
|
19924
|
+
destinationID: "dest-1",
|
|
19925
|
+
destinationLabel: "Selftest Room",
|
|
19926
|
+
});
|
|
19927
|
+
const routeBot2 = normalizeRunnerRoute({
|
|
19928
|
+
name: "telegram-monitor-selftest-bot-2",
|
|
19929
|
+
projectID: selftestProjectID,
|
|
19930
|
+
provider: "telegram",
|
|
19931
|
+
role: "monitor",
|
|
19932
|
+
botID: "bot-2",
|
|
19933
|
+
botName: "SelftestBot2",
|
|
19934
|
+
destinationID: "dest-1",
|
|
19935
|
+
destinationLabel: "Selftest Room",
|
|
19936
|
+
});
|
|
19937
|
+
const subsetLaunch = buildRunnerDetachedLaunchRecord(
|
|
19938
|
+
process.pid,
|
|
19939
|
+
[routeBot2],
|
|
19940
|
+
{ tui: false },
|
|
19941
|
+
"runner start-detached",
|
|
19942
|
+
);
|
|
19943
|
+
const decision = classifyDetachedRunnerLaunchReuse(
|
|
19944
|
+
{ launches: { [subsetLaunch.launch_id]: subsetLaunch } },
|
|
19945
|
+
[routeBot1, routeBot2],
|
|
19946
|
+
);
|
|
19947
|
+
push(
|
|
19948
|
+
"detached_runner_blocks_partial_overlap_for_same_target",
|
|
19949
|
+
decision.kind === "conflict"
|
|
19950
|
+
&& ensureArray(decision.overlapping_route_names).includes("telegram-monitor-selftest-bot-2")
|
|
19951
|
+
&& String(decision.detail || "").includes("runner project tui"),
|
|
19952
|
+
`kind=${String(decision.kind || "")} overlap=${ensureArray(decision.overlapping_route_names).join(", ")} detail=${String(decision.detail || "")}`,
|
|
19953
|
+
);
|
|
19954
|
+
} catch (err) {
|
|
19955
|
+
push("detached_runner_blocks_partial_overlap_for_same_target", false, String(err?.message || err));
|
|
19956
|
+
}
|
|
19957
|
+
|
|
19958
|
+
try {
|
|
19959
|
+
const routeBot1 = normalizeRunnerRoute({
|
|
19960
|
+
name: "telegram-monitor-selftest-bot-1",
|
|
19961
|
+
projectID: selftestProjectID,
|
|
19962
|
+
provider: "telegram",
|
|
19963
|
+
role: "monitor",
|
|
19964
|
+
botID: "bot-1",
|
|
19965
|
+
botName: "SelftestBot1",
|
|
19966
|
+
destinationID: "dest-1",
|
|
19967
|
+
destinationLabel: "Selftest Room",
|
|
19968
|
+
});
|
|
19969
|
+
const routeBot2 = normalizeRunnerRoute({
|
|
19970
|
+
name: "telegram-monitor-selftest-bot-2",
|
|
19971
|
+
projectID: selftestProjectID,
|
|
19972
|
+
provider: "telegram",
|
|
19973
|
+
role: "monitor",
|
|
19974
|
+
botID: "bot-2",
|
|
19975
|
+
botName: "SelftestBot2",
|
|
19976
|
+
destinationID: "dest-1",
|
|
19977
|
+
destinationLabel: "Selftest Room",
|
|
19978
|
+
});
|
|
19979
|
+
const singleLaunch = buildRunnerDetachedLaunchRecord(
|
|
19980
|
+
process.pid,
|
|
19981
|
+
[routeBot1],
|
|
19982
|
+
{ tui: false },
|
|
19983
|
+
"runner start-detached",
|
|
19984
|
+
);
|
|
19985
|
+
const decision = classifyDetachedRunnerLaunchReuse(
|
|
19986
|
+
{ launches: { [singleLaunch.launch_id]: singleLaunch } },
|
|
19987
|
+
[routeBot2],
|
|
19988
|
+
);
|
|
19989
|
+
push(
|
|
19990
|
+
"detached_runner_blocks_disjoint_same_target_parallel_launches",
|
|
19991
|
+
decision.kind === "conflict"
|
|
19992
|
+
&& ensureArray(decision.overlapping_route_names).length === 0
|
|
19993
|
+
&& String(decision.detail || "").includes("same project/provider/destination target"),
|
|
19994
|
+
`kind=${String(decision.kind || "")} overlap=${ensureArray(decision.overlapping_route_names).join(", ")} detail=${String(decision.detail || "")}`,
|
|
19995
|
+
);
|
|
19996
|
+
} catch (err) {
|
|
19997
|
+
push("detached_runner_blocks_disjoint_same_target_parallel_launches", false, String(err?.message || err));
|
|
19998
|
+
}
|
|
19999
|
+
|
|
20000
|
+
let detachedRunnerPosixTempDir = "";
|
|
20001
|
+
try {
|
|
20002
|
+
detachedRunnerPosixTempDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-detached-script-selftest-"));
|
|
19665
20003
|
const scriptPath = path.join(detachedRunnerPosixTempDir, "start-runner.sh");
|
|
19666
20004
|
const pidFilePath = path.join(detachedRunnerPosixTempDir, "runner.pid");
|
|
19667
20005
|
buildRunnerDetachedPosixLaunchScript({
|