@sailorbridge/client 0.2.0 → 0.2.2

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/src/cli.js CHANGED
@@ -2,10 +2,10 @@
2
2
  import { createRequire } from "node:module";
3
3
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
- // .dist-releases/1784809313131-4113078/src/cli.js
5
+ // .dist-releases/1784862378006-1249051/src/cli.js
6
6
  import { hostname as hostname3 } from "node:os";
7
7
  import { writeSync } from "node:fs";
8
- // .dist-releases/1784809313131-4113078/src/auth/host-pairing.js
8
+ // .dist-releases/1784862378006-1249051/src/auth/host-pairing.js
9
9
  import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
10
10
  import { homedir, hostname, platform } from "node:os";
11
11
  import path from "node:path";
@@ -220,7 +220,7 @@ function _assertObject(value) {
220
220
  throw new Error("host pairing response must be an object");
221
221
  return value;
222
222
  }
223
- // .dist-releases/1784809313131-4113078/src/host/repo-tip-reporter.js
223
+ // .dist-releases/1784862378006-1249051/src/host/repo-tip-reporter.js
224
224
  import { createHash } from "node:crypto";
225
225
  import { existsSync, mkdirSync } from "node:fs";
226
226
  import { homedir as homedir2 } from "node:os";
@@ -402,10 +402,10 @@ ${error.message}`));
402
402
  proc.on("close", (code) => finish(code ?? 1));
403
403
  });
404
404
  }
405
- // .dist-releases/1784809313131-4113078/src/host/request-consumer.js
405
+ // .dist-releases/1784862378006-1249051/src/host/request-consumer.js
406
406
  import { hostname as hostname2 } from "node:os";
407
407
 
408
- // .dist-releases/1784809313131-4113078/src/driver/secrets.js
408
+ // .dist-releases/1784862378006-1249051/src/driver/secrets.js
409
409
  function redactSensitiveText(text, env = {}, explicitSecrets = []) {
410
410
  let redacted = text;
411
411
  for (const [key, value] of Object.entries(env)) {
@@ -424,16 +424,16 @@ function isSensitiveEnvKey(key) {
424
424
  return /(TOKEN|KEY|SECRET|AUTHORIZATION|PASSWORD)/i.test(key);
425
425
  }
426
426
 
427
- // .dist-releases/1784809313131-4113078/src/version.js
428
- var CLIENT_VERSION = "0.2.0";
429
- var CLIENT_GIT_SHA = "d1e5ed371492-dirty";
427
+ // .dist-releases/1784862378006-1249051/src/version.js
428
+ var CLIENT_VERSION = "0.2.2";
429
+ var CLIENT_GIT_SHA = "f943ffe8e731-dirty";
430
430
  var CLIENT_BUILD_DIRTY = true;
431
431
  var CLIENT_VERSION_LABEL = formatClientVersion(CLIENT_VERSION, CLIENT_GIT_SHA);
432
432
  function formatClientVersion(version, gitSha) {
433
433
  return `${version}+git.${gitSha}`;
434
434
  }
435
435
 
436
- // .dist-releases/1784809313131-4113078/src/host/request-consumer.js
436
+ // .dist-releases/1784862378006-1249051/src/host/request-consumer.js
437
437
  var MAX_ERROR_LENGTH = 500;
438
438
  function defaultHostIdentity(config) {
439
439
  const captainId = config.captainId?.trim();
@@ -488,7 +488,7 @@ function limitErrorMessage(error, explicitSecrets) {
488
488
  const trimmed = redactSensitiveText(message, process.env, explicitSecrets).trim() || "host request starter failed";
489
489
  return trimmed.length <= MAX_ERROR_LENGTH ? trimmed : `${trimmed.slice(0, MAX_ERROR_LENGTH - 3)}...`;
490
490
  }
491
- // .dist-releases/1784809313131-4113078/src/product/start-request.js
491
+ // .dist-releases/1784862378006-1249051/src/product/start-request.js
492
492
  async function createAgentStartRequest(options) {
493
493
  const apiUrl = _required(options.apiUrl, "product API URL").replace(/\/+$/u, "");
494
494
  const accessToken = _required(options.accessToken, "product API bearer token");
@@ -548,7 +548,7 @@ function _string(value, label) {
548
548
  throw new Error(`${label} must be a non-empty string`);
549
549
  return value;
550
550
  }
551
- // .dist-releases/1784809313131-4113078/src/product/pair.js
551
+ // .dist-releases/1784862378006-1249051/src/product/pair.js
552
552
  async function createPair(options) {
553
553
  const apiUrl = _required2(options.apiUrl, "product API URL").replace(/\/+$/u, "");
554
554
  const accessToken = _required2(options.accessToken, "product API bearer token");
@@ -625,7 +625,7 @@ function _string2(value, label) {
625
625
  throw new Error(`${label} must be a non-empty string`);
626
626
  return value;
627
627
  }
628
- // .dist-releases/1784809313131-4113078/src/product/worker.js
628
+ // .dist-releases/1784862378006-1249051/src/product/worker.js
629
629
  async function createWorker(options) {
630
630
  const apiUrl = _required3(options.apiUrl, "product API URL").replace(/\/+$/u, "");
631
631
  const accessToken = _required3(options.accessToken, "product API bearer token");
@@ -689,7 +689,7 @@ function _string3(value, label) {
689
689
  throw new Error(`${label} must be a non-empty string`);
690
690
  return value;
691
691
  }
692
- // .dist-releases/1784809313131-4113078/src/driver/events.js
692
+ // .dist-releases/1784862378006-1249051/src/driver/events.js
693
693
  class DriverEventEmitter {
694
694
  callbacks = new Map;
695
695
  on(type, callback) {
@@ -704,7 +704,7 @@ class DriverEventEmitter {
704
704
  }
705
705
  }
706
706
  }
707
- // .dist-releases/1784809313131-4113078/src/driver/async-queue.js
707
+ // .dist-releases/1784862378006-1249051/src/driver/async-queue.js
708
708
  class AsyncQueue {
709
709
  values = [];
710
710
  waiters = [];
@@ -743,7 +743,7 @@ class AsyncQueue {
743
743
  }
744
744
  }
745
745
 
746
- // .dist-releases/1784809313131-4113078/src/driver/types.js
746
+ // .dist-releases/1784862378006-1249051/src/driver/types.js
747
747
  class DriverUnsupportedError extends Error {
748
748
  constructor(message) {
749
749
  super(message);
@@ -758,7 +758,7 @@ class DriverTargetNotReadyError extends Error {
758
758
  }
759
759
  }
760
760
 
761
- // .dist-releases/1784809313131-4113078/src/driver/sdk-loader.js
761
+ // .dist-releases/1784862378006-1249051/src/driver/sdk-loader.js
762
762
  async function loadSdk(loader, packageName, env = {}, installHint = `Install the optional SDK dependency ${packageName} and its peers for this transport.`) {
763
763
  try {
764
764
  return await loader();
@@ -768,7 +768,7 @@ async function loadSdk(loader, packageName, env = {}, installHint = `Install the
768
768
  }
769
769
  }
770
770
 
771
- // .dist-releases/1784809313131-4113078/src/driver/claude/driver.js
771
+ // .dist-releases/1784862378006-1249051/src/driver/claude/driver.js
772
772
  class ClaudeAgentSdkDriver {
773
773
  options;
774
774
  capabilities = { structured: true, resume: true, attach: false };
@@ -944,7 +944,7 @@ function asRecord(value) {
944
944
  function stringValue(value) {
945
945
  return typeof value === "string" ? value : undefined;
946
946
  }
947
- // .dist-releases/1784809313131-4113078/src/driver/codex/driver.js
947
+ // .dist-releases/1784862378006-1249051/src/driver/codex/driver.js
948
948
  class CodexSdkDriver {
949
949
  options;
950
950
  capabilities = { structured: true, resume: true, attach: false };
@@ -1125,7 +1125,7 @@ function asRecord2(value) {
1125
1125
  function stringValue2(value) {
1126
1126
  return typeof value === "string" ? value : undefined;
1127
1127
  }
1128
- // .dist-releases/1784809313131-4113078/src/driver/liveness-heuristics.js
1128
+ // .dist-releases/1784862378006-1249051/src/driver/liveness-heuristics.js
1129
1129
  var BUSY_MARKERS = ["Working (", "Waiting for background", "Pasted Content", "tab to queue"];
1130
1130
  var ANSI_PATTERN = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\)|[PX^_][\s\S]*?\x1B\\)/g;
1131
1131
  function stripAnsi(text) {
@@ -1152,7 +1152,7 @@ function classifyInteractiveOutput(input) {
1152
1152
  function isOutputStable(now, lastOutputAt, idleStableMs) {
1153
1153
  return Boolean(lastOutputAt && now.getTime() - lastOutputAt.getTime() >= idleStableMs);
1154
1154
  }
1155
- // .dist-releases/1784809313131-4113078/src/driver/pty/loader.js
1155
+ // .dist-releases/1784862378006-1249051/src/driver/pty/loader.js
1156
1156
  async function loadNodePty(loader = defaultNodePtyLoader) {
1157
1157
  try {
1158
1158
  return await loader();
@@ -1178,7 +1178,7 @@ async function defaultNodePtyLoader() {
1178
1178
  return loaded;
1179
1179
  }
1180
1180
 
1181
- // .dist-releases/1784809313131-4113078/src/driver/pty/driver.js
1181
+ // .dist-releases/1784862378006-1249051/src/driver/pty/driver.js
1182
1182
  class PtyDriver {
1183
1183
  options;
1184
1184
  capabilities = { structured: false, resume: false, attach: false };
@@ -1330,12 +1330,12 @@ function tail(text, lines) {
1330
1330
  function sleep(ms) {
1331
1331
  return new Promise((resolve) => setTimeout(resolve, ms));
1332
1332
  }
1333
- // .dist-releases/1784809313131-4113078/src/driver/tmux/driver.js
1333
+ // .dist-releases/1784862378006-1249051/src/driver/tmux/driver.js
1334
1334
  import { mkdtemp, rm, writeFile as writeFile2 } from "node:fs/promises";
1335
1335
  import { tmpdir } from "node:os";
1336
1336
  import { join } from "node:path";
1337
1337
 
1338
- // .dist-releases/1784809313131-4113078/src/driver/tmux/launch.js
1338
+ // .dist-releases/1784862378006-1249051/src/driver/tmux/launch.js
1339
1339
  var ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
1340
1340
  var PANE_BOOTSTRAP_COMMAND = 'tmux set-option -t "$TMUX_PANE" remain-on-exit on >/dev/null 2>&1 || true; tmux wait-for -S "$1"; shift; exec "$@"';
1341
1341
  function buildTmuxNewSessionArgs(input) {
@@ -1358,7 +1358,7 @@ function validateEnv(env) {
1358
1358
  }
1359
1359
  }
1360
1360
 
1361
- // .dist-releases/1784809313131-4113078/src/driver/tmux/paste.js
1361
+ // .dist-releases/1784862378006-1249051/src/driver/tmux/paste.js
1362
1362
  function buildDispatchPlan(text, useTyping) {
1363
1363
  return {
1364
1364
  mode: useTyping ? "type-literal" : "paste-buffer",
@@ -1382,7 +1382,7 @@ function arraysEqual(left, right) {
1382
1382
  return left.length === right.length && left.every((item, index) => item === right[index]);
1383
1383
  }
1384
1384
 
1385
- // .dist-releases/1784809313131-4113078/src/driver/tmux/liveness.js
1385
+ // .dist-releases/1784862378006-1249051/src/driver/tmux/liveness.js
1386
1386
  var SHELL_COMMANDS = new Set(["", "bash", "zsh", "sh", "fish", "pwsh", "powershell"]);
1387
1387
  function classifyTmuxLiveness(input) {
1388
1388
  if (!input.hasSession) {
@@ -1402,7 +1402,7 @@ function isTmuxProcessAlive(command, runningCommands) {
1402
1402
  return !SHELL_COMMANDS.has(command);
1403
1403
  }
1404
1404
 
1405
- // .dist-releases/1784809313131-4113078/src/driver/tmux/runner.js
1405
+ // .dist-releases/1784862378006-1249051/src/driver/tmux/runner.js
1406
1406
  import { spawn as spawn2 } from "node:child_process";
1407
1407
  var DEFAULT_TIMEOUT_MS = 15000;
1408
1408
 
@@ -1527,7 +1527,7 @@ class TmuxClient {
1527
1527
  }
1528
1528
  }
1529
1529
 
1530
- // .dist-releases/1784809313131-4113078/src/driver/tmux/driver.js
1530
+ // .dist-releases/1784862378006-1249051/src/driver/tmux/driver.js
1531
1531
  var ATTACHED_STOP_GRACE_MS = 1e4;
1532
1532
  var DEFAULT_STOP_TIMEOUT_MS = 15000;
1533
1533
  var DEFAULT_READY_TIMEOUT_MS = 90000;
@@ -1922,12 +1922,12 @@ function isMissingTmuxSessionError(error) {
1922
1922
  ${error.result.stderr}`.toLowerCase();
1923
1923
  return text.includes("can't find session") || text.includes("cannot find session") || text.includes("no such session") || text.includes("session not found");
1924
1924
  }
1925
- // .dist-releases/1784809313131-4113078/src/relay/client.js
1925
+ // .dist-releases/1784862378006-1249051/src/relay/client.js
1926
1926
  import { Buffer as Buffer2 } from "node:buffer";
1927
1927
  import http from "node:http";
1928
1928
  import https from "node:https";
1929
1929
 
1930
- // .dist-releases/1784809313131-4113078/src/relay/errors.js
1930
+ // .dist-releases/1784862378006-1249051/src/relay/errors.js
1931
1931
  class RelayError extends Error {
1932
1932
  method;
1933
1933
  path;
@@ -1964,7 +1964,37 @@ class RelayProtocolError extends RelayError {
1964
1964
  }
1965
1965
  }
1966
1966
 
1967
- // .dist-releases/1784809313131-4113078/src/relay/client.js
1967
+ // .dist-releases/1784862378006-1249051/src/worker/protocol.js
1968
+ var DEFAULT_RELAY_URL = DEFAULT_PRODUCT_API_URL;
1969
+ var DEFAULT_WORKER = "host";
1970
+ var DEFAULT_WORKDIR = "~/sailorbridge-workdir";
1971
+ var DEFAULT_JOB_TIMEOUT_SECONDS = 300;
1972
+ var MAX_JOB_TIMEOUT_SECONDS = 24 * 60 * 60;
1973
+ var MIN_JOB_TIMEOUT_SECONDS = 1;
1974
+ var WORKER_JOB_RESULT_GRACE_SECONDS = 30;
1975
+ var ACTIVE_JOB_KINDS = new Set(["apply_diff", "bash", "read_file", "write_file", "compose", "stream", "status"]);
1976
+ var READONLY_ALLOWED_KINDS = new Set(["read_file", "status"]);
1977
+ var WORKER_SOCK_CONNECT_MS = 1e4;
1978
+ var WORKER_PULL_READ_IDLE_MS = 45000;
1979
+ var WORKER_QUICK_READ_IDLE_MS = 15000;
1980
+ var WORKER_UPLOAD_READ_IDLE_MS = (MAX_JOB_TIMEOUT_SECONDS + 300) * 1000;
1981
+ var DRAIN_AFTER_KILL_MS = 5000;
1982
+ var WORKER_RETRY_INITIAL_MS = 1000;
1983
+ var WORKER_RETRY_MAX_MS = 30000;
1984
+ var WORKER_SESSION_REBUILD_FAILURES = 10;
1985
+ var MAX_WORKER_RESULT_OUTPUT_BYTES = 256 * 1024;
1986
+ function coerceJobTimeoutSeconds(spec) {
1987
+ const value = spec.timeout_sec === undefined ? DEFAULT_JOB_TIMEOUT_SECONDS : spec.timeout_sec;
1988
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
1989
+ throw new Error("spec.timeout_sec must be a finite positive number");
1990
+ }
1991
+ return Math.max(MIN_JOB_TIMEOUT_SECONDS, Math.min(value, MAX_JOB_TIMEOUT_SECONDS));
1992
+ }
1993
+ function effectiveJobKind(kind, spec) {
1994
+ return kind === "stream" || spec.stream === true ? "stream" : kind;
1995
+ }
1996
+
1997
+ // .dist-releases/1784862378006-1249051/src/relay/client.js
1968
1998
  var JSON_CONTENT_TYPE = "application/json";
1969
1999
  var MAX_MAILBOX_LIMIT = 16;
1970
2000
  var MAX_WAIT_TIMEOUT_SEC = 120;
@@ -1999,6 +2029,44 @@ class RelayClient {
1999
2029
  close() {
2000
2030
  this.httpClient?.close();
2001
2031
  }
2032
+ async listWorkers(captainId, session) {
2033
+ requireCaptainId(captainId);
2034
+ if (session !== undefined)
2035
+ requireSegment(session, "session");
2036
+ const suffix = session === undefined ? "" : `?session=${segment(session)}`;
2037
+ const path3 = `/api/v1/captains/${segment(captainId)}/workers${suffix}`;
2038
+ const response = await this.requestJson("GET", path3);
2039
+ if (!Array.isArray(response.workers)) {
2040
+ throw new RelayProtocolError("GET", path3, "workers must be an array");
2041
+ }
2042
+ return { workers: response.workers.map((worker) => _parseWorkerRecord(worker, path3)) };
2043
+ }
2044
+ async submitJob(captainId, body) {
2045
+ requireCaptainId(captainId);
2046
+ _validateWorkerJobSubmit(body);
2047
+ const path3 = `/api/v1/captains/${segment(captainId)}/task`;
2048
+ const spec = {
2049
+ argv: [...body.argv],
2050
+ ...body.timeoutSec === undefined ? {} : { timeout_sec: body.timeoutSec }
2051
+ };
2052
+ const response = await this.requestJson("POST", path3, { kind: "bash", worker: body.worker, spec });
2053
+ return _parseWorkerJobSubmitResponse(response, path3);
2054
+ }
2055
+ async getJobStatus(captainId, jobId) {
2056
+ return this._getJob(captainId, jobId);
2057
+ }
2058
+ async getJobResult(captainId, jobId, timeoutSec) {
2059
+ _validateWorkerJobWaitTimeout(timeoutSec);
2060
+ return this._getJob(captainId, jobId, timeoutSec);
2061
+ }
2062
+ async _getJob(captainId, jobId, timeoutSec) {
2063
+ requireCaptainId(captainId);
2064
+ requireSegment(jobId, "jobId");
2065
+ const base = `/api/v1/captains/${segment(captainId)}/task/${segment(jobId)}`;
2066
+ const path3 = timeoutSec === undefined ? base : `${base}?wait=true&timeout_sec=${timeoutSec}`;
2067
+ const timeoutMs = timeoutSec === undefined ? this.defaultTimeoutMs : (timeoutSec + 5) * 1000;
2068
+ return _parseWorkerJobResultResponse(await this.requestJson("GET", path3, undefined, timeoutMs), path3);
2069
+ }
2002
2070
  async claimMailbox(agentName, options = {}) {
2003
2071
  requireSegment(agentName, "agentName");
2004
2072
  if (options.messageId)
@@ -2917,14 +2985,139 @@ function parseAgentMonitor(value) {
2917
2985
  if (typeof value.context_checkpoint_percent !== "number" || !Number.isFinite(value.context_checkpoint_percent)) {
2918
2986
  throw new RelayProtocolError("GET", "/api/v1/agents/{name}/monitor", "context_checkpoint_percent must be a finite number");
2919
2987
  }
2988
+ if (value.idle_presave_min !== null && (typeof value.idle_presave_min !== "number" || !Number.isFinite(value.idle_presave_min))) {
2989
+ throw new RelayProtocolError("GET", "/api/v1/agents/{name}/monitor", "idle_presave_min must be a finite number or null");
2990
+ }
2991
+ const latestInbound = value.latest_message_inbound_at;
2992
+ const latestOutbound = value.latest_message_outbound_at;
2993
+ if (latestInbound !== null && typeof latestInbound !== "string") {
2994
+ throw new RelayProtocolError("GET", "/api/v1/agents/{name}/monitor", "latest_message_inbound_at must be string or null");
2995
+ }
2996
+ if (latestOutbound !== null && typeof latestOutbound !== "string") {
2997
+ throw new RelayProtocolError("GET", "/api/v1/agents/{name}/monitor", "latest_message_outbound_at must be string or null");
2998
+ }
2920
2999
  return {
2921
3000
  name: value.name,
2922
3001
  engine: value.engine,
2923
3002
  should_resume: value.should_resume,
2924
3003
  session_handle: value.session_handle,
2925
- context_checkpoint_percent: value.context_checkpoint_percent
3004
+ context_checkpoint_percent: value.context_checkpoint_percent,
3005
+ idle_presave_min: value.idle_presave_min,
3006
+ latest_message_inbound_at: latestInbound,
3007
+ latest_message_outbound_at: latestOutbound
2926
3008
  };
2927
3009
  }
3010
+ function _parseWorkerRecord(value, path3) {
3011
+ const worker = _requireObject(value, "GET", path3, "worker");
3012
+ for (const field of ["id", "session", "name", "host", "consumer_status", "last_seen"]) {
3013
+ _requireStringField(worker, field, "GET", path3, "worker");
3014
+ }
3015
+ if (!isStringArray(worker.capabilities) || !isStringArray(worker.caps)) {
3016
+ throw new RelayProtocolError("GET", path3, "worker capabilities and caps must be string arrays");
3017
+ }
3018
+ if (!Number.isSafeInteger(worker.max_concurrency) || worker.max_concurrency < 1) {
3019
+ throw new RelayProtocolError("GET", path3, "worker.max_concurrency must be a positive integer");
3020
+ }
3021
+ if (typeof worker.online !== "boolean") {
3022
+ throw new RelayProtocolError("GET", path3, "worker.online must be boolean");
3023
+ }
3024
+ return worker;
3025
+ }
3026
+ function _validateWorkerJobSubmit(body) {
3027
+ requireSegment(body.worker, "worker");
3028
+ if (body.argv.length === 0 || !body.argv[0]?.trim())
3029
+ throw new Error("job argv requires a command");
3030
+ if (body.argv.some((arg) => typeof arg !== "string"))
3031
+ throw new Error("job argv must contain strings");
3032
+ if (body.timeoutSec !== undefined)
3033
+ _validateWorkerJobTimeout(body.timeoutSec);
3034
+ }
3035
+ function _validateWorkerJobTimeout(value) {
3036
+ if (!Number.isFinite(value) || value < 1 || value > MAX_JOB_TIMEOUT_SECONDS) {
3037
+ throw new Error(`timeoutSec must be a number from 1 to ${MAX_JOB_TIMEOUT_SECONDS}`);
3038
+ }
3039
+ }
3040
+ function _validateWorkerJobWaitTimeout(value) {
3041
+ const maximum = MAX_JOB_TIMEOUT_SECONDS + WORKER_JOB_RESULT_GRACE_SECONDS;
3042
+ if (!Number.isFinite(value) || value < 1 || value > maximum) {
3043
+ throw new Error(`result wait timeoutSec must be a number from 1 to ${maximum}`);
3044
+ }
3045
+ }
3046
+ function _parseWorkerJobSubmitResponse(value, path3) {
3047
+ if (typeof value.id !== "string" || !value.id || value.status !== "queued") {
3048
+ throw new RelayProtocolError("POST", path3, "job submit response requires id and queued status");
3049
+ }
3050
+ if (value.warning !== undefined && typeof value.warning !== "string") {
3051
+ throw new RelayProtocolError("POST", path3, "job submit warning must be a string");
3052
+ }
3053
+ return {
3054
+ id: value.id,
3055
+ status: "queued",
3056
+ ...value.warning === undefined ? {} : { warning: value.warning }
3057
+ };
3058
+ }
3059
+ function _parseWorkerJobResultResponse(value, path3) {
3060
+ const status = value.status;
3061
+ if (!_isWorkerJobStatus(status))
3062
+ throw new RelayProtocolError("GET", path3, "job status is invalid");
3063
+ for (const field of ["id", "created_at"])
3064
+ _requireStringField(value, field, "GET", path3, "job");
3065
+ _requireNullableStringFields(value, ["started_at", "finished_at", "submitter_agent_name"], path3);
3066
+ _requireNullableNumberFields(value, ["duration_sec"], path3);
3067
+ if (value.exit_code !== null && !Number.isSafeInteger(value.exit_code)) {
3068
+ throw new RelayProtocolError("GET", path3, "job.exit_code must be an integer or null");
3069
+ }
3070
+ const result = value.result === undefined ? undefined : _parseWorkerJobResult(value.result, path3);
3071
+ if (_isTerminalJobStatus(status) && (result === undefined || value.exit_code !== result.exit_code)) {
3072
+ throw new RelayProtocolError("GET", path3, "terminal job response requires a matching result");
3073
+ }
3074
+ return { ...value, status, ...result ? { result } : {} };
3075
+ }
3076
+ function _parseWorkerJobResult(value, path3) {
3077
+ const result = _requireObject(value, "GET", path3, "job result");
3078
+ for (const field of ["stdout", "stderr"])
3079
+ _requireStringField(result, field, "GET", path3, "job result", true);
3080
+ _requireNullableStringFields(result, ["started_at", "ended_at"], path3, "job result");
3081
+ if (!Number.isSafeInteger(result.exit_code)) {
3082
+ throw new RelayProtocolError("GET", path3, "job result.exit_code must be an integer");
3083
+ }
3084
+ _requireNullableNumberFields(result, ["duration_sec"], path3, "job result");
3085
+ if (!isStringArray(result.attached_file_paths)) {
3086
+ throw new RelayProtocolError("GET", path3, "job result.attached_file_paths must be a string array");
3087
+ }
3088
+ return result;
3089
+ }
3090
+ function _isWorkerJobStatus(value) {
3091
+ return value === "queued" || value === "running" || value === "done" || value === "failed" || value === "cancelled";
3092
+ }
3093
+ function _isTerminalJobStatus(value) {
3094
+ return value === "done" || value === "failed" || value === "cancelled";
3095
+ }
3096
+ function _requireObject(value, method, path3, label) {
3097
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3098
+ throw new RelayProtocolError(method, path3, `${label} must be an object`);
3099
+ }
3100
+ return value;
3101
+ }
3102
+ function _requireStringField(value, field, method, path3, label, allowEmpty = false) {
3103
+ if (typeof value[field] !== "string" || !allowEmpty && !value[field]) {
3104
+ throw new RelayProtocolError(method, path3, `${label}.${field} must be a string`);
3105
+ }
3106
+ }
3107
+ function _requireNullableStringFields(value, fields, path3, label = "job") {
3108
+ for (const field of fields) {
3109
+ if (value[field] !== null && typeof value[field] !== "string") {
3110
+ throw new RelayProtocolError("GET", path3, `${label}.${field} must be a string or null`);
3111
+ }
3112
+ }
3113
+ }
3114
+ function _requireNullableNumberFields(value, fields, path3, label = "job") {
3115
+ for (const field of fields) {
3116
+ if (value[field] !== null && (typeof value[field] !== "number" || !Number.isFinite(value[field]))) {
3117
+ throw new RelayProtocolError("GET", path3, `${label}.${field} must be a finite number or null`);
3118
+ }
3119
+ }
3120
+ }
2928
3121
  function parseScheduledEventResponse(value, method, path3) {
2929
3122
  return { event: parseScheduledEvent(value.event, method, path3) };
2930
3123
  }
@@ -3854,41 +4047,10 @@ function redact(value, token) {
3854
4047
  function bytesToBase64(bytes) {
3855
4048
  return Buffer2.from(bytes).toString("base64");
3856
4049
  }
3857
- // .dist-releases/1784809313131-4113078/src/relay/config.js
4050
+ // .dist-releases/1784862378006-1249051/src/relay/config.js
3858
4051
  import { existsSync as existsSync2, readFileSync } from "node:fs";
3859
4052
  import { homedir as homedir3 } from "node:os";
3860
4053
  import path3 from "node:path";
3861
-
3862
- // .dist-releases/1784809313131-4113078/src/worker/protocol.js
3863
- var DEFAULT_RELAY_URL = DEFAULT_PRODUCT_API_URL;
3864
- var DEFAULT_WORKER = "host";
3865
- var DEFAULT_WORKDIR = "~/sailorbridge-workdir";
3866
- var DEFAULT_JOB_TIMEOUT_SECONDS = 300;
3867
- var MAX_JOB_TIMEOUT_SECONDS = 24 * 60 * 60;
3868
- var MIN_JOB_TIMEOUT_SECONDS = 1;
3869
- var ACTIVE_JOB_KINDS = new Set(["apply_diff", "bash", "read_file", "write_file", "compose", "stream", "status"]);
3870
- var READONLY_ALLOWED_KINDS = new Set(["read_file", "status"]);
3871
- var WORKER_SOCK_CONNECT_MS = 1e4;
3872
- var WORKER_PULL_READ_IDLE_MS = 45000;
3873
- var WORKER_QUICK_READ_IDLE_MS = 15000;
3874
- var WORKER_UPLOAD_READ_IDLE_MS = (MAX_JOB_TIMEOUT_SECONDS + 300) * 1000;
3875
- var DRAIN_AFTER_KILL_MS = 5000;
3876
- var WORKER_RETRY_INITIAL_MS = 1000;
3877
- var WORKER_RETRY_MAX_MS = 30000;
3878
- var WORKER_SESSION_REBUILD_FAILURES = 10;
3879
- var MAX_WORKER_RESULT_OUTPUT_BYTES = 256 * 1024;
3880
- function coerceJobTimeoutSeconds(spec) {
3881
- const value = spec.timeout_sec === undefined ? DEFAULT_JOB_TIMEOUT_SECONDS : spec.timeout_sec;
3882
- if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
3883
- throw new Error("spec.timeout_sec must be a finite positive number");
3884
- }
3885
- return Math.max(MIN_JOB_TIMEOUT_SECONDS, Math.min(value, MAX_JOB_TIMEOUT_SECONDS));
3886
- }
3887
- function effectiveJobKind(kind, spec) {
3888
- return kind === "stream" || spec.stream === true ? "stream" : kind;
3889
- }
3890
-
3891
- // .dist-releases/1784809313131-4113078/src/relay/config.js
3892
4054
  function loadRelayConfig(env = process.env) {
3893
4055
  const envRelayUrl = nonEmpty(env.SAILORBRIDGE_RELAY_URL);
3894
4056
  const envBearerToken = nonEmpty(env.SAILORBRIDGE_TOKEN);
@@ -3960,7 +4122,7 @@ function storedCaptainId(value) {
3960
4122
  }
3961
4123
  return;
3962
4124
  }
3963
- // .dist-releases/1784809313131-4113078/src/supervisor/retirement.js
4125
+ // .dist-releases/1784862378006-1249051/src/supervisor/retirement.js
3964
4126
  var DEFAULT_THRESHOLD = 3;
3965
4127
  var DEFAULT_WINDOW_MS = 30000;
3966
4128
  var DEFAULT_COOLDOWN_MS = 1000;
@@ -4035,7 +4197,7 @@ function isAuthoritativeRetirementReason(reason) {
4035
4197
  return reason === "monitor_fetch" || reason === "agent_session_patch";
4036
4198
  }
4037
4199
 
4038
- // .dist-releases/1784809313131-4113078/src/supervisor/wait.js
4200
+ // .dist-releases/1784862378006-1249051/src/supervisor/wait.js
4039
4201
  async function waitForWakeableTimeout(ms, setWake, sleep3) {
4040
4202
  if (sleep3) {
4041
4203
  await Promise.race([
@@ -4066,7 +4228,7 @@ async function waitForWakeableTimeout(ms, setWake, sleep3) {
4066
4228
  setWake(null);
4067
4229
  }
4068
4230
 
4069
- // .dist-releases/1784809313131-4113078/src/supervisor/mailbox.js
4231
+ // .dist-releases/1784862378006-1249051/src/supervisor/mailbox.js
4070
4232
  var CLEAR_TAG = "CLEAR";
4071
4233
  var REFRESH_TAG = "WORKFLOW-REFRESH";
4072
4234
  var SYNC_TAG = "SYNC";
@@ -4602,19 +4764,19 @@ function ordinaryDeliveryAttempt(error) {
4602
4764
  function _isMissingModerator(error) {
4603
4765
  return error instanceof RelayHttpError && error.status === 400 && error.body.includes("no agents match");
4604
4766
  }
4605
- // .dist-releases/1784809313131-4113078/src/supervisor/runtime.js
4767
+ // .dist-releases/1784862378006-1249051/src/supervisor/runtime.js
4606
4768
  import path9 from "node:path";
4607
4769
  import { execFile } from "node:child_process";
4608
4770
  import { promisify } from "node:util";
4609
4771
 
4610
- // .dist-releases/1784809313131-4113078/src/host/managed-materialization.js
4772
+ // .dist-releases/1784862378006-1249051/src/host/managed-materialization.js
4611
4773
  import { createHash as createHash2, randomBytes } from "node:crypto";
4612
4774
  import { constants as fsConstants } from "node:fs";
4613
4775
  import { lstat, mkdir as mkdir3, open, readdir, rename, rm as rm2 } from "node:fs/promises";
4614
4776
  import { homedir as homedir5 } from "node:os";
4615
4777
  import path5 from "node:path";
4616
4778
 
4617
- // .dist-releases/1784809313131-4113078/src/host/memory-stub.js
4779
+ // .dist-releases/1784862378006-1249051/src/host/memory-stub.js
4618
4780
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile3 } from "node:fs/promises";
4619
4781
  import { homedir as homedir4 } from "node:os";
4620
4782
  import path4 from "node:path";
@@ -4761,7 +4923,7 @@ function _safePathLabel(value, field) {
4761
4923
  return trimmed;
4762
4924
  }
4763
4925
 
4764
- // .dist-releases/1784809313131-4113078/src/host/managed-materialization.js
4926
+ // .dist-releases/1784862378006-1249051/src/host/managed-materialization.js
4765
4927
  var MANIFEST_FILE = ".sailorbridge-managed-manifest.json";
4766
4928
  var BANNER_PREFIX = "# SailorBridge managed file";
4767
4929
  var HASH_RE = /^sha256:[a-f0-9]{64}$/u;
@@ -5287,7 +5449,7 @@ function _expandHome2(value) {
5287
5449
  return value;
5288
5450
  }
5289
5451
 
5290
- // .dist-releases/1784809313131-4113078/src/host/credential-guardian.js
5452
+ // .dist-releases/1784862378006-1249051/src/host/credential-guardian.js
5291
5453
  import { lstat as lstat2, readFile as readFile3, rename as rename2, symlink, unlink, writeFile as writeFile4 } from "node:fs/promises";
5292
5454
  import path6 from "node:path";
5293
5455
  var DEFAULT_CREDENTIAL_SWEEP_INTERVAL_MS = 60000;
@@ -5433,7 +5595,7 @@ function _tmpSibling(filePath) {
5433
5595
  return path6.join(path6.dirname(filePath), `.${path6.basename(filePath)}.sb-guardian.${process.pid}.${tmpSequence}.tmp`);
5434
5596
  }
5435
5597
 
5436
- // .dist-releases/1784809313131-4113078/src/supervisor/resolve.js
5598
+ // .dist-releases/1784862378006-1249051/src/supervisor/resolve.js
5437
5599
  var DEFAULT_CAPTURE_LINES = 200;
5438
5600
  var MAX_CAPTURE_LINES = 1000;
5439
5601
 
@@ -5639,7 +5801,7 @@ function defaultSleep(ms) {
5639
5801
  return new Promise((resolve) => setTimeout(resolve, ms));
5640
5802
  }
5641
5803
 
5642
- // .dist-releases/1784809313131-4113078/src/supervisor/transcript-backfill.js
5804
+ // .dist-releases/1784862378006-1249051/src/supervisor/transcript-backfill.js
5643
5805
  import { createHash as createHash3 } from "node:crypto";
5644
5806
  import {
5645
5807
  closeSync,
@@ -6296,7 +6458,7 @@ function errorMessage4(error) {
6296
6458
  return error instanceof Error ? error.message : String(error);
6297
6459
  }
6298
6460
 
6299
- // .dist-releases/1784809313131-4113078/src/supervisor/usage-collector.js
6461
+ // .dist-releases/1784862378006-1249051/src/supervisor/usage-collector.js
6300
6462
  import { statSync as statSync2 } from "node:fs";
6301
6463
  import path8 from "node:path";
6302
6464
  var SUPPORTED_USAGE_ENGINES = new Set(["claude", "codex"]);
@@ -6705,7 +6867,7 @@ function sourceUnreadableReason(source) {
6705
6867
  }
6706
6868
  }
6707
6869
 
6708
- // .dist-releases/1784809313131-4113078/src/supervisor/runtime.js
6870
+ // .dist-releases/1784862378006-1249051/src/supervisor/runtime.js
6709
6871
  var RESOLVE_CAPTURE_LINES = 200;
6710
6872
  var SHORT_LIVED_EXIT_MS = 5000;
6711
6873
  var STARTUP_OBSERVATION_MS = 1e4;
@@ -7375,7 +7537,7 @@ function errorMessage5(error) {
7375
7537
  function formatRestartCommand(command) {
7376
7538
  return command.map((part) => /^[A-Za-z0-9_./:=@%+-]+$/.test(part) ? part : `'${part.replaceAll("'", "'\\''")}'`).join(" ");
7377
7539
  }
7378
- // .dist-releases/1784809313131-4113078/src/supervisor/transport.js
7540
+ // .dist-releases/1784862378006-1249051/src/supervisor/transport.js
7379
7541
  var SUPPORTED_PHASE1_CLI_KINDS = ["claude", "codex"];
7380
7542
  var PHASE1_ENGINE_GATE_MESSAGE = "Only claude and codex engines are available in this release; opencode and custom CLI engines will open in the next release.";
7381
7543
  var PHASE1_TRANSPORT_GATE_MESSAGE = "Only tmux transport is available in this release; sdk, headless, and pty transports will open in the next release.";
@@ -7583,7 +7745,7 @@ function hasCodexSafetyDriverOptions(options) {
7583
7745
  return options?.codex?.sandboxMode !== undefined || options?.codex?.approvalPolicy !== undefined || options?.codex?.yolo !== undefined;
7584
7746
  }
7585
7747
 
7586
- // .dist-releases/1784809313131-4113078/src/supervisor/cli-options.js
7748
+ // .dist-releases/1784862378006-1249051/src/supervisor/cli-options.js
7587
7749
  function parseSuperviseArgs(argv) {
7588
7750
  const split = argv.indexOf("--");
7589
7751
  const flagArgs = split >= 0 ? argv.slice(0, split) : argv;
@@ -7735,7 +7897,7 @@ function optionalBoolean(value, label) {
7735
7897
  return false;
7736
7898
  throw new Error(`${label} must be true or false`);
7737
7899
  }
7738
- // .dist-releases/1784809313131-4113078/src/supervisor/tmux-availability.js
7900
+ // .dist-releases/1784862378006-1249051/src/supervisor/tmux-availability.js
7739
7901
  import { spawn as spawn3 } from "node:child_process";
7740
7902
  import { createInterface } from "node:readline";
7741
7903
  function isTmuxAvailable(spawnFn = spawn3) {
@@ -7852,26 +8014,29 @@ function _tmuxMissingMessage(platform2) {
7852
8014
  const how = platform2 === "darwin" ? "brew install tmux" : platform2 === "linux" ? "install tmux via your package manager (e.g. sudo apt-get install -y tmux)" : "install tmux";
7853
8015
  return `tmux is required in this release but was not found. Install tmux (${how}) and re-run.`;
7854
8016
  }
7855
- // .dist-releases/1784809313131-4113078/src/output-format.js
8017
+ // .dist-releases/1784862378006-1249051/src/output-format.js
7856
8018
  function parseOutputArgs(argv) {
8019
+ const separator = argv.indexOf("--");
8020
+ const localArgv = separator < 0 ? argv : argv.slice(0, separator);
8021
+ const remoteArgv = separator < 0 ? [] : argv.slice(separator);
7857
8022
  const cleaned = [];
7858
8023
  let format = "plain";
7859
8024
  let seen = false;
7860
- for (let index = 0;index < argv.length; index += 1) {
7861
- const token = argv[index];
8025
+ for (let index = 0;index < localArgv.length; index += 1) {
8026
+ const token = localArgv[index];
7862
8027
  if (token !== "--format" && !token.startsWith("--format=")) {
7863
8028
  cleaned.push(token);
7864
8029
  continue;
7865
8030
  }
7866
8031
  if (seen)
7867
8032
  throw new Error("--format may only be provided once");
7868
- const value = token === "--format" ? argv[++index] : token.slice("--format=".length);
8033
+ const value = token === "--format" ? localArgv[++index] : token.slice("--format=".length);
7869
8034
  if (value !== "plain" && value !== "json")
7870
8035
  throw new Error("--format must be plain or json");
7871
8036
  format = value;
7872
8037
  seen = true;
7873
8038
  }
7874
- return { argv: cleaned, format, provided: seen };
8039
+ return { argv: [...cleaned, ...remoteArgv], format, provided: seen };
7875
8040
  }
7876
8041
  function _renderOutput(value, format, plain) {
7877
8042
  if (format === "json") {
@@ -7942,7 +8107,7 @@ ${normalized.split(`
7942
8107
  `)}`;
7943
8108
  }
7944
8109
 
7945
- // .dist-releases/1784809313131-4113078/src/plain-output-fields.js
8110
+ // .dist-releases/1784862378006-1249051/src/plain-output-fields.js
7946
8111
  var PREVIEW_LIMIT = 20;
7947
8112
  var UNSAFE_DISPLAY_CODE_POINT = /[\u007f-\u009f\u2028-\u202e\u2066-\u2069]/gu;
7948
8113
  function renderTaskPlain(command, value) {
@@ -8350,7 +8515,7 @@ function _omissionLine(total) {
8350
8515
  return total > PREVIEW_LIMIT ? `Omitted ${total - PREVIEW_LIMIT}; rerun with --format json for the complete response.` : undefined;
8351
8516
  }
8352
8517
 
8353
- // .dist-releases/1784809313131-4113078/src/host/cli.js
8518
+ // .dist-releases/1784862378006-1249051/src/host/cli.js
8354
8519
  var HOST_RELAY_SUBCOMMANDS = new Set(["consume", "heartbeat", "list", "register"]);
8355
8520
  async function runHostCommandFromCli(client, argv, config, io) {
8356
8521
  const sub = argv[0] ?? "list";
@@ -8588,7 +8753,7 @@ function optionalDisplayName(value) {
8588
8753
  const trimmed = value?.trim();
8589
8754
  return trimmed || undefined;
8590
8755
  }
8591
- // .dist-releases/1784809313131-4113078/src/host/service.js
8756
+ // .dist-releases/1784862378006-1249051/src/host/service.js
8592
8757
  import { execFile as execFileCb } from "node:child_process";
8593
8758
  import { createHash as createHash4 } from "node:crypto";
8594
8759
  import { constants as fsConstants2 } from "node:fs";
@@ -9071,7 +9236,7 @@ function writeServiceLog(io, event, fields) {
9071
9236
  io.stderr.write(`${JSON.stringify({ ts: new Date().toISOString(), event, ...fields ?? {} })}
9072
9237
  `);
9073
9238
  }
9074
- // .dist-releases/1784809313131-4113078/src/host/git-remote.js
9239
+ // .dist-releases/1784862378006-1249051/src/host/git-remote.js
9075
9240
  var GIT_SECRET_PATH_MARKERS = [
9076
9241
  "token",
9077
9242
  "credential",
@@ -9163,7 +9328,7 @@ function looksLikeCredential(segment2) {
9163
9328
  return value.length >= 32 && /^[A-Za-z0-9_-]+$/u.test(value);
9164
9329
  }
9165
9330
 
9166
- // .dist-releases/1784809313131-4113078/src/host/start-spec.js
9331
+ // .dist-releases/1784862378006-1249051/src/host/start-spec.js
9167
9332
  var TRANSPORTS = ["auto", "sdk", "pty", "tmux", "headless"];
9168
9333
  var SUPPORTED_PHASE1_ENGINES = ["claude", "codex"];
9169
9334
  var ENGINE_PATTERN = /^[a-z][a-z0-9._-]{0,63}$/u;
@@ -9413,7 +9578,7 @@ function _trimmed(value, label) {
9413
9578
  throw new Error(`${label} must not be empty`);
9414
9579
  return trimmed;
9415
9580
  }
9416
- // .dist-releases/1784809313131-4113078/src/host/workspace.js
9581
+ // .dist-releases/1784862378006-1249051/src/host/workspace.js
9417
9582
  import { spawn as spawn4 } from "node:child_process";
9418
9583
  import { existsSync as existsSync4 } from "node:fs";
9419
9584
  import { mkdir as mkdir5, readdir as readdir2, rename as rename4, stat } from "node:fs/promises";
@@ -9644,7 +9809,7 @@ function _safeSegment(value) {
9644
9809
  throw new Error("workspace path segment is empty after sanitizing");
9645
9810
  return segment2;
9646
9811
  }
9647
- // .dist-releases/1784809313131-4113078/src/host/workspace-inventory.js
9812
+ // .dist-releases/1784862378006-1249051/src/host/workspace-inventory.js
9648
9813
  import { access as access2 } from "node:fs/promises";
9649
9814
  import { constants } from "node:fs";
9650
9815
  import { execFile as execFile2 } from "node:child_process";
@@ -9736,7 +9901,7 @@ async function canAccess(path12, mode, deps) {
9736
9901
  return false;
9737
9902
  }
9738
9903
  }
9739
- // .dist-releases/1784809313131-4113078/src/host/host-config.js
9904
+ // .dist-releases/1784862378006-1249051/src/host/host-config.js
9740
9905
  import { chmod as chmod3, mkdir as mkdir6, readFile as readFile5, writeFile as writeFile6 } from "node:fs/promises";
9741
9906
  import { homedir as homedir10 } from "node:os";
9742
9907
  import path12 from "node:path";
@@ -9821,7 +9986,7 @@ function _parseHostConfig(text, configPath) {
9821
9986
  });
9822
9987
  return { kind: HOST_CONFIG_KIND, workspaces };
9823
9988
  }
9824
- // .dist-releases/1784809313131-4113078/src/host/host-workspace-cli.js
9989
+ // .dist-releases/1784862378006-1249051/src/host/host-workspace-cli.js
9825
9990
  async function runHostWorkspaceCommandFromCli(argv, io, deps = {}) {
9826
9991
  const configPath = deps.configPath ?? defaultHostConfigPath();
9827
9992
  const sub = argv[0] ?? "list";
@@ -9865,7 +10030,7 @@ function _renderAddPlain(result) {
9865
10030
  return result.note ? `${status} ${result.path}
9866
10031
  ${result.note}` : `${status} ${result.path}`;
9867
10032
  }
9868
- // .dist-releases/1784809313131-4113078/src/host/agent-env.js
10033
+ // .dist-releases/1784862378006-1249051/src/host/agent-env.js
9869
10034
  var RUNTIME_NATIVE_HOME_ENV = new Set(["CLAUDE_CONFIG_DIR", "CODEX_HOME"]);
9870
10035
  function buildAgentEnv(options) {
9871
10036
  const env = {};
@@ -9894,15 +10059,15 @@ function buildAgentEnv(options) {
9894
10059
  }
9895
10060
  return env;
9896
10061
  }
9897
- // .dist-releases/1784809313131-4113078/src/host/runtime-env.js
10062
+ // .dist-releases/1784862378006-1249051/src/host/runtime-env.js
9898
10063
  import path16 from "node:path";
9899
10064
 
9900
- // .dist-releases/1784809313131-4113078/src/host/agent-defaults.js
10065
+ // .dist-releases/1784862378006-1249051/src/host/agent-defaults.js
9901
10066
  import { chmod as chmod4, mkdir as mkdir7, readFile as readFile7, stat as stat2, writeFile as writeFile7 } from "node:fs/promises";
9902
10067
  import { homedir as homedir11 } from "node:os";
9903
10068
  import path14 from "node:path";
9904
10069
 
9905
- // .dist-releases/1784809313131-4113078/src/host/claude-settings.js
10070
+ // .dist-releases/1784862378006-1249051/src/host/claude-settings.js
9906
10071
  import { readFile as readFile6 } from "node:fs/promises";
9907
10072
  import path13 from "node:path";
9908
10073
  async function readHostClaudeSettings(claudeConfigDir) {
@@ -9943,7 +10108,7 @@ function _nonEmptyString(value) {
9943
10108
  return typeof value === "string" && value.trim() ? value : null;
9944
10109
  }
9945
10110
 
9946
- // .dist-releases/1784809313131-4113078/src/host/agent-defaults.js
10111
+ // .dist-releases/1784862378006-1249051/src/host/agent-defaults.js
9947
10112
  var CODEX_BLOCK_BEGIN = "# sailorbridge-terse-defaults:begin";
9948
10113
  var CODEX_BLOCK_END = "# sailorbridge-terse-defaults:end";
9949
10114
  var CODEX_MODEL_BLOCK_BEGIN = "# sailorbridge-host-model-settings:begin";
@@ -11073,7 +11238,7 @@ function _escapeRegExp(value) {
11073
11238
  return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
11074
11239
  }
11075
11240
 
11076
- // .dist-releases/1784809313131-4113078/src/host/engine-auth.js
11241
+ // .dist-releases/1784862378006-1249051/src/host/engine-auth.js
11077
11242
  import { lstat as lstat4, mkdir as mkdir8, readlink, realpath, stat as stat3, symlink as symlink2, unlink as unlink2 } from "node:fs/promises";
11078
11243
  import { homedir as homedir12 } from "node:os";
11079
11244
  import path15 from "node:path";
@@ -11163,7 +11328,7 @@ async function _ensureSymlink(linkPath, targetPath, sharedTargetPath) {
11163
11328
  await symlink2(targetPath, linkPath);
11164
11329
  }
11165
11330
 
11166
- // .dist-releases/1784809313131-4113078/src/host/runtime-env.js
11331
+ // .dist-releases/1784862378006-1249051/src/host/runtime-env.js
11167
11332
  async function materializeAgentRuntimeEnv(options) {
11168
11333
  const env = buildAgentEnv({
11169
11334
  hostEnv: options.hostEnv,
@@ -11203,7 +11368,7 @@ function _ensureRootSandboxEscape(env, engine, uid) {
11203
11368
  function _currentUid() {
11204
11369
  return typeof process.getuid === "function" ? process.getuid() : null;
11205
11370
  }
11206
- // .dist-releases/1784809313131-4113078/src/host/context-statusline.js
11371
+ // .dist-releases/1784862378006-1249051/src/host/context-statusline.js
11207
11372
  function renderClaudeContextStatusLine(payload) {
11208
11373
  const percent = _usedPercentage(payload);
11209
11374
  if (percent === null)
@@ -11241,7 +11406,7 @@ function _bar(percent) {
11241
11406
  const filled = Math.min(10, Math.max(0, Math.trunc(percent / 10)));
11242
11407
  return "█".repeat(filled) + "░".repeat(10 - filled);
11243
11408
  }
11244
- // .dist-releases/1784809313131-4113078/src/host/scratch-sweeper.js
11409
+ // .dist-releases/1784862378006-1249051/src/host/scratch-sweeper.js
11245
11410
  import { execFile as execFile3 } from "node:child_process";
11246
11411
  import { lstat as lstat5, readdir as readdir3, realpath as realpath2, unlink as unlink3 } from "node:fs/promises";
11247
11412
  import path17 from "node:path";
@@ -11384,7 +11549,7 @@ function _errno(error) {
11384
11549
  return String(error.code);
11385
11550
  return error instanceof Error ? error.message : String(error);
11386
11551
  }
11387
- // .dist-releases/1784809313131-4113078/src/host/launcher.js
11552
+ // .dist-releases/1784862378006-1249051/src/host/launcher.js
11388
11553
  var MAX_TMUX_SESSION_SUFFIX = 100;
11389
11554
  async function launchSupervisedAgent(input, deps = {}) {
11390
11555
  const spec = parseAgentStartSpec(input.spec);
@@ -11535,7 +11700,7 @@ async function _tmuxSessionExists(session) {
11535
11700
  throw error;
11536
11701
  }
11537
11702
  }
11538
- // .dist-releases/1784809313131-4113078/src/worker/capability.js
11703
+ // .dist-releases/1784862378006-1249051/src/worker/capability.js
11539
11704
  import { accessSync, constants as constants2, mkdirSync as mkdirSync3, mkdtempSync, readFileSync as readFileSync3, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
11540
11705
  import os from "node:os";
11541
11706
  import path18 from "node:path";
@@ -11678,7 +11843,7 @@ function commandPath(command) {
11678
11843
  return null;
11679
11844
  }
11680
11845
 
11681
- // .dist-releases/1784809313131-4113078/src/host/worker-start-spec.js
11846
+ // .dist-releases/1784862378006-1249051/src/host/worker-start-spec.js
11682
11847
  var MAX_TAGS = 32;
11683
11848
  var TAG_PATTERN = /^[a-z0-9][a-z0-9=._:/-]{0,63}$/iu;
11684
11849
  var RESERVED_TAG_KEYS = new Set(["os", "kernel", "arch", "gpu", "mode", "role", "tool"]);
@@ -11738,16 +11903,16 @@ function _trimmed2(value, label) {
11738
11903
  return trimmed;
11739
11904
  }
11740
11905
 
11741
- // .dist-releases/1784809313131-4113078/src/host/worker-launcher.js
11906
+ // .dist-releases/1784862378006-1249051/src/host/worker-launcher.js
11742
11907
  import { statSync as statSync3 } from "node:fs";
11743
11908
  import os4 from "node:os";
11744
11909
  import path21 from "node:path";
11745
- // .dist-releases/1784809313131-4113078/src/worker/client.js
11910
+ // .dist-releases/1784862378006-1249051/src/worker/client.js
11746
11911
  import { readFileSync as readFileSync5 } from "node:fs";
11747
11912
  import os3 from "node:os";
11748
11913
  import path20 from "node:path";
11749
11914
 
11750
- // .dist-releases/1784809313131-4113078/src/worker/errors.js
11915
+ // .dist-releases/1784862378006-1249051/src/worker/errors.js
11751
11916
  class LeaseFencingRejected extends Error {
11752
11917
  status;
11753
11918
  constructor(status, message) {
@@ -11811,7 +11976,7 @@ function countsTowardWorkerSessionRebuild(path19) {
11811
11976
  return true;
11812
11977
  }
11813
11978
 
11814
- // .dist-releases/1784809313131-4113078/src/worker/executor.js
11979
+ // .dist-releases/1784862378006-1249051/src/worker/executor.js
11815
11980
  import { spawn as spawn5 } from "node:child_process";
11816
11981
  import { mkdirSync as mkdirSync4, mkdtempSync as mkdtempSync2, readFileSync as readFileSync4, realpathSync, rmSync as rmSync2, statfsSync, writeFileSync as writeFileSync3 } from "node:fs";
11817
11982
  import { tmpdir as tmpdir2, loadavg } from "node:os";
@@ -11819,7 +11984,7 @@ import os2 from "node:os";
11819
11984
  import path19 from "node:path";
11820
11985
  import { TextDecoder } from "node:util";
11821
11986
 
11822
- // .dist-releases/1784809313131-4113078/src/worker/async-queue.js
11987
+ // .dist-releases/1784862378006-1249051/src/worker/async-queue.js
11823
11988
  class AsyncByteQueue {
11824
11989
  maxSize;
11825
11990
  values = [];
@@ -11879,7 +12044,7 @@ class AsyncByteQueue {
11879
12044
  }
11880
12045
  }
11881
12046
 
11882
- // .dist-releases/1784809313131-4113078/src/worker/http.js
12047
+ // .dist-releases/1784862378006-1249051/src/worker/http.js
11883
12048
  import { request as httpRequest } from "node:http";
11884
12049
  import { request as httpsRequest } from "node:https";
11885
12050
  import { URLSearchParams as URLSearchParams2 } from "node:url";
@@ -12073,7 +12238,7 @@ function pathOnly(path19) {
12073
12238
  return path19.split("?", 1)[0] || path19;
12074
12239
  }
12075
12240
 
12076
- // .dist-releases/1784809313131-4113078/src/worker/stream-upload.js
12241
+ // .dist-releases/1784862378006-1249051/src/worker/stream-upload.js
12077
12242
  async function uploadStream(session, jobId, stream, leaseToken, chunks, workerId) {
12078
12243
  if (!leaseToken.trim())
12079
12244
  throw new Error("lease_token is required");
@@ -12083,7 +12248,7 @@ async function uploadStream(session, jobId, stream, leaseToken, chunks, workerId
12083
12248
  await session.http.postBytes(`/api/v1/jobs/${encodeURIComponent(jobId)}/stream-upload?${query.toString()}`, chunks, { "X-Lease-Token": leaseToken }, UPLOAD_TIMEOUTS);
12084
12249
  }
12085
12250
 
12086
- // .dist-releases/1784809313131-4113078/src/worker/executor.js
12251
+ // .dist-releases/1784862378006-1249051/src/worker/executor.js
12087
12252
  class StreamingProcessFailure extends Error {
12088
12253
  exitCode;
12089
12254
  stdout;
@@ -12615,7 +12780,7 @@ function errorMessage6(error) {
12615
12780
  return error instanceof Error ? error.message : String(error);
12616
12781
  }
12617
12782
 
12618
- // .dist-releases/1784809313131-4113078/src/worker/client.js
12783
+ // .dist-releases/1784862378006-1249051/src/worker/client.js
12619
12784
  var TOKEN_FILES = [
12620
12785
  "~/.config/sailorbridge/token",
12621
12786
  "/etc/sailorbridge/token"
@@ -13012,7 +13177,7 @@ function errorName2(error) {
13012
13177
  function errorMessage7(error) {
13013
13178
  return error instanceof Error ? error.message : String(error);
13014
13179
  }
13015
- // .dist-releases/1784809313131-4113078/src/host/worker-launcher.js
13180
+ // .dist-releases/1784862378006-1249051/src/host/worker-launcher.js
13016
13181
  var DEFAULT_WORKER_READY_TIMEOUT_MS = 60000;
13017
13182
  async function launchSupervisedWorker(input, deps = {}) {
13018
13183
  const logger = input.logger ?? (() => {});
@@ -13109,7 +13274,7 @@ function _assertWorkdir(workdir) {
13109
13274
  return workdir;
13110
13275
  }
13111
13276
 
13112
- // .dist-releases/1784809313131-4113078/src/host/daemon.js
13277
+ // .dist-releases/1784862378006-1249051/src/host/daemon.js
13113
13278
  async function runHostDaemon(options) {
13114
13279
  const logger = options.logger ?? (() => {});
13115
13280
  const sleep3 = options.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
@@ -13458,7 +13623,7 @@ async function _drainOnce(options, identity, starter, logger, redactSecrets) {
13458
13623
  return false;
13459
13624
  }
13460
13625
  }
13461
- // .dist-releases/1784809313131-4113078/src/host/enrollment-redeem.js
13626
+ // .dist-releases/1784862378006-1249051/src/host/enrollment-redeem.js
13462
13627
  var DEFAULT_NOT_FOUND_RETRIES = 3;
13463
13628
  var NOT_FOUND_RETRY_DELAY_MS = 500;
13464
13629
  function createEnrollmentRedeemer(options) {
@@ -13595,7 +13760,7 @@ function _role2(enrollment) {
13595
13760
  }
13596
13761
  return _string4(enrollment.roles[0], "roles[0]").trim();
13597
13762
  }
13598
- // .dist-releases/1784809313131-4113078/src/host/tutorial.js
13763
+ // .dist-releases/1784862378006-1249051/src/host/tutorial.js
13599
13764
  function buildTutorialSteps(state) {
13600
13765
  const paired = state.hasCredential;
13601
13766
  return [
@@ -13666,7 +13831,7 @@ async function runTutorial(options) {
13666
13831
  }
13667
13832
  return steps;
13668
13833
  }
13669
- // .dist-releases/1784809313131-4113078/src/events.js
13834
+ // .dist-releases/1784862378006-1249051/src/events.js
13670
13835
  var EVENT_COMMANDS = new Set(["add", "list", "enable", "disable", "rm"]);
13671
13836
  var EVENT_FLAGS = new Set(["at", "days", "message", "run-date", "session", "tag", "tz"]);
13672
13837
  var PRODUCT_DAYS_MODES = new Set(["once", "daily", "weekday"]);
@@ -13756,7 +13921,7 @@ function _assertListArgs(args) {
13756
13921
  }
13757
13922
  }
13758
13923
 
13759
- // .dist-releases/1784809313131-4113078/src/text-input.js
13924
+ // .dist-releases/1784862378006-1249051/src/text-input.js
13760
13925
  import { readFile as readFile8 } from "node:fs/promises";
13761
13926
  var _readUtf8File = async (file) => await readFile8(file, "utf8");
13762
13927
  async function readTextInput(input) {
@@ -13800,7 +13965,7 @@ async function readTextFileOrStdin(path22, input, unusedInputMessage, readUtf8Fi
13800
13965
  return await readTextFile(path22, readUtf8File2);
13801
13966
  }
13802
13967
 
13803
- // .dist-releases/1784809313131-4113078/src/ask.js
13968
+ // .dist-releases/1784862378006-1249051/src/ask.js
13804
13969
  var ASK_FLAGS = new Set(["body-file", "option", "reason", "recommend", "session", "title"]);
13805
13970
  async function runAskFromCli(client2, argv, config, input) {
13806
13971
  const args = _parseAskArgs(argv);
@@ -13915,7 +14080,7 @@ function _singleAskValue(values, name) {
13915
14080
  return entries[0];
13916
14081
  }
13917
14082
 
13918
- // .dist-releases/1784809313131-4113078/src/task.js
14083
+ // .dist-releases/1784862378006-1249051/src/task.js
13919
14084
  import { readFile as readFile9 } from "node:fs/promises";
13920
14085
  import { basename } from "node:path";
13921
14086
  var TASK_FLAGS = new Set(["after", "all", "artifact-id", "before", "body-file", "commit", "depends", "description", "description-file", "draft", "findings", "image", "level", "limit", "owner-of-resume", "pair", "priority", "query", "reason", "resume-condition", "resume-dependency", "session", "status", "type", "version"]);
@@ -14405,7 +14570,7 @@ function _taskListFooter(shown, returned, total, hasMore, cursor) {
14405
14570
  return `Shown ${shown} of ${total}${local}${next}`;
14406
14571
  }
14407
14572
 
14408
- // .dist-releases/1784809313131-4113078/src/team.js
14573
+ // .dist-releases/1784862378006-1249051/src/team.js
14409
14574
  var TEAM_BOOTSTRAP_ENGINES = new Set(["claude", "codex"]);
14410
14575
  var SLOT_SPEC_RE = /^([^=\s]+)=([^:@\s]+):([^:@\s]+)$/;
14411
14576
  var TEAM_PLAIN_LIMIT = 20;
@@ -14560,7 +14725,7 @@ function _slotTable(slots) {
14560
14725
  Shown ${shown.length} of ${slots.length}${omitted ? `; ${omitted} omitted (use --format json)` : ""}`;
14561
14726
  }
14562
14727
 
14563
- // .dist-releases/1784809313131-4113078/src/memory.js
14728
+ // .dist-releases/1784862378006-1249051/src/memory.js
14564
14729
  var VALUE_FLAGS = new Set(["file", "source"]);
14565
14730
  var MEMORY_SEARCH_PLAIN_LIMIT = 20;
14566
14731
  async function runMemoryFromCli(client2, argv, config, input, format) {
@@ -14695,7 +14860,7 @@ function _summary(value) {
14695
14860
  return value.replace(/\s+/gu, " ").trim().slice(0, 180);
14696
14861
  }
14697
14862
 
14698
- // .dist-releases/1784809313131-4113078/src/mailbox-send.js
14863
+ // .dist-releases/1784862378006-1249051/src/mailbox-send.js
14699
14864
  var SEND_FLAGS = new Set(["body-file", "commit", "session", "tag", "task-id", "task-version", "to-agents", "to-roles", "to-host"]);
14700
14865
  function parseMailboxSendArgs(argv) {
14701
14866
  const parsed = argsFromValues(parseFlagValues(argv));
@@ -14794,7 +14959,7 @@ function splitList(value) {
14794
14959
  return list?.length ? list : undefined;
14795
14960
  }
14796
14961
 
14797
- // .dist-releases/1784809313131-4113078/src/reply.js
14962
+ // .dist-releases/1784862378006-1249051/src/reply.js
14798
14963
  var REPLY_FLAGS = new Set(["agent", "body-file", "in-reply-to", "session"]);
14799
14964
  async function sendReplyFromCli(client2, argv, config, io) {
14800
14965
  const args = _parseReplyArgs(argv);
@@ -14851,11 +15016,192 @@ function _requiredReplyValue(values, name) {
14851
15016
  return value;
14852
15017
  }
14853
15018
 
14854
- // .dist-releases/1784809313131-4113078/src/create.js
15019
+ // .dist-releases/1784862378006-1249051/src/worker-command.js
15020
+ async function runWorkerCommandFromCli(client2, argv, config, io, format = "plain") {
15021
+ const captainId = config.captainId;
15022
+ if (!captainId)
15023
+ throw new Error("SAILORBRIDGE_CAPTAIN_ID is required for worker commands");
15024
+ const command = argv[0];
15025
+ switch (command) {
15026
+ case "list":
15027
+ return _listWorkers(client2, captainId, _requireSession(config.session), argv.slice(1), io, format);
15028
+ case "run":
15029
+ return _runWorker(client2, captainId, _requireSession(config.session), argv.slice(1), io, format);
15030
+ case "job":
15031
+ return _showJob(client2, captainId, argv.slice(1), io, format);
15032
+ default:
15033
+ throw new Error("worker command must be job, list, run, or serve");
15034
+ }
15035
+ }
15036
+ function _requireSession(session) {
15037
+ if (!session)
15038
+ throw new Error("SAILORBRIDGE_SESSION is required for worker list and run");
15039
+ return session;
15040
+ }
15041
+ async function _listWorkers(client2, captainId, session, argv, io, format) {
15042
+ if (argv.length > 0)
15043
+ throw new Error(`unexpected argument: ${argv[0]}`);
15044
+ const response = await client2.listWorkers(captainId, session);
15045
+ if (format === "json") {
15046
+ writeOutput(io.stdout, response, format);
15047
+ return;
15048
+ }
15049
+ const { workers } = response;
15050
+ if (workers.length === 0) {
15051
+ io.stdout.write(`No workers registered.
15052
+ `);
15053
+ return;
15054
+ }
15055
+ for (const worker of workers.slice(0, 20)) {
15056
+ io.stdout.write(`${worker.name} ${worker.online ? "online" : "offline"} ${worker.host}
15057
+ `);
15058
+ }
15059
+ const omitted = workers.length - 20;
15060
+ if (omitted > 0)
15061
+ io.stdout.write(`${omitted} omitted; run sail worker list --format json for all.
15062
+ `);
15063
+ }
15064
+ async function _runWorker(client2, captainId, session, argv, io, format) {
15065
+ const input = _parseRunArgs(argv);
15066
+ const workers = (await client2.listWorkers(captainId, session)).workers;
15067
+ const worker = _requireRunnableWorker(workers, input.worker);
15068
+ const submitted = await _submitWorkerJob(client2, captainId, input);
15069
+ if (!input.detach || format === "plain" || !submitted.warning) {
15070
+ _writeWarning(submitted.warning, worker, io);
15071
+ }
15072
+ if (input.detach) {
15073
+ if (format === "json")
15074
+ writeOutput(io.stdout, submitted, format);
15075
+ else
15076
+ io.stdout.write(`${submitted.id}
15077
+ `);
15078
+ return;
15079
+ }
15080
+ const remoteBudget = input.timeoutSec ?? DEFAULT_JOB_TIMEOUT_SECONDS;
15081
+ const result = await client2.getJobResult(captainId, submitted.id, remoteBudget + WORKER_JOB_RESULT_GRACE_SECONDS);
15082
+ _writeResult(result, io, format);
15083
+ }
15084
+ function _requireRunnableWorker(workers, name) {
15085
+ const worker = workers.find((item) => item.name === name);
15086
+ if (!worker)
15087
+ throw new Error(`worker '${name}' is not registered in this team`);
15088
+ if (worker.capabilities.includes("mode=readonly")) {
15089
+ throw new Error(`worker '${name}' does not support bash jobs in readonly mode`);
15090
+ }
15091
+ return worker;
15092
+ }
15093
+ function _submitWorkerJob(client2, captainId, input) {
15094
+ return client2.submitJob(captainId, {
15095
+ worker: input.worker,
15096
+ argv: input.argv,
15097
+ ...input.timeoutSec === undefined ? {} : { timeoutSec: input.timeoutSec }
15098
+ });
15099
+ }
15100
+ async function _showJob(client2, captainId, argv, io, format) {
15101
+ if (argv.length !== 1 || !argv[0])
15102
+ throw new Error("worker job requires exactly one job id");
15103
+ const result = await client2.getJobStatus(captainId, argv[0]);
15104
+ if (format === "json") {
15105
+ writeOutput(io.stdout, result, format);
15106
+ if (result.exit_code !== null)
15107
+ io.setExitCode(_localExitStatus(result.exit_code));
15108
+ return;
15109
+ }
15110
+ if (result.status === "queued" || result.status === "running") {
15111
+ io.stdout.write(`${result.id} ${result.status}
15112
+ `);
15113
+ return;
15114
+ }
15115
+ _writeResult(result, io, format);
15116
+ }
15117
+ function _parseRunArgs(argv) {
15118
+ const separator = argv.indexOf("--");
15119
+ if (separator < 0)
15120
+ throw new Error("worker run requires '--' before argv");
15121
+ const remoteArgv = argv.slice(separator + 1);
15122
+ if (remoteArgv.length === 0 || !remoteArgv[0]?.trim())
15123
+ throw new Error("worker run requires a command after '--'");
15124
+ const { worker, timeoutSec, detach } = _parseRunFlags(argv.slice(0, separator));
15125
+ if (!worker)
15126
+ throw new Error("--worker is required");
15127
+ return { worker, argv: remoteArgv, detach, ...timeoutSec === undefined ? {} : { timeoutSec } };
15128
+ }
15129
+ function _parseRunFlags(flags) {
15130
+ let worker = "";
15131
+ let timeoutSec;
15132
+ let detach = false;
15133
+ for (let index = 0;index < flags.length; index += 1) {
15134
+ const flag = flags[index];
15135
+ if (flag === "--detach") {
15136
+ detach = true;
15137
+ continue;
15138
+ }
15139
+ const value = flags[index + 1];
15140
+ if (!value)
15141
+ throw new Error(`${flag} requires a value`);
15142
+ if (flag === "--worker")
15143
+ worker = value.trim();
15144
+ else if (flag === "--timeout")
15145
+ timeoutSec = _parseTimeout(value);
15146
+ else
15147
+ throw new Error(`unexpected argument: ${flag}`);
15148
+ index += 1;
15149
+ }
15150
+ return { worker, detach, ...timeoutSec === undefined ? {} : { timeoutSec } };
15151
+ }
15152
+ function _parseTimeout(value) {
15153
+ if (!/^(?:0|[1-9]\d*)(?:\.\d+)?$/u.test(value)) {
15154
+ throw new Error(`timeout must be a number from 1 to ${MAX_JOB_TIMEOUT_SECONDS} seconds`);
15155
+ }
15156
+ const timeout = Number(value);
15157
+ if (!Number.isFinite(timeout) || timeout < 1 || timeout > MAX_JOB_TIMEOUT_SECONDS) {
15158
+ throw new Error(`timeout must be a number from 1 to ${MAX_JOB_TIMEOUT_SECONDS} seconds`);
15159
+ }
15160
+ return timeout;
15161
+ }
15162
+ function _writeWarning(warning, worker, io) {
15163
+ if (warning) {
15164
+ io.stderr.write(`Warning: ${warning}
15165
+ `);
15166
+ return;
15167
+ }
15168
+ if (!worker.online)
15169
+ io.stderr.write(`Warning: worker '${worker.name}' is registered but offline.
15170
+ `);
15171
+ }
15172
+ function _writeResult(response, io, format) {
15173
+ if (!response.result || response.exit_code === null) {
15174
+ throw new Error(`job ${response.id} did not finish before the timeout (status=${response.status})`);
15175
+ }
15176
+ const localStatus = _localExitStatus(response.exit_code);
15177
+ if (format === "json") {
15178
+ writeOutput(io.stdout, response, format);
15179
+ io.setExitCode(localStatus);
15180
+ return;
15181
+ }
15182
+ io.stdout.write(response.result.stdout);
15183
+ io.stderr.write(response.result.stderr);
15184
+ if (response.exit_code < 0)
15185
+ _writeSignalStatus(response.exit_code, localStatus, response.result.stderr, io);
15186
+ io.setExitCode(localStatus);
15187
+ }
15188
+ function _localExitStatus(remoteCode) {
15189
+ return remoteCode < 0 ? 128 + Math.abs(remoteCode) : remoteCode;
15190
+ }
15191
+ function _writeSignalStatus(remoteCode, localStatus, remoteStderr, io) {
15192
+ if (remoteStderr && !remoteStderr.endsWith(`
15193
+ `))
15194
+ io.stderr.write(`
15195
+ `);
15196
+ io.stderr.write(`Remote exit code ${remoteCode}; local exit status ${localStatus}.
15197
+ `);
15198
+ }
15199
+
15200
+ // .dist-releases/1784862378006-1249051/src/create.js
14855
15201
  var CREATE_ENGINES = new Set(["claude", "codex"]);
14856
15202
  var DEFAULT_ENGINE = "claude";
14857
15203
  var WORKSPACE_PLAIN_LIMIT = 20;
14858
- var CREATE_SUBCOMMANDS = new Set(["team", "pair", "worker"]);
15204
+ var CREATE_SUBCOMMANDS = new Set(["team", "pair", "planner", "tester", "worker"]);
14859
15205
  async function runCreateFromCli(argv, io, deps = {}) {
14860
15206
  const sub = argv[0];
14861
15207
  if (!sub || sub.startsWith("--"))
@@ -14867,6 +15213,8 @@ async function runCreateFromCli(argv, io, deps = {}) {
14867
15213
  return await _createTeam(flags, io, deps);
14868
15214
  if (sub === "pair")
14869
15215
  return await _createPair(flags, io, deps);
15216
+ if (sub === "planner" || sub === "tester")
15217
+ return await _createMember(sub, flags, io, deps);
14870
15218
  return await _createWorker(flags, io, deps);
14871
15219
  }
14872
15220
  async function _createTeam(flags, io, deps) {
@@ -14875,7 +15223,7 @@ async function _createTeam(flags, io, deps) {
14875
15223
  const displayName = _required4(flags.name, "--name");
14876
15224
  const template = _required4(flags.template, "--template");
14877
15225
  const engine = _engine3(flags.engine);
14878
- const ctx = await _teamConsoleContext(flags, deps);
15226
+ const ctx = await _teamConsoleContext("team", flags, deps);
14879
15227
  const host = await _resolveOnlineHost(ctx.relay, ctx.captainId, flags.host);
14880
15228
  const workspace = await _resolveHostWorkspace(ctx.relay, ctx.captainId, host.host_id, flags.workspace);
14881
15229
  const { team, slots } = await _createAndApplyTeam(deps, ctx, displayName, template);
@@ -14918,6 +15266,19 @@ async function _postConsoleJson(deps, ctx, path22, body) {
14918
15266
  throw new Error("SailorBridge API returned a malformed object");
14919
15267
  return parsed;
14920
15268
  }
15269
+ async function _getConsoleJson(deps, ctx, path22) {
15270
+ const fetchImpl = deps.fetchImpl ?? fetch;
15271
+ const response = await fetchImpl(`${ctx.apiUrl.replace(/\/$/, "")}${path22}`, {
15272
+ headers: { Authorization: `Bearer ${ctx.token}` }
15273
+ });
15274
+ const text = await response.text();
15275
+ if (!response.ok)
15276
+ throw new Error(`SailorBridge API ${response.status}: ${text}`);
15277
+ const parsed = JSON.parse(text);
15278
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
15279
+ throw new Error("SailorBridge API returned a malformed object");
15280
+ return parsed;
15281
+ }
14921
15282
  function _responseString(body, field) {
14922
15283
  const value = body[field];
14923
15284
  if (typeof value !== "string" || !value)
@@ -14959,6 +15320,77 @@ async function _createPair(flags, io, deps) {
14959
15320
  });
14960
15321
  writeOutput(io.stdout, result, io.format, () => _pairPlain(host.host_id, workspace, engine, result));
14961
15322
  }
15323
+ async function _createMember(role, flags, io, deps) {
15324
+ _rejectMemberFlags(role, flags);
15325
+ const engine = _engine3(flags.engine);
15326
+ const session = _requiredSession(flags.session, _envSession(deps));
15327
+ const ctx = await _teamConsoleContext(role, flags, deps);
15328
+ const host = await _resolveOnlineHost(ctx.relay, ctx.captainId, flags.host);
15329
+ const workspace = await _resolveHostWorkspace(ctx.relay, ctx.captainId, host.host_id, flags.workspace);
15330
+ const slot = await _memberSlotForCreate(deps, ctx, session, role);
15331
+ const request = await _startMember(deps, ctx, session, host.host_id, workspace.path, engine, role, slot);
15332
+ const result = { role, host: host.host_id, workspace: workspace.path, engine, slot, request };
15333
+ writeOutput(io.stdout, result, io.format, () => _memberPlain(result));
15334
+ }
15335
+ function _rejectMemberFlags(role, flags) {
15336
+ const unsupported = [
15337
+ ["--name", flags.name],
15338
+ ["--template", flags.template],
15339
+ ["--concurrency", flags.concurrency]
15340
+ ];
15341
+ const found = unsupported.find(([, value]) => value !== undefined);
15342
+ if (found)
15343
+ throw new Error(`create ${role} does not accept ${found[0]}`);
15344
+ }
15345
+ async function _memberSlotForCreate(deps, ctx, session, role) {
15346
+ const path22 = `/api/console/teams/${encodeURIComponent(session)}/agent-slots`;
15347
+ const slots = _responseMemberSlots(await _getConsoleJson(deps, ctx, path22));
15348
+ const retryable = slots.find((slot) => _isRetryableMemberSlot(slot, role));
15349
+ if (retryable)
15350
+ return retryable;
15351
+ if (role === "planner" && slots.some((slot) => slot.role === role)) {
15352
+ throw new Error("duplicate-singleton-role: team already has a planner slot");
15353
+ }
15354
+ const created = _responseMemberSlot(await _postConsoleJson(deps, ctx, path22, { role }));
15355
+ if (created.role !== role)
15356
+ throw new Error("SailorBridge API returned a member slot with the wrong role");
15357
+ return created;
15358
+ }
15359
+ function _isRetryableMemberSlot(slot, role) {
15360
+ return slot.role === role && slot.status === "planned" && slot.template_origin === "single-add";
15361
+ }
15362
+ async function _startMember(deps, ctx, session, hostId, workspaceRoot, engine, role, slot) {
15363
+ const response = await _postConsoleJson(deps, ctx, `/api/console/teams/${encodeURIComponent(session)}/agent-start-requests`, { host_id: hostId, slot_id: slot.slot_id, spec: { engine, role, workspace: { mode: "worktree", root: workspaceRoot } } });
15364
+ const request = _responseStartRequest(response);
15365
+ if (request.host_id !== hostId || request.agent_name !== slot.agent_name) {
15366
+ throw new Error("SailorBridge API returned a start request with mismatched identity");
15367
+ }
15368
+ return request;
15369
+ }
15370
+ function _responseMemberSlots(body) {
15371
+ if (!Array.isArray(body.agent_slots))
15372
+ throw new Error("SailorBridge API response is missing agent_slots");
15373
+ return body.agent_slots.map(_responseMemberSlotValue);
15374
+ }
15375
+ function _responseMemberSlot(body) {
15376
+ if (!body.agent_slot || typeof body.agent_slot !== "object" || Array.isArray(body.agent_slot)) {
15377
+ throw new Error("SailorBridge API response is missing agent_slot");
15378
+ }
15379
+ return _responseMemberSlotValue(body.agent_slot);
15380
+ }
15381
+ function _responseMemberSlotValue(raw) {
15382
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
15383
+ throw new Error("SailorBridge API returned a malformed agent slot");
15384
+ }
15385
+ const slot = raw;
15386
+ return {
15387
+ slot_id: _responseString(slot, "slot_id"),
15388
+ agent_name: _responseString(slot, "agent_name"),
15389
+ role: _responseString(slot, "role"),
15390
+ status: _responseString(slot, "status"),
15391
+ template_origin: _responseString(slot, "template_origin")
15392
+ };
15393
+ }
14962
15394
  async function _createWorker(flags, io, deps) {
14963
15395
  const workerName = _required4(flags.name, "--name");
14964
15396
  const maxConcurrency = _concurrency(flags.concurrency);
@@ -14995,11 +15427,11 @@ async function _consoleContext(flags, deps) {
14995
15427
  token: credential.token
14996
15428
  };
14997
15429
  }
14998
- async function _teamConsoleContext(flags, deps) {
15430
+ async function _teamConsoleContext(command, flags, deps) {
14999
15431
  const env = deps.env ?? process.env;
15000
15432
  const accessToken = env.SAILORBRIDGE_CAPTAIN_ACCESS_TOKEN?.trim();
15001
15433
  if (!accessToken) {
15002
- throw new Error("create team requires SAILORBRIDGE_CAPTAIN_ACCESS_TOKEN for captain-authorized console operations");
15434
+ throw new Error(`create ${command} requires SAILORBRIDGE_CAPTAIN_ACCESS_TOKEN for captain-authorized console operations`);
15003
15435
  }
15004
15436
  return { ...await _consoleContext(flags, deps), token: accessToken };
15005
15437
  }
@@ -15132,6 +15564,18 @@ function _pairPlain(hostId, workspace, engine, result) {
15132
15564
  ].join(`
15133
15565
  `);
15134
15566
  }
15567
+ function _memberPlain(result) {
15568
+ return [
15569
+ `${result.role}: ${result.slot.agent_name}`,
15570
+ `slot: ${result.slot.slot_id}`,
15571
+ `host: ${result.host}`,
15572
+ `workspace: ${result.workspace}`,
15573
+ `engine: ${result.engine}`,
15574
+ `request: ${result.request.id}`,
15575
+ `status: ${result.request.status}`
15576
+ ].join(`
15577
+ `);
15578
+ }
15135
15579
  function _workerPlain(hostId, workdir, result) {
15136
15580
  return [
15137
15581
  `worker: ${result.worker.name}`,
@@ -15143,7 +15587,7 @@ function _workerPlain(hostId, workdir, result) {
15143
15587
  `);
15144
15588
  }
15145
15589
 
15146
- // .dist-releases/1784809313131-4113078/src/cli.js
15590
+ // .dist-releases/1784862378006-1249051/src/cli.js
15147
15591
  var FORMATTED_COMMANDS = new Set([
15148
15592
  "ask",
15149
15593
  "create",
@@ -15156,7 +15600,8 @@ var FORMATTED_COMMANDS = new Set([
15156
15600
  "reply",
15157
15601
  "task",
15158
15602
  "team",
15159
- "tutorial"
15603
+ "tutorial",
15604
+ "worker"
15160
15605
  ]);
15161
15606
  var SERVICE_INSTALL_COMMAND = "sailorbridge host service install";
15162
15607
  var SERVICE_UNINSTALL_COMMAND = "sailorbridge host service uninstall";
@@ -15183,6 +15628,9 @@ async function main(argv) {
15183
15628
  if (command === "host" && argv[1] === "run" && output2.provided) {
15184
15629
  throw new Error("host run does not produce a finite result; remove --format");
15185
15630
  }
15631
+ if (command === "worker" && argv[1] === "serve" && output2.provided) {
15632
+ throw new Error("worker serve does not produce a finite result; remove --format");
15633
+ }
15186
15634
  await _rejectUnsupportedCliInput(command, argv.slice(1), process.stdin);
15187
15635
  if (command === "login") {
15188
15636
  await _hostLogin(argv.slice(1), output2.format);
@@ -15219,7 +15667,7 @@ async function main(argv) {
15219
15667
  await runHostWorkspaceCommandFromCli(argv.slice(2), { stdout: process.stdout, format: output2.format });
15220
15668
  return;
15221
15669
  }
15222
- if (command === "worker") {
15670
+ if (command === "worker" && argv[1] === "serve") {
15223
15671
  await runWorkerFromCli(argv.slice(2));
15224
15672
  return;
15225
15673
  }
@@ -15266,6 +15714,16 @@ async function main(argv) {
15266
15714
  writeOutput(process.stdout, await runMemoryFromCli(client2, argv.slice(1), config, process.stdin, output2.format), output2.format);
15267
15715
  return;
15268
15716
  }
15717
+ if (command === "worker") {
15718
+ await runWorkerCommandFromCli(client2, argv.slice(1), config, {
15719
+ stdout: process.stdout,
15720
+ stderr: process.stderr,
15721
+ setExitCode: (code) => {
15722
+ process.exitCode = code;
15723
+ }
15724
+ }, output2.format);
15725
+ return;
15726
+ }
15269
15727
  if (command === "enrollment") {
15270
15728
  await _enrollment(client2, argv.slice(1), config.captainId, output2.format);
15271
15729
  return;
@@ -15322,7 +15780,7 @@ var SUBCOMMANDS = {
15322
15780
  "unpark"
15323
15781
  ]),
15324
15782
  team: new Set(["bootstrap"]),
15325
- worker: new Set(["serve"])
15783
+ worker: new Set(["job", "list", "run", "serve"])
15326
15784
  };
15327
15785
  function _assertKnownCommand(command, subcommand) {
15328
15786
  if (!KNOWN_COMMANDS.has(command))
@@ -15807,6 +16265,10 @@ function _usageText() {
15807
16265
  creates an auto-numbered team and starts every template slot on one paired online host.
15808
16266
  sailorbridge create pair [--host H] [--workspace PATH|GIT_URL] [--engine claude|codex] [--name N] [--session S]
15809
16267
  builds a writer+reviewer dev pair on a paired online host; workspace defaults to the host's reported one.
16268
+ sailorbridge create planner [--host H] [--workspace PATH] [--engine claude|codex] [--session S]
16269
+ adds the team's single planner through the console slot + start flow.
16270
+ sailorbridge create tester [--host H] [--workspace PATH] [--engine claude|codex] [--session S]
16271
+ adds one tester through the console slot + start flow.
15810
16272
  sailorbridge create worker --name N --concurrency N [--host H] [--workspace PATH] [--session S]
15811
16273
  starts a worker on a paired online host. All create commands: [--api-url URL] [--credentials-path PATH] [--format plain|json]
15812
16274
  sailorbridge team bootstrap --template classic3 --slot SLOT=HOST:claude|codex [--slot ...] [--session S]
@@ -15822,13 +16284,19 @@ function _usageText() {
15822
16284
  sailorbridge host service install|uninstall|status [--binary PATH] [--credentials-path PATH] [--workspace-root DIR] [--service-dir DIR] [--service-name NAME]
15823
16285
  sailorbridge host start-request [--credentials-path PATH] [--api-url URL] [--access-token TOKEN] --team T [--host-id H] --agent A --engine codex --roles operator --workspace-mode fresh|git [--git-url URL]
15824
16286
  sailorbridge host run [--credentials-path PATH] [--api-url URL] [--host-id H] [--display-name N] [--workspace-root DIR]
16287
+ sailorbridge worker list [--format plain|json]
16288
+ sailorbridge worker run [--detach] --worker NAME [--timeout SEC] [--format plain|json] -- ARGV...
16289
+ sailorbridge worker job ID [--format plain|json]
16290
+ list/run/job require SAILORBRIDGE_CAPTAIN_ID; list/run also require SAILORBRIDGE_SESSION.
15825
16291
  sailorbridge worker serve [--relay-url URL] [--worker NAME] [--workdir DIR] [--max-concurrency N]
15826
16292
  sailorbridge supervise --agent A --cli claude|codex --cwd DIR --i-own-this-agent [--transport tmux] [--tmux-session S] [-- COMMAND...]
15827
16293
 
15828
16294
  Environment:
15829
16295
  SAILORBRIDGE_RELAY_URL / SAILORBRIDGE_TOKEN
15830
16296
  SAILORBRIDGE_API_URL overrides the default product API for login/tutorial (${DEFAULT_PRODUCT_API_URL}); --api-url wins
15831
- SAILORBRIDGE_AGENT_NAME / SAILORBRIDGE_SESSION / SAILORBRIDGE_CAPTAIN_ID
16297
+ SAILORBRIDGE_AGENT_NAME
16298
+ SAILORBRIDGE_CAPTAIN_ID is required for worker list/run/job.
16299
+ SAILORBRIDGE_SESSION is also required for worker list/run.
15832
16300
  SAILORBRIDGE_HOST_ID / SAILORBRIDGE_HOST_DISPLAY_NAME
15833
16301
  SAILORBRIDGE_NO_SERVICE=1 makes login skip the host service install
15834
16302
  SAILORBRIDGE_DEBUG=1 prints the internal pairing id during login (diagnostics only)
@@ -15869,5 +16337,5 @@ function _looksLikeNetworkFailure(message) {
15869
16337
  return /fetch failed|network|ECONN(?:REFUSED|RESET)|ENOTFOUND|ETIMEDOUT/iu.test(message);
15870
16338
  }
15871
16339
 
15872
- //# debugId=D1FAA8446085164F64756E2164756E21
16340
+ //# debugId=3B810968682A4A6F64756E2164756E21
15873
16341
  //# sourceMappingURL=cli.js.map