@botbuddy/cli 1.60.0 → 1.60.1
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/daemon/bin/darwin-arm64/botbuddy-daemon +0 -0
- package/daemon/bin/linux-x64/botbuddy-daemon +0 -0
- package/daemon-release/darwin-arm64.json +4 -4
- package/daemon-release/linux-x64.json +4 -4
- package/daemon-release/manifest.json +5 -5
- package/package.json +1 -1
- package/src/commands.mjs +16 -4
- package/src/run.mjs +165 -7
|
Binary file
|
|
Binary file
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"release_version": "1.60.
|
|
3
|
-
"source_commit": "
|
|
4
|
-
"source_sha256": "
|
|
2
|
+
"release_version": "1.60.1",
|
|
3
|
+
"source_commit": "ac9d46a93d57bb9d5af94cba238532767c1f19cc",
|
|
4
|
+
"source_sha256": "667acf404a591bd0e09357645192040cba7a0b2085f33d89be6a4bbc84a6dd53",
|
|
5
5
|
"target": "darwin-arm64",
|
|
6
|
-
"sha256": "
|
|
6
|
+
"sha256": "9dd4af84ad4131cf69c94bbed69f3c51087ca8ac4d742df6e9367faadbeddb97"
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"release_version": "1.60.
|
|
3
|
-
"source_commit": "
|
|
4
|
-
"source_sha256": "
|
|
2
|
+
"release_version": "1.60.1",
|
|
3
|
+
"source_commit": "ac9d46a93d57bb9d5af94cba238532767c1f19cc",
|
|
4
|
+
"source_sha256": "667acf404a591bd0e09357645192040cba7a0b2085f33d89be6a4bbc84a6dd53",
|
|
5
5
|
"target": "linux-x64",
|
|
6
|
-
"sha256": "
|
|
6
|
+
"sha256": "61db8eb2108fcf9407460e96f487316728e262927241b5ee672ffca3443a11d3"
|
|
7
7
|
}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
|
-
"release_version": "1.60.
|
|
4
|
-
"source_commit": "
|
|
5
|
-
"source_sha256": "
|
|
3
|
+
"release_version": "1.60.1",
|
|
4
|
+
"source_commit": "ac9d46a93d57bb9d5af94cba238532767c1f19cc",
|
|
5
|
+
"source_sha256": "667acf404a591bd0e09357645192040cba7a0b2085f33d89be6a4bbc84a6dd53",
|
|
6
6
|
"targets": {
|
|
7
7
|
"darwin-arm64": {
|
|
8
8
|
"goos": "darwin",
|
|
9
9
|
"goarch": "arm64",
|
|
10
10
|
"binary": "daemon/bin/darwin-arm64/botbuddy-daemon",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "9dd4af84ad4131cf69c94bbed69f3c51087ca8ac4d742df6e9367faadbeddb97",
|
|
12
12
|
"mode": "0755"
|
|
13
13
|
},
|
|
14
14
|
"linux-x64": {
|
|
15
15
|
"goos": "linux",
|
|
16
16
|
"goarch": "amd64",
|
|
17
17
|
"binary": "daemon/bin/linux-x64/botbuddy-daemon",
|
|
18
|
-
"sha256": "
|
|
18
|
+
"sha256": "61db8eb2108fcf9407460e96f487316728e262927241b5ee672ffca3443a11d3",
|
|
19
19
|
"mode": "0755"
|
|
20
20
|
}
|
|
21
21
|
}
|
package/package.json
CHANGED
package/src/commands.mjs
CHANGED
|
@@ -483,12 +483,24 @@ does not prove that the workload is still running.
|
|
|
483
483
|
|
|
484
484
|
${bold("Safe recovery")}
|
|
485
485
|
1. Preserve the command-run ID and the original result.
|
|
486
|
-
2.
|
|
487
|
-
3.
|
|
486
|
+
2. Run ${cyan("bb run inspect <command-run-id>")} from the owning checkout.
|
|
487
|
+
3. Prefer the owning runner's local terminal receipt for the workload verdict.
|
|
488
|
+
4. If the receipt is terminal, do not cancel or duplicate the command solely
|
|
488
489
|
because remote state remains ${cyan("registered")} or ${cyan("running")}.
|
|
489
|
-
|
|
490
|
+
5. If the receipt is absent/non-terminal, retain the original runner result;
|
|
490
491
|
do not poll or infer completion from elapsed time.
|
|
491
|
-
|
|
492
|
+
6. Report the receipt and remote discrepancy as telemetry evidence. A stale
|
|
493
|
+
test-run or command-run is never validation evidence.
|
|
494
|
+
|
|
495
|
+
${bold("One controlled push retry")}
|
|
496
|
+
Only after inspection leaves the original push unresolved, arm an exact-SHA CI
|
|
497
|
+
wait before one new push, then verify the resulting CI run reports that SHA:
|
|
498
|
+
|
|
499
|
+
${cyan("bb wait 'ci:repo=SupplySpark/supplyspark-ent,scope=next,sha=<full-sha>' --heartbeat")}
|
|
500
|
+
|
|
501
|
+
Do not substitute ${cyan("pr-state")}, a timer, or a status-polling loop for a bare
|
|
502
|
+
push. If the prior push has no terminal receipt, keep it unresolved rather than
|
|
503
|
+
counting it as validation evidence.
|
|
492
504
|
|
|
493
505
|
${bold("Boundary")}
|
|
494
506
|
Command-run status is agent-owned. A different agent may be denied access; that
|
package/src/run.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { stackLeaseIdFromEnv, withStackLeaseCorrelation } from "./stack-lease-co
|
|
|
11
11
|
import { mkdir, readFile, writeFile, chmod, unlink, stat, rename } from "node:fs/promises";
|
|
12
12
|
import { dirname, join } from "node:path";
|
|
13
13
|
import { homedir } from "node:os";
|
|
14
|
-
import { spawn } from "node:child_process";
|
|
14
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
import { performance } from "node:perf_hooks";
|
|
17
17
|
import { callToolJson } from "./api.mjs";
|
|
@@ -26,7 +26,7 @@ import { deliverExecutionEvent } from "./telemetry-delivery.mjs";
|
|
|
26
26
|
import { attestTelemetryCredential, loadTelemetryIdentity } from "./telemetry-config.mjs";
|
|
27
27
|
import { VERSION } from "./version.mjs";
|
|
28
28
|
import {
|
|
29
|
-
TERMINAL_RETRY_BUDGET_MS, DELIVERY_CALL_TIMEOUT_MS, callWithContextIdentity, classifyTerminalUpdateFailure,
|
|
29
|
+
TERMINAL_RETRY_BUDGET_MS, DELIVERY_CALL_TIMEOUT_MS, callWithContextIdentity, classifyTerminalUpdateFailure, contextServerUrl,
|
|
30
30
|
ensureRunDelivery, parkedRecord, runDelivery, terminalUpdateAccepted,
|
|
31
31
|
} from "./run-delivery.mjs";
|
|
32
32
|
|
|
@@ -221,6 +221,154 @@ export function commandHash(command) { return createHash("sha256").update(JSON.s
|
|
|
221
221
|
export function environmentHash(cwd, environment) { return createHash("sha256").update(`${cwd}\0${environment}`).digest("hex"); }
|
|
222
222
|
export function receiptPath(runId) { return join(homedir(), ".botbuddy", "command-runs", `${runId}.receipt.json`); }
|
|
223
223
|
export function contextPath(runId) { return join(homedir(), ".botbuddy", "command-runs", `${runId}.context.json`); }
|
|
224
|
+
|
|
225
|
+
// A command receipt is evidence about one checkout revision, not a claim that
|
|
226
|
+
// its working tree was clean. Resolving this once at launch lets recovery
|
|
227
|
+
// distinguish a stale callback from an unrelated later command without ever
|
|
228
|
+
// returning the command line itself.
|
|
229
|
+
export function sourceSha(cwd, { exec = execFileSync } = {}) {
|
|
230
|
+
try {
|
|
231
|
+
const sha = String(exec("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] })).trim();
|
|
232
|
+
return /^[0-9a-f]{40,64}$/i.test(sha) ? sha.toLowerCase() : null;
|
|
233
|
+
} catch { return null; }
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const TERMINAL_COMMAND_STATUSES = new Set(["success", "failure", "cancelled", "timed_out", "owner_lost"]);
|
|
237
|
+
const TERMINAL_REMOTE_STATUSES = new Set(["succeeded", "failed", "cancelled", "timed_out", "owner_lost", "invalidated"]);
|
|
238
|
+
const REMOTE_STATUS_FOR_COMMAND_STATUS = Object.freeze({
|
|
239
|
+
success: "succeeded",
|
|
240
|
+
failure: "failed",
|
|
241
|
+
cancelled: "cancelled",
|
|
242
|
+
timed_out: "timed_out",
|
|
243
|
+
owner_lost: "owner_lost",
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
function parseOwnedRunFile(raw, runId) {
|
|
247
|
+
try {
|
|
248
|
+
const value = JSON.parse(raw);
|
|
249
|
+
return value && value.run_id === runId ? value : null;
|
|
250
|
+
} catch { return null; }
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function freshContext(context, now) {
|
|
254
|
+
const launchedAt = Date.parse(context?.launched_at ?? "");
|
|
255
|
+
const timeoutMs = Number(context?.timeout_seconds) * 1_000;
|
|
256
|
+
return Number.isFinite(launchedAt) && Number.isFinite(timeoutMs) && timeoutMs > 0
|
|
257
|
+
&& now <= launchedAt + timeoutMs + TIMEOUT_GRACE_MS + 60_000;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function publicRemoteSummary(result) {
|
|
261
|
+
if (!result?.ok || result.isError || !result.data || typeof result.data !== "object") return { state: "unavailable" };
|
|
262
|
+
const status = typeof result.data.status === "string" ? result.data.status : null;
|
|
263
|
+
return {
|
|
264
|
+
state: status ? "available" : "unavailable",
|
|
265
|
+
...(status ? { status } : {}),
|
|
266
|
+
...(typeof result.data.exit_code === "number" ? { exit_code: result.data.exit_code } : {}),
|
|
267
|
+
...(typeof result.data.ended_at === "string" ? { ended_at: result.data.ended_at } : {}),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function terminalOutcomesAgree(receipt, remote) {
|
|
272
|
+
if (!receipt || remote.state !== "available") return false;
|
|
273
|
+
if (REMOTE_STATUS_FOR_COMMAND_STATUS[receipt.status] !== remote.status) return false;
|
|
274
|
+
// A missing exit code is meaningful for cancellation, timeout, and
|
|
275
|
+
// owner-loss. When both sides do have one, a disagreement is never hidden.
|
|
276
|
+
return typeof receipt.exit_code !== "number" || typeof remote.exit_code !== "number"
|
|
277
|
+
|| receipt.exit_code === remote.exit_code;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Reconcile only evidence the current OS user and owning BotBuddy agent can
|
|
281
|
+
// read. This is intentionally advisory: an inspector must never terminate a
|
|
282
|
+
// worker, replay a callback, or make a retry safe merely by observing a file.
|
|
283
|
+
export async function inspectRun(runId, {
|
|
284
|
+
call = callToolJson,
|
|
285
|
+
read = readFile,
|
|
286
|
+
now = Date.now(),
|
|
287
|
+
processAlive = workerProcessGroupAlive,
|
|
288
|
+
receiptFile = receiptPath,
|
|
289
|
+
contextFile = contextPath,
|
|
290
|
+
} = {}) {
|
|
291
|
+
if (typeof runId !== "string" || !runId.trim()) {
|
|
292
|
+
return { schema_version: RUN_SCHEMA_VERSION, classification: "rejected", rerun_safe: false, cancellation_safe: false, error: "run_id_required" };
|
|
293
|
+
}
|
|
294
|
+
const id = runId.trim();
|
|
295
|
+
const [receiptRaw, contextRaw] = await Promise.all([
|
|
296
|
+
read(receiptFile(id), "utf8").catch(() => null),
|
|
297
|
+
read(contextFile(id), "utf8").catch(() => null),
|
|
298
|
+
]);
|
|
299
|
+
const receipt = typeof receiptRaw === "string" ? parseOwnedRunFile(receiptRaw, id) : null;
|
|
300
|
+
const context = typeof contextRaw === "string" ? parseOwnedRunFile(contextRaw, id) : null;
|
|
301
|
+
// A durable run belongs to the agent that launched it. Prefer its sealed
|
|
302
|
+
// session identity; older contexts must use a current worktree session and
|
|
303
|
+
// never silently fall back to an unrelated durable machine credential.
|
|
304
|
+
const remoteResult = await callWithContextIdentity(context, call)("get_command_run", { run_id: id }, {
|
|
305
|
+
...(context ? { serverUrl: contextServerUrl(context) } : {}),
|
|
306
|
+
}).catch(() => null);
|
|
307
|
+
const remote = publicRemoteSummary(remoteResult);
|
|
308
|
+
const receiptTerminal = Boolean(receipt && TERMINAL_COMMAND_STATUSES.has(receipt.status));
|
|
309
|
+
const receiptFingerprint = receipt && typeof receipt.command_hash === "string" && /^[a-f0-9]{64}$/i.test(receipt.command_hash)
|
|
310
|
+
&& typeof receipt.source_sha === "string" && /^[a-f0-9]{40,64}$/i.test(receipt.source_sha);
|
|
311
|
+
const fingerprintMismatch = Boolean(context && (
|
|
312
|
+
(context.command_hash && context.command_hash !== receipt?.command_hash)
|
|
313
|
+
|| (context.source_sha && context.source_sha !== receipt?.source_sha)
|
|
314
|
+
));
|
|
315
|
+
const contextMatchesReceipt = !fingerprintMismatch;
|
|
316
|
+
const sourceBound = Boolean(receiptTerminal && receiptFingerprint && contextMatchesReceipt);
|
|
317
|
+
|
|
318
|
+
if (receiptTerminal) {
|
|
319
|
+
// A context that identifies a different command/revision must never be
|
|
320
|
+
// promoted to completed evidence, even if its receipt is terminal.
|
|
321
|
+
if (fingerprintMismatch) {
|
|
322
|
+
return {
|
|
323
|
+
schema_version: RUN_SCHEMA_VERSION,
|
|
324
|
+
run_id: id,
|
|
325
|
+
classification: "unknown",
|
|
326
|
+
rerun_safe: false,
|
|
327
|
+
cancellation_safe: false,
|
|
328
|
+
source_bound: false,
|
|
329
|
+
local: { status: null, exit_code: null },
|
|
330
|
+
remote,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
const remoteTerminal = remote.state === "available" && TERMINAL_REMOTE_STATUSES.has(remote.status);
|
|
334
|
+
const outcomesAgree = remoteTerminal && terminalOutcomesAgree(receipt, remote);
|
|
335
|
+
return {
|
|
336
|
+
schema_version: RUN_SCHEMA_VERSION,
|
|
337
|
+
run_id: id,
|
|
338
|
+
classification: outcomesAgree ? "completed" : remoteTerminal ? "completed_locally_remote_conflict" : remote.status === "registered" || remote.status === "running" || remote.status === "approval_required" ? "completed_locally_remote_stale" : "completed_locally_remote_unknown",
|
|
339
|
+
rerun_safe: false,
|
|
340
|
+
cancellation_safe: false,
|
|
341
|
+
source_bound: sourceBound,
|
|
342
|
+
local: { status: receipt.status, exit_code: receipt.exit_code ?? null },
|
|
343
|
+
remote,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const pids = [context?.worker_pid, context?.workload_pid].filter((pid) => Number.isInteger(pid) && pid > 0);
|
|
348
|
+
if (context && freshContext(context, now) && pids.some((pid) => processAlive(pid))) {
|
|
349
|
+
return {
|
|
350
|
+
schema_version: RUN_SCHEMA_VERSION,
|
|
351
|
+
run_id: id,
|
|
352
|
+
classification: "running",
|
|
353
|
+
rerun_safe: false,
|
|
354
|
+
cancellation_safe: false,
|
|
355
|
+
source_bound: false,
|
|
356
|
+
local: { status: null, exit_code: null },
|
|
357
|
+
remote,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
schema_version: RUN_SCHEMA_VERSION,
|
|
363
|
+
run_id: id,
|
|
364
|
+
classification: "unknown",
|
|
365
|
+
rerun_safe: false,
|
|
366
|
+
cancellation_safe: false,
|
|
367
|
+
source_bound: false,
|
|
368
|
+
local: { status: null, exit_code: null },
|
|
369
|
+
remote,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
224
372
|
function cancellationPath(contextFile) { return `${contextFile}.cancelled`; }
|
|
225
373
|
|
|
226
374
|
async function markCancellation(contextFile, runId) {
|
|
@@ -250,6 +398,8 @@ async function preWorkloadCancellationReceipt(context) {
|
|
|
250
398
|
ended_at: now,
|
|
251
399
|
exit_code: null,
|
|
252
400
|
signal: "SIGTERM",
|
|
401
|
+
command_hash: context.command_hash ?? commandHash(context.command ?? []),
|
|
402
|
+
source_sha: context.source_sha ?? null,
|
|
253
403
|
recovery_command: null,
|
|
254
404
|
bounded_log: "",
|
|
255
405
|
output_characters: 0,
|
|
@@ -347,7 +497,7 @@ function rejectedSessionReceipt(error, runId) {
|
|
|
347
497
|
|
|
348
498
|
export async function launchRun(argv, {
|
|
349
499
|
cwd = process.cwd(), spawnImpl = spawn, call = null, childEnv = null, testRun = null,
|
|
350
|
-
resolveCredential = null, renewCredential = selfHealAgentSession, env = process.env,
|
|
500
|
+
resolveCredential = null, renewCredential = selfHealAgentSession, env = process.env, getSourceSha = sourceSha,
|
|
351
501
|
} = {}) {
|
|
352
502
|
const parsed = parseRunArgs(argv, env);
|
|
353
503
|
let managedCredential = null;
|
|
@@ -376,12 +526,14 @@ export async function launchRun(argv, {
|
|
|
376
526
|
const effectiveCall = (t, a) => (call ?? callToolJson)(t, a,
|
|
377
527
|
opts.sessionToken ? { auth: { "x-agent-api-key": opts.sessionToken } } : {});
|
|
378
528
|
const runId = randomUUID();
|
|
529
|
+
const commandFingerprint = commandHash(command);
|
|
530
|
+
const launchSourceSha = getSourceSha(cwd);
|
|
379
531
|
const registerArgs = () => ({
|
|
380
532
|
// Omit session_id entirely when only a token is present — the backend derives
|
|
381
533
|
// it. A plain id is still sent (and honoured) when supplied.
|
|
382
534
|
...(opts.sessionId ? { session_id: opts.sessionId } : {}),
|
|
383
535
|
run_id: runId, tool_category: opts.category,
|
|
384
|
-
command_hash:
|
|
536
|
+
command_hash: commandFingerprint, ...(launchSourceSha ? { source_sha: launchSourceSha } : {}), environment_hash: environmentHash(cwd, opts.environment),
|
|
385
537
|
environment: opts.environment, command_kind: opts.kind, expected_duration_seconds: opts.expectedDuration,
|
|
386
538
|
rerun_reason: opts.rerunReason,
|
|
387
539
|
});
|
|
@@ -411,7 +563,7 @@ export async function launchRun(argv, {
|
|
|
411
563
|
// every callback is attributable to the same agent. It deliberately stays
|
|
412
564
|
// outside child_env, receipts, and the workload's process environment.
|
|
413
565
|
const knownSession = opts.sessionId ?? managedCredential?.identity?.sessionId ?? managedCredential?.sessionId;
|
|
414
|
-
await writeJson(context, { schema_version: RUN_SCHEMA_VERSION, context_path: context, run_id: runId, run_credential: registration.data.run_credential, receipt_upload: registration.data.receipt_upload, command, cwd, timeout_seconds: opts.timeout, receipt_path: receipt, launched_at: new Date().toISOString(), server_url: SERVER_URL
|
|
566
|
+
await writeJson(context, { schema_version: RUN_SCHEMA_VERSION, context_path: context, run_id: runId, run_credential: registration.data.run_credential, receipt_upload: registration.data.receipt_upload, command, command_hash: commandFingerprint, source_sha: launchSourceSha, cwd, timeout_seconds: opts.timeout, receipt_path: receipt, launched_at: new Date().toISOString(), server_url: SERVER_URL, ...(opts.sessionToken ? { callback_session_token: opts.sessionToken } : {}), ...(knownSession ? { callback_session_id: knownSession } : {}), ...(childEnv && Object.keys(childEnv).length ? { child_env: childEnv } : {}), ...(testRun ? { test_run: testRun } : {}) });
|
|
415
567
|
const [file, args] = workerInvocation(context);
|
|
416
568
|
try {
|
|
417
569
|
const worker = spawnImpl(file, args, { detached: true, stdio: "ignore", windowsHide: true });
|
|
@@ -804,7 +956,7 @@ export async function cancelRun(runId, {
|
|
|
804
956
|
async function finalize(context, state, call, retry = {}) {
|
|
805
957
|
const boundedLog = String(state.output ?? "").slice(-8192);
|
|
806
958
|
const outputCharacters = state.outputCharacters ?? String(state.output ?? "").length;
|
|
807
|
-
const receipt = { schema_version: RUN_SCHEMA_VERSION, run_id: context.run_id, status: state.status, started_at: state.startedAt, ended_at: state.endedAt, exit_code: state.exitCode, signal: state.signal, recovery_command: state.status === "failure" ? `botbuddy run --session-id <new-session-id> --environment local -- ${context.command.map((x) => JSON.stringify(x)).join(" ")}` : null, bounded_log: boundedLog, output_characters: outputCharacters, reason: state.reason ?? null };
|
|
959
|
+
const receipt = { schema_version: RUN_SCHEMA_VERSION, run_id: context.run_id, status: state.status, started_at: state.startedAt, ended_at: state.endedAt, exit_code: state.exitCode, signal: state.signal, command_hash: context.command_hash ?? commandHash(context.command ?? []), source_sha: context.source_sha ?? null, recovery_command: state.status === "failure" ? `botbuddy run --session-id <new-session-id> --environment local -- ${context.command.map((x) => JSON.stringify(x)).join(" ")}` : null, bounded_log: boundedLog, output_characters: outputCharacters, reason: state.reason ?? null };
|
|
808
960
|
await writeJson(context.receipt_path, receipt);
|
|
809
961
|
const bytes = await stat(context.receipt_path);
|
|
810
962
|
const sha256 = createHash("sha256").update(await readFile(context.receipt_path)).digest("hex");
|
|
@@ -882,6 +1034,12 @@ export async function cmdRun(args) {
|
|
|
882
1034
|
if (args.includes("--json")) oneLine({ schema_version: RUN_SCHEMA_VERSION, ...result }, false);
|
|
883
1035
|
return;
|
|
884
1036
|
}
|
|
1037
|
+
if (args[0] === "inspect") {
|
|
1038
|
+
const result = await inspectRun(args[1] ?? "");
|
|
1039
|
+
oneLine(result, false);
|
|
1040
|
+
process.exitCode = result.classification === "rejected" ? EXIT.INVALID : EXIT.OK;
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
885
1043
|
if (args[0] === "cancel") {
|
|
886
1044
|
const result = await cancelRun(args[1] ?? "");
|
|
887
1045
|
oneLine(result.receipt, false);
|
|
@@ -889,7 +1047,7 @@ export async function cmdRun(args) {
|
|
|
889
1047
|
return;
|
|
890
1048
|
}
|
|
891
1049
|
if (args[0] === "help" || args[0] === "--help") {
|
|
892
|
-
console.log("botbuddy run --session-id <id> --environment <local|preview|staging|production|none> [--timeout <sec>] [--rerun-reason <json>] -- <command> [args...]\nbotbuddy run --foreground --kind <test|wait|browser|ci|hook|stack> --environment <local|preview|staging|production|none> -- <command> [args...]\nbotbuddy run cancel <run_id>\n --agent-session-token <token> session credential (env $BOTBUDDY_AGENT_SESSION_TOKEN); --agent-key / --session-token are one-release legacy aliases");
|
|
1050
|
+
console.log("botbuddy run --session-id <id> --environment <local|preview|staging|production|none> [--timeout <sec>] [--rerun-reason <json>] -- <command> [args...]\nbotbuddy run --foreground --kind <test|wait|browser|ci|hook|stack> --environment <local|preview|staging|production|none> -- <command> [args...]\nbotbuddy run inspect <run_id>\nbotbuddy run cancel <run_id>\n --agent-session-token <token> session credential (env $BOTBUDDY_AGENT_SESSION_TOKEN); --agent-key / --session-token are one-release legacy aliases");
|
|
893
1051
|
return;
|
|
894
1052
|
}
|
|
895
1053
|
// BOT-1701 (AC-6): one stderr nudge when a tier-3 legacy flag/env alias is used
|