@evident-ai/cli 3.5.2-dev.03d3dab → 3.5.2-dev.06654ec
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/dist/index.js +206 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -703,6 +703,31 @@ async function reportResourceUsage(agentId, authHeader, usage) {
|
|
|
703
703
|
opencode_db_bytes: usage.opencodeDbBytes
|
|
704
704
|
});
|
|
705
705
|
}
|
|
706
|
+
async function getRunnerCredentialVault(agentId, authHeader) {
|
|
707
|
+
const empty = { prefix: null, vaultId: null, source: null };
|
|
708
|
+
try {
|
|
709
|
+
const apiUrl = getApiUrlConfig();
|
|
710
|
+
const response = await fetch(`${apiUrl}/runners/${agentId}/credential-vault`, {
|
|
711
|
+
headers: { Authorization: authHeader },
|
|
712
|
+
signal: AbortSignal.timeout(1e4)
|
|
713
|
+
});
|
|
714
|
+
if (!response.ok) {
|
|
715
|
+
const serverMessage = await readErrorMessage(response);
|
|
716
|
+
return {
|
|
717
|
+
...empty,
|
|
718
|
+
error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
const data = await response.json();
|
|
722
|
+
return {
|
|
723
|
+
prefix: data.credential_vault_prefix,
|
|
724
|
+
vaultId: data.vault_id,
|
|
725
|
+
source: data.source
|
|
726
|
+
};
|
|
727
|
+
} catch (error2) {
|
|
728
|
+
return { ...empty, error: describeTimeoutError(error2, 1e4) };
|
|
729
|
+
}
|
|
730
|
+
}
|
|
706
731
|
async function getAgentInfo(agentId, authHeader) {
|
|
707
732
|
const apiUrl = getApiUrlConfig();
|
|
708
733
|
try {
|
|
@@ -877,6 +902,76 @@ async function status(options = {}) {
|
|
|
877
902
|
process.exit(result.exitCode);
|
|
878
903
|
}
|
|
879
904
|
|
|
905
|
+
// src/commands/credential-vault-env.ts
|
|
906
|
+
function shellQuote(value) {
|
|
907
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
908
|
+
}
|
|
909
|
+
function printAssignment(prefix) {
|
|
910
|
+
console.log(`CREDENTIAL_VAULT_PREFIX=${shellQuote(prefix ?? "")}`);
|
|
911
|
+
}
|
|
912
|
+
function safeReason(reason, credentials2, authHeader) {
|
|
913
|
+
let safe = reason;
|
|
914
|
+
for (const secret of [
|
|
915
|
+
authHeader,
|
|
916
|
+
credentials2?.token,
|
|
917
|
+
process.env.EVIDENT_RUNNER_KEY,
|
|
918
|
+
process.env.EVIDENT_AGENT_KEY,
|
|
919
|
+
process.env.EVIDENT_TOKEN
|
|
920
|
+
]) {
|
|
921
|
+
if (secret) safe = safe.replaceAll(secret, "[redacted]");
|
|
922
|
+
}
|
|
923
|
+
return safe.replace(/[\r\n]+/g, " ");
|
|
924
|
+
}
|
|
925
|
+
function printFallback(reason, credentials2, authHeader) {
|
|
926
|
+
printAssignment(null);
|
|
927
|
+
console.error(
|
|
928
|
+
`WARNING: ${safeReason(reason, credentials2, authHeader)}; using this runner's own credential prefix.`
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
async function credentialVaultEnv() {
|
|
932
|
+
let credentials2 = null;
|
|
933
|
+
let authHeader;
|
|
934
|
+
try {
|
|
935
|
+
credentials2 = await getAuthCredentials();
|
|
936
|
+
if (!credentials2) {
|
|
937
|
+
printFallback("no runner credentials are configured");
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
authHeader = getAuthHeader(credentials2);
|
|
941
|
+
const runner = await resolveAgentIdFromKey(authHeader);
|
|
942
|
+
if (!runner.agent_id) {
|
|
943
|
+
printFallback(
|
|
944
|
+
runner.error ?? "the runner identity could not be resolved",
|
|
945
|
+
credentials2,
|
|
946
|
+
authHeader
|
|
947
|
+
);
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
const lookup = await getRunnerCredentialVault(runner.agent_id, authHeader);
|
|
951
|
+
if (lookup.error) {
|
|
952
|
+
printFallback(`credential vault lookup failed: ${lookup.error}`, credentials2, authHeader);
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
if (lookup.prefix === null) {
|
|
956
|
+
printFallback("this runner is not linked to a credential vault", credentials2, authHeader);
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
if (/[\r\n]/.test(lookup.prefix)) {
|
|
960
|
+
printFallback("credential vault lookup returned an invalid prefix", credentials2, authHeader);
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
printAssignment(lookup.prefix);
|
|
964
|
+
console.error(
|
|
965
|
+
`VAULT-MODE: credential stores use vault=${lookup.vaultId} source=${lookup.source} prefix=${lookup.prefix}.`
|
|
966
|
+
);
|
|
967
|
+
} catch (error2) {
|
|
968
|
+
const detail = credentials2 === null ? "credential resolution failed" : `credential vault lookup could not be completed: ${error2 instanceof Error ? error2.message : String(error2)}`;
|
|
969
|
+
printFallback(detail, credentials2, authHeader);
|
|
970
|
+
} finally {
|
|
971
|
+
process.exitCode = 0;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
880
975
|
// src/lib/claude-usage.ts
|
|
881
976
|
import { execFileSync } from "node:child_process";
|
|
882
977
|
import { readFileSync } from "node:fs";
|
|
@@ -1227,7 +1322,7 @@ import ora4 from "ora";
|
|
|
1227
1322
|
import { select as select4 } from "@inquirer/prompts";
|
|
1228
1323
|
|
|
1229
1324
|
// src/lib/telemetry.ts
|
|
1230
|
-
var CLI_VERSION = (true ? "3.5.2-dev.
|
|
1325
|
+
var CLI_VERSION = (true ? "3.5.2-dev.06654ec" : void 0) ?? process.env.npm_package_version ?? "unknown";
|
|
1231
1326
|
function getCliVersion() {
|
|
1232
1327
|
return CLI_VERSION;
|
|
1233
1328
|
}
|
|
@@ -2621,18 +2716,16 @@ function findDispatchedUserMessageId(messages, content, excludeIds) {
|
|
|
2621
2716
|
}
|
|
2622
2717
|
return best?.id ?? null;
|
|
2623
2718
|
}
|
|
2624
|
-
async function sendPromptAsync(port, sessionId, content, options, attachments, client,
|
|
2719
|
+
async function sendPromptAsync(port, sessionId, content, options, attachments, client, callbacks) {
|
|
2625
2720
|
const before = await getSessionMessages(port, sessionId, client);
|
|
2626
2721
|
const knownUserIds = new Set(
|
|
2627
2722
|
(before ?? []).filter((m) => roleOf2(m) === "user").map((m) => idOf2(m)).filter((id) => typeof id === "string")
|
|
2628
2723
|
);
|
|
2629
|
-
if (
|
|
2724
|
+
if (callbacks) {
|
|
2630
2725
|
try {
|
|
2631
|
-
onKnownUserIds(before === null ? null : knownUserIds);
|
|
2726
|
+
callbacks.onKnownUserIds(before === null ? null : knownUserIds);
|
|
2632
2727
|
} catch (error2) {
|
|
2633
|
-
|
|
2634
|
-
`[sendPromptAsync] pre-dispatch snapshot callback failed: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
2635
|
-
);
|
|
2728
|
+
callbacks.onKnownUserIdsError(error2);
|
|
2636
2729
|
}
|
|
2637
2730
|
}
|
|
2638
2731
|
const parts = [{ type: "text", text: content }];
|
|
@@ -7112,6 +7205,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7112
7205
|
* row is `processing` or `pending`; clear it only when absent from both lists.
|
|
7113
7206
|
*/
|
|
7114
7207
|
untrackableAck = /* @__PURE__ */ new Map();
|
|
7208
|
+
/** Session binding used by the first untrackable-ack report, for retries. */
|
|
7209
|
+
untrackableAckSessionIds = /* @__PURE__ */ new Map();
|
|
7115
7210
|
/** Messages whose untrackable-ack failure signal was emitted while fenced. */
|
|
7116
7211
|
untrackableAckSignalled = /* @__PURE__ */ new Set();
|
|
7117
7212
|
/** Messages whose first untrackable-ack fence warning was already logged. */
|
|
@@ -7561,6 +7656,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7561
7656
|
const dispatchDescription = source === "readopt" ? "re-dispatched message" : "message";
|
|
7562
7657
|
const errorMessage3 = `OpenCode accepted this ${dispatchDescription} once, but its assigned id was never confirmed after ${attempts} attempts to match it up \u2014 the message was deliberately not sent again, so no duplicate turn was created; session ${sessionId.slice(0, 8)} was abandoned; a fresh one is used for further messages.`;
|
|
7563
7658
|
this.untrackableAck.set(messageId, errorMessage3);
|
|
7659
|
+
this.untrackableAckSessionIds.set(messageId, null);
|
|
7564
7660
|
this.log({
|
|
7565
7661
|
level: "error",
|
|
7566
7662
|
message: errorMessage3,
|
|
@@ -7866,8 +7962,18 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7866
7962
|
options,
|
|
7867
7963
|
sendAttachments,
|
|
7868
7964
|
this.openCodeClient,
|
|
7869
|
-
|
|
7870
|
-
|
|
7965
|
+
{
|
|
7966
|
+
onKnownUserIds: (ids) => {
|
|
7967
|
+
preDispatchUserIds = ids;
|
|
7968
|
+
},
|
|
7969
|
+
onKnownUserIdsError: (error2) => {
|
|
7970
|
+
this.log({
|
|
7971
|
+
level: "warn",
|
|
7972
|
+
message: `Pre-dispatch snapshot callback failed for message ${message.id.slice(0, 8)}: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
7973
|
+
conversation_id: conv.id,
|
|
7974
|
+
message_id: message.id
|
|
7975
|
+
});
|
|
7976
|
+
}
|
|
7871
7977
|
}
|
|
7872
7978
|
)
|
|
7873
7979
|
);
|
|
@@ -7876,6 +7982,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7876
7982
|
if (this.isV2 && err instanceof OpenCodeV2PromptAckError) {
|
|
7877
7983
|
const errorMessage4 = err instanceof Error ? err.message : String(err);
|
|
7878
7984
|
this.untrackableAck.set(message.id, errorMessage4);
|
|
7985
|
+
this.untrackableAckSessionIds.set(message.id, null);
|
|
7879
7986
|
this.log({
|
|
7880
7987
|
level: "error",
|
|
7881
7988
|
message: `V2 prompt dispatch for message ${message.id.slice(0, 8)} failed after a positive ack with no usable id: ${errorMessage4}`,
|
|
@@ -10171,6 +10278,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10171
10278
|
for (const id of [...this.untrackableAck.keys()]) {
|
|
10172
10279
|
if (!stillProcessing.has(id) && !this.pendingMessageIds.has(id)) {
|
|
10173
10280
|
this.untrackableAck.delete(id);
|
|
10281
|
+
this.untrackableAckSessionIds.delete(id);
|
|
10174
10282
|
this.untrackableAckSignalled.delete(id);
|
|
10175
10283
|
this.untrackableAckWarned.delete(id);
|
|
10176
10284
|
this.log({
|
|
@@ -10319,8 +10427,10 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10319
10427
|
* Branches on `messageRunState(messages, row.opencode_message_id)` — the
|
|
10320
10428
|
* opencode-assigned user-message id persisted on the first `processing` PATCH
|
|
10321
10429
|
* (#218). A row with a NULL stored id (dispatched but the read-back never landed
|
|
10322
|
-
* before the restart)
|
|
10323
|
-
*
|
|
10430
|
+
* before the restart) is first offered to an awaiting-correlation fence recorded
|
|
10431
|
+
* by this process; without that fence, it has no id to correlate after restart
|
|
10432
|
+
* and is treated as an orphan and re-dispatched (at most once, see
|
|
10433
|
+
* `forceReadoptRun`):
|
|
10324
10434
|
* - `done` → `markDone` now (guarded like the watcher's done branch);
|
|
10325
10435
|
* - `failed` → `markFailed` with the surfaced error (issue #182), so an
|
|
10326
10436
|
* errored turn is reported failed on restart, NOT re-dispatched —
|
|
@@ -10329,7 +10439,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10329
10439
|
* re-dispatched instead (issue #1310, see the branch below);
|
|
10330
10440
|
* - `running`/`queued` → re-attach a watcher via `registerReadopted` (no re-dispatch),
|
|
10331
10441
|
* tracking the stored id so the reply correlates by it;
|
|
10332
|
-
* - `unknown`/null id →
|
|
10442
|
+
* - `unknown`/null id → correlate through an awaiting fence when present;
|
|
10443
|
+
* otherwise re-dispatch (opencode assigns a fresh id) + attach a watcher.
|
|
10333
10444
|
*
|
|
10334
10445
|
* Only `ChannelAuthError` propagates.
|
|
10335
10446
|
*/
|
|
@@ -10410,12 +10521,48 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10410
10521
|
await this.reportSubagentAuthFailures(row.conversation_id, ocId ?? "", row.id, messages);
|
|
10411
10522
|
this.dontRedispatch.delete(row.id);
|
|
10412
10523
|
this.untrackableAck.delete(row.id);
|
|
10524
|
+
this.untrackableAckSessionIds.delete(row.id);
|
|
10413
10525
|
this.untrackableAckSignalled.delete(row.id);
|
|
10414
10526
|
this.untrackableAckWarned.delete(row.id);
|
|
10415
10527
|
void this.postSignal(row.conversation_id, row.id, "readopt_failed");
|
|
10416
10528
|
return;
|
|
10417
10529
|
}
|
|
10418
|
-
if (this.
|
|
10530
|
+
if (this.untrackableAck.has(row.id)) {
|
|
10531
|
+
if (this.untrackableAckSignalled.has(row.id)) {
|
|
10532
|
+
const firstFenceWarning = !this.untrackableAckWarned.has(row.id);
|
|
10533
|
+
this.untrackableAckWarned.add(row.id);
|
|
10534
|
+
this.log({
|
|
10535
|
+
level: firstFenceWarning ? "warn" : "debug",
|
|
10536
|
+
message: `Re-adopt: message ${row.id.slice(0, 8)} is fenced after an untrackable OpenCode turn \u2014 skipping re-dispatch and retrying the terminal report`,
|
|
10537
|
+
conversation_id: row.conversation_id,
|
|
10538
|
+
message_id: row.id
|
|
10539
|
+
});
|
|
10540
|
+
const errorMessage3 = this.untrackableAck.get(row.id);
|
|
10541
|
+
const reportError = await this.reportUntrackableAck(
|
|
10542
|
+
row.conversation_id,
|
|
10543
|
+
row.id,
|
|
10544
|
+
this.untrackableAckSessionIds.get(row.id) ?? null,
|
|
10545
|
+
errorMessage3
|
|
10546
|
+
);
|
|
10547
|
+
if (reportError !== null) {
|
|
10548
|
+
this.log({
|
|
10549
|
+
level: "warn",
|
|
10550
|
+
message: `Re-adopt: failed to report untrackable message ${row.id.slice(0, 8)} (will retry next drain): ${reportError.message}`,
|
|
10551
|
+
conversation_id: row.conversation_id,
|
|
10552
|
+
message_id: row.id
|
|
10553
|
+
});
|
|
10554
|
+
}
|
|
10555
|
+
} else {
|
|
10556
|
+
this.log({
|
|
10557
|
+
level: "debug",
|
|
10558
|
+
message: `Re-adopt: message ${row.id.slice(0, 8)} already gave up \u2014 left to the cron; skipping until it leaves processing`,
|
|
10559
|
+
conversation_id: row.conversation_id,
|
|
10560
|
+
message_id: row.id
|
|
10561
|
+
});
|
|
10562
|
+
}
|
|
10563
|
+
return;
|
|
10564
|
+
}
|
|
10565
|
+
if (this.dontRedispatch.has(row.id)) {
|
|
10419
10566
|
this.log({
|
|
10420
10567
|
level: "debug",
|
|
10421
10568
|
message: `Re-adopt: message ${row.id.slice(0, 8)} already gave up \u2014 left to the cron; skipping until it leaves processing`,
|
|
@@ -10593,6 +10740,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10593
10740
|
}
|
|
10594
10741
|
this.dontRedispatch.delete(row.id);
|
|
10595
10742
|
this.untrackableAck.delete(row.id);
|
|
10743
|
+
this.untrackableAckSessionIds.delete(row.id);
|
|
10596
10744
|
this.untrackableAckSignalled.delete(row.id);
|
|
10597
10745
|
this.untrackableAckWarned.delete(row.id);
|
|
10598
10746
|
void this.postSignal(row.conversation_id, row.id, "readopt_done");
|
|
@@ -10688,8 +10836,18 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10688
10836
|
options,
|
|
10689
10837
|
sendAttachments,
|
|
10690
10838
|
this.openCodeClient,
|
|
10691
|
-
|
|
10692
|
-
|
|
10839
|
+
{
|
|
10840
|
+
onKnownUserIds: (ids) => {
|
|
10841
|
+
preDispatchUserIds = ids;
|
|
10842
|
+
},
|
|
10843
|
+
onKnownUserIdsError: (error2) => {
|
|
10844
|
+
this.log({
|
|
10845
|
+
level: "warn",
|
|
10846
|
+
message: `Pre-dispatch snapshot callback failed for message ${row.id.slice(0, 8)}: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
10847
|
+
conversation_id: row.conversation_id,
|
|
10848
|
+
message_id: row.id
|
|
10849
|
+
});
|
|
10850
|
+
}
|
|
10693
10851
|
}
|
|
10694
10852
|
)
|
|
10695
10853
|
);
|
|
@@ -10723,6 +10881,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10723
10881
|
}
|
|
10724
10882
|
} else {
|
|
10725
10883
|
this.untrackableAck.set(row.id, errorMessage3);
|
|
10884
|
+
this.untrackableAckSessionIds.set(row.id, sessionId);
|
|
10726
10885
|
}
|
|
10727
10886
|
this.log({
|
|
10728
10887
|
level: "error",
|
|
@@ -14263,6 +14422,34 @@ async function run(options) {
|
|
|
14263
14422
|
} else {
|
|
14264
14423
|
log2(state, "Not running in a MicroVM (MICROVM_ID unset) \u2014 nothing to report", "debug");
|
|
14265
14424
|
}
|
|
14425
|
+
if (options.restoreRunnerCredentials || options.credentialSyncMarker) {
|
|
14426
|
+
const logCredentialVaultResolution = (message, level) => {
|
|
14427
|
+
log2(state, message, level);
|
|
14428
|
+
if (state.interactive && !state.json) {
|
|
14429
|
+
logActivity(state, { type: "info", level, message });
|
|
14430
|
+
}
|
|
14431
|
+
};
|
|
14432
|
+
const vaultResolution = await getRunnerCredentialVault(state.agentId, state.authHeader);
|
|
14433
|
+
if (vaultResolution.error) {
|
|
14434
|
+
delete process.env.CREDENTIAL_VAULT_PREFIX;
|
|
14435
|
+
logCredentialVaultResolution(
|
|
14436
|
+
`CREDENTIAL-VAULT-LOOKUP-FAILED: ${vaultResolution.error}; credentials will be read from and written to this runner's own prefix this boot`,
|
|
14437
|
+
"warn"
|
|
14438
|
+
);
|
|
14439
|
+
} else if (vaultResolution.prefix !== null && vaultResolution.vaultId !== null && vaultResolution.source !== null) {
|
|
14440
|
+
process.env.CREDENTIAL_VAULT_PREFIX = vaultResolution.prefix;
|
|
14441
|
+
logCredentialVaultResolution(
|
|
14442
|
+
`CREDENTIAL-VAULT: source=${vaultResolution.source} vault=${vaultResolution.vaultId} prefix=${vaultResolution.prefix}`,
|
|
14443
|
+
"info"
|
|
14444
|
+
);
|
|
14445
|
+
} else {
|
|
14446
|
+
delete process.env.CREDENTIAL_VAULT_PREFIX;
|
|
14447
|
+
logCredentialVaultResolution(
|
|
14448
|
+
"CREDENTIAL-VAULT-NONE: this runner is not linked to a credential vault; using its own prefix",
|
|
14449
|
+
"debug"
|
|
14450
|
+
);
|
|
14451
|
+
}
|
|
14452
|
+
}
|
|
14266
14453
|
if (options.restoreRunnerCredentials) {
|
|
14267
14454
|
log2(state, "Restoring runner credentials before starting OpenCode");
|
|
14268
14455
|
const credentialContext = {
|
|
@@ -14660,14 +14847,14 @@ async function run(options) {
|
|
|
14660
14847
|
},
|
|
14661
14848
|
onDisconnected: (code, reason, transition) => {
|
|
14662
14849
|
state.connected = false;
|
|
14663
|
-
const
|
|
14850
|
+
const safeReason2 = redactDiagnosticText(reason).slice(0, MAX_DISCONNECT_REASON_LENGTH);
|
|
14664
14851
|
logActivity(state, {
|
|
14665
14852
|
type: "info",
|
|
14666
|
-
message: `Tunnel disconnected (generation: ${transition.generation}, code: ${code}, reason: ${
|
|
14853
|
+
message: `Tunnel disconnected (generation: ${transition.generation}, code: ${code}, reason: ${safeReason2})`
|
|
14667
14854
|
});
|
|
14668
14855
|
emitAgentDisconnected(state.agentId, {
|
|
14669
14856
|
code,
|
|
14670
|
-
reason:
|
|
14857
|
+
reason: safeReason2,
|
|
14671
14858
|
connection_generation: transition.generation
|
|
14672
14859
|
});
|
|
14673
14860
|
if (state.interactive) displayStatus(state);
|
|
@@ -14830,6 +15017,7 @@ program.command("login").description("Authenticate with Evident").option("--toke
|
|
|
14830
15017
|
program.command("logout").description("Remove stored credentials for the current endpoint").option("--all", "Remove stored credentials for all endpoints").action((options) => logout({ all: options.all }));
|
|
14831
15018
|
program.command("whoami").description("Show the currently logged in user").action(whoami);
|
|
14832
15019
|
program.command("status").description("Check whether the configured credentials can reach Evident").option("--json", "Output in JSON format").action((options) => status({ json: options.json }));
|
|
15020
|
+
program.command("credential-vault-env").description("Print the shell assignment a runner image evals before starting the synchroniser").action(credentialVaultEnv);
|
|
14833
15021
|
program.command("claude-usage").description("[spike] Show Claude subscription usage (requires a local `claude login`)").action(claudeUsage);
|
|
14834
15022
|
program.command("run").description("Connect to Evident and process messages").option("--runner [id]", "Runner ID to connect to (optional when EVIDENT_RUNNER_KEY is set)").option(
|
|
14835
15023
|
"-a, --agent [id]",
|