metheus-governance-mcp-cli 0.2.290 → 0.2.292
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 +2 -0
- package/cli.mjs +562 -178
- package/lib/local-ai-adapters.mjs +1 -0
- package/lib/runner-orchestration-intent-contracts.mjs +68 -8
- package/lib/selftest-runner-scenarios.mjs +73 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -380,6 +380,7 @@ Behavior:
|
|
|
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
382
|
- `runner project up` is the direct CLI preparation path for Telegram project operations: it runs the same room audit and applies the selected role routes. Use `runner start-detached` for persistent polling, or let the TUI call `runner.project_up` to bootstrap detached polling in one step.
|
|
383
|
+
- `runner project tui` is the direct CLI project-scoped dashboard: it audits one project destination, ensures one detached runner is reused or started for the selected route set, and then shows status, route selection, and recent log tail in one interactive screen.
|
|
383
384
|
- `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
385
|
- `bot remove` without flags starts a guided numbered flow: provider -> bot entry -> confirm removal.
|
|
385
386
|
- Telegram stores one bot file per entry under `~/.metheus/telegram-bots/<ServerBotName>.env` with generic fields:
|
|
@@ -423,6 +424,7 @@ metheus-governance-mcp-cli bot room-audit --provider telegram --project-id <proj
|
|
|
423
424
|
metheus-governance-mcp-cli bot room-audit --provider telegram --project-id <project_uuid> --destination-label <room_label> --apply true --json true
|
|
424
425
|
metheus-governance-mcp-cli runner project up --project-id <project_uuid> --provider telegram --destination-label <room_label> --start false
|
|
425
426
|
metheus-governance-mcp-cli runner start-detached --project-id <project_uuid> --provider telegram --destination-label <room_label>
|
|
427
|
+
metheus-governance-mcp-cli runner project tui --project-id <project_uuid> --provider telegram --destination-label <room_label>
|
|
426
428
|
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
|
|
427
429
|
```
|
|
428
430
|
|
package/cli.mjs
CHANGED
|
@@ -402,10 +402,11 @@ function printUsage() {
|
|
|
402
402
|
` ${cmd} selftest [--json <true|false>]`,
|
|
403
403
|
` ${cmd} local-bot-bridge [--client <gpt|claude|gemini|sample>] [--cwd <path>] [--model <name>] [--permission-mode <read_only|workspace_write|danger_full_access>] [--reasoning-effort <low|medium|high>]`,
|
|
404
404
|
` ${cmd} runner list [--json <true|false>]`,
|
|
405
|
-
` ${cmd} runner route list [--json <true|false>]`,
|
|
406
|
-
` ${cmd} runner route add [--project-id <uuid>] [--provider <telegram|slack|kakaotalk>] [--role <monitor|review|worker|approval>] [--bot-name <server_name> | --bot-id <uuid>] [--destination-label <label> | --destination-id <uuid>] [--poll-interval-ms <n>] [--enabled <true|false>]`,
|
|
407
|
-
` ${cmd} runner project up [--project-id <uuid>] [--provider <telegram>] [--destination-label <label> | --destination-id <uuid>] [--bot-name <server_name> | --bot-id <uuid>] [--role <monitor|review|worker|approval> | --roles <csv>] [--apply <true|false>] [--start <true|false>] [--start-detached <true|false>] [--tui <true|false>] [--log-file <path>] [--dry-run-delivery <true|false>] [--concurrency <n>] [--json <true|false>]`,
|
|
408
|
-
` ${cmd} runner
|
|
405
|
+
` ${cmd} runner route list [--json <true|false>]`,
|
|
406
|
+
` ${cmd} runner route add [--project-id <uuid>] [--provider <telegram|slack|kakaotalk>] [--role <monitor|review|worker|approval>] [--bot-name <server_name> | --bot-id <uuid>] [--destination-label <label> | --destination-id <uuid>] [--poll-interval-ms <n>] [--enabled <true|false>]`,
|
|
407
|
+
` ${cmd} runner project up [--project-id <uuid>] [--provider <telegram>] [--destination-label <label> | --destination-id <uuid>] [--bot-name <server_name> | --bot-id <uuid>] [--role <monitor|review|worker|approval> | --roles <csv>] [--apply <true|false>] [--start <true|false>] [--start-detached <true|false>] [--tui <true|false>] [--log-file <path>] [--dry-run-delivery <true|false>] [--concurrency <n>] [--json <true|false>]`,
|
|
408
|
+
` ${cmd} runner project tui [--project-id <uuid>] [--provider <telegram>] [--destination-label <label> | --destination-id <uuid>] [--bot-name <server_name> | --bot-id <uuid>] [--role <monitor|review|worker|approval> | --roles <csv>] [--start <true|false>]`,
|
|
409
|
+
` ${cmd} runner artifact scan [--workspace-dir <path>] [--file-limit <n>] [--json <true|false>]`,
|
|
409
410
|
` ${cmd} runner route edit [--route-name <name> | --bot-name <server_name> | --bot-id <uuid>]`,
|
|
410
411
|
` ${cmd} runner route remove [--route-name <name> | --bot-name <server_name> | --bot-id <uuid>]`,
|
|
411
412
|
` ${cmd} runner show [--route-name <name> | --bot-name <server_name> | --bot-id <uuid>] [--json <true|false>]`,
|
|
@@ -12203,8 +12204,8 @@ function resolveRunnerProjectUpRoutes({
|
|
|
12203
12204
|
});
|
|
12204
12205
|
}
|
|
12205
12206
|
|
|
12206
|
-
async function runRunnerProjectUp(flags) {
|
|
12207
|
-
const result = await buildRunnerProjectUpResult(flags);
|
|
12207
|
+
async function runRunnerProjectUp(flags) {
|
|
12208
|
+
const result = await buildRunnerProjectUpResult(flags);
|
|
12208
12209
|
const {
|
|
12209
12210
|
summaryPayload,
|
|
12210
12211
|
applyRequested,
|
|
@@ -12267,16 +12268,316 @@ async function runRunnerProjectUp(flags) {
|
|
|
12267
12268
|
await runRunnerStartDetachedResolvedRoutes(matchingRoutes, startFlags, "runner project up");
|
|
12268
12269
|
return;
|
|
12269
12270
|
}
|
|
12270
|
-
await runRunnerStartResolvedRoutes(matchingRoutes, startFlags, {
|
|
12271
|
-
sourceLabel: "runner project up",
|
|
12272
|
-
bootstrapEvent: {
|
|
12273
|
-
route_name: "runner project up",
|
|
12271
|
+
await runRunnerStartResolvedRoutes(matchingRoutes, startFlags, {
|
|
12272
|
+
sourceLabel: "runner project up",
|
|
12273
|
+
bootstrapEvent: {
|
|
12274
|
+
route_name: "runner project up",
|
|
12274
12275
|
outcome: "prepared",
|
|
12275
12276
|
detail: `prepared ${matchingRoutes.length} enabled route(s) for ${summaryPayload.destination_label || summaryPayload.destination_id || summaryPayload.project_id || "project"}`,
|
|
12276
|
-
},
|
|
12277
|
-
});
|
|
12278
|
-
}
|
|
12279
|
-
|
|
12277
|
+
},
|
|
12278
|
+
});
|
|
12279
|
+
}
|
|
12280
|
+
|
|
12281
|
+
function resolveRunnerProjectTUIRuntimePolicy(flags = {}) {
|
|
12282
|
+
const explicitStartRequested = Object.prototype.hasOwnProperty.call(flags, "start");
|
|
12283
|
+
return {
|
|
12284
|
+
autoStart: explicitStartRequested
|
|
12285
|
+
? boolFromRaw(flags.start, true)
|
|
12286
|
+
: true,
|
|
12287
|
+
auditFlags: {
|
|
12288
|
+
...safeObject(flags),
|
|
12289
|
+
provider: String(flags.provider || "").trim()
|
|
12290
|
+
? normalizeBotProvider(flags.provider)
|
|
12291
|
+
: "telegram",
|
|
12292
|
+
start: false,
|
|
12293
|
+
"start-detached": false,
|
|
12294
|
+
tui: false,
|
|
12295
|
+
json: true,
|
|
12296
|
+
},
|
|
12297
|
+
};
|
|
12298
|
+
}
|
|
12299
|
+
|
|
12300
|
+
function readTextFileTailLines(filePath = "", maxLines = 12) {
|
|
12301
|
+
const normalizedPath = String(filePath || "").trim();
|
|
12302
|
+
if (!normalizedPath) return [];
|
|
12303
|
+
try {
|
|
12304
|
+
if (!fs.existsSync(normalizedPath)) {
|
|
12305
|
+
return ["(log file not created yet)"];
|
|
12306
|
+
}
|
|
12307
|
+
const lines = String(fs.readFileSync(normalizedPath, "utf8") || "")
|
|
12308
|
+
.split(/\r?\n/)
|
|
12309
|
+
.map((line) => String(line || "").trimEnd());
|
|
12310
|
+
return lines.filter((line) => line.length > 0).slice(-Math.max(1, intFromRaw(maxLines, 12)));
|
|
12311
|
+
} catch (err) {
|
|
12312
|
+
return [`(failed to read log: ${String(err?.message || err).trim()})`];
|
|
12313
|
+
}
|
|
12314
|
+
}
|
|
12315
|
+
|
|
12316
|
+
function buildRunnerProjectTUISnapshot({
|
|
12317
|
+
auditResult,
|
|
12318
|
+
autoStart = true,
|
|
12319
|
+
statusMessage = "",
|
|
12320
|
+
errorMessage = "",
|
|
12321
|
+
lastAuditAt = "",
|
|
12322
|
+
}) {
|
|
12323
|
+
const normalizedAuditResult = safeObject(auditResult);
|
|
12324
|
+
const summaryPayload = safeObject(normalizedAuditResult.summaryPayload);
|
|
12325
|
+
const matchingRoutes = ensureArray(normalizedAuditResult.matchingRoutes)
|
|
12326
|
+
.map((route) => normalizeRunnerRoute(route));
|
|
12327
|
+
const registry = loadBotRunnerProcessRegistry({ persistIfNeeded: true });
|
|
12328
|
+
const existingLaunch = matchingRoutes.length
|
|
12329
|
+
? findExistingDetachedRunnerLaunch(registry, matchingRoutes)
|
|
12330
|
+
: null;
|
|
12331
|
+
const launch = existingLaunch
|
|
12332
|
+
? {
|
|
12333
|
+
...normalizeRunnerProcessLaunchEntry(existingLaunch),
|
|
12334
|
+
alive: true,
|
|
12335
|
+
}
|
|
12336
|
+
: null;
|
|
12337
|
+
const logFilePath = String(launch?.log_file || "").trim();
|
|
12338
|
+
const logLines = readTextFileTailLines(logFilePath, 12);
|
|
12339
|
+
return {
|
|
12340
|
+
project_id: String(summaryPayload.project_id || "").trim(),
|
|
12341
|
+
destination_label: String(summaryPayload.destination_label || "").trim(),
|
|
12342
|
+
destination_id: String(summaryPayload.destination_id || "").trim(),
|
|
12343
|
+
room_probe_ok: Boolean(summaryPayload.room_probe_ok),
|
|
12344
|
+
route_apply_requested: Boolean(summaryPayload.route_apply_requested),
|
|
12345
|
+
route_apply_changed: Boolean(summaryPayload.route_apply_changed),
|
|
12346
|
+
route_config_file: String(summaryPayload.route_config_file || "").trim(),
|
|
12347
|
+
enabled_routes: ensureArray(summaryPayload.enabled_routes_for_selection).map((item) => String(item || "").trim()).filter(Boolean),
|
|
12348
|
+
matching_routes: matchingRoutes.map((route) => String(route.name || runnerRouteKey(route)).trim()).filter(Boolean),
|
|
12349
|
+
next_steps: ensureArray(summaryPayload.next_steps).map((item) => String(item || "").trim()).filter(Boolean),
|
|
12350
|
+
warning: String(summaryPayload.warning || "").trim(),
|
|
12351
|
+
error: String(summaryPayload.error || errorMessage || "").trim(),
|
|
12352
|
+
launch,
|
|
12353
|
+
auto_start: autoStart,
|
|
12354
|
+
status_message: String(statusMessage || "").trim(),
|
|
12355
|
+
last_audit_at: String(lastAuditAt || "").trim() || new Date().toISOString(),
|
|
12356
|
+
log_file: logFilePath,
|
|
12357
|
+
log_lines: logLines.length
|
|
12358
|
+
? logLines
|
|
12359
|
+
: [launch ? "(waiting for runner log output)" : "(no active detached runner)"],
|
|
12360
|
+
};
|
|
12361
|
+
}
|
|
12362
|
+
|
|
12363
|
+
function buildRunnerProjectTUIFrame({
|
|
12364
|
+
snapshot,
|
|
12365
|
+
now = Date.now(),
|
|
12366
|
+
stopping = false,
|
|
12367
|
+
useColor = false,
|
|
12368
|
+
}) {
|
|
12369
|
+
const normalizedSnapshot = safeObject(snapshot);
|
|
12370
|
+
const alive = Boolean(safeObject(normalizedSnapshot.launch).alive);
|
|
12371
|
+
const statusLabel = alive ? "RUNNING" : "STOPPED";
|
|
12372
|
+
const statusDetail = alive
|
|
12373
|
+
? `launch=${String(safeObject(normalizedSnapshot.launch).launch_id || "").trim() || "-"} pid=${String(safeObject(normalizedSnapshot.launch).pid || "").trim() || "-"}`
|
|
12374
|
+
: "no detached runner is active for this selection";
|
|
12375
|
+
const lines = [
|
|
12376
|
+
"+----------------------------------------------------------------+",
|
|
12377
|
+
"| RUNNER PROJECT TUI |",
|
|
12378
|
+
"| Project-scoped detached runner dashboard |",
|
|
12379
|
+
"+----------------------------------------------------------------+",
|
|
12380
|
+
`Status: ${statusLabel}${stopping ? " (closing TUI)" : ""}`,
|
|
12381
|
+
`Detail: ${statusDetail}`,
|
|
12382
|
+
`Project: ${String(normalizedSnapshot.project_id || "").trim() || "-"}`,
|
|
12383
|
+
`Destination: ${String(normalizedSnapshot.destination_label || normalizedSnapshot.destination_id || "").trim() || "-"}`,
|
|
12384
|
+
`Auto Start: ${normalizedSnapshot.auto_start ? "true" : "false"}`,
|
|
12385
|
+
`Routes: ${ensureArray(normalizedSnapshot.enabled_routes).join(", ") || "-"}`,
|
|
12386
|
+
`Room Probe OK: ${normalizedSnapshot.room_probe_ok ? "true" : "false"}`,
|
|
12387
|
+
`Route Apply Changed: ${normalizedSnapshot.route_apply_changed ? "true" : "false"}`,
|
|
12388
|
+
`Log File: ${String(normalizedSnapshot.log_file || "").trim() || "-"}`,
|
|
12389
|
+
`Last Audit: ${String(normalizedSnapshot.last_audit_at || "").trim() || "-"}`,
|
|
12390
|
+
`Updated: ${new Date(now).toLocaleString("sv-SE", { hour12: false })}`,
|
|
12391
|
+
`Message: ${String(normalizedSnapshot.status_message || "").trim() || "-"}`,
|
|
12392
|
+
`Warning: ${String(normalizedSnapshot.warning || "").trim() || "-"}`,
|
|
12393
|
+
`Error: ${String(normalizedSnapshot.error || "").trim() || "-"}`,
|
|
12394
|
+
"",
|
|
12395
|
+
"Controls: [s] start/reuse [x] stop [r] refresh [a] re-audit [q] quit",
|
|
12396
|
+
"",
|
|
12397
|
+
"Recent Log Tail:",
|
|
12398
|
+
...ensureArray(normalizedSnapshot.log_lines).map((line) => ` ${truncateRunnerTUIText(line, 140)}`),
|
|
12399
|
+
];
|
|
12400
|
+
const frame = lines.join("\n");
|
|
12401
|
+
if (!useColor) {
|
|
12402
|
+
return frame;
|
|
12403
|
+
}
|
|
12404
|
+
const statusColor = alive ? "\u001b[32m" : "\u001b[33m";
|
|
12405
|
+
return frame.replace(`Status: ${statusLabel}`, `Status: ${statusColor}${statusLabel}\u001b[0m`);
|
|
12406
|
+
}
|
|
12407
|
+
|
|
12408
|
+
async function runRunnerProjectTUI(flags) {
|
|
12409
|
+
if (!process.stdout?.isTTY || !process.stdin?.isTTY) {
|
|
12410
|
+
throw new Error("runner project tui requires an interactive terminal (TTY)");
|
|
12411
|
+
}
|
|
12412
|
+
const { autoStart, auditFlags } = resolveRunnerProjectTUIRuntimePolicy(flags);
|
|
12413
|
+
const useColor = bootstrapSupportsANSIColors();
|
|
12414
|
+
const state = {
|
|
12415
|
+
auditResult: null,
|
|
12416
|
+
snapshot: null,
|
|
12417
|
+
busy: false,
|
|
12418
|
+
disposed: false,
|
|
12419
|
+
};
|
|
12420
|
+
let intervalHandle = null;
|
|
12421
|
+
let stdinHandler = null;
|
|
12422
|
+
|
|
12423
|
+
const render = (stopping = false) => {
|
|
12424
|
+
if (state.disposed) return;
|
|
12425
|
+
const frame = buildRunnerProjectTUIFrame({
|
|
12426
|
+
snapshot: state.snapshot,
|
|
12427
|
+
now: Date.now(),
|
|
12428
|
+
stopping,
|
|
12429
|
+
useColor,
|
|
12430
|
+
});
|
|
12431
|
+
process.stdout.write(`\u001b[?25l\u001b[2J\u001b[H${frame}`);
|
|
12432
|
+
};
|
|
12433
|
+
|
|
12434
|
+
const refreshSnapshot = async ({
|
|
12435
|
+
reAudit = false,
|
|
12436
|
+
ensureStarted = false,
|
|
12437
|
+
stopRunner = false,
|
|
12438
|
+
statusMessage = "",
|
|
12439
|
+
} = {}) => {
|
|
12440
|
+
if (state.busy) return;
|
|
12441
|
+
state.busy = true;
|
|
12442
|
+
try {
|
|
12443
|
+
if (reAudit || !state.auditResult) {
|
|
12444
|
+
state.auditResult = await buildRunnerProjectUpResult(auditFlags);
|
|
12445
|
+
}
|
|
12446
|
+
let nextStatusMessage = String(statusMessage || "").trim();
|
|
12447
|
+
let nextErrorMessage = "";
|
|
12448
|
+
if (stopRunner) {
|
|
12449
|
+
const currentSnapshot = buildRunnerProjectTUISnapshot({
|
|
12450
|
+
auditResult: state.auditResult,
|
|
12451
|
+
autoStart,
|
|
12452
|
+
statusMessage: nextStatusMessage || "stopping detached runner",
|
|
12453
|
+
});
|
|
12454
|
+
if (safeObject(currentSnapshot.launch).launch_id) {
|
|
12455
|
+
const stopPayload = stopDetachedRunnerWithFlags({
|
|
12456
|
+
"launch-id": safeObject(currentSnapshot.launch).launch_id,
|
|
12457
|
+
});
|
|
12458
|
+
nextStatusMessage = `stopped ${ensureArray(stopPayload.stopped).map((item) => String(item.launch_id || "").trim()).filter(Boolean).join(", ") || "runner launch"}`;
|
|
12459
|
+
} else {
|
|
12460
|
+
nextStatusMessage = "no detached runner was active for this selection";
|
|
12461
|
+
}
|
|
12462
|
+
}
|
|
12463
|
+
if (ensureStarted) {
|
|
12464
|
+
if (state.auditResult.applyFailureBlocksStart) {
|
|
12465
|
+
throw new Error(String(safeObject(state.auditResult.applyResult).error || "runner project tui could not apply runner routes").trim());
|
|
12466
|
+
}
|
|
12467
|
+
if (!ensureArray(state.auditResult.matchingRoutes).length) {
|
|
12468
|
+
throw new Error("runner project tui did not find any enabled routes for the selected project destination");
|
|
12469
|
+
}
|
|
12470
|
+
const launchPayload = await runRunnerStartDetachedResolvedRoutes(
|
|
12471
|
+
state.auditResult.matchingRoutes,
|
|
12472
|
+
state.auditResult.startFlags,
|
|
12473
|
+
"runner project tui",
|
|
12474
|
+
{ silent: true },
|
|
12475
|
+
);
|
|
12476
|
+
nextStatusMessage = launchPayload.already_running
|
|
12477
|
+
? `detached runner already running: ${String(safeObject(launchPayload.launch).launch_id || "").trim() || "-"}`
|
|
12478
|
+
: `detached runner started: ${String(safeObject(launchPayload.launch).launch_id || "").trim() || "-"}`;
|
|
12479
|
+
}
|
|
12480
|
+
state.snapshot = buildRunnerProjectTUISnapshot({
|
|
12481
|
+
auditResult: state.auditResult,
|
|
12482
|
+
autoStart,
|
|
12483
|
+
statusMessage: nextStatusMessage,
|
|
12484
|
+
errorMessage: nextErrorMessage,
|
|
12485
|
+
lastAuditAt: new Date().toISOString(),
|
|
12486
|
+
});
|
|
12487
|
+
} catch (err) {
|
|
12488
|
+
state.snapshot = buildRunnerProjectTUISnapshot({
|
|
12489
|
+
auditResult: state.auditResult || {
|
|
12490
|
+
summaryPayload: {
|
|
12491
|
+
project_id: String(auditFlags["project-id"] || auditFlags.project_id || "").trim(),
|
|
12492
|
+
destination_label: String(auditFlags["destination-label"] || auditFlags.destination_label || "").trim(),
|
|
12493
|
+
destination_id: String(auditFlags["destination-id"] || auditFlags.destination_id || "").trim(),
|
|
12494
|
+
},
|
|
12495
|
+
matchingRoutes: [],
|
|
12496
|
+
},
|
|
12497
|
+
autoStart,
|
|
12498
|
+
statusMessage,
|
|
12499
|
+
errorMessage: String(err?.message || err).trim(),
|
|
12500
|
+
lastAuditAt: new Date().toISOString(),
|
|
12501
|
+
});
|
|
12502
|
+
} finally {
|
|
12503
|
+
state.busy = false;
|
|
12504
|
+
render(false);
|
|
12505
|
+
}
|
|
12506
|
+
};
|
|
12507
|
+
|
|
12508
|
+
const queueRefresh = (options = {}) => {
|
|
12509
|
+
void refreshSnapshot(options);
|
|
12510
|
+
};
|
|
12511
|
+
|
|
12512
|
+
try {
|
|
12513
|
+
await refreshSnapshot({
|
|
12514
|
+
reAudit: true,
|
|
12515
|
+
ensureStarted: autoStart,
|
|
12516
|
+
statusMessage: autoStart
|
|
12517
|
+
? "audited routes and ensured detached runner is running"
|
|
12518
|
+
: "audited routes in prepare-only mode",
|
|
12519
|
+
});
|
|
12520
|
+
|
|
12521
|
+
intervalHandle = setInterval(() => {
|
|
12522
|
+
queueRefresh({ reAudit: false, ensureStarted: false, statusMessage: "status refreshed from detached registry" });
|
|
12523
|
+
}, 2000);
|
|
12524
|
+
|
|
12525
|
+
process.stdin.setRawMode(true);
|
|
12526
|
+
process.stdin.resume();
|
|
12527
|
+
stdinHandler = (chunk) => {
|
|
12528
|
+
const key = String(chunk || "");
|
|
12529
|
+
if (key === "\u0003" || key.toLowerCase() === "q") {
|
|
12530
|
+
state.disposed = true;
|
|
12531
|
+
if (intervalHandle) clearInterval(intervalHandle);
|
|
12532
|
+
render(true);
|
|
12533
|
+
process.stdin.setRawMode(false);
|
|
12534
|
+
process.stdin.pause();
|
|
12535
|
+
process.stdin.off("data", stdinHandler);
|
|
12536
|
+
process.stdout.write("\u001b[?25h\n");
|
|
12537
|
+
return;
|
|
12538
|
+
}
|
|
12539
|
+
if (key.toLowerCase() === "r") {
|
|
12540
|
+
queueRefresh({ reAudit: false, ensureStarted: false, statusMessage: "status refreshed from detached registry" });
|
|
12541
|
+
return;
|
|
12542
|
+
}
|
|
12543
|
+
if (key.toLowerCase() === "a") {
|
|
12544
|
+
queueRefresh({ reAudit: true, ensureStarted: false, statusMessage: "route audit refreshed" });
|
|
12545
|
+
return;
|
|
12546
|
+
}
|
|
12547
|
+
if (key.toLowerCase() === "s") {
|
|
12548
|
+
queueRefresh({ reAudit: false, ensureStarted: true, statusMessage: "ensuring detached runner is active" });
|
|
12549
|
+
return;
|
|
12550
|
+
}
|
|
12551
|
+
if (key.toLowerCase() === "x") {
|
|
12552
|
+
queueRefresh({ reAudit: false, ensureStarted: false, stopRunner: true, statusMessage: "stopping detached runner" });
|
|
12553
|
+
}
|
|
12554
|
+
};
|
|
12555
|
+
process.stdin.on("data", stdinHandler);
|
|
12556
|
+
await new Promise((resolve) => {
|
|
12557
|
+
const waitForDispose = setInterval(() => {
|
|
12558
|
+
if (!state.disposed) return;
|
|
12559
|
+
clearInterval(waitForDispose);
|
|
12560
|
+
resolve();
|
|
12561
|
+
}, 100);
|
|
12562
|
+
});
|
|
12563
|
+
} finally {
|
|
12564
|
+
if (intervalHandle) clearInterval(intervalHandle);
|
|
12565
|
+
if (stdinHandler) {
|
|
12566
|
+
try {
|
|
12567
|
+
process.stdin.off("data", stdinHandler);
|
|
12568
|
+
} catch {}
|
|
12569
|
+
}
|
|
12570
|
+
if (process.stdin?.isTTY) {
|
|
12571
|
+
try { process.stdin.setRawMode(false); } catch {}
|
|
12572
|
+
try { process.stdin.pause(); } catch {}
|
|
12573
|
+
}
|
|
12574
|
+
if (!state.disposed) {
|
|
12575
|
+
render(true);
|
|
12576
|
+
process.stdout.write("\u001b[?25h\n");
|
|
12577
|
+
}
|
|
12578
|
+
}
|
|
12579
|
+
}
|
|
12580
|
+
|
|
12280
12581
|
function canStartRunnerDespiteProjectUpApplyFailure({ applyRequested, applyResult, matchingRoutes }) {
|
|
12281
12582
|
if (!applyRequested) return false;
|
|
12282
12583
|
const normalizedApplyResult = safeObject(applyResult);
|
|
@@ -12329,16 +12630,53 @@ function sleepSyncMilliseconds(durationMs) {
|
|
|
12329
12630
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, numericDurationMs);
|
|
12330
12631
|
}
|
|
12331
12632
|
|
|
12332
|
-
function
|
|
12333
|
-
|
|
12334
|
-
if (
|
|
12335
|
-
return;
|
|
12633
|
+
function buildDetachedRunnerBootstrapLogPaths(logFilePath = "") {
|
|
12634
|
+
const normalizedLogFilePath = String(logFilePath || "").trim();
|
|
12635
|
+
if (!normalizedLogFilePath) {
|
|
12636
|
+
return { stdoutPath: "", stderrPath: "" };
|
|
12336
12637
|
}
|
|
12638
|
+
const parsed = path.parse(normalizedLogFilePath);
|
|
12639
|
+
const baseName = parsed.name || "runner-start";
|
|
12640
|
+
return {
|
|
12641
|
+
stdoutPath: path.join(parsed.dir, `${baseName}.bootstrap.out.log`),
|
|
12642
|
+
stderrPath: path.join(parsed.dir, `${baseName}.bootstrap.err.log`),
|
|
12643
|
+
};
|
|
12644
|
+
}
|
|
12645
|
+
|
|
12646
|
+
function ensureDetachedRunnerBootstrapped(childPID, logFilePath = "", bootstrapLogPaths = {}) {
|
|
12337
12647
|
const normalizedLogFilePath = String(logFilePath || "").trim();
|
|
12338
|
-
|
|
12339
|
-
|
|
12648
|
+
const normalizedBootstrapStdoutPath = String(safeObject(bootstrapLogPaths).stdoutPath || "").trim();
|
|
12649
|
+
const normalizedBootstrapStderrPath = String(safeObject(bootstrapLogPaths).stderrPath || "").trim();
|
|
12650
|
+
const deadlineMs = Date.now() + 5000;
|
|
12651
|
+
while (Date.now() < deadlineMs) {
|
|
12652
|
+
if (!isProcessAlive(childPID)) {
|
|
12653
|
+
if (normalizedBootstrapStderrPath && fs.existsSync(normalizedBootstrapStderrPath)) {
|
|
12654
|
+
throw new Error(`detached runner exited during bootstrap; inspect ${normalizedBootstrapStderrPath}`);
|
|
12655
|
+
}
|
|
12656
|
+
if (normalizedBootstrapStdoutPath && fs.existsSync(normalizedBootstrapStdoutPath)) {
|
|
12657
|
+
throw new Error(`detached runner exited during bootstrap; inspect ${normalizedBootstrapStdoutPath}`);
|
|
12658
|
+
}
|
|
12659
|
+
if (normalizedLogFilePath && fs.existsSync(normalizedLogFilePath)) {
|
|
12660
|
+
throw new Error(`detached runner exited during bootstrap; inspect ${normalizedLogFilePath}`);
|
|
12661
|
+
}
|
|
12662
|
+
throw new Error("detached runner exited during bootstrap before the polling loop became stable");
|
|
12663
|
+
}
|
|
12664
|
+
if (normalizedLogFilePath && fs.existsSync(normalizedLogFilePath)) {
|
|
12665
|
+
try {
|
|
12666
|
+
if (fs.statSync(normalizedLogFilePath).size > 0) {
|
|
12667
|
+
return;
|
|
12668
|
+
}
|
|
12669
|
+
} catch {}
|
|
12670
|
+
}
|
|
12671
|
+
sleepSyncMilliseconds(250);
|
|
12672
|
+
}
|
|
12673
|
+
if (normalizedBootstrapStderrPath && fs.existsSync(normalizedBootstrapStderrPath)) {
|
|
12674
|
+
throw new Error(`detached runner stayed alive but did not create the runner log; inspect ${normalizedBootstrapStderrPath}`);
|
|
12340
12675
|
}
|
|
12341
|
-
|
|
12676
|
+
if (normalizedBootstrapStdoutPath && fs.existsSync(normalizedBootstrapStdoutPath)) {
|
|
12677
|
+
throw new Error(`detached runner stayed alive but did not create the runner log; inspect ${normalizedBootstrapStdoutPath}`);
|
|
12678
|
+
}
|
|
12679
|
+
throw new Error("detached runner stayed alive but did not create the runner log during bootstrap");
|
|
12342
12680
|
}
|
|
12343
12681
|
|
|
12344
12682
|
function buildRunnerProjectUpNextSteps({
|
|
@@ -12800,16 +13138,16 @@ async function waitForRunnerPIDFile(pidFilePath, timeoutMs = 8000) {
|
|
|
12800
13138
|
throw new Error(`detached runner launch did not publish a child process id: ${pidFilePath}`);
|
|
12801
13139
|
}
|
|
12802
13140
|
|
|
12803
|
-
function buildRunnerDetachedLaunchRecord(childPID, routes, flags, sourceCommand, logFilePath = "") {
|
|
12804
|
-
const normalizedRoutes = ensureArray(routes).map((route) => normalizeRunnerRoute(route));
|
|
12805
|
-
const cliPath = fileURLToPath(import.meta.url);
|
|
12806
|
-
return normalizeRunnerProcessLaunchEntry({
|
|
12807
|
-
launch_id: `${Date.now()}-${childPID}`,
|
|
12808
|
-
pid: childPID,
|
|
12809
|
-
started_at: new Date().toISOString(),
|
|
12810
|
-
command: [process.execPath, cliPath, "
|
|
12811
|
-
cli_path: cliPath,
|
|
12812
|
-
working_directory: path.dirname(cliPath),
|
|
13141
|
+
function buildRunnerDetachedLaunchRecord(childPID, routes, flags, sourceCommand, logFilePath = "") {
|
|
13142
|
+
const normalizedRoutes = ensureArray(routes).map((route) => normalizeRunnerRoute(route));
|
|
13143
|
+
const cliPath = fileURLToPath(import.meta.url);
|
|
13144
|
+
return normalizeRunnerProcessLaunchEntry({
|
|
13145
|
+
launch_id: `${Date.now()}-${childPID}`,
|
|
13146
|
+
pid: childPID,
|
|
13147
|
+
started_at: new Date().toISOString(),
|
|
13148
|
+
command: [process.execPath, cliPath, "runner", "start", "--no-update", ...serializeCLIFlags(flags, { omit: ["json", "start", "start-detached", "detached"] })].join(" "),
|
|
13149
|
+
cli_path: cliPath,
|
|
13150
|
+
working_directory: path.dirname(cliPath),
|
|
12813
13151
|
route_set_signature: runnerDetachedRouteSetSignature(normalizedRoutes),
|
|
12814
13152
|
route_keys: normalizedRoutes.map((route) => runnerRouteKey(route)),
|
|
12815
13153
|
route_names: normalizedRoutes.map((route) => route.name || runnerRouteKey(route)),
|
|
@@ -12831,46 +13169,43 @@ function findExistingDetachedRunnerLaunch(registry, routes) {
|
|
|
12831
13169
|
}) || null;
|
|
12832
13170
|
}
|
|
12833
13171
|
|
|
12834
|
-
async function launchDetachedRunnerProcess(flags, routes, sourceCommand) {
|
|
12835
|
-
const cliPath = fileURLToPath(import.meta.url);
|
|
12836
|
-
const detachedLogFilePath = resolveRunnerLogFilePath(flags["log-file"], routes, sourceCommand);
|
|
12837
|
-
const
|
|
12838
|
-
|
|
12839
|
-
...(
|
|
12840
|
-
|
|
12841
|
-
|
|
12842
|
-
|
|
12843
|
-
|
|
12844
|
-
|
|
12845
|
-
|
|
12846
|
-
|
|
12847
|
-
|
|
12848
|
-
|
|
12849
|
-
|
|
12850
|
-
|
|
12851
|
-
|
|
12852
|
-
|
|
12853
|
-
|
|
12854
|
-
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
|
|
12861
|
-
|
|
12862
|
-
|
|
12863
|
-
|
|
12864
|
-
|
|
12865
|
-
|
|
12866
|
-
|
|
12867
|
-
|
|
12868
|
-
}
|
|
12869
|
-
const childPID = intFromRawAllowZero(String(launched.stdout || "").trim().split(/\r?\n/).pop(), 0);
|
|
12870
|
-
if (childPID <= 0) {
|
|
12871
|
-
throw new Error("detached runner launch did not return a child process id");
|
|
13172
|
+
async function launchDetachedRunnerProcess(flags, routes, sourceCommand) {
|
|
13173
|
+
const cliPath = fileURLToPath(import.meta.url);
|
|
13174
|
+
const detachedLogFilePath = resolveRunnerLogFilePath(flags["log-file"], routes, sourceCommand);
|
|
13175
|
+
const bootstrapLogPaths = buildDetachedRunnerBootstrapLogPaths(detachedLogFilePath);
|
|
13176
|
+
const startFlags = {
|
|
13177
|
+
...safeObject(flags),
|
|
13178
|
+
tui: boolFromRaw(flags.tui, false),
|
|
13179
|
+
...(detachedLogFilePath ? { "log-file": detachedLogFilePath } : {}),
|
|
13180
|
+
};
|
|
13181
|
+
const startArgs = [cliPath, "runner", "start", "--no-update", ...serializeCLIFlags(startFlags, {
|
|
13182
|
+
omit: ["json", "start", "start-detached", "detached"],
|
|
13183
|
+
})];
|
|
13184
|
+
if (process.platform === "win32") {
|
|
13185
|
+
const stdoutFd = bootstrapLogPaths.stdoutPath ? fs.openSync(bootstrapLogPaths.stdoutPath, "a") : "ignore";
|
|
13186
|
+
const stderrFd = bootstrapLogPaths.stderrPath ? fs.openSync(bootstrapLogPaths.stderrPath, "a") : "ignore";
|
|
13187
|
+
let child = null;
|
|
13188
|
+
try {
|
|
13189
|
+
child = spawn(process.execPath, startArgs, {
|
|
13190
|
+
cwd: path.dirname(cliPath),
|
|
13191
|
+
detached: true,
|
|
13192
|
+
stdio: ["ignore", stdoutFd, stderrFd],
|
|
13193
|
+
windowsHide: true,
|
|
13194
|
+
});
|
|
13195
|
+
if (!child.pid) {
|
|
13196
|
+
throw new Error("detached runner launch did not return a child process id");
|
|
13197
|
+
}
|
|
13198
|
+
child.unref();
|
|
13199
|
+
} finally {
|
|
13200
|
+
if (typeof stdoutFd === "number") {
|
|
13201
|
+
try { fs.closeSync(stdoutFd); } catch {}
|
|
13202
|
+
}
|
|
13203
|
+
if (typeof stderrFd === "number") {
|
|
13204
|
+
try { fs.closeSync(stderrFd); } catch {}
|
|
13205
|
+
}
|
|
12872
13206
|
}
|
|
12873
|
-
|
|
13207
|
+
const childPID = intFromRawAllowZero(child?.pid, 0);
|
|
13208
|
+
ensureDetachedRunnerBootstrapped(childPID, detachedLogFilePath, bootstrapLogPaths);
|
|
12874
13209
|
return buildRunnerDetachedLaunchRecord(childPID, routes, startFlags, sourceCommand, detachedLogFilePath);
|
|
12875
13210
|
}
|
|
12876
13211
|
const launchTempDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-launch-"));
|
|
@@ -12901,7 +13236,7 @@ async function launchDetachedRunnerProcess(flags, routes, sourceCommand) {
|
|
|
12901
13236
|
throw new Error(String(launched.stderr || launched.stdout || "failed to launch detached runner in Terminal.app").trim());
|
|
12902
13237
|
}
|
|
12903
13238
|
const childPID = await waitForRunnerPIDFile(pidFilePath);
|
|
12904
|
-
ensureDetachedRunnerBootstrapped(childPID, detachedLogFilePath);
|
|
13239
|
+
ensureDetachedRunnerBootstrapped(childPID, detachedLogFilePath, bootstrapLogPaths);
|
|
12905
13240
|
return buildRunnerDetachedLaunchRecord(childPID, routes, startFlags, sourceCommand, detachedLogFilePath);
|
|
12906
13241
|
}
|
|
12907
13242
|
const terminalLauncher = resolveLinuxDetachedTerminalLauncher(scriptPath);
|
|
@@ -12918,7 +13253,7 @@ async function launchDetachedRunnerProcess(flags, routes, sourceCommand) {
|
|
|
12918
13253
|
throw new Error("detached terminal launcher did not return a process id");
|
|
12919
13254
|
}
|
|
12920
13255
|
const runnerPID = await waitForRunnerPIDFile(pidFilePath);
|
|
12921
|
-
ensureDetachedRunnerBootstrapped(runnerPID, detachedLogFilePath);
|
|
13256
|
+
ensureDetachedRunnerBootstrapped(runnerPID, detachedLogFilePath, bootstrapLogPaths);
|
|
12922
13257
|
return buildRunnerDetachedLaunchRecord(runnerPID, routes, startFlags, sourceCommand, detachedLogFilePath);
|
|
12923
13258
|
} finally {
|
|
12924
13259
|
try {
|
|
@@ -13021,30 +13356,33 @@ async function runRunnerStop(flags) {
|
|
|
13021
13356
|
}
|
|
13022
13357
|
}
|
|
13023
13358
|
|
|
13024
|
-
async function runRunnerStartDetachedResolvedRoutes(routes, flags, sourceCommand = "runner start-detached") {
|
|
13025
|
-
const
|
|
13026
|
-
const
|
|
13027
|
-
|
|
13028
|
-
|
|
13029
|
-
|
|
13359
|
+
async function runRunnerStartDetachedResolvedRoutes(routes, flags, sourceCommand = "runner start-detached", options = {}) {
|
|
13360
|
+
const silent = boolFromRaw(safeObject(options).silent, false);
|
|
13361
|
+
const registry = loadBotRunnerProcessRegistry({ persistIfNeeded: true });
|
|
13362
|
+
const existing = findExistingDetachedRunnerLaunch(registry, routes);
|
|
13363
|
+
if (existing) {
|
|
13364
|
+
const payload = {
|
|
13365
|
+
ok: true,
|
|
13030
13366
|
already_running: true,
|
|
13031
13367
|
registry_file: registry.filePath,
|
|
13032
13368
|
launch: {
|
|
13033
|
-
...existing,
|
|
13034
|
-
alive: true,
|
|
13035
|
-
},
|
|
13036
|
-
};
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13042
|
-
|
|
13043
|
-
|
|
13044
|
-
|
|
13045
|
-
|
|
13046
|
-
|
|
13047
|
-
|
|
13369
|
+
...existing,
|
|
13370
|
+
alive: true,
|
|
13371
|
+
},
|
|
13372
|
+
};
|
|
13373
|
+
if (!silent) {
|
|
13374
|
+
if (boolFromRaw(flags.json, false)) {
|
|
13375
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
13376
|
+
return payload;
|
|
13377
|
+
}
|
|
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`);
|
|
13379
|
+
}
|
|
13380
|
+
return payload;
|
|
13381
|
+
}
|
|
13382
|
+
const launch = await launchDetachedRunnerProcess(flags, routes, sourceCommand);
|
|
13383
|
+
const nextLaunches = {
|
|
13384
|
+
...safeObject(registry).launches,
|
|
13385
|
+
[launch.launch_id]: launch,
|
|
13048
13386
|
};
|
|
13049
13387
|
saveBotRunnerProcessRegistry({ launches: nextLaunches });
|
|
13050
13388
|
const payload = {
|
|
@@ -13052,29 +13390,31 @@ async function runRunnerStartDetachedResolvedRoutes(routes, flags, sourceCommand
|
|
|
13052
13390
|
already_running: false,
|
|
13053
13391
|
registry_file: registry.filePath,
|
|
13054
13392
|
launch: {
|
|
13055
|
-
...launch,
|
|
13056
|
-
alive: true,
|
|
13057
|
-
},
|
|
13058
|
-
};
|
|
13059
|
-
if (
|
|
13060
|
-
|
|
13061
|
-
|
|
13062
|
-
|
|
13063
|
-
|
|
13064
|
-
|
|
13065
|
-
|
|
13066
|
-
|
|
13067
|
-
|
|
13068
|
-
|
|
13069
|
-
|
|
13070
|
-
|
|
13071
|
-
|
|
13072
|
-
|
|
13073
|
-
|
|
13074
|
-
|
|
13075
|
-
|
|
13076
|
-
|
|
13077
|
-
}
|
|
13393
|
+
...launch,
|
|
13394
|
+
alive: true,
|
|
13395
|
+
},
|
|
13396
|
+
};
|
|
13397
|
+
if (!silent) {
|
|
13398
|
+
if (boolFromRaw(flags.json, false)) {
|
|
13399
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
13400
|
+
return payload;
|
|
13401
|
+
}
|
|
13402
|
+
process.stdout.write(
|
|
13403
|
+
[
|
|
13404
|
+
"Detached runner start: OK",
|
|
13405
|
+
`launch_id: ${launch.launch_id}`,
|
|
13406
|
+
`pid: ${launch.pid}`,
|
|
13407
|
+
`registry_file: ${registry.filePath}`,
|
|
13408
|
+
`project_ids: ${launch.project_ids.join(", ") || "-"}`,
|
|
13409
|
+
`route_names: ${launch.route_names.join(", ") || "-"}`,
|
|
13410
|
+
`destination_labels: ${launch.destination_labels.join(", ") || "-"}`,
|
|
13411
|
+
`log_file: ${launch.log_file || "-"}`,
|
|
13412
|
+
`command: ${launch.command}`,
|
|
13413
|
+
].join("\n") + "\n",
|
|
13414
|
+
);
|
|
13415
|
+
}
|
|
13416
|
+
return payload;
|
|
13417
|
+
}
|
|
13078
13418
|
|
|
13079
13419
|
function stopDetachedRunnerWithFlags(flags) {
|
|
13080
13420
|
const registry = loadBotRunnerProcessRegistry({ persistIfNeeded: true });
|
|
@@ -13107,10 +13447,10 @@ function stopDetachedRunnerWithFlags(flags) {
|
|
|
13107
13447
|
};
|
|
13108
13448
|
}
|
|
13109
13449
|
|
|
13110
|
-
function startDetachedRunnerWithFlags(flags, sourceCommand = "runner.start_detached") {
|
|
13111
|
-
const routes = resolveRunnerRoutes(flags, "start");
|
|
13112
|
-
return runRunnerStartDetachedResolvedRoutes(routes, flags, sourceCommand);
|
|
13113
|
-
}
|
|
13450
|
+
function startDetachedRunnerWithFlags(flags, sourceCommand = "runner.start_detached", options = {}) {
|
|
13451
|
+
const routes = resolveRunnerRoutes(flags, "start");
|
|
13452
|
+
return runRunnerStartDetachedResolvedRoutes(routes, flags, sourceCommand, options);
|
|
13453
|
+
}
|
|
13114
13454
|
|
|
13115
13455
|
async function runRunnerStartDetached(flags) {
|
|
13116
13456
|
await startDetachedRunnerWithFlags(flags, "runner start-detached");
|
|
@@ -14250,19 +14590,23 @@ async function runRunner(argv) {
|
|
|
14250
14590
|
}
|
|
14251
14591
|
throw new Error("runner route requires a subcommand: list | add | edit | remove");
|
|
14252
14592
|
}
|
|
14253
|
-
if (subcommand === "project" || subcommand === "projects") {
|
|
14593
|
+
if (subcommand === "project" || subcommand === "projects") {
|
|
14254
14594
|
const [projectSubcommandRaw = "", ...projectRest] = rest;
|
|
14255
14595
|
const projectSubcommand = String(projectSubcommandRaw || "").trim().toLowerCase();
|
|
14256
14596
|
const projectArgv = !projectSubcommand || projectSubcommand.startsWith("-")
|
|
14257
14597
|
? [projectSubcommandRaw, ...projectRest].filter((value) => String(value || "").trim())
|
|
14258
14598
|
: projectRest;
|
|
14259
|
-
const projectFlags = parseArgs(projectArgv);
|
|
14260
|
-
if (projectSubcommand === "up") {
|
|
14261
|
-
await runRunnerProjectUp(projectFlags);
|
|
14262
|
-
return;
|
|
14263
|
-
}
|
|
14264
|
-
|
|
14265
|
-
|
|
14599
|
+
const projectFlags = parseArgs(projectArgv);
|
|
14600
|
+
if (projectSubcommand === "up") {
|
|
14601
|
+
await runRunnerProjectUp(projectFlags);
|
|
14602
|
+
return;
|
|
14603
|
+
}
|
|
14604
|
+
if (projectSubcommand === "tui") {
|
|
14605
|
+
await runRunnerProjectTUI(projectFlags);
|
|
14606
|
+
return;
|
|
14607
|
+
}
|
|
14608
|
+
throw new Error("runner project requires a subcommand: up | tui");
|
|
14609
|
+
}
|
|
14266
14610
|
if (subcommand === "artifact" || subcommand === "artifacts") {
|
|
14267
14611
|
const [artifactSubcommandRaw = "", ...artifactRest] = rest;
|
|
14268
14612
|
const artifactSubcommand = String(artifactSubcommandRaw || "").trim().toLowerCase();
|
|
@@ -14305,8 +14649,8 @@ async function runRunner(argv) {
|
|
|
14305
14649
|
await runRunnerStop(flags);
|
|
14306
14650
|
return;
|
|
14307
14651
|
}
|
|
14308
|
-
throw new Error("runner requires a subcommand: list | show | status | once | start | start-detached | stop | route | project | artifact");
|
|
14309
|
-
}
|
|
14652
|
+
throw new Error("runner requires a subcommand: list | show | status | once | start | start-detached | stop | route | project | artifact");
|
|
14653
|
+
}
|
|
14310
14654
|
|
|
14311
14655
|
async function runLocalBotBridge(argv) {
|
|
14312
14656
|
const helperPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "scripts", "local-bot-ai-bridge.mjs");
|
|
@@ -19251,25 +19595,28 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
19251
19595
|
}
|
|
19252
19596
|
}
|
|
19253
19597
|
|
|
19254
|
-
try {
|
|
19255
|
-
const detachedArgs = serializeCLIFlags({
|
|
19256
|
-
"project-id": selftestProjectID,
|
|
19257
|
-
provider: "telegram",
|
|
19258
|
-
"destination-id": "dest-1",
|
|
19259
|
-
|
|
19260
|
-
detached: "true",
|
|
19261
|
-
|
|
19262
|
-
|
|
19263
|
-
|
|
19598
|
+
try {
|
|
19599
|
+
const detachedArgs = serializeCLIFlags({
|
|
19600
|
+
"project-id": selftestProjectID,
|
|
19601
|
+
provider: "telegram",
|
|
19602
|
+
"destination-id": "dest-1",
|
|
19603
|
+
tui: "false",
|
|
19604
|
+
"start-detached": "true",
|
|
19605
|
+
detached: "true",
|
|
19606
|
+
start: "true",
|
|
19607
|
+
}, {
|
|
19608
|
+
omit: ["start", "start-detached", "detached"],
|
|
19264
19609
|
});
|
|
19265
|
-
push(
|
|
19266
|
-
"detached_runner_cli_args_strip_control_flags",
|
|
19267
|
-
detachedArgs.includes("--project-id")
|
|
19268
|
-
&& detachedArgs.includes(selftestProjectID)
|
|
19269
|
-
&&
|
|
19270
|
-
&&
|
|
19271
|
-
&& !detachedArgs.includes("--
|
|
19272
|
-
|
|
19610
|
+
push(
|
|
19611
|
+
"detached_runner_cli_args_strip_control_flags",
|
|
19612
|
+
detachedArgs.includes("--project-id")
|
|
19613
|
+
&& detachedArgs.includes(selftestProjectID)
|
|
19614
|
+
&& detachedArgs.includes("--tui")
|
|
19615
|
+
&& detachedArgs.includes("false")
|
|
19616
|
+
&& !detachedArgs.includes("--start")
|
|
19617
|
+
&& !detachedArgs.includes("--start-detached")
|
|
19618
|
+
&& !detachedArgs.includes("--detached"),
|
|
19619
|
+
detachedArgs.join(" "),
|
|
19273
19620
|
);
|
|
19274
19621
|
} catch (err) {
|
|
19275
19622
|
push("detached_runner_cli_args_strip_control_flags", false, String(err?.message || err));
|
|
@@ -19293,43 +19640,45 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
19293
19640
|
push("runner_default_log_file_path_uses_runner_logs_directory", false, String(err?.message || err));
|
|
19294
19641
|
}
|
|
19295
19642
|
|
|
19296
|
-
try {
|
|
19297
|
-
const detachedLaunch = buildRunnerDetachedLaunchRecord(
|
|
19298
|
-
4321,
|
|
19299
|
-
[{ name: "telegram-monitor-selftest", projectID: selftestProjectID, provider: "telegram", destinationLabel: "Selftest Room" }],
|
|
19300
|
-
{},
|
|
19301
|
-
"runner start-detached",
|
|
19302
|
-
"C:\\logs\\runner-selftest.jsonl",
|
|
19303
|
-
);
|
|
19304
|
-
push(
|
|
19305
|
-
"detached_runner_launch_record_persists_log_file",
|
|
19306
|
-
String(detachedLaunch.log_file || "").trim() === "C:\\logs\\runner-selftest.jsonl"
|
|
19307
|
-
|
|
19308
|
-
|
|
19309
|
-
|
|
19310
|
-
|
|
19311
|
-
}
|
|
19643
|
+
try {
|
|
19644
|
+
const detachedLaunch = buildRunnerDetachedLaunchRecord(
|
|
19645
|
+
4321,
|
|
19646
|
+
[{ name: "telegram-monitor-selftest", projectID: selftestProjectID, provider: "telegram", destinationLabel: "Selftest Room" }],
|
|
19647
|
+
{ tui: false },
|
|
19648
|
+
"runner start-detached",
|
|
19649
|
+
"C:\\logs\\runner-selftest.jsonl",
|
|
19650
|
+
);
|
|
19651
|
+
push(
|
|
19652
|
+
"detached_runner_launch_record_persists_log_file",
|
|
19653
|
+
String(detachedLaunch.log_file || "").trim() === "C:\\logs\\runner-selftest.jsonl"
|
|
19654
|
+
&& String(detachedLaunch.command || "").includes(" runner start --no-update ")
|
|
19655
|
+
&& String(detachedLaunch.command || "").includes("--tui false"),
|
|
19656
|
+
`log_file=${String(detachedLaunch.log_file || "").trim() || "(none)"} command=${String(detachedLaunch.command || "").trim() || "(none)"}`,
|
|
19657
|
+
);
|
|
19658
|
+
} catch (err) {
|
|
19659
|
+
push("detached_runner_launch_record_persists_log_file", false, String(err?.message || err));
|
|
19660
|
+
}
|
|
19312
19661
|
|
|
19313
19662
|
let detachedRunnerPosixTempDir = "";
|
|
19314
19663
|
try {
|
|
19315
19664
|
detachedRunnerPosixTempDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-detached-script-selftest-"));
|
|
19316
19665
|
const scriptPath = path.join(detachedRunnerPosixTempDir, "start-runner.sh");
|
|
19317
19666
|
const pidFilePath = path.join(detachedRunnerPosixTempDir, "runner.pid");
|
|
19318
|
-
buildRunnerDetachedPosixLaunchScript({
|
|
19319
|
-
scriptPath,
|
|
19320
|
-
pidFilePath,
|
|
19321
|
-
workingDirectory: detachedRunnerPosixTempDir,
|
|
19322
|
-
nodePath: "/usr/local/bin/node",
|
|
19323
|
-
startArgs: ["/tmp/cli.mjs", "
|
|
19324
|
-
});
|
|
19667
|
+
buildRunnerDetachedPosixLaunchScript({
|
|
19668
|
+
scriptPath,
|
|
19669
|
+
pidFilePath,
|
|
19670
|
+
workingDirectory: detachedRunnerPosixTempDir,
|
|
19671
|
+
nodePath: "/usr/local/bin/node",
|
|
19672
|
+
startArgs: ["/tmp/cli.mjs", "runner", "start", "--no-update", "--project-id", selftestProjectID],
|
|
19673
|
+
});
|
|
19325
19674
|
const scriptText = fs.readFileSync(scriptPath, "utf8");
|
|
19326
|
-
push(
|
|
19327
|
-
"detached_runner_posix_launch_script_serializes_runner_command",
|
|
19328
|
-
scriptText.includes(`echo $$ > ${posixSingleQuote(pidFilePath)}`)
|
|
19329
|
-
&& scriptText.includes(`cd ${posixSingleQuote(detachedRunnerPosixTempDir)} || exit 1`)
|
|
19330
|
-
&& scriptText.includes(`exec ${posixSingleQuote("/usr/local/bin/node")} ${posixSingleQuote("/tmp/cli.mjs")} ${posixSingleQuote("
|
|
19331
|
-
scriptText.replace(/\r?\n/g, " | "),
|
|
19332
|
-
);
|
|
19675
|
+
push(
|
|
19676
|
+
"detached_runner_posix_launch_script_serializes_runner_command",
|
|
19677
|
+
scriptText.includes(`echo $$ > ${posixSingleQuote(pidFilePath)}`)
|
|
19678
|
+
&& scriptText.includes(`cd ${posixSingleQuote(detachedRunnerPosixTempDir)} || exit 1`)
|
|
19679
|
+
&& scriptText.includes(`exec ${posixSingleQuote("/usr/local/bin/node")} ${posixSingleQuote("/tmp/cli.mjs")} ${posixSingleQuote("runner")} ${posixSingleQuote("start")} ${posixSingleQuote("--no-update")}`),
|
|
19680
|
+
scriptText.replace(/\r?\n/g, " | "),
|
|
19681
|
+
);
|
|
19333
19682
|
} catch (err) {
|
|
19334
19683
|
push("detached_runner_posix_launch_script_serializes_runner_command", false, String(err?.message || err));
|
|
19335
19684
|
} finally {
|
|
@@ -19446,6 +19795,41 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
19446
19795
|
push("runner_project_up_tool_flag_accepts_start_detached_snake_case", false, String(err?.message || err));
|
|
19447
19796
|
}
|
|
19448
19797
|
|
|
19798
|
+
try {
|
|
19799
|
+
const projectTUIRuntime = resolveRunnerProjectTUIRuntimePolicy({
|
|
19800
|
+
"project-id": selftestProjectID,
|
|
19801
|
+
"destination-label": "Selftest Room",
|
|
19802
|
+
});
|
|
19803
|
+
push(
|
|
19804
|
+
"runner_project_tui_defaults_to_detached_bootstrap_mode",
|
|
19805
|
+
projectTUIRuntime.autoStart === true
|
|
19806
|
+
&& projectTUIRuntime.auditFlags.start === false
|
|
19807
|
+
&& projectTUIRuntime.auditFlags["start-detached"] === false
|
|
19808
|
+
&& projectTUIRuntime.auditFlags.tui === false
|
|
19809
|
+
&& projectTUIRuntime.auditFlags.json === true,
|
|
19810
|
+
`autoStart=${String(projectTUIRuntime.autoStart)} start=${String(projectTUIRuntime.auditFlags.start)} detached=${String(projectTUIRuntime.auditFlags["start-detached"])} tui=${String(projectTUIRuntime.auditFlags.tui)} json=${String(projectTUIRuntime.auditFlags.json)}`,
|
|
19811
|
+
);
|
|
19812
|
+
} catch (err) {
|
|
19813
|
+
push("runner_project_tui_defaults_to_detached_bootstrap_mode", false, String(err?.message || err));
|
|
19814
|
+
}
|
|
19815
|
+
|
|
19816
|
+
try {
|
|
19817
|
+
const projectTUIRuntime = resolveRunnerProjectTUIRuntimePolicy({
|
|
19818
|
+
"project-id": selftestProjectID,
|
|
19819
|
+
"destination-label": "Selftest Room",
|
|
19820
|
+
start: false,
|
|
19821
|
+
});
|
|
19822
|
+
push(
|
|
19823
|
+
"runner_project_tui_respects_prepare_only_flag",
|
|
19824
|
+
projectTUIRuntime.autoStart === false
|
|
19825
|
+
&& projectTUIRuntime.auditFlags.start === false
|
|
19826
|
+
&& projectTUIRuntime.auditFlags["start-detached"] === false,
|
|
19827
|
+
`autoStart=${String(projectTUIRuntime.autoStart)} start=${String(projectTUIRuntime.auditFlags.start)} detached=${String(projectTUIRuntime.auditFlags["start-detached"])}`,
|
|
19828
|
+
);
|
|
19829
|
+
} catch (err) {
|
|
19830
|
+
push("runner_project_tui_respects_prepare_only_flag", false, String(err?.message || err));
|
|
19831
|
+
}
|
|
19832
|
+
|
|
19449
19833
|
try {
|
|
19450
19834
|
const projectUpResponse = await handleLocalProjectToolDispatchImpl(
|
|
19451
19835
|
{
|
|
@@ -2408,6 +2408,7 @@ function buildConversationIntentAnalysisPrompt({
|
|
|
2408
2408
|
"- allowed_responders must be the full set of bots allowed to speak in this conversation.",
|
|
2409
2409
|
"- If mode is single_bot, initial_responders and allowed_responders should contain only that bot.",
|
|
2410
2410
|
"- If mode is delegated_single_lead, lead_bot must be set and initial_responders should contain only the lead bot.",
|
|
2411
|
+
"- If the human tells one managed bot to say, greet, mention, introduce, or relay something to another managed bot, use delegated_single_lead. The instructed bot is lead_bot, initial_responders must contain only that bot, and the target bot belongs in allowed_responders rather than initial_responders.",
|
|
2411
2412
|
"- If the human explicitly asks one bot to summarize or finalize, set summary_bot to that bot.",
|
|
2412
2413
|
"- intent_type is optional secondary metadata, not the main control surface. Omit the key entirely unless a more specific lookup or execution subtype is clearly explicit.",
|
|
2413
2414
|
"- If the human is only greeting or chatting socially, intent_type may be small_talk if you choose to include it.",
|
|
@@ -93,6 +93,70 @@ function detectDirectedManagedReplyTargetV2({
|
|
|
93
93
|
return "";
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
function normalizeDirectedManagedLeadHumanIntent({
|
|
97
|
+
text,
|
|
98
|
+
humanIntent,
|
|
99
|
+
managedMentions = [],
|
|
100
|
+
normalizeMentionSelector,
|
|
101
|
+
uniqueOrdered,
|
|
102
|
+
}) {
|
|
103
|
+
const base = safeObject(humanIntent);
|
|
104
|
+
const participants = uniqueOrdered([
|
|
105
|
+
...ensureArray(base.participantSelectors || base.participants),
|
|
106
|
+
...ensureArray(managedMentions),
|
|
107
|
+
].map((item) => normalizeMentionSelector(item)).filter(Boolean));
|
|
108
|
+
if (participants.length < 2) {
|
|
109
|
+
return base;
|
|
110
|
+
}
|
|
111
|
+
const normalizedText = String(text || "").trim();
|
|
112
|
+
if (!normalizedText) {
|
|
113
|
+
return base;
|
|
114
|
+
}
|
|
115
|
+
const instructionPattern = new RegExp(DIRECTED_MANAGED_REPLY_ACTION_PATTERN, "iu");
|
|
116
|
+
if (!instructionPattern.test(normalizedText)) {
|
|
117
|
+
return base;
|
|
118
|
+
}
|
|
119
|
+
const explicitLeadBotSelector = normalizeMentionSelector(
|
|
120
|
+
base.leadBotSelector || base.lead_bot,
|
|
121
|
+
);
|
|
122
|
+
const leadBotSelector = explicitLeadBotSelector && participants.includes(explicitLeadBotSelector)
|
|
123
|
+
? explicitLeadBotSelector
|
|
124
|
+
: participants[0] || "";
|
|
125
|
+
if (!leadBotSelector) {
|
|
126
|
+
return base;
|
|
127
|
+
}
|
|
128
|
+
const replyTargetBotSelector = detectDirectedManagedReplyTargetV2({
|
|
129
|
+
text: normalizedText,
|
|
130
|
+
currentBotSelector: leadBotSelector,
|
|
131
|
+
managedMentions: participants,
|
|
132
|
+
});
|
|
133
|
+
if (!replyTargetBotSelector || replyTargetBotSelector === leadBotSelector) {
|
|
134
|
+
return base;
|
|
135
|
+
}
|
|
136
|
+
const allowedResponderSelectors = uniqueOrdered([
|
|
137
|
+
...ensureArray(base.allowedResponderSelectors || base.allowed_responders),
|
|
138
|
+
...participants,
|
|
139
|
+
leadBotSelector,
|
|
140
|
+
replyTargetBotSelector,
|
|
141
|
+
].map((item) => normalizeMentionSelector(item)).filter(Boolean));
|
|
142
|
+
const summaryBotSelector = normalizeMentionSelector(
|
|
143
|
+
base.summaryBotSelector || base.summary_bot,
|
|
144
|
+
);
|
|
145
|
+
return {
|
|
146
|
+
...base,
|
|
147
|
+
intentMode: "delegated_single_lead",
|
|
148
|
+
allowBotToBot: true,
|
|
149
|
+
participantSelectors: participants,
|
|
150
|
+
initialResponderSelectors: [leadBotSelector],
|
|
151
|
+
allowedResponderSelectors,
|
|
152
|
+
leadBotSelector,
|
|
153
|
+
summaryBotSelector: summaryBotSelector && allowedResponderSelectors.includes(summaryBotSelector)
|
|
154
|
+
? summaryBotSelector
|
|
155
|
+
: "",
|
|
156
|
+
replyTargetBotSelector,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
96
160
|
export async function resolveHumanIntentContext({
|
|
97
161
|
selectedRecord,
|
|
98
162
|
normalizedRoute,
|
|
@@ -160,17 +224,13 @@ export async function resolveHumanIntentContext({
|
|
|
160
224
|
runnerHumanIntentPromises.set(cacheKey, promise);
|
|
161
225
|
}
|
|
162
226
|
let humanIntent = await runnerHumanIntentPromises.get(cacheKey);
|
|
163
|
-
|
|
227
|
+
humanIntent = normalizeDirectedManagedLeadHumanIntent({
|
|
164
228
|
text: parsed.body,
|
|
165
|
-
|
|
229
|
+
humanIntent,
|
|
166
230
|
managedMentions,
|
|
231
|
+
normalizeMentionSelector,
|
|
232
|
+
uniqueOrdered,
|
|
167
233
|
});
|
|
168
|
-
if (directedReplyTargetSelector) {
|
|
169
|
-
humanIntent = {
|
|
170
|
-
...safeObject(humanIntent),
|
|
171
|
-
replyTargetBotSelector: directedReplyTargetSelector,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
234
|
if (!isCompleteHumanIntentContract(humanIntent)) {
|
|
175
235
|
return {
|
|
176
236
|
currentBotSelector,
|
|
@@ -4171,9 +4171,76 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
4171
4171
|
);
|
|
4172
4172
|
}
|
|
4173
4173
|
|
|
4174
|
+
try {
|
|
4175
|
+
const promotedDirectedLeadIntentContext = await resolveHumanIntentContext({
|
|
4176
|
+
selectedRecord: {
|
|
4177
|
+
id: "comment-directed-lead-korean",
|
|
4178
|
+
parsedArchive: {
|
|
4179
|
+
kind: "telegram_message",
|
|
4180
|
+
body: "@RyoAI_bot 너가 @SangHoon01_bot 인사 시켜봐",
|
|
4181
|
+
senderIsBot: false,
|
|
4182
|
+
},
|
|
4183
|
+
},
|
|
4184
|
+
normalizedRoute: {
|
|
4185
|
+
name: "telegram-monitor-ryoai-bot-2",
|
|
4186
|
+
},
|
|
4187
|
+
bot: {
|
|
4188
|
+
username: "ryoai_bot",
|
|
4189
|
+
name: "RyoAI_bot",
|
|
4190
|
+
},
|
|
4191
|
+
executionPlan: {},
|
|
4192
|
+
deps: {
|
|
4193
|
+
resolveConversationPeerBots: () => [
|
|
4194
|
+
{ id: "bot-self-1", name: "RyoAI_bot" },
|
|
4195
|
+
{ id: "bot-peer-1", name: "SangHoon01_bot" },
|
|
4196
|
+
],
|
|
4197
|
+
},
|
|
4198
|
+
intentDeps: {
|
|
4199
|
+
normalizeMentionSelector: normalizeSelftestMentionSelector,
|
|
4200
|
+
buildConversationPeerMap: (_bot, _route, runtimeDeps) => new Map(
|
|
4201
|
+
ensureArray(runtimeDeps?.resolveConversationPeerBots?.() || []).map((item) => {
|
|
4202
|
+
const selector = normalizeSelftestMentionSelector(item?.name || item?.username);
|
|
4203
|
+
return [selector, item];
|
|
4204
|
+
}).filter(([selector]) => selector),
|
|
4205
|
+
),
|
|
4206
|
+
extractOrderedMentionSelectors: (text) => Array.from(String(text || "").matchAll(/@([A-Za-z0-9_]+)/g)).map((match) => normalizeSelftestMentionSelector(match[1] || "")),
|
|
4207
|
+
uniqueOrdered: normalizeSelftestConversationSelectorList,
|
|
4208
|
+
buildHumanIntentFromPersistedRunnerRequest: () => null,
|
|
4209
|
+
buildRunnerHumanIntentCacheKey: () => "directed-lead-korean",
|
|
4210
|
+
runnerHumanIntentPromises: new Map(),
|
|
4211
|
+
analyzeHumanConversationIntentWithContractResolver: async () => ({
|
|
4212
|
+
mode: "multi_bot_direct",
|
|
4213
|
+
participants: ["ryoai_bot", "sanghoon01_bot"],
|
|
4214
|
+
initial_responders: ["ryoai_bot", "sanghoon01_bot"],
|
|
4215
|
+
allowed_responders: ["ryoai_bot", "sanghoon01_bot"],
|
|
4216
|
+
reply_expectation: "actionable",
|
|
4217
|
+
intent_type: "actionable_request",
|
|
4218
|
+
}),
|
|
4219
|
+
scheduleRunnerHumanIntentCacheCleanup: () => {},
|
|
4220
|
+
isCompleteHumanIntentContract: () => true,
|
|
4221
|
+
normalizeHumanIntentType: (value, fallback = "") => String(value || "").trim() || fallback,
|
|
4222
|
+
},
|
|
4223
|
+
});
|
|
4224
|
+
push(
|
|
4225
|
+
"runner_human_intent_context_promotes_directed_multi_bot_direct_to_delegated_single_lead",
|
|
4226
|
+
String(promotedDirectedLeadIntentContext?.humanIntent?.intentMode || promotedDirectedLeadIntentContext?.humanIntent?.mode || "") === "delegated_single_lead"
|
|
4227
|
+
&& String(promotedDirectedLeadIntentContext?.humanIntent?.leadBotSelector || "") === "ryoai_bot"
|
|
4228
|
+
&& JSON.stringify(ensureArray(promotedDirectedLeadIntentContext?.humanIntent?.initialResponderSelectors || [])) === JSON.stringify(["ryoai_bot"])
|
|
4229
|
+
&& JSON.stringify(ensureArray(promotedDirectedLeadIntentContext?.humanIntent?.allowedResponderSelectors || [])) === JSON.stringify(["ryoai_bot", "sanghoon01_bot"])
|
|
4230
|
+
&& String(promotedDirectedLeadIntentContext?.humanIntent?.replyTargetBotSelector || "") === "sanghoon01_bot",
|
|
4231
|
+
`mode=${String(promotedDirectedLeadIntentContext?.humanIntent?.intentMode || promotedDirectedLeadIntentContext?.humanIntent?.mode || "(none)")} lead=${String(promotedDirectedLeadIntentContext?.humanIntent?.leadBotSelector || "(none)")} initial=${JSON.stringify(ensureArray(promotedDirectedLeadIntentContext?.humanIntent?.initialResponderSelectors || []))} allowed=${JSON.stringify(ensureArray(promotedDirectedLeadIntentContext?.humanIntent?.allowedResponderSelectors || []))} target=${String(promotedDirectedLeadIntentContext?.humanIntent?.replyTargetBotSelector || "(none)")}`,
|
|
4232
|
+
);
|
|
4233
|
+
} catch (err) {
|
|
4234
|
+
push(
|
|
4235
|
+
"runner_human_intent_context_promotes_directed_multi_bot_direct_to_delegated_single_lead",
|
|
4236
|
+
false,
|
|
4237
|
+
String(err?.message || err),
|
|
4238
|
+
);
|
|
4239
|
+
}
|
|
4240
|
+
|
|
4174
4241
|
const mentionOverridesReplyForUnmentionedBot = evaluateTelegramRunnerTrigger(
|
|
4175
|
-
{
|
|
4176
|
-
id: "comment-2b",
|
|
4242
|
+
{
|
|
4243
|
+
id: "comment-2b",
|
|
4177
4244
|
parsedArchive: {
|
|
4178
4245
|
kind: "telegram_message",
|
|
4179
4246
|
chatID: "-100123",
|
|
@@ -7704,7 +7771,10 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
7704
7771
|
&& plannerCalls === 0
|
|
7705
7772
|
&& deliveryCalls === 0
|
|
7706
7773
|
&& String(processed.result?.outcome || "") === "invalid_ai_output"
|
|
7707
|
-
&&
|
|
7774
|
+
&& [
|
|
7775
|
+
"reply_target_not_visible_in_reply",
|
|
7776
|
+
"missing_required_contract",
|
|
7777
|
+
].includes(String(processed.result?.response_contract_validation_status || "")),
|
|
7708
7778
|
`kind=${String(processed.kind || "(none)")} ai_calls=${aiCalls} planner_calls=${plannerCalls} delivery_calls=${deliveryCalls} delivered=${String(deliveredText || "(none)")} validation=${String(processed.result?.response_contract_validation_status || "(none)")}`,
|
|
7709
7779
|
);
|
|
7710
7780
|
} catch (err) {
|