baro-ai 0.72.0 → 0.73.0

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/runner.mjs CHANGED
@@ -3717,6 +3717,7 @@ import { execFileSync, spawn } from "child_process";
3717
3717
  import { appendFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
3718
3718
  import { hostname, homedir, tmpdir } from "os";
3719
3719
  import { join } from "path";
3720
+ import { createInterface } from "readline/promises";
3720
3721
 
3721
3722
  // ../../node_modules/ws/wrapper.mjs
3722
3723
  var import_stream = __toESM(require_stream(), 1);
@@ -3728,14 +3729,7 @@ var import_subprotocol = __toESM(require_subprotocol(), 1);
3728
3729
  var import_websocket = __toESM(require_websocket(), 1);
3729
3730
  var import_websocket_server = __toESM(require_websocket_server(), 1);
3730
3731
 
3731
- // ../baro-orchestrator/scripts/runner.ts
3732
- var encode = (m) => JSON.stringify(m);
3733
- var url = process.env.CONTROL_URL ?? "wss://api.baro.jigjoy.ai";
3734
- var token = process.env.RUNNER_TOKEN;
3735
- var httpBase = url.replace(/^ws/, "http").replace(/\/+$/, "");
3736
- var credsPath = join(homedir(), ".baro", "credentials.json");
3737
- var VERSION = "0.72.0";
3738
- var updateCachePath = join(homedir(), ".baro", "update-check.json");
3732
+ // ../baro-orchestrator/scripts/runner-helpers.ts
3739
3733
  function semverLt(a, b) {
3740
3734
  const pa = a.split(".").map(Number);
3741
3735
  const pb = b.split(".").map(Number);
@@ -3746,6 +3740,23 @@ function semverLt(a, b) {
3746
3740
  }
3747
3741
  return false;
3748
3742
  }
3743
+ function buildReexec(execPath, argv, env) {
3744
+ return { cmd: execPath, args: argv.slice(1), env: { ...env, BARO_UPDATED: "1" } };
3745
+ }
3746
+ function buildInstallServiceArgs(opts) {
3747
+ const args = ["connect", "--install-service", "--token", opts.token, "--workspace", opts.workspace];
3748
+ if (opts.controlUrl) args.push("--control-url", opts.controlUrl);
3749
+ return args;
3750
+ }
3751
+
3752
+ // ../baro-orchestrator/scripts/runner.ts
3753
+ var encode = (m) => JSON.stringify(m);
3754
+ var url = process.env.CONTROL_URL ?? "wss://api.baro.jigjoy.ai";
3755
+ var token = process.env.RUNNER_TOKEN;
3756
+ var httpBase = url.replace(/^ws/, "http").replace(/\/+$/, "");
3757
+ var credsPath = join(homedir(), ".baro", "credentials.json");
3758
+ var VERSION = "0.73.0";
3759
+ var updateCachePath = join(homedir(), ".baro", "update-check.json");
3749
3760
  async function getLatest(force = false) {
3750
3761
  if (!force) {
3751
3762
  try {
@@ -3779,6 +3790,15 @@ async function selfUpdate(latest) {
3779
3790
  child.on("error", () => resolve(false));
3780
3791
  });
3781
3792
  }
3793
+ function reexecUpdated() {
3794
+ const { cmd, args, env } = buildReexec(process.execPath, process.argv, process.env);
3795
+ const child = spawn(cmd, args, { stdio: "inherit", detached: false, env });
3796
+ child.on("exit", (code, signal) => process.exit(signal ? 0 : code ?? 0));
3797
+ child.on("error", (e) => {
3798
+ console.error(`[baro] could not restart into the updated runner (${e.message}) \u2014 run \`baro connect\` again`);
3799
+ process.exit(1);
3800
+ });
3801
+ }
3782
3802
  var readCliToken = () => {
3783
3803
  try {
3784
3804
  return JSON.parse(readFileSync(credsPath, "utf8")).token;
@@ -3818,6 +3838,7 @@ var workspaceDir = process.env.WORKSPACE_DIR ?? process.cwd();
3818
3838
  var baroBin = process.env.BARO_BIN ?? "baro";
3819
3839
  var runnerId = process.env.RUNNER_ID ?? hostname();
3820
3840
  var runOnce = process.env.BARO_RUN_ONCE === "1";
3841
+ var isService = process.env.BARO_SERVICE === "1";
3821
3842
  function cloneRepo(fullName, token2, emit) {
3822
3843
  return new Promise((resolve, reject) => {
3823
3844
  const dir = mkdtempSync(join(tmpdir(), "baro-clone-"));
@@ -3915,8 +3936,11 @@ async function runGoal(d, emit, signal) {
3915
3936
  const child = spawn(
3916
3937
  baroBin,
3917
3938
  ["--headless", d.goal, "--cwd", cwd, "--llm", d.route?.backend ?? "claude", "--parallel", String(d.parallel), "--timeout", String(d.timeoutSecs), ...d.quick ? ["--quick"] : [], ...d.followUp ? ["--continue"] : []],
3918
- { cwd, env, stdio: ["ignore", "pipe", "pipe"] }
3939
+ // stdin is piped: baro --headless forwards JSON command lines
3940
+ // (agent_message) into the orchestrator's stdin lane.
3941
+ { cwd, env, stdio: ["pipe", "pipe", "pipe"] }
3919
3942
  );
3943
+ activeChild = child;
3920
3944
  const started = Date.now();
3921
3945
  const secs = () => Math.max(1, Math.round((Date.now() - started) / 1e3));
3922
3946
  const stories = /* @__PURE__ */ new Set();
@@ -3958,6 +3982,7 @@ async function runGoal(d, emit, signal) {
3958
3982
  });
3959
3983
  signal.addEventListener("abort", () => child.kill("SIGTERM"));
3960
3984
  child.on("close", (code) => {
3985
+ if (activeChild === child) activeChild = null;
3961
3986
  const ok = doneSuccess ?? (code === 0 && failed === 0 && passed > 0);
3962
3987
  const goalLines = new Set(d.goal.split("\n").map((s) => s.trim()).filter(Boolean));
3963
3988
  const isNoise = (l) => /no stdin data received|redirect stdin explicitly|proceeding without it/i.test(l);
@@ -4028,6 +4053,7 @@ ${fails || r.out.slice(-500)}`);
4028
4053
  var rejected;
4029
4054
  var currentWs = null;
4030
4055
  var inflight = /* @__PURE__ */ new Map();
4056
+ var activeChild = null;
4031
4057
  var send = (m) => {
4032
4058
  if (currentWs?.readyState === import_websocket.default.OPEN) currentWs.send(encode(m));
4033
4059
  };
@@ -4040,6 +4066,13 @@ function handleMessage(m) {
4040
4066
  send({ t: "pong", ts: m.ts });
4041
4067
  } else if (m.t === "cancel") {
4042
4068
  inflight.get(m.storyId)?.abort();
4069
+ } else if (m.t === "agent_message") {
4070
+ const { storyId, text } = m;
4071
+ const stdin = activeChild?.stdin;
4072
+ if (stdin && stdin.writable && !stdin.destroyed) {
4073
+ stdin.write(`${JSON.stringify({ type: "agent_message", id: storyId, text })}
4074
+ `);
4075
+ }
4043
4076
  } else if (m.t === "dispatch_run") {
4044
4077
  const d = m;
4045
4078
  if (inflight.has(d.runId)) return;
@@ -4060,6 +4093,43 @@ function handleMessage(m) {
4060
4093
  });
4061
4094
  }
4062
4095
  }
4096
+ function goodbyeAndExit() {
4097
+ console.log("\n[baro] runner going offline \u2014 runs will fall back to cloud. Keep it always-on: baro connect --install-service");
4098
+ process.exit(0);
4099
+ }
4100
+ var serviceOffered = false;
4101
+ async function maybeOfferServiceInstall() {
4102
+ if (serviceOffered || isService || runOnce || process.env.BARO_NO_SERVICE_PROMPT === "1") return;
4103
+ serviceOffered = true;
4104
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return;
4105
+ await new Promise((r) => setTimeout(r, 1500));
4106
+ if (rejected || !token || currentWs?.readyState !== import_websocket.default.OPEN) return;
4107
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
4108
+ rl.on("SIGINT", () => {
4109
+ rl.close();
4110
+ goodbyeAndExit();
4111
+ });
4112
+ const answer = (await rl.question("\nKeep this runner online in the background (installs a login service)? [Y/n] ")).trim().toLowerCase();
4113
+ rl.close();
4114
+ if (answer === "n" || answer === "no") {
4115
+ console.log("[baro] staying in the foreground \u2014 this runner goes offline when the terminal closes.\n[baro] install the service any time: baro connect --install-service --token <rt_\u2026>");
4116
+ return;
4117
+ }
4118
+ console.log("[baro] installing the background service\u2026");
4119
+ const args = buildInstallServiceArgs({ token, workspace: workspaceDir, controlUrl: process.env.CONTROL_URL });
4120
+ const ok = await new Promise((resolve) => {
4121
+ const ch = spawn(baroBin, args, { stdio: "inherit", shell: process.platform === "win32" });
4122
+ ch.on("exit", (code) => resolve(code === 0));
4123
+ ch.on("error", () => resolve(false));
4124
+ });
4125
+ if (ok) {
4126
+ console.log("[baro] \u2713 service installed \u2014 the runner now stays online across terminal close, logout, and reboot.");
4127
+ console.log("[baro] handing off to the service; this foreground runner is exiting.");
4128
+ currentWs?.close();
4129
+ process.exit(0);
4130
+ }
4131
+ console.warn("[baro] service install failed \u2014 staying in the foreground. Try manually: baro connect --install-service --token <rt_\u2026>");
4132
+ }
4063
4133
  function connectOnce() {
4064
4134
  return new Promise((resolve) => {
4065
4135
  const ws = new import_websocket.default(url);
@@ -4067,6 +4137,7 @@ function connectOnce() {
4067
4137
  ws.on("open", () => {
4068
4138
  ws.send(encode({ t: "register", runnerId, hostname: hostname(), token, backends: ["claude"], workspaceIds: ["default"], version: VERSION }));
4069
4139
  console.log(inflight.size ? `[baro] reconnected to ${url} \u2014 resuming ${inflight.size} in-flight run(s)` : `[baro] connected to ${url} \u2014 workspace ${workspaceDir}`);
4140
+ void maybeOfferServiceInstall();
4070
4141
  });
4071
4142
  ws.on("message", (data) => {
4072
4143
  let m;
@@ -4093,20 +4164,37 @@ async function main() {
4093
4164
  await getLatest(true);
4094
4165
  return;
4095
4166
  }
4096
- try {
4097
- const latest = await getLatest();
4098
- if (latest && semverLt(VERSION, latest)) {
4099
- if (process.env.BARO_SERVICE === "1") {
4167
+ if (process.env.BARO_UPDATED !== "1") {
4168
+ try {
4169
+ const latest = await getLatest(true);
4170
+ if (latest && semverLt(VERSION, latest)) {
4100
4171
  if (await selfUpdate(latest)) {
4101
- console.log(`[baro] updated to ${latest} \u2014 restarting service\u2026`);
4102
- process.exit(0);
4172
+ if (isService) {
4173
+ console.log(`[baro] updated to ${latest} \u2014 restarting service\u2026`);
4174
+ process.exit(0);
4175
+ }
4176
+ console.log(`[baro] updated to ${latest} \u2014 restarting the runner\u2026`);
4177
+ reexecUpdated();
4178
+ return;
4103
4179
  }
4104
4180
  console.warn(`[baro] could not self-update (likely a root-owned global install). Update manually: npm i -g baro-ai@latest`);
4105
- } else {
4106
- console.warn(`[baro] a newer baro is available (${VERSION} \u2192 ${latest}). Update: npm i -g baro-ai`);
4107
4181
  }
4182
+ } catch {
4108
4183
  }
4109
- } catch {
4184
+ }
4185
+ if (isService) {
4186
+ setInterval(() => {
4187
+ void (async () => {
4188
+ try {
4189
+ const latest = await getLatest(true);
4190
+ if (latest && semverLt(VERSION, latest) && inflight.size === 0 && await selfUpdate(latest)) {
4191
+ console.log(`[baro] updated to ${latest} \u2014 restarting service\u2026`);
4192
+ process.exit(0);
4193
+ }
4194
+ } catch {
4195
+ }
4196
+ })();
4197
+ }, 6 * 36e5).unref();
4110
4198
  }
4111
4199
  if (!token) {
4112
4200
  const cli = readCliToken();
@@ -4122,6 +4210,10 @@ async function main() {
4122
4210
  console.error("[baro] not signed in. Run `baro login` first, or pass --token <rt_\u2026> (get one from the dashboard).");
4123
4211
  process.exit(1);
4124
4212
  }
4213
+ if (!isService && !runOnce) {
4214
+ process.on("SIGINT", goodbyeAndExit);
4215
+ process.on("SIGTERM", goodbyeAndExit);
4216
+ }
4125
4217
  if (runOnce) {
4126
4218
  setTimeout(() => {
4127
4219
  if (inflight.size === 0) {