@proagentstore/cli 0.4.129 → 0.4.131

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 CHANGED
@@ -1174,7 +1174,7 @@ var publishCommand = new Command7("publish").description("Publish an agent to Pr
1174
1174
 
1175
1175
  // src/commands/runner/command.ts
1176
1176
  import { spawn as spawn4 } from "child_process";
1177
- import { randomUUID as randomUUID3 } from "crypto";
1177
+ import { randomUUID as randomUUID4 } from "crypto";
1178
1178
  import { Command as Command8 } from "commander";
1179
1179
 
1180
1180
  // src/commands/runner/process.ts
@@ -2905,6 +2905,274 @@ function openRelaySocket(instanceId, wsBase, mintToken, localUrl, runnerToken, f
2905
2905
  };
2906
2906
  }
2907
2907
 
2908
+ // src/crash-reports.ts
2909
+ import { closeSync as closeSync2, copyFileSync, existsSync as existsSync8, fsyncSync, mkdirSync as mkdirSync7, openSync as openSync2, readdirSync, readFileSync as readFileSync8, renameSync as renameSync3, unlinkSync as unlinkSync3, writeSync as writeSync2 } from "fs";
2910
+ import { basename, dirname as dirname4, join as join9 } from "path";
2911
+ import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
2912
+ var MAX_REPORTS = 100;
2913
+ var MAX_QUARANTINED = 5;
2914
+ var MAX_MESSAGE = 1e3;
2915
+ var MAX_STACK = 4e3;
2916
+ function maskSecrets(text) {
2917
+ return text.replace(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]{8,}/gi, "$1 [redacted]").replace(/\b(sk-[A-Za-z0-9_-]{8,}|ghp_[A-Za-z0-9]{8,}|gho_[A-Za-z0-9]{8,}|github_pat_[A-Za-z0-9_]{8,}|xox[abp]-[A-Za-z0-9-]{8,}|pags_runner_[A-Za-z0-9-]{8,})/g, "[redacted]").replace(/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g, "[redacted-jwt]").replace(/\b((?:api[_-]?key|token|secret|password|authorization)\s*[=:]\s*)("?)[^\s"&]{4,}\2/gi, "$1[redacted]");
2918
+ }
2919
+ function crashAccountKey(login) {
2920
+ return createHash2("sha256").update(login.trim().toLowerCase()).digest("hex").substring(0, 12);
2921
+ }
2922
+ function clip(text, max) {
2923
+ if (text.length <= max) return text;
2924
+ return `${text.substring(0, max)}
2925
+ [cut: showing the first ${max} of ${text.length} characters]`;
2926
+ }
2927
+ var clean2 = (value, max) => clip(maskSecrets(String(value ?? "")), max);
2928
+ var shortId = (value) => typeof value === "string" && /^[A-Za-z0-9._:-]{1,128}$/.test(value) ? value : void 0;
2929
+ function describeError(error) {
2930
+ if (error instanceof Error) {
2931
+ const cause = error.cause;
2932
+ return {
2933
+ message: clean2(`${error.name}: ${error.message}`, MAX_MESSAGE),
2934
+ stack: error.stack ? clean2(error.stack, MAX_STACK) : void 0,
2935
+ cause: cause === void 0 ? void 0 : clean2(cause instanceof Error ? `${cause.name}: ${cause.message}` : cause, MAX_MESSAGE)
2936
+ };
2937
+ }
2938
+ return { message: clean2(error, MAX_MESSAGE) };
2939
+ }
2940
+ function crashReport(input) {
2941
+ return {
2942
+ id: shortId(input.id) ?? `crash_${randomUUID3()}`,
2943
+ at: input.at ?? (/* @__PURE__ */ new Date()).toISOString(),
2944
+ source: input.source,
2945
+ kind: input.kind,
2946
+ message: clean2(input.message, MAX_MESSAGE),
2947
+ ...input.stack ? { stack: clean2(input.stack, MAX_STACK) } : {},
2948
+ ...input.cause ? { cause: clean2(input.cause, MAX_MESSAGE) } : {},
2949
+ ...input.exitCode !== void 0 ? { exitCode: input.exitCode } : {},
2950
+ ...input.signal !== void 0 ? { signal: input.signal } : {},
2951
+ ...shortId(input.build) ? { build: input.build } : {},
2952
+ ...shortId(input.account) ? { account: input.account } : {},
2953
+ ...shortId(input.instanceId) ? { instanceId: input.instanceId } : {},
2954
+ ...shortId(input.sessionId) ? { sessionId: input.sessionId } : {},
2955
+ ...shortId(input.taskId) ? { taskId: input.taskId } : {},
2956
+ ...input.route ? { route: clean2(input.route, 200) } : {},
2957
+ ...input.tail?.length ? { tail: input.tail.slice(-20).map((line) => clean2(line, 300)) } : {}
2958
+ };
2959
+ }
2960
+ function writeJsonAtomic(path, data, mode = 384) {
2961
+ mkdirSync7(dirname4(path), { recursive: true });
2962
+ const tmp = `${path}.${process.pid}.${randomUUID3()}.tmp`;
2963
+ const fd = openSync2(tmp, "w", mode);
2964
+ try {
2965
+ writeSync2(fd, `${JSON.stringify(data, null, 2)}
2966
+ `);
2967
+ fsyncSync(fd);
2968
+ } finally {
2969
+ closeSync2(fd);
2970
+ }
2971
+ try {
2972
+ renameSync3(tmp, path);
2973
+ } catch (error) {
2974
+ try {
2975
+ unlinkSync3(tmp);
2976
+ } catch {
2977
+ }
2978
+ throw error;
2979
+ }
2980
+ }
2981
+ function removeStaleTemps(path) {
2982
+ const dir = dirname4(path);
2983
+ const prefix = `${basename(path)}.`;
2984
+ if (!existsSync8(dir)) return;
2985
+ for (const name of readdirSync(dir)) {
2986
+ if (!name.startsWith(prefix) || !name.endsWith(".tmp")) continue;
2987
+ try {
2988
+ unlinkSync3(join9(dir, name));
2989
+ } catch {
2990
+ }
2991
+ }
2992
+ }
2993
+ function readJsonOrQuarantine(path) {
2994
+ removeStaleTemps(path);
2995
+ if (!existsSync8(path)) return { status: "missing" };
2996
+ try {
2997
+ return { status: "ok", value: JSON.parse(readFileSync8(path, "utf-8")) };
2998
+ } catch (error) {
2999
+ const quarantinedTo = `${path}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
3000
+ try {
3001
+ renameSync3(path, quarantinedTo);
3002
+ } catch {
3003
+ try {
3004
+ copyFileSync(path, quarantinedTo);
3005
+ } catch (copyError) {
3006
+ return { status: "quarantined", quarantinedTo: "(not preserved)", error: `${describeError(error).message}; preserving it failed: ${describeError(copyError).message}` };
3007
+ }
3008
+ }
3009
+ pruneQuarantine(path);
3010
+ return { status: "quarantined", quarantinedTo, error: describeError(error).message };
3011
+ }
3012
+ }
3013
+ function pruneQuarantine(path) {
3014
+ const dir = dirname4(path);
3015
+ const prefix = `${basename(path)}.corrupt-`;
3016
+ const copies = readdirSync(dir).filter((name) => name.startsWith(prefix)).sort();
3017
+ const excess = copies.length - MAX_QUARANTINED;
3018
+ for (const name of excess > 0 ? copies.slice(0, excess) : []) {
3019
+ try {
3020
+ unlinkSync3(join9(dir, name));
3021
+ } catch {
3022
+ }
3023
+ }
3024
+ }
3025
+ var CrashLog = class {
3026
+ /** `base` stamps the reports this log writes about ITSELF (a quarantined file). */
3027
+ constructor(path, base = { source: "runtime" }) {
3028
+ this.path = path;
3029
+ this.base = base;
3030
+ }
3031
+ path;
3032
+ base;
3033
+ /** Everything still on disk, oldest first. A corrupt file is quarantined and reported, then read as a fresh file. */
3034
+ read() {
3035
+ const got = readJsonOrQuarantine(this.path);
3036
+ if (got.status === "ok") {
3037
+ const reports = Array.isArray(got.value.reports) ? got.value.reports.filter((r) => !!r && typeof r.id === "string") : [];
3038
+ return { reports, dropped: Number(got.value.dropped) || 0 };
3039
+ }
3040
+ if (got.status === "missing") return { reports: [], dropped: 0 };
3041
+ const file = {
3042
+ reports: [crashReport({ ...this.base, kind: "store_corrupt", message: `crash-report file was unreadable and was moved to ${basename(got.quarantinedTo)}: ${got.error}` })],
3043
+ dropped: 0
3044
+ };
3045
+ this.write(file);
3046
+ return file;
3047
+ }
3048
+ /** Append a report (bounded). Never throws: a crash path must not crash on its own record. */
3049
+ record(report) {
3050
+ try {
3051
+ const file = this.read();
3052
+ if (file.reports.some((r) => r.id === report.id)) return true;
3053
+ file.reports.push(report);
3054
+ const over = file.reports.length - MAX_REPORTS;
3055
+ if (over > 0) {
3056
+ file.reports = file.reports.slice(over);
3057
+ file.dropped += over;
3058
+ }
3059
+ this.write(file);
3060
+ return true;
3061
+ } catch (error) {
3062
+ process.stderr.write(`[crash-reports] could not record ${report.kind}: ${describeError(error).message}
3063
+ `);
3064
+ return false;
3065
+ }
3066
+ }
3067
+ /** Drop reports by id — only by this file's own writer. */
3068
+ remove(ids) {
3069
+ if (!ids.length) return;
3070
+ const file = this.read();
3071
+ const gone = new Set(ids);
3072
+ file.reports = file.reports.filter((r) => !gone.has(r.id));
3073
+ this.write(file);
3074
+ }
3075
+ write(file) {
3076
+ writeJsonAtomic(this.path, file);
3077
+ }
3078
+ };
3079
+
3080
+ // src/crash-delivery.ts
3081
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
3082
+ import { homedir as homedir4 } from "os";
3083
+ import { join as join10 } from "path";
3084
+ var BATCH = 50;
3085
+ var LEDGER_MAX = 1e3;
3086
+ var TIMEOUT_MS = 1e4;
3087
+ function runnerDataDir(dataDir, env = process.env) {
3088
+ return dataDir || env.PAGS_RUNNER_DATA_DIR || join10(homedir4(), ".config", "proagentstore", "browser-runner");
3089
+ }
3090
+ function crashPaths(configDir2, account, dataDir) {
3091
+ const key = crashAccountKey(account);
3092
+ return {
3093
+ runtimeFile: join10(runnerDataDir(dataDir), "crash-reports.json"),
3094
+ cliFile: join10(configDir2, `runner-crashes-${key}.json`),
3095
+ ledgerFile: join10(configDir2, `crash-delivered-${key}.json`)
3096
+ };
3097
+ }
3098
+ function readRuntimeReports(path) {
3099
+ if (!existsSync9(path)) return [];
3100
+ try {
3101
+ const parsed = JSON.parse(readFileSync9(path, "utf-8"));
3102
+ return Array.isArray(parsed.reports) ? parsed.reports.filter((r) => r && typeof r.id === "string") : [];
3103
+ } catch {
3104
+ return [];
3105
+ }
3106
+ }
3107
+ function readLedger(path) {
3108
+ const got = readJsonOrQuarantine(path);
3109
+ return got.status === "ok" && Array.isArray(got.value.ids) ? got.value.ids.filter((id) => typeof id === "string") : [];
3110
+ }
3111
+ async function deliverCrashReports(opts) {
3112
+ const key = crashAccountKey(opts.account);
3113
+ const cliLog = new CrashLog(opts.paths.cliFile, { source: "cli", account: key });
3114
+ const acked = new Set(readLedger(opts.paths.ledgerFile));
3115
+ const mine = (r) => r.account === key && !acked.has(r.id);
3116
+ const pending = [...readRuntimeReports(opts.paths.runtimeFile).filter(mine), ...cliLog.read().reports.filter(mine)];
3117
+ if (!pending.length) return { delivered: 0, pending: 0 };
3118
+ const doFetch = opts.fetchImpl ?? fetch;
3119
+ const accepted = [];
3120
+ let error;
3121
+ for (let i = 0; i < pending.length; i += BATCH) {
3122
+ const batch = pending.slice(i, i + BATCH);
3123
+ try {
3124
+ const res = await doFetch(`${opts.apiBase.replace(/\/$/, "")}/v1/errors/runner-crashes`, {
3125
+ method: "POST",
3126
+ headers: { Authorization: `Bearer ${opts.token}`, "Content-Type": "application/json" },
3127
+ body: JSON.stringify({ reports: batch }),
3128
+ signal: AbortSignal.timeout(TIMEOUT_MS)
3129
+ });
3130
+ if (!res.ok) {
3131
+ error = `the platform answered ${res.status}`;
3132
+ break;
3133
+ }
3134
+ const body = await res.json().catch(() => ({}));
3135
+ if (Array.isArray(body.accepted)) accepted.push(...body.accepted.filter((id) => typeof id === "string"));
3136
+ } catch (e) {
3137
+ error = e instanceof Error ? e.message : String(e);
3138
+ break;
3139
+ }
3140
+ }
3141
+ if (accepted.length) {
3142
+ const ledger = [...acked, ...accepted];
3143
+ writeJsonAtomic(opts.paths.ledgerFile, { ids: ledger.slice(Math.max(0, ledger.length - LEDGER_MAX)) });
3144
+ cliLog.remove(accepted);
3145
+ }
3146
+ return { delivered: accepted.length, pending: pending.length - accepted.length, ...error ? { error } : {} };
3147
+ }
3148
+ var OutputTail = class {
3149
+ constructor(max) {
3150
+ this.max = max;
3151
+ }
3152
+ max;
3153
+ buffer = [];
3154
+ partial = "";
3155
+ push(chunk) {
3156
+ const parts = (this.partial + chunk.toString()).split("\n");
3157
+ this.partial = parts.pop() ?? "";
3158
+ for (const line of parts) {
3159
+ if (!line.trim()) continue;
3160
+ this.buffer.push(line);
3161
+ if (this.buffer.length > this.max) this.buffer.shift();
3162
+ }
3163
+ }
3164
+ lines() {
3165
+ return this.partial.trim() ? [...this.buffer, this.partial].slice(-this.max) : [...this.buffer];
3166
+ }
3167
+ };
3168
+ function recordChildExit(o) {
3169
+ const account = crashAccountKey(o.account);
3170
+ const how = o.signal ? `was killed by ${o.signal}` : `exited unexpectedly${o.code !== null ? ` (code ${o.code})` : ""}`;
3171
+ const report = crashReport({ source: "cli", account, build: o.build, kind: o.kind, message: `${o.what} ${how}`, exitCode: o.code, signal: o.signal, tail: o.tail });
3172
+ new CrashLog(o.paths.cliFile, { source: "cli", account }).record(report);
3173
+ return report;
3174
+ }
3175
+
2908
3176
  // src/commands/runner/command.ts
2909
3177
  import { hostname as hostname5 } from "os";
2910
3178
  function collectCapability(value, previous = []) {
@@ -2918,7 +3186,7 @@ function createRunnerCommand() {
2918
3186
  await startRunnerForeground(opts);
2919
3187
  });
2920
3188
  command.command("connect <instanceIds...>").description("Start ONE local runtime, connect via WebSocket relay, and register it for every given PAGS instance").option("--host <host>", "Host to bind", "127.0.0.1").option("--port <port>", "Port to bind", "49171").option("--data-dir <path>", "Runner data directory").option("--token <token>", "Runner bearer token. Defaults to PAGS_RUNNER_TOKEN or a generated token").option("--headless", "Run Playwright headless").option("--api-base <url>", "PAGS API base URL").option("--pags-token <token>", "PAGS session token. Defaults to PAGS_TOKEN").option("--runner-version <version>", "Runner version").option("--force", "Take over from another connected machine").option("--watch-instances", "Attach newly eligible agents while running, without a restart").action(async (instanceIds, opts) => {
2921
- const runnerToken = clean(opts.token) || clean(process.env.PAGS_RUNNER_TOKEN) || `pags_runner_${randomUUID3()}`;
3189
+ const runnerToken = clean(opts.token) || clean(process.env.PAGS_RUNNER_TOKEN) || `pags_runner_${randomUUID4()}`;
2922
3190
  const host = clean(opts.host) || "127.0.0.1";
2923
3191
  const port = clean(opts.port) || String(await findFreePort2(49171));
2924
3192
  const lockAccount = clean(process.env.PAGS_LOCK_ACCOUNT) || "";
@@ -2939,6 +3207,8 @@ function createRunnerCommand() {
2939
3207
  const primary = instanceIds[0];
2940
3208
  const runnerOpts = { ...opts, host, port, token: runnerToken };
2941
3209
  const spec = runnerSpawnSpec(runnerOpts);
3210
+ const crashAccount = lockAccount || loadSession()?.user.login || "";
3211
+ const crashes = crashAccount ? crashPaths(configDir(), crashAccount, opts.dataDir) : null;
2942
3212
  const runner = spawn4(spec.command, spec.args, {
2943
3213
  cwd: spec.cwd,
2944
3214
  stdio: ["ignore", "pipe", "pipe"],
@@ -2948,16 +3218,36 @@ function createRunnerCommand() {
2948
3218
  // replace is an authorisation rather than a signal aimed at a name pattern.
2949
3219
  env: {
2950
3220
  ...process.env,
3221
+ PAGS_RUNNER_BUILD: CLI_VERSION,
3222
+ ...crashAccount ? { PAGS_RUNNER_ACCOUNT: crashAccountKey(crashAccount) } : {},
2951
3223
  ...lockHeld ? { PAGS_RUNNER_RSID: lockHeld.rsid, PAGS_RUNNER_CONTROL_NONCE: lockHeld.nonce, PAGS_RUNNER_LAUNCH: lockHeld.launch } : {}
2952
3224
  }
2953
3225
  });
3226
+ const tail = new OutputTail(20);
2954
3227
  runner.stdout?.on("data", (data) => process.stdout.write(data));
2955
- runner.stderr?.on("data", (data) => process.stderr.write(data));
3228
+ runner.stderr?.on("data", (data) => {
3229
+ process.stderr.write(data);
3230
+ tail.push(data);
3231
+ });
3232
+ const deliver = async () => {
3233
+ if (!crashes) return;
3234
+ const token = clean(opts.pagsToken) || clean(process.env.PAGS_TOKEN) || clean(loadSession()?.token);
3235
+ if (!token) return;
3236
+ const result = await deliverCrashReports({ apiBase: pagsApiBase(opts.apiBase), token, account: crashAccount, paths: crashes });
3237
+ if (result.delivered) writeLine(`Delivered ${result.delivered} crash report(s) to ProAgentStore.`);
3238
+ };
2956
3239
  let shuttingDown = false;
2957
- runner.on("exit", (code) => {
3240
+ runner.on("exit", (code, signal) => {
2958
3241
  if (shuttingDown) return;
2959
- writeError(`Local browser runtime exited unexpectedly${code ? ` (code ${code})` : ""}. Run \`pags up\` again to reconnect.`);
2960
- process.exit(code ?? 1);
3242
+ const how = signal ? `was killed by ${signal}` : `exited unexpectedly${code !== null ? ` (code ${code})` : ""}`;
3243
+ writeError(`Local browser runtime ${how}. Run \`pags up\` again to reconnect.`);
3244
+ if (crashes) recordChildExit({ paths: crashes, account: crashAccount, build: CLI_VERSION, kind: "runner_exit", what: "the local runtime", code, signal, tail: tail.lines() });
3245
+ const finish = () => process.exit(code ?? 1);
3246
+ const deadline = setTimeout(finish, 5e3);
3247
+ void deliver().catch(() => void 0).finally(() => {
3248
+ clearTimeout(deadline);
3249
+ finish();
3250
+ });
2961
3251
  });
2962
3252
  const shutdown = () => {
2963
3253
  shuttingDown = true;
@@ -2977,6 +3267,8 @@ function createRunnerCommand() {
2977
3267
  await waitForLocalRunner({ url: localUrl, token: runnerToken, instanceId: primary });
2978
3268
  writeLine(`Local browser runtime healthy at ${localUrl}`);
2979
3269
  await connectViaRelay(instanceIds, localUrl, runnerToken, opts, Boolean(opts.force), Boolean(opts.watchInstances));
3270
+ void deliver().catch(() => void 0);
3271
+ setInterval(() => void deliver().catch(() => void 0), 5 * 6e4).unref();
2980
3272
  await new Promise((resolvePromise) => {
2981
3273
  runner.on("exit", () => resolvePromise());
2982
3274
  });
@@ -3098,27 +3390,27 @@ function createRunnerCommand() {
3098
3390
  var runnerCommand = createRunnerCommand();
3099
3391
 
3100
3392
  // src/commands/status.ts
3101
- import { readFileSync as readFileSync9, statSync as statSync2, watchFile } from "fs";
3393
+ import { readFileSync as readFileSync11, statSync as statSync2, watchFile } from "fs";
3102
3394
  import { Command as Command9 } from "commander";
3103
3395
 
3104
3396
  // src/background.ts
3105
3397
  import { spawn as nodeSpawn } from "child_process";
3106
- import { closeSync as closeSync2, existsSync as existsSync8, mkdirSync as mkdirSync7, openSync as openSync2, readFileSync as readFileSync8, renameSync as renameSync3, statSync, writeFileSync as writeFileSync6 } from "fs";
3107
- import { dirname as dirname4, join as join9 } from "path";
3398
+ import { closeSync as closeSync3, existsSync as existsSync10, mkdirSync as mkdirSync8, openSync as openSync3, readFileSync as readFileSync10, renameSync as renameSync4, statSync, writeFileSync as writeFileSync6 } from "fs";
3399
+ import { dirname as dirname5, join as join11 } from "path";
3108
3400
  var BACKGROUND_ENV = "PAGS_BACKGROUND";
3109
3401
  var BACKGROUND_LOG_ENV = "PAGS_BACKGROUND_LOG";
3110
3402
  var BACKGROUND_RESTART_ENV = "PAGS_BACKGROUND_RESTART";
3111
3403
  var LOG_ROTATE_BYTES = 5 * 1024 * 1024;
3112
- var backgroundLogPath = (account, env = process.env) => join9(configDir(env), "logs", `runner-${accountKey(account)}.log`);
3113
- var backgroundStatePath = (account, env = process.env) => join9(configDir(env), `background-${accountKey(account)}.json`);
3404
+ var backgroundLogPath = (account, env = process.env) => join11(configDir(env), "logs", `runner-${accountKey(account)}.log`);
3405
+ var backgroundStatePath = (account, env = process.env) => join11(configDir(env), `background-${accountKey(account)}.json`);
3114
3406
  function writeBackgroundState(path, state) {
3115
- mkdirSync7(dirname4(path), { recursive: true });
3407
+ mkdirSync8(dirname5(path), { recursive: true });
3116
3408
  writeFileSync6(path, `${JSON.stringify(state)}
3117
3409
  `, { mode: 384 });
3118
3410
  }
3119
3411
  function readBackgroundState(path) {
3120
3412
  try {
3121
- const parsed = JSON.parse(readFileSync8(path, "utf-8"));
3413
+ const parsed = JSON.parse(readFileSync10(path, "utf-8"));
3122
3414
  return parsed?.v === 1 && Number.isInteger(parsed.pid) ? parsed : null;
3123
3415
  } catch {
3124
3416
  return null;
@@ -3130,7 +3422,7 @@ function rotateLog(path, maxBytes = LOG_ROTATE_BYTES) {
3130
3422
  } catch {
3131
3423
  return false;
3132
3424
  }
3133
- renameSync3(path, `${path}.1`);
3425
+ renameSync4(path, `${path}.1`);
3134
3426
  return true;
3135
3427
  }
3136
3428
  function shellQuote(arg, platform = process.platform) {
@@ -3142,16 +3434,16 @@ function backgroundRestartCommand(execPath, script, upArgs, platform = process.p
3142
3434
  return platform === "win32" ? `set ${BACKGROUND_RESTART_ENV}=1&& ${argv}` : `${BACKGROUND_RESTART_ENV}=1 ${argv}`;
3143
3435
  }
3144
3436
  function spawnDetached(command, args, env, logPath, spawn5 = nodeSpawn) {
3145
- mkdirSync7(dirname4(logPath), { recursive: true });
3437
+ mkdirSync8(dirname5(logPath), { recursive: true });
3146
3438
  rotateLog(logPath);
3147
- const fd = openSync2(logPath, "a", 384);
3439
+ const fd = openSync3(logPath, "a", 384);
3148
3440
  try {
3149
3441
  const child = spawn5(command, args, { detached: true, stdio: ["ignore", fd, fd], env, windowsHide: true });
3150
3442
  child.unref();
3151
3443
  if (!child.pid) throw new Error("the runner process did not start");
3152
3444
  return child.pid;
3153
3445
  } finally {
3154
- closeSync2(fd);
3446
+ closeSync3(fd);
3155
3447
  }
3156
3448
  }
3157
3449
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -3175,8 +3467,8 @@ async function waitUntilTakeable(read, opts = {}) {
3175
3467
  }
3176
3468
  }
3177
3469
  function tailLines(path, n) {
3178
- if (!existsSync8(path)) return null;
3179
- const lines = readFileSync8(path, "utf-8").split("\n");
3470
+ if (!existsSync10(path)) return null;
3471
+ const lines = readFileSync10(path, "utf-8").split("\n");
3180
3472
  if (lines.at(-1) === "") lines.pop();
3181
3473
  return lines.slice(Math.max(0, lines.length - n));
3182
3474
  }
@@ -3231,7 +3523,7 @@ var logsCommand = new Command9("logs").description("Show the background runner's
3231
3523
  watchFile(path, { interval: 1e3 }, (cur) => {
3232
3524
  if (cur.size < offset) offset = 0;
3233
3525
  if (cur.size === offset) return;
3234
- process.stdout.write(readFileSync9(path).subarray(offset, cur.size));
3526
+ process.stdout.write(readFileSync11(path).subarray(offset, cur.size));
3235
3527
  offset = cur.size;
3236
3528
  });
3237
3529
  });
@@ -3735,14 +4027,21 @@ var upCommand = new Command10("up").description("Start the browser runner for al
3735
4027
  }
3736
4028
  };
3737
4029
  let childDead = false;
4030
+ let stopping = false;
3738
4031
  child.stdout?.on("data", handleOutput);
3739
4032
  child.stderr?.on("data", handleOutput);
3740
- child.on("exit", (code) => {
4033
+ const crashes = crashPaths(configDir(), session.user.login);
4034
+ const recordConnectExit = (code, signal, tail) => {
4035
+ recordChildExit({ paths: crashes, account: session.user.login, build: CLI_VERSION2, kind: "connect_exit", what: "runner connect", code, signal, tail });
4036
+ void deliverCrashReports({ apiBase: API_BASE3, token: session.token, account: session.user.login, paths: crashes }).catch(() => void 0);
4037
+ };
4038
+ child.on("exit", (code, signal) => {
3741
4039
  if (code === RUNNER_RESTART_EXIT_CODE) {
3742
4040
  restartUp("Runner updated remotely \u2014 restarting pags up on the new version\u2026", { PAGS_NO_PROMPT: "1" });
3743
4041
  return;
3744
4042
  }
3745
4043
  childDead = true;
4044
+ if (code !== 0 && !stopping) recordConnectExit(code, signal ?? null, logs.slice(-20));
3746
4045
  if (code === null) {
3747
4046
  state.runner = "error";
3748
4047
  state.lastEvent = "Runner stopped by another process (signal) \u2014 it is not connected any more";
@@ -3765,6 +4064,7 @@ var upCommand = new Command10("up").description("Start the browser runner for al
3765
4064
  process.exit(1);
3766
4065
  }
3767
4066
  const shutdown = () => {
4067
+ stopping = true;
3768
4068
  child.kill();
3769
4069
  clearScreen();
3770
4070
  writeLine(" Runner stopped.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proagentstore/cli",
3
- "version": "0.4.129",
3
+ "version": "0.4.131",
4
4
  "description": "CLI for creating, publishing, and running ProAgentStore agents",
5
5
  "license": "MIT",
6
6
  "type": "module",