@rosthq/cli 0.7.29 → 0.7.30

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
@@ -472,13 +472,13 @@ function __disposeResources(env) {
472
472
  }
473
473
  return next();
474
474
  }
475
- function __rewriteRelativeImportExtension(path5, preserveJsx) {
476
- if (typeof path5 === "string" && /^\.\.?\//.test(path5)) {
477
- return path5.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function(m, tsx, d, ext, cm) {
475
+ function __rewriteRelativeImportExtension(path6, preserveJsx) {
476
+ if (typeof path6 === "string" && /^\.\.?\//.test(path6)) {
477
+ return path6.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function(m, tsx, d, ext, cm) {
478
478
  return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : d + ext + "." + cm.toLowerCase() + "js";
479
479
  });
480
480
  }
481
- return path5;
481
+ return path6;
482
482
  }
483
483
  var extendStatics, __assign, __createBinding, __setModuleDefault, ownKeys, _SuppressedError, tslib_es6_default;
484
484
  var init_tslib_es6 = __esm({
@@ -13257,7 +13257,7 @@ var require_main3 = __commonJS({
13257
13257
  });
13258
13258
 
13259
13259
  // src/index.ts
13260
- import { realpathSync as realpathSync2 } from "node:fs";
13260
+ import { realpathSync as realpathSync3 } from "node:fs";
13261
13261
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
13262
13262
 
13263
13263
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
@@ -14026,10 +14026,10 @@ function mergeDefs(...defs) {
14026
14026
  function cloneDef(schema) {
14027
14027
  return mergeDefs(schema._zod.def);
14028
14028
  }
14029
- function getElementAtPath(obj, path5) {
14030
- if (!path5)
14029
+ function getElementAtPath(obj, path6) {
14030
+ if (!path6)
14031
14031
  return obj;
14032
- return path5.reduce((acc, key) => acc?.[key], obj);
14032
+ return path6.reduce((acc, key) => acc?.[key], obj);
14033
14033
  }
14034
14034
  function promiseAllObject(promisesObj) {
14035
14035
  const keys = Object.keys(promisesObj);
@@ -14438,11 +14438,11 @@ function explicitlyAborted(x, startIndex = 0) {
14438
14438
  }
14439
14439
  return false;
14440
14440
  }
14441
- function prefixIssues(path5, issues) {
14441
+ function prefixIssues(path6, issues) {
14442
14442
  return issues.map((iss) => {
14443
14443
  var _a3;
14444
14444
  (_a3 = iss).path ?? (_a3.path = []);
14445
- iss.path.unshift(path5);
14445
+ iss.path.unshift(path6);
14446
14446
  return iss;
14447
14447
  });
14448
14448
  }
@@ -14589,16 +14589,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
14589
14589
  }
14590
14590
  function formatError(error51, mapper = (issue2) => issue2.message) {
14591
14591
  const fieldErrors = { _errors: [] };
14592
- const processError = (error52, path5 = []) => {
14592
+ const processError = (error52, path6 = []) => {
14593
14593
  for (const issue2 of error52.issues) {
14594
14594
  if (issue2.code === "invalid_union" && issue2.errors.length) {
14595
- issue2.errors.map((issues) => processError({ issues }, [...path5, ...issue2.path]));
14595
+ issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
14596
14596
  } else if (issue2.code === "invalid_key") {
14597
- processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
14597
+ processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
14598
14598
  } else if (issue2.code === "invalid_element") {
14599
- processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
14599
+ processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
14600
14600
  } else {
14601
- const fullpath = [...path5, ...issue2.path];
14601
+ const fullpath = [...path6, ...issue2.path];
14602
14602
  if (fullpath.length === 0) {
14603
14603
  fieldErrors._errors.push(mapper(issue2));
14604
14604
  } else {
@@ -14625,17 +14625,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
14625
14625
  }
14626
14626
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
14627
14627
  const result = { errors: [] };
14628
- const processError = (error52, path5 = []) => {
14628
+ const processError = (error52, path6 = []) => {
14629
14629
  var _a3, _b;
14630
14630
  for (const issue2 of error52.issues) {
14631
14631
  if (issue2.code === "invalid_union" && issue2.errors.length) {
14632
- issue2.errors.map((issues) => processError({ issues }, [...path5, ...issue2.path]));
14632
+ issue2.errors.map((issues) => processError({ issues }, [...path6, ...issue2.path]));
14633
14633
  } else if (issue2.code === "invalid_key") {
14634
- processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
14634
+ processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
14635
14635
  } else if (issue2.code === "invalid_element") {
14636
- processError({ issues: issue2.issues }, [...path5, ...issue2.path]);
14636
+ processError({ issues: issue2.issues }, [...path6, ...issue2.path]);
14637
14637
  } else {
14638
- const fullpath = [...path5, ...issue2.path];
14638
+ const fullpath = [...path6, ...issue2.path];
14639
14639
  if (fullpath.length === 0) {
14640
14640
  result.errors.push(mapper(issue2));
14641
14641
  continue;
@@ -14667,8 +14667,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
14667
14667
  }
14668
14668
  function toDotPath(_path) {
14669
14669
  const segs = [];
14670
- const path5 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
14671
- for (const seg of path5) {
14670
+ const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
14671
+ for (const seg of path6) {
14672
14672
  if (typeof seg === "number")
14673
14673
  segs.push(`[${seg}]`);
14674
14674
  else if (typeof seg === "symbol")
@@ -27360,13 +27360,13 @@ function resolveRef(ref, ctx) {
27360
27360
  if (!ref.startsWith("#")) {
27361
27361
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
27362
27362
  }
27363
- const path5 = ref.slice(1).split("/").filter(Boolean);
27364
- if (path5.length === 0) {
27363
+ const path6 = ref.slice(1).split("/").filter(Boolean);
27364
+ if (path6.length === 0) {
27365
27365
  return ctx.rootSchema;
27366
27366
  }
27367
27367
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
27368
- if (path5[0] === defsKey) {
27369
- const key = path5[1];
27368
+ if (path6[0] === defsKey) {
27369
+ const key = path6[1];
27370
27370
  if (!key || !ctx.defs[key]) {
27371
27371
  throw new Error(`Reference not found: ${ref}`);
27372
27372
  }
@@ -27828,7 +27828,7 @@ var CommandClient = class {
27828
27828
  this.fetchImpl = options.fetchImpl ?? fetch;
27829
27829
  }
27830
27830
  async execute(commandId, body = {}, options = {}) {
27831
- const path5 = `/api/commands/${encodeURIComponent(commandId)}`;
27831
+ const path6 = `/api/commands/${encodeURIComponent(commandId)}`;
27832
27832
  const headers = {
27833
27833
  authorization: `Bearer ${this.token}`,
27834
27834
  "content-type": "application/json"
@@ -27836,7 +27836,7 @@ var CommandClient = class {
27836
27836
  if (options.targetSeatId !== void 0 && options.targetSeatId.length > 0) {
27837
27837
  headers["x-rost-seat"] = options.targetSeatId;
27838
27838
  }
27839
- const response = await this.fetchImpl(`${this.appUrl}${path5}`, {
27839
+ const response = await this.fetchImpl(`${this.appUrl}${path6}`, {
27840
27840
  method: "POST",
27841
27841
  headers,
27842
27842
  body: JSON.stringify(body)
@@ -27849,7 +27849,7 @@ var CommandClient = class {
27849
27849
  const snippet = redactSecrets(text.replace(/\s+/g, " ").trim()).slice(0, 200);
27850
27850
  throw new CommandClientError(
27851
27851
  response.status,
27852
- `server returned HTTP ${response.status} (non-JSON) from ${path5}` + (snippet ? ` \u2014 ${snippet}` : "") + " \u2014 the deployment may be unhealthy"
27852
+ `server returned HTTP ${response.status} (non-JSON) from ${path6}` + (snippet ? ` \u2014 ${snippet}` : "") + " \u2014 the deployment may be unhealthy"
27853
27853
  );
27854
27854
  }
27855
27855
  const parsed = commandResponseSchema.parse(raw);
@@ -33064,8 +33064,8 @@ var IcebergError = class extends Error {
33064
33064
  return this.status === 419;
33065
33065
  }
33066
33066
  };
33067
- function buildUrl(baseUrl, path5, query) {
33068
- const url2 = new URL(path5, baseUrl);
33067
+ function buildUrl(baseUrl, path6, query) {
33068
+ const url2 = new URL(path6, baseUrl);
33069
33069
  if (query) {
33070
33070
  for (const [key, value] of Object.entries(query)) {
33071
33071
  if (value !== void 0) {
@@ -33095,12 +33095,12 @@ function createFetchClient(options) {
33095
33095
  return {
33096
33096
  async request({
33097
33097
  method,
33098
- path: path5,
33098
+ path: path6,
33099
33099
  query,
33100
33100
  body,
33101
33101
  headers
33102
33102
  }) {
33103
- const url2 = buildUrl(options.baseUrl, path5, query);
33103
+ const url2 = buildUrl(options.baseUrl, path6, query);
33104
33104
  const authHeaders = await buildAuthHeaders(options.auth);
33105
33105
  const res = await fetchFn(url2, {
33106
33106
  method,
@@ -33962,7 +33962,7 @@ var StorageFileApi = class extends BaseApiClient {
33962
33962
  * @param path The relative file path. Should be of the format `folder/subfolder/filename.png`. The bucket must already exist before attempting to upload.
33963
33963
  * @param fileBody The body of the file to be stored in the bucket.
33964
33964
  */
33965
- async uploadOrUpdate(method, path5, fileBody, fileOptions) {
33965
+ async uploadOrUpdate(method, path6, fileBody, fileOptions) {
33966
33966
  var _this = this;
33967
33967
  return _this.handleOperation(async () => {
33968
33968
  let body;
@@ -33986,7 +33986,7 @@ var StorageFileApi = class extends BaseApiClient {
33986
33986
  if ((typeof ReadableStream !== "undefined" && body instanceof ReadableStream || body && typeof body === "object" && "pipe" in body && typeof body.pipe === "function") && !options.duplex) options.duplex = "half";
33987
33987
  }
33988
33988
  if (fileOptions === null || fileOptions === void 0 ? void 0 : fileOptions.headers) for (const [key, value] of Object.entries(fileOptions.headers)) headers = setHeader(headers, key, value);
33989
- const cleanPath = _this._removeEmptyFolders(path5);
33989
+ const cleanPath = _this._removeEmptyFolders(path6);
33990
33990
  const _path = _this._getFinalPath(cleanPath);
33991
33991
  const data = await (method == "PUT" ? put : post)(_this.fetch, `${_this.url}/object/${_path}`, body, _objectSpread22({ headers }, (options === null || options === void 0 ? void 0 : options.duplex) ? { duplex: options.duplex } : {}));
33992
33992
  return {
@@ -34063,8 +34063,8 @@ var StorageFileApi = class extends BaseApiClient {
34063
34063
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
34064
34064
  * - For React Native, using either `Blob`, `File` or `FormData` does not work as intended. Upload file using `ArrayBuffer` from base64 file data instead, see example below.
34065
34065
  */
34066
- async upload(path5, fileBody, fileOptions) {
34067
- return this.uploadOrUpdate("POST", path5, fileBody, fileOptions);
34066
+ async upload(path6, fileBody, fileOptions) {
34067
+ return this.uploadOrUpdate("POST", path6, fileBody, fileOptions);
34068
34068
  }
34069
34069
  /**
34070
34070
  * Upload a file with a token generated from `createSignedUploadUrl`.
@@ -34104,9 +34104,9 @@ var StorageFileApi = class extends BaseApiClient {
34104
34104
  * - `objects` table permissions: none
34105
34105
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
34106
34106
  */
34107
- async uploadToSignedUrl(path5, token, fileBody, fileOptions) {
34107
+ async uploadToSignedUrl(path6, token, fileBody, fileOptions) {
34108
34108
  var _this3 = this;
34109
- const cleanPath = _this3._removeEmptyFolders(path5);
34109
+ const cleanPath = _this3._removeEmptyFolders(path6);
34110
34110
  const _path = _this3._getFinalPath(cleanPath);
34111
34111
  const url2 = new URL(_this3.url + `/object/upload/sign/${_path}`);
34112
34112
  url2.searchParams.set("token", token);
@@ -34175,10 +34175,10 @@ var StorageFileApi = class extends BaseApiClient {
34175
34175
  * - `objects` table permissions: `insert`
34176
34176
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
34177
34177
  */
34178
- async createSignedUploadUrl(path5, options) {
34178
+ async createSignedUploadUrl(path6, options) {
34179
34179
  var _this4 = this;
34180
34180
  return _this4.handleOperation(async () => {
34181
- let _path = _this4._getFinalPath(path5);
34181
+ let _path = _this4._getFinalPath(path6);
34182
34182
  const headers = _objectSpread22({}, _this4.headers);
34183
34183
  if (options === null || options === void 0 ? void 0 : options.upsert) headers["x-upsert"] = "true";
34184
34184
  const data = await post(_this4.fetch, `${_this4.url}/object/upload/sign/${_path}`, {}, { headers });
@@ -34187,7 +34187,7 @@ var StorageFileApi = class extends BaseApiClient {
34187
34187
  if (!token) throw new StorageError("No token returned by API");
34188
34188
  return {
34189
34189
  signedUrl: url2.toString(),
34190
- path: path5,
34190
+ path: path6,
34191
34191
  token
34192
34192
  };
34193
34193
  });
@@ -34247,8 +34247,8 @@ var StorageFileApi = class extends BaseApiClient {
34247
34247
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
34248
34248
  * - For React Native, using either `Blob`, `File` or `FormData` does not work as intended. Update file using `ArrayBuffer` from base64 file data instead, see example below.
34249
34249
  */
34250
- async update(path5, fileBody, fileOptions) {
34251
- return this.uploadOrUpdate("PUT", path5, fileBody, fileOptions);
34250
+ async update(path6, fileBody, fileOptions) {
34251
+ return this.uploadOrUpdate("PUT", path6, fileBody, fileOptions);
34252
34252
  }
34253
34253
  /**
34254
34254
  * Moves an existing file to a new path in the same bucket.
@@ -34399,10 +34399,10 @@ var StorageFileApi = class extends BaseApiClient {
34399
34399
  * - `objects` table permissions: `select`
34400
34400
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
34401
34401
  */
34402
- async createSignedUrl(path5, expiresIn, options) {
34402
+ async createSignedUrl(path6, expiresIn, options) {
34403
34403
  var _this8 = this;
34404
34404
  return _this8.handleOperation(async () => {
34405
- let _path = _this8._getFinalPath(path5);
34405
+ let _path = _this8._getFinalPath(path6);
34406
34406
  const hasTransform = typeof (options === null || options === void 0 ? void 0 : options.transform) === "object" && options.transform !== null && Object.keys(options.transform).length > 0;
34407
34407
  let data = await post(_this8.fetch, `${_this8.url}/object/sign/${_path}`, _objectSpread22({ expiresIn }, hasTransform ? { transform: options.transform } : {}), { headers: _this8.headers });
34408
34408
  const query = new URLSearchParams();
@@ -34538,13 +34538,13 @@ var StorageFileApi = class extends BaseApiClient {
34538
34538
  * - `objects` table permissions: `select`
34539
34539
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
34540
34540
  */
34541
- download(path5, options, parameters) {
34541
+ download(path6, options, parameters) {
34542
34542
  const renderPath = typeof (options === null || options === void 0 ? void 0 : options.transform) === "object" && options.transform !== null && Object.keys(options.transform).length > 0 ? "render/image/authenticated" : "object";
34543
34543
  const query = new URLSearchParams();
34544
34544
  if (options === null || options === void 0 ? void 0 : options.transform) this.applyTransformOptsToQuery(query, options.transform);
34545
34545
  if ((options === null || options === void 0 ? void 0 : options.cacheNonce) != null) query.set("cacheNonce", String(options.cacheNonce));
34546
34546
  const queryString = query.toString();
34547
- const _path = this._getFinalPath(path5);
34547
+ const _path = this._getFinalPath(path6);
34548
34548
  const downloadFn = () => get(this.fetch, `${this.url}/${renderPath}/${_path}${queryString ? `?${queryString}` : ""}`, {
34549
34549
  headers: this.headers,
34550
34550
  noResolveJson: true
@@ -34575,9 +34575,9 @@ var StorageFileApi = class extends BaseApiClient {
34575
34575
  * }
34576
34576
  * ```
34577
34577
  */
34578
- async info(path5) {
34578
+ async info(path6) {
34579
34579
  var _this10 = this;
34580
- const _path = _this10._getFinalPath(path5);
34580
+ const _path = _this10._getFinalPath(path6);
34581
34581
  return _this10.handleOperation(async () => {
34582
34582
  return recursiveToCamel(await get(_this10.fetch, `${_this10.url}/object/info/${_path}`, { headers: _this10.headers }));
34583
34583
  });
@@ -34598,9 +34598,9 @@ var StorageFileApi = class extends BaseApiClient {
34598
34598
  * .exists('folder/avatar1.png')
34599
34599
  * ```
34600
34600
  */
34601
- async exists(path5) {
34601
+ async exists(path6) {
34602
34602
  var _this11 = this;
34603
- const _path = _this11._getFinalPath(path5);
34603
+ const _path = _this11._getFinalPath(path6);
34604
34604
  try {
34605
34605
  await head(_this11.fetch, `${_this11.url}/object/${_path}`, { headers: _this11.headers });
34606
34606
  return {
@@ -34679,8 +34679,8 @@ var StorageFileApi = class extends BaseApiClient {
34679
34679
  * - `objects` table permissions: none
34680
34680
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
34681
34681
  */
34682
- getPublicUrl(path5, options) {
34683
- const _path = this._getFinalPath(path5);
34682
+ getPublicUrl(path6, options) {
34683
+ const _path = this._getFinalPath(path6);
34684
34684
  const query = new URLSearchParams();
34685
34685
  if (options === null || options === void 0 ? void 0 : options.download) query.set("download", options.download === true ? "" : options.download);
34686
34686
  if (options === null || options === void 0 ? void 0 : options.transform) this.applyTransformOptsToQuery(query, options.transform);
@@ -34819,10 +34819,10 @@ var StorageFileApi = class extends BaseApiClient {
34819
34819
  * - `objects` table permissions: `select`
34820
34820
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
34821
34821
  */
34822
- async list(path5, options, parameters) {
34822
+ async list(path6, options, parameters) {
34823
34823
  var _this13 = this;
34824
34824
  return _this13.handleOperation(async () => {
34825
- const body = _objectSpread22(_objectSpread22(_objectSpread22({}, DEFAULT_SEARCH_OPTIONS), options), {}, { prefix: path5 || "" });
34825
+ const body = _objectSpread22(_objectSpread22(_objectSpread22({}, DEFAULT_SEARCH_OPTIONS), options), {}, { prefix: path6 || "" });
34826
34826
  return await post(_this13.fetch, `${_this13.url}/object/list/${_this13.bucketId}`, body, { headers: _this13.headers }, parameters);
34827
34827
  });
34828
34828
  }
@@ -34887,11 +34887,11 @@ var StorageFileApi = class extends BaseApiClient {
34887
34887
  if (typeof Buffer !== "undefined") return Buffer.from(data).toString("base64");
34888
34888
  return btoa(data);
34889
34889
  }
34890
- _getFinalPath(path5) {
34891
- return `${this.bucketId}/${path5.replace(/^\/+/, "")}`;
34890
+ _getFinalPath(path6) {
34891
+ return `${this.bucketId}/${path6.replace(/^\/+/, "")}`;
34892
34892
  }
34893
- _removeEmptyFolders(path5) {
34894
- return path5.replace(/^\/|\/$/g, "").replace(/\/+/g, "/");
34893
+ _removeEmptyFolders(path6) {
34894
+ return path6.replace(/^\/|\/$/g, "").replace(/\/+/g, "/");
34895
34895
  }
34896
34896
  /** Modifies the `query`, appending values the from `transform` */
34897
34897
  applyTransformOptsToQuery(query, transform2) {
@@ -42664,7 +42664,14 @@ var softwareCapacityObservationSummarySchema = external_exports.object({
42664
42664
  account_alias: external_exports.string().nullable()
42665
42665
  });
42666
42666
  var softwareRequestCreateInputSchema = external_exports.object({
42667
- software_project_id: uuid10,
42667
+ // DER-1310: the project the request opens against. Either identifier works —
42668
+ // the raw id (still fully supported) or `project`, the project's slug or name,
42669
+ // resolved server-side to the tenant's active project. The command layer (not
42670
+ // this schema) enforces that exactly one resolves, with an actionable error
42671
+ // when neither is given or neither matches — keeping this shape a plain
42672
+ // `z.object` so JSON-schema/CLI-flag projection stays simple (no `.refine`).
42673
+ software_project_id: uuid10.optional(),
42674
+ project: external_exports.string().trim().min(1).max(200).optional(),
42668
42675
  // Untrusted display text (ADR-0018 §5) — bounded, never treated as an instruction.
42669
42676
  title: external_exports.string().trim().min(1).max(500),
42670
42677
  source_channel: softwareSourceChannelSchema.optional(),
@@ -45125,7 +45132,7 @@ External connectors are being rolled out provider by provider, conservatively (r
45125
45132
  order: 48,
45126
45133
  title: "CLI and MCP installation guide",
45127
45134
  summary: "Install the public CLI, register remote token-backed MCP clients, and find the full command and tool catalog.",
45128
- version: "2026-07-04.21",
45135
+ version: "2026-07-05.1",
45129
45136
  public: true,
45130
45137
  audiences: ["human", "cli", "mcp", "in_app_agent"],
45131
45138
  stages: ["company_setup", "staffing"],
@@ -45604,7 +45611,7 @@ The signed-in app exposes the same Skill command surface at **Skills**, linked f
45604
45611
  | \`{{cli}} deliverable list|get|create|attach\` | \`deliverable.list\`, \`deliverable.get\`, \`deliverable.create\`, \`deliverable.attach\` | List, get, create, or attach agent deliverables for a seat. Deliverables are durable work outputs visible across UI, CLI, and MCP. | Tenant (list/get), Seat (create/attach) | \`{{cli}} deliverable list --seat-id <id> --json\`; \`{{cli}} deliverable create --title "Brief" --kind brief\` |
45605
45612
  | \`{{cli}} graph show\` | \`graph.get\` | Print a table view of the Responsibility Graph with explicit \`seat_id\` and \`parent_seat_id\` columns. | Tenant | \`{{cli}} graph show\` |
45606
45613
  | \`{{cli}} seat list|get|create|rename|reparent|decommission\` | \`graph.get\`, \`seat.get\`, \`seat.create\`, \`seat.rename\`, \`seat.reparent\`, \`seat.decommission\`, \`seat.decommission_preview\` | Work with seats as first-class CLI primitives. \`seat decommission --dry-run\` previews affected occupancies, agents, Charters, tokens, credentials, work orders, and schedules before the human-gated teardown. | Tenant / Seat for targeted mutations | \`{{cli}} seat list\`; \`{{cli}} seat decommission --seat-id <id> --dry-run\` |
45607
- | \`{{cli}} forge project-create|project-list|request-create|request-list|request-show|request-pause|request-cancel|request-resume\` | \`software_factory.project.create\`, \`software_factory.project.list\`, \`software_factory.request.create\`, \`software_factory.request.list\`, \`software_factory.request.show\`, \`software_factory.request.pause\`, \`software_factory.request.cancel\`, \`software_factory.request.resume\` | Create/list Forge projects, open/read governed Forge build requests, and control request lifecycle. Project creation plus pause/cancel/resume are tenant-admin, entitlement-gated, and human-gated; reads and request creation require the Forge add-on. Pause halts new runner claims at the next task checkpoint, cancel drives terminal canceled, and resume requeues from the last completed task checkpoint. | Tenant / Tenant-admin for create and lifecycle controls | \`{{cli}} forge project-create --name "Leiluna app" --base-branch main\`; \`{{cli}} forge request-create --software-project-id <id> --title "Add invoice export"\`; \`{{cli}} forge request-pause --build-request-id <id> --reason "Operator hold"\` |
45614
+ | \`{{cli}} forge project-create|project-list|request-create|request-list|request-show|request-pause|request-cancel|request-resume\` | \`software_factory.project.create\`, \`software_factory.project.list\`, \`software_factory.request.create\`, \`software_factory.request.list\`, \`software_factory.request.show\`, \`software_factory.request.pause\`, \`software_factory.request.cancel\`, \`software_factory.request.resume\` | Create/list Forge projects, open/read governed Forge build requests, and control request lifecycle. Project creation plus pause/cancel/resume are tenant-admin, entitlement-gated, and human-gated; reads and request creation require the Forge add-on. Pause halts new runner claims at the next task checkpoint, cancel drives terminal canceled, and resume requeues from the last completed task checkpoint. | Tenant / Tenant-admin for create and lifecycle controls | \`{{cli}} forge project-create --name "Leiluna app" --base-branch main\`; \`{{cli}} forge request-create --project <slug-or-name> --title "Add invoice export"\` (accepts \`--software-project-id <id>\` instead of \`--project\` too); \`{{cli}} forge request-pause --build-request-id <id> --reason "Operator hold"\` |
45608
45615
 
45609
45616
  Skills wrapper help:
45610
45617
 
@@ -45845,7 +45852,7 @@ Several rows here are seat-operating commands (\`task.create\`, the \`signal.*\`
45845
45852
  | \`rost_revoke_skill_from_seat\` | \`skill.revoke_from_seat\` | Human-gated revocation that stops future Skill use without deleting historical activations. | Tenant-admin | Call with \`{"assignment_id":"<assignment-id>"}\`; non-interactive callers receive a confirmation handoff. |
45846
45853
  | \`rost_list_model_catalog\` | \`model.catalog\` | List guided model tiers \u2014 recommendations, token prices, cost bands, best-fit work, and model ids for \`--model\`. | Tenant | Call with \`{}\`. |
45847
45854
  | \`rost_create_a_forge_project\` | \`software_factory.project.create\` | Create an active Forge software project before binding repositories or opening build requests. Tenant-admin, human-gated, and entitlement-gated. | Tenant-admin | Call with \`{"name":"Leiluna app","slug":"leiluna-app","base_branch":"main"}\`; non-interactive callers receive a confirmation handoff. |
45848
- | \`rost_create_a_forge_build_request\` | \`software_factory.request.create\` | Open a governed Forge build request against a connected software project; records the request plus its initial intake phase run. Requires the Forge add-on; the title is untrusted display text. | Tenant | Call with \`{"software_project_id":"<project-id>","title":"Add invoice export"}\`. |
45855
+ | \`rost_create_a_forge_build_request\` | \`software_factory.request.create\` | Open a governed Forge build request against a connected software project; records the request plus its initial intake phase run. Requires the Forge add-on; the title is untrusted display text. | Tenant | Call with \`{"project":"<slug-or-name>","title":"Add invoice export"}\` (or \`{"software_project_id":"<project-id>", ...}\`). |
45849
45856
  | \`rost_pause_a_forge_build_request\` | \`software_factory.request.pause\` | Pause an in-flight Forge build request. New runner claims stop immediately; running work halts at the next task checkpoint. Human-gated. | Tenant-admin | Call with \`{"build_request_id":"<request-id>","reason":"Operator hold"}\`; non-interactive callers receive a confirmation handoff. |
45850
45857
  | \`rost_cancel_a_forge_build_request\` | \`software_factory.request.cancel\` | Cancel a Forge build request and expire active runner work orders. This is terminal; use pause for reversible holds. Human-gated. | Tenant-admin | Call with \`{"build_request_id":"<request-id>","reason":"Out of scope"}\`; non-interactive callers receive a confirmation handoff. |
45851
45858
  | \`rost_resume_a_forge_build_request\` | \`software_factory.request.resume\` | Resume a paused or human-blocked Forge build request from the last completed task checkpoint and requeue the current phase. Human-gated. | Tenant-admin | Call with \`{"build_request_id":"<request-id>","reason":"Budget raised"}\`; non-interactive callers receive a confirmation handoff. |
@@ -49089,7 +49096,7 @@ var COMMAND_MANIFEST = [
49089
49096
  { "id": "software_factory.project.list", "namespace": "software_factory", "action": "project.list", "title": "List Forge projects", "description": "List active Forge software projects so a tenant can choose a project for build requests, GitHub repository bindings, and config. Read-only and entitlement-gated.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [], "hasComplexInput": false, "help": "List active Forge software projects so a tenant can choose a project for build requests, GitHub repository bindings, and config. Read-only; requires the Forge add-on." },
49090
49097
  { "id": "software_factory.request.answer_clarification", "namespace": "software_factory", "action": "request.answer_clarification", "title": "Answer Forge plan clarifications", "description": "Write structured human answers to a draft plan's clarifying questions and optionally mark it ready for plan review. Answers append to the versioned planning artifact; this never widens authority or automation mode.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "build_request_id", "flag": "build-request-id", "type": "string", "required": true }, { "name": "mark_ready_for_plan_review", "flag": "mark-ready-for-plan-review", "type": "boolean", "required": false }], "hasComplexInput": true, "help": "Write structured human answers to a draft plan's clarifying questions and optionally mark it ready for plan review. Never widens authority or automation mode." },
49091
49098
  { "id": "software_factory.request.cancel", "namespace": "software_factory", "action": "request.cancel", "title": "Cancel a Forge build request", "description": "Cancel a Forge build request and expire active runner work orders. This is terminal; use pause for reversible holds. Human-gated and entitlement-gated.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "build_request_id", "flag": "build-request-id", "type": "string", "required": true }, { "name": "reason", "flag": "reason", "type": "string", "required": false }], "hasComplexInput": false, "help": "Cancel a Forge build request, expire unstarted runner work, and terminalize active runner work. Terminal, tenant-admin, and human-gated." },
49092
- { "id": "software_factory.request.create", "namespace": "software_factory", "action": "request.create", "title": "Create a Forge build request", "description": "Open a governed Forge build request against a connected software project. Records the request plus its initial intake phase run. Entitlement-gated (Forge add-on); the title is untrusted display text, never an instruction.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "software_project_id", "flag": "software-project-id", "type": "string", "required": true }, { "name": "title", "flag": "title", "type": "string", "required": true }, { "name": "source_channel", "flag": "source-channel", "type": "enum", "required": false, "enumValues": ["ui", "cli", "github_issue", "linear", "api"] }, { "name": "source_ref", "flag": "source-ref", "type": "string", "required": false }], "hasComplexInput": true, "help": "Open a governed Forge build request against a connected software project; the title is untrusted display text. Requires the Forge add-on." },
49099
+ { "id": "software_factory.request.create", "namespace": "software_factory", "action": "request.create", "title": "Create a Forge build request", "description": "Open a governed Forge build request against a connected software project. Records the request plus its initial intake phase run. Entitlement-gated (Forge add-on); the title is untrusted display text, never an instruction.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "software_project_id", "flag": "software-project-id", "type": "string", "required": false }, { "name": "project", "flag": "project", "type": "string", "required": false }, { "name": "title", "flag": "title", "type": "string", "required": true }, { "name": "source_channel", "flag": "source-channel", "type": "enum", "required": false, "enumValues": ["ui", "cli", "github_issue", "linear", "api"] }, { "name": "source_ref", "flag": "source-ref", "type": "string", "required": false }], "hasComplexInput": true, "help": "Open a governed Forge build request against a connected software project; the title is untrusted display text. Requires the Forge add-on." },
49093
49100
  { "id": "software_factory.request.list", "namespace": "software_factory", "action": "request.list", "title": "List Forge build requests", "description": "The Forge control-room board: the tenant's build requests with status, current phase, and risk/automation mode. Read-only, entitlement-gated.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "limit", "flag": "limit", "type": "integer", "required": false }], "hasComplexInput": false, "help": "List Forge build requests (the control-room board) with status, current phase, and risk. Read-only; requires the Forge add-on." },
49094
49101
  { "id": "software_factory.request.pause", "namespace": "software_factory", "action": "request.pause", "title": "Pause a Forge build request", "description": "Pause an in-flight Forge build request. New runner claims stop immediately; any running task halts at the next task checkpoint before resume. Human-gated and entitlement-gated.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "build_request_id", "flag": "build-request-id", "type": "string", "required": true }, { "name": "reason", "flag": "reason", "type": "string", "required": false }], "hasComplexInput": false, "help": "Pause an in-flight Forge build request so no new runner work starts; running work stops at the next task checkpoint. Tenant-admin and human-gated." },
49095
49102
  { "id": "software_factory.request.resume", "namespace": "software_factory", "action": "request.resume", "title": "Resume a Forge build request", "description": "Resume a paused or human-blocked Forge build request from the last completed task checkpoint and requeue the current phase. Human-gated and entitlement-gated.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "build_request_id", "flag": "build-request-id", "type": "string", "required": true }, { "name": "reason", "flag": "reason", "type": "string", "required": false }], "hasComplexInput": true, "help": "Resume a paused or blocked Forge build request from its last completed task checkpoint after human review. Tenant-admin and human-gated." },
@@ -49319,9 +49326,9 @@ function coerceScalar(field3, raw) {
49319
49326
  }
49320
49327
  }
49321
49328
  function rawValueForField(entry, field3, parsed, aliasFlags) {
49322
- const canonical = parsed.values.get(field3.flag);
49323
- if (canonical !== void 0) {
49324
- return canonical;
49329
+ const canonical2 = parsed.values.get(field3.flag);
49330
+ if (canonical2 !== void 0) {
49331
+ return canonical2;
49325
49332
  }
49326
49333
  for (const [aliasFlag, targetField] of aliasFlags) {
49327
49334
  if (targetField === field3.name) {
@@ -51655,11 +51662,247 @@ function parseSemver2(value) {
51655
51662
  // src/runner-serve.ts
51656
51663
  import { execFile as execFileCallback3, spawn } from "node:child_process";
51657
51664
  import { createHash } from "node:crypto";
51658
- import { chmod, mkdir as mkdir2, readFile as readFile4, rename, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
51659
- import { cpus, hostname as hostname3, platform, totalmem, tmpdir } from "node:os";
51660
- import path3 from "node:path";
51665
+ import { chmod, mkdir as mkdir2, readFile as readFile4, rename, rm as rm3, writeFile as writeFile2 } from "node:fs/promises";
51666
+ import { cpus, homedir as homedir2, hostname as hostname3, platform, totalmem, tmpdir } from "node:os";
51667
+ import path4 from "node:path";
51661
51668
  import { setTimeout as sleep2 } from "node:timers/promises";
51662
51669
  import { promisify as promisify3 } from "node:util";
51670
+
51671
+ // src/runner-sandbox.ts
51672
+ import { existsSync as existsSync2, realpathSync } from "node:fs";
51673
+ import { readdir, rm as rm2, stat as stat2 } from "node:fs/promises";
51674
+ import path3 from "node:path";
51675
+ var SANDBOX_EXEC_PATH = "/usr/bin/sandbox-exec";
51676
+ var RUNNER_TEMP_PREFIXES = [
51677
+ "rost-runner-mcp-",
51678
+ "rost-runner-sandbox-",
51679
+ "rost-runner-askpass-"
51680
+ ];
51681
+ var SENSITIVE_READ_SUBPATHS = [
51682
+ ".ssh",
51683
+ ".aws",
51684
+ ".gnupg",
51685
+ ".config/gcloud",
51686
+ ".config/gh",
51687
+ ".kube",
51688
+ ".docker",
51689
+ ".npmrc"
51690
+ ];
51691
+ var SENSITIVE_READ_LITERALS = [".netrc"];
51692
+ var SENSITIVE_WRITE_SUBPATHS = [
51693
+ ".ssh",
51694
+ ".aws",
51695
+ ".gnupg",
51696
+ ".config/git",
51697
+ "bin",
51698
+ "Library/LaunchAgents"
51699
+ ];
51700
+ var SENSITIVE_WRITE_LITERALS = [
51701
+ ".gitconfig",
51702
+ ".zshrc",
51703
+ ".zprofile",
51704
+ ".zshenv",
51705
+ ".zlogin",
51706
+ ".bashrc",
51707
+ ".bash_profile",
51708
+ ".bash_login",
51709
+ ".profile"
51710
+ ];
51711
+ function parseSandboxMode(value) {
51712
+ const normalized = (value ?? "auto").trim().toLowerCase();
51713
+ if (normalized === "off" || normalized === "strict" || normalized === "auto" || normalized === "") {
51714
+ return normalized === "" ? "auto" : normalized;
51715
+ }
51716
+ return "auto";
51717
+ }
51718
+ function parseSandboxNetwork(value) {
51719
+ const normalized = (value ?? "").trim().toLowerCase();
51720
+ if (normalized === "" || normalized === "allow") {
51721
+ return { allowNetwork: true, unrecognized: null };
51722
+ }
51723
+ if (normalized === "deny") {
51724
+ return { allowNetwork: false, unrecognized: null };
51725
+ }
51726
+ return { allowNetwork: true, unrecognized: value ?? "" };
51727
+ }
51728
+ function sbplString(value) {
51729
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
51730
+ }
51731
+ function subpath(value) {
51732
+ return `(subpath ${sbplString(value)})`;
51733
+ }
51734
+ function literal2(value) {
51735
+ return `(literal ${sbplString(value)})`;
51736
+ }
51737
+ var STRICT_SYSTEM_READ_SUBPATHS = [
51738
+ "/usr",
51739
+ "/bin",
51740
+ "/sbin",
51741
+ "/System",
51742
+ "/Library",
51743
+ "/private/etc",
51744
+ "/private/var/db",
51745
+ "/dev",
51746
+ "/opt",
51747
+ "/nix"
51748
+ ];
51749
+ function buildSeatbeltProfile(input) {
51750
+ const home = input.homeDir.replace(/\/+$/, "");
51751
+ const lines = ["(version 1)"];
51752
+ const denyReadBaseSubpaths = input.denyReadPaths.filter((p) => p.endsWith("/")).map((p) => subpath(p.replace(/\/+$/, "")));
51753
+ const denyReadLiterals = input.denyReadPaths.filter((p) => !p.endsWith("/")).map(literal2);
51754
+ const credentialReadDenies = [
51755
+ ...SENSITIVE_READ_SUBPATHS.map((rel) => subpath(`${home}/${rel}`)),
51756
+ ...SENSITIVE_READ_LITERALS.map((rel) => literal2(`${home}/${rel}`))
51757
+ ];
51758
+ if (input.strict) {
51759
+ lines.push("(deny default)");
51760
+ lines.push("(allow process*)");
51761
+ lines.push("(allow sysctl-read)");
51762
+ lines.push("(allow mach-lookup)");
51763
+ lines.push("(allow iokit-open)");
51764
+ lines.push("(allow system-socket)");
51765
+ lines.push("(allow signal (target self))");
51766
+ lines.push("(allow file-read-metadata)");
51767
+ const strictReads = [
51768
+ ...STRICT_SYSTEM_READ_SUBPATHS.map(subpath),
51769
+ subpath(`${home}/.claude`),
51770
+ subpath(`${home}/.codex`),
51771
+ subpath(input.tmpDir)
51772
+ ];
51773
+ lines.push(`(allow file-read* ${strictReads.join(" ")})`);
51774
+ } else {
51775
+ lines.push("(allow default)");
51776
+ }
51777
+ lines.push("(deny appleevent-send)");
51778
+ if (denyReadBaseSubpaths.length > 0) {
51779
+ lines.push(`(deny file-read* ${denyReadBaseSubpaths.join(" ")})`);
51780
+ }
51781
+ const workspaceReads = [subpath(input.workspaceDir), ...input.allowWritePaths.map(subpath)];
51782
+ lines.push(`(allow file-read* ${workspaceReads.join(" ")})`);
51783
+ lines.push("(deny file-write*)");
51784
+ const allowWrites = [
51785
+ subpath(input.workspaceDir),
51786
+ subpath(input.tmpDir),
51787
+ subpath(`${home}/.claude`),
51788
+ subpath(`${home}/.codex`),
51789
+ ...input.allowWritePaths.map(subpath),
51790
+ subpath("/dev/fd"),
51791
+ literal2("/dev/null"),
51792
+ literal2("/dev/stdout"),
51793
+ literal2("/dev/stderr"),
51794
+ literal2("/dev/tty"),
51795
+ literal2("/dev/dtracehelper")
51796
+ ];
51797
+ lines.push(`(allow file-write* ${allowWrites.join(" ")})`);
51798
+ lines.push(`(deny file-read* ${[...credentialReadDenies, ...denyReadLiterals].join(" ")})`);
51799
+ const denyWrites = [
51800
+ ...SENSITIVE_WRITE_SUBPATHS.map((rel) => subpath(`${home}/${rel}`)),
51801
+ ...SENSITIVE_WRITE_LITERALS.map((rel) => literal2(`${home}/${rel}`))
51802
+ ];
51803
+ lines.push(`(deny file-write* ${denyWrites.join(" ")})`);
51804
+ lines.push('(deny mach-lookup (global-name "com.apple.coreservices.appleevents"))');
51805
+ if (!input.allowNetwork) {
51806
+ lines.push("(deny network*)");
51807
+ } else if (input.strict) {
51808
+ lines.push("(allow network*)");
51809
+ }
51810
+ return `${lines.join("\n")}
51811
+ `;
51812
+ }
51813
+ function canonical(p) {
51814
+ try {
51815
+ return realpathSync(p);
51816
+ } catch {
51817
+ return p;
51818
+ }
51819
+ }
51820
+ function canonicalFile(p) {
51821
+ return path3.join(canonical(path3.dirname(p)), path3.basename(p));
51822
+ }
51823
+ function isSandboxExecAvailable() {
51824
+ try {
51825
+ return existsSync2(SANDBOX_EXEC_PATH);
51826
+ } catch {
51827
+ return false;
51828
+ }
51829
+ }
51830
+ function resolveSandboxSpec(options) {
51831
+ if (options.mode === "off") {
51832
+ return { kind: "none", reason: "RUNNER_SANDBOX=off (loosened fallback - OS confinement disabled)" };
51833
+ }
51834
+ if (options.platform !== "darwin") {
51835
+ return { kind: "none", reason: `no OS sandbox on ${options.platform} (seatbelt is macOS-only; Linux confinement is a follow-up)` };
51836
+ }
51837
+ const available = options.sandboxExecAvailable ?? isSandboxExecAvailable();
51838
+ if (!available) {
51839
+ return { kind: "none", reason: `sandbox-exec not found at ${SANDBOX_EXEC_PATH}` };
51840
+ }
51841
+ const workspaceDir = canonical(options.workspaceDir);
51842
+ const tmpDir = canonical(options.tmpDir);
51843
+ const homeDir = canonical(options.homeDir);
51844
+ const allowNetwork = options.allowNetwork ?? true;
51845
+ const denyReadPaths = [canonicalFile(options.stateFile)];
51846
+ if (options.runnerBaseDir) {
51847
+ denyReadPaths.push(`${canonical(options.runnerBaseDir).replace(/\/+$/, "")}/`);
51848
+ }
51849
+ const profile = buildSeatbeltProfile({
51850
+ workspaceDir,
51851
+ tmpDir,
51852
+ homeDir,
51853
+ denyReadPaths,
51854
+ allowWritePaths: (options.allowWritePaths ?? []).map(canonical),
51855
+ allowNetwork,
51856
+ strict: options.mode === "strict"
51857
+ });
51858
+ return {
51859
+ kind: "seatbelt",
51860
+ reason: options.mode === "strict" ? "seatbelt (strict: deny-default reads + write confinement)" : "seatbelt (guard: deny sensitive reads/writes)",
51861
+ profile,
51862
+ allowNetwork
51863
+ };
51864
+ }
51865
+ function wrapCommandWithSandbox(spec, command, args, profilePath) {
51866
+ if (spec.kind === "none" || profilePath === null) {
51867
+ return { command, args, profilePath: null };
51868
+ }
51869
+ return {
51870
+ command: SANDBOX_EXEC_PATH,
51871
+ args: ["-f", profilePath, command, ...args],
51872
+ profilePath
51873
+ };
51874
+ }
51875
+ async function sweepStaleRunnerTempFiles(dir, options = {}) {
51876
+ const maxAgeMs = options.maxAgeMs ?? 60 * 60 * 1e3;
51877
+ const now = options.now ?? Date.now();
51878
+ let entries;
51879
+ try {
51880
+ entries = await readdir(dir);
51881
+ } catch {
51882
+ return [];
51883
+ }
51884
+ const removed = [];
51885
+ await Promise.all(
51886
+ entries.map(async (name) => {
51887
+ if (!RUNNER_TEMP_PREFIXES.some((prefix) => name.startsWith(prefix))) {
51888
+ return;
51889
+ }
51890
+ const full = path3.join(dir, name);
51891
+ try {
51892
+ const info = await stat2(full);
51893
+ if (now - info.mtimeMs < maxAgeMs) {
51894
+ return;
51895
+ }
51896
+ await rm2(full, { force: true });
51897
+ removed.push(name);
51898
+ } catch {
51899
+ }
51900
+ })
51901
+ );
51902
+ return removed;
51903
+ }
51904
+
51905
+ // src/runner-serve.ts
51663
51906
  var execFile3 = promisify3(execFileCallback3);
51664
51907
  var RUNNER_WORK_ORDER_TOOLS = [
51665
51908
  "mcp__rost__rost_get_context",
@@ -51685,6 +51928,20 @@ ${usage()}
51685
51928
  }
51686
51929
  options.io.stdout.write(`runner serve: ${options.appUrl} name=${config2.name} execute=${config2.execute}
51687
51930
  `);
51931
+ options.io.stdout.write(`runner sandbox: ${config2.sandbox.kind} - ${config2.sandbox.reason}
51932
+ `);
51933
+ if (config2.sandboxNetworkWarning) {
51934
+ options.io.stderr.write(`warning: ${config2.sandboxNetworkWarning}
51935
+ `);
51936
+ }
51937
+ try {
51938
+ const swept = await sweepStaleRunnerTempFiles(tmpdir());
51939
+ if (swept.length > 0) {
51940
+ options.io.stdout.write(`swept ${swept.length} stale runner temp file(s)
51941
+ `);
51942
+ }
51943
+ } catch {
51944
+ }
51688
51945
  let state;
51689
51946
  try {
51690
51947
  state = await pairIfNeeded(options.client ?? null, fetchImpl, options.appUrl, config2, options.io);
@@ -51770,7 +52027,7 @@ function detectTelemetry(capabilities, env, activeSessions) {
51770
52027
  }
51771
52028
  function parseServeArgs(args, appUrl2, env) {
51772
52029
  let name = env.RUNNER_NAME ?? `headless-${hostname3()}`;
51773
- let stateFile = env.RUNNER_STATE_FILE ?? path3.join(tmpdir(), `rost-runner-${createHash("sha1").update(appUrl2).digest("hex").slice(0, 8)}.json`);
52030
+ let stateFile = env.RUNNER_STATE_FILE ?? path4.join(tmpdir(), `rost-runner-${createHash("sha1").update(appUrl2).digest("hex").slice(0, 8)}.json`);
51774
52031
  let heartbeatMs = Number(env.RUNNER_HEARTBEAT_MS ?? 3e4);
51775
52032
  let interactiveClaimMs = Number(env.RUNNER_INTERACTIVE_CLAIM_MS ?? 1e3);
51776
52033
  let execute2 = env.RUNNER_EXECUTE === "1";
@@ -51812,7 +52069,29 @@ function parseServeArgs(args, appUrl2, env) {
51812
52069
  throw new Error("--interactive-claim-ms must be an integer >= 250.");
51813
52070
  }
51814
52071
  interactiveClaimMs = Math.min(interactiveClaimMs, heartbeatMs);
51815
- return { name, stateFile, heartbeatMs, interactiveClaimMs, execute: execute2, once, runtime, ...userCode ? { userCode } : {} };
52072
+ const network = parseSandboxNetwork(env.RUNNER_SANDBOX_NETWORK);
52073
+ const sandbox = resolveSandboxSpec({
52074
+ mode: parseSandboxMode(env.RUNNER_SANDBOX),
52075
+ platform: process.platform,
52076
+ workspaceDir: tmpdir(),
52077
+ tmpDir: tmpdir(),
52078
+ homeDir: homedir2(),
52079
+ stateFile,
52080
+ runnerBaseDir: path4.join(homedir2(), ".rost"),
52081
+ allowNetwork: network.allowNetwork
52082
+ });
52083
+ return {
52084
+ name,
52085
+ stateFile,
52086
+ heartbeatMs,
52087
+ interactiveClaimMs,
52088
+ execute: execute2,
52089
+ once,
52090
+ runtime,
52091
+ sandbox,
52092
+ ...network.unrecognized !== null ? { sandboxNetworkWarning: `RUNNER_SANDBOX_NETWORK='${network.unrecognized}' not recognized (use allow|deny); defaulting to allow egress.` } : {},
52093
+ ...userCode ? { userCode } : {}
52094
+ };
51816
52095
  }
51817
52096
  function parseRuntime(value) {
51818
52097
  if (value === "auto" || value === "claude" || value === "codex") {
@@ -51932,7 +52211,7 @@ function isRuntimeSessionMap(value) {
51932
52211
  });
51933
52212
  }
51934
52213
  async function saveState(filePath, state) {
51935
- await mkdir2(path3.dirname(filePath), { recursive: true, mode: 448 });
52214
+ await mkdir2(path4.dirname(filePath), { recursive: true, mode: 448 });
51936
52215
  const tempPath = `${filePath}.${process.pid}.${createHash("sha1").update(`${Date.now()}:${Math.random()}`).digest("hex").slice(0, 8)}.tmp`;
51937
52216
  try {
51938
52217
  await writeFile2(tempPath, `${JSON.stringify(state)}
@@ -51940,7 +52219,7 @@ async function saveState(filePath, state) {
51940
52219
  await chmod(tempPath, 384);
51941
52220
  await rename(tempPath, filePath);
51942
52221
  } catch (error51) {
51943
- await rm2(tempPath, { force: true });
52222
+ await rm3(tempPath, { force: true });
51944
52223
  throw error51;
51945
52224
  }
51946
52225
  }
@@ -52026,7 +52305,7 @@ async function executeClaimedUnit(fetchImpl, appUrl2, state, config2, io, runtim
52026
52305
  throw new Error(`${claimedUnitLabel(input.kind)} start failed ${started.status}: ${redactForLog(JSON.stringify(started.json))}`);
52027
52306
  }
52028
52307
  const turnRuntime = runtimeForClaimedUnit(input.item, runtime, input.kind);
52029
- const result = turnRuntime ? await runLocalTurn(appUrl2, input.item, turnRuntime, input.kind, state) : {
52308
+ const result = turnRuntime ? await runLocalTurn(appUrl2, input.item, turnRuntime, input.kind, state, config2.sandbox) : {
52030
52309
  ok: false,
52031
52310
  summary: `Runner runtime ${runtime} cannot execute requested ${String(input.item.brain ?? "unknown")} turn.`
52032
52311
  };
@@ -52066,14 +52345,14 @@ function runtimeForClaimedUnit(item, fallback, kind) {
52066
52345
  }
52067
52346
  return fallback === "claude" ? "claude" : null;
52068
52347
  }
52069
- function runLocalTurn(appUrl2, workOrder, runtime, kind, state) {
52070
- return runtime === "codex" ? runCodexTurn(appUrl2, workOrder, kind) : runClaudeTurn(appUrl2, workOrder, kind, state);
52348
+ function runLocalTurn(appUrl2, workOrder, runtime, kind, state, sandbox) {
52349
+ return runtime === "codex" ? runCodexTurn(appUrl2, workOrder, kind, sandbox) : runClaudeTurn(appUrl2, workOrder, kind, state, sandbox);
52071
52350
  }
52072
- function runClaudeTurn(appUrl2, workOrder, kind, state) {
52073
- return spawnRunnerTurn(appUrl2, workOrder, "claude", kind, state);
52351
+ function runClaudeTurn(appUrl2, workOrder, kind, state, sandbox) {
52352
+ return spawnRunnerTurn(appUrl2, workOrder, "claude", kind, sandbox, state);
52074
52353
  }
52075
- function runCodexTurn(appUrl2, workOrder, kind) {
52076
- return spawnRunnerTurn(appUrl2, workOrder, "codex", kind);
52354
+ function runCodexTurn(appUrl2, workOrder, kind, sandbox) {
52355
+ return spawnRunnerTurn(appUrl2, workOrder, "codex", kind, sandbox);
52077
52356
  }
52078
52357
  function buildTurnCommand(runtime, prompt, configPath, kind, resumeSessionId) {
52079
52358
  if (runtime === "codex") {
@@ -52098,12 +52377,12 @@ function buildTurnCommand(runtime, prompt, configPath, kind, resumeSessionId) {
52098
52377
  ]
52099
52378
  };
52100
52379
  }
52101
- async function spawnRunnerTurn(appUrl2, workOrder, runtime, kind, state) {
52380
+ async function spawnRunnerTurn(appUrl2, workOrder, runtime, kind, sandbox, state) {
52102
52381
  const mcp = typeof workOrder.mcp === "object" && workOrder.mcp !== null ? workOrder.mcp : {};
52103
52382
  const token = typeof mcp.token === "string" ? mcp.token : "";
52104
52383
  const url2 = typeof mcp.url === "string" ? `${appUrl2.replace(/\/+$/, "")}${mcp.url}` : `${appUrl2.replace(/\/+$/, "")}/mcp`;
52105
52384
  const mcpConfig = JSON.stringify({ mcpServers: { rost: { type: "http", url: url2, headers: { Authorization: `Bearer ${token}` } } } });
52106
- const configPath = path3.join(tmpdir(), `rost-runner-mcp-${createHash("sha256").update(`${url2}:${Date.now()}:${Math.random()}`).digest("hex").slice(0, 16)}.json`);
52385
+ const configPath = path4.join(tmpdir(), `rost-runner-mcp-${createHash("sha256").update(`${url2}:${Date.now()}:${Math.random()}`).digest("hex").slice(0, 16)}.json`);
52107
52386
  const context = {
52108
52387
  work_order_id: kind === "work_order" ? workOrder.id : null,
52109
52388
  turn_execution_id: kind === "turn_execution" ? workOrder.id : null,
@@ -52133,7 +52412,17 @@ async function spawnRunnerTurn(appUrl2, workOrder, runtime, kind, state) {
52133
52412
  `, { mode: 384 });
52134
52413
  await chmod(configPath, 384);
52135
52414
  const resumeSessionId = runtime === "claude" ? getClaudeResumeSessionId(state, workOrder) : void 0;
52136
- const { command, args } = buildTurnCommand(runtime, prompt, configPath, kind, resumeSessionId);
52415
+ const built = buildTurnCommand(runtime, prompt, configPath, kind, resumeSessionId);
52416
+ let sandboxProfilePath = null;
52417
+ if (sandbox.kind === "seatbelt") {
52418
+ sandboxProfilePath = path4.join(
52419
+ tmpdir(),
52420
+ `rost-runner-sandbox-${createHash("sha256").update(`${configPath}:${Math.random()}`).digest("hex").slice(0, 16)}.sb`
52421
+ );
52422
+ await writeFile2(sandboxProfilePath, sandbox.profile, { mode: 384 });
52423
+ await chmod(sandboxProfilePath, 384);
52424
+ }
52425
+ const { command, args } = wrapCommandWithSandbox(sandbox, built.command, built.args, sandboxProfilePath);
52137
52426
  const expectSessionEnvelope = runtime === "claude" && kind === "turn_execution";
52138
52427
  return new Promise((resolve2) => {
52139
52428
  let settled = false;
@@ -52142,7 +52431,10 @@ async function spawnRunnerTurn(appUrl2, workOrder, runtime, kind, state) {
52142
52431
  return;
52143
52432
  }
52144
52433
  settled = true;
52145
- void rm2(configPath, { force: true });
52434
+ void rm3(configPath, { force: true });
52435
+ if (sandboxProfilePath) {
52436
+ void rm3(sandboxProfilePath, { force: true });
52437
+ }
52146
52438
  resolve2(resumeSessionId ? { ...result, resumedFromSessionId: resumeSessionId } : result);
52147
52439
  };
52148
52440
  const child = spawn(command, args, {
@@ -52275,10 +52567,10 @@ function usage() {
52275
52567
 
52276
52568
  // src/runner-service.ts
52277
52569
  import { execFile as execFileCallback4 } from "node:child_process";
52278
- import { realpathSync } from "node:fs";
52279
- import { access as access2, mkdir as mkdir3, readFile as readFile5, rm as rm3, writeFile as writeFile3 } from "node:fs/promises";
52280
- import { homedir as homedir2 } from "node:os";
52281
- import path4 from "node:path";
52570
+ import { realpathSync as realpathSync2 } from "node:fs";
52571
+ import { access as access2, mkdir as mkdir3, readFile as readFile5, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
52572
+ import { homedir as homedir3 } from "node:os";
52573
+ import path5 from "node:path";
52282
52574
  import { promisify as promisify4 } from "node:util";
52283
52575
  var execFile4 = promisify4(execFileCallback4);
52284
52576
  var SERVICE_VERBS = ["install-service", "start", "stop", "restart", "status", "logs", "uninstall"];
@@ -52295,9 +52587,9 @@ function isRunnerServiceInvocation(args) {
52295
52587
  function runnerServicePaths(homeDir, runnerName) {
52296
52588
  const safeName = safeRunnerName(runnerName);
52297
52589
  return {
52298
- launchAgentPath: path4.join(homeDir, "Library", "LaunchAgents", `${serviceLabel(safeName)}.plist`),
52299
- stateFile: path4.join(homeDir, "Library", "Application Support", cliBrand.name, `runner-${safeName}.json`),
52300
- logDir: path4.join(homeDir, "Library", "Logs", cliBrand.name, "runner", safeName)
52590
+ launchAgentPath: path5.join(homeDir, "Library", "LaunchAgents", `${serviceLabel(safeName)}.plist`),
52591
+ stateFile: path5.join(homeDir, "Library", "Application Support", cliBrand.name, `runner-${safeName}.json`),
52592
+ logDir: path5.join(homeDir, "Library", "Logs", cliBrand.name, "runner", safeName)
52301
52593
  };
52302
52594
  }
52303
52595
  function renderLaunchdPlist(config2) {
@@ -52337,9 +52629,9 @@ ${programArgsXml}
52337
52629
  <key>RunAtLoad</key>
52338
52630
  <true/>
52339
52631
  <key>StandardOutPath</key>
52340
- <string>${escapePlist(path4.join(config2.logDir, "stdout.log"))}</string>
52632
+ <string>${escapePlist(path5.join(config2.logDir, "stdout.log"))}</string>
52341
52633
  <key>StandardErrorPath</key>
52342
- <string>${escapePlist(path4.join(config2.logDir, "stderr.log"))}</string>
52634
+ <string>${escapePlist(path5.join(config2.logDir, "stderr.log"))}</string>
52343
52635
  </dict>
52344
52636
  </plist>
52345
52637
  `;
@@ -52374,7 +52666,7 @@ ${serviceUsage()}
52374
52666
  userCode: parsed.userCode,
52375
52667
  nodePath: options.nodePath ?? resolveNodePath(),
52376
52668
  binPath: options.binPath ?? resolveBinPath(),
52377
- homeDir: options.homeDir ?? homedir2(),
52669
+ homeDir: options.homeDir ?? homedir3(),
52378
52670
  platform: options.platform ?? process.platform,
52379
52671
  exec: options.exec ?? defaultExec,
52380
52672
  uid: options.uid ?? (process.getuid ? process.getuid() : 0)
@@ -52406,8 +52698,8 @@ async function installService(options) {
52406
52698
  const paths = runnerServicePaths(options.homeDir, safeName);
52407
52699
  const label = serviceLabel(safeName);
52408
52700
  await mkdir3(paths.logDir, { recursive: true });
52409
- await mkdir3(path4.dirname(paths.stateFile), { recursive: true });
52410
- await mkdir3(path4.dirname(paths.launchAgentPath), { recursive: true });
52701
+ await mkdir3(path5.dirname(paths.stateFile), { recursive: true });
52702
+ await mkdir3(path5.dirname(paths.launchAgentPath), { recursive: true });
52411
52703
  if (options.userCode !== void 0) {
52412
52704
  const paired = await pairRunnerOnce(options, safeName, paths.stateFile, options.userCode);
52413
52705
  if (!paired) {
@@ -52585,8 +52877,8 @@ async function logsService(options) {
52585
52877
  const safeName = safeRunnerName(options.runnerName);
52586
52878
  const paths = runnerServicePaths(options.homeDir, safeName);
52587
52879
  const label = serviceLabel(safeName);
52588
- const stdoutLog = path4.join(paths.logDir, "stdout.log");
52589
- const stderrLog = path4.join(paths.logDir, "stderr.log");
52880
+ const stdoutLog = path5.join(paths.logDir, "stdout.log");
52881
+ const stderrLog = path5.join(paths.logDir, "stderr.log");
52590
52882
  options.io.stdout.write(
52591
52883
  `${[
52592
52884
  `runner service logs for ${label}:`,
@@ -52617,7 +52909,7 @@ async function uninstallService(options) {
52617
52909
  await bootoutQuietly(options.exec, options.uid, paths.launchAgentPath);
52618
52910
  let removed = false;
52619
52911
  try {
52620
- await rm3(paths.launchAgentPath, { force: true });
52912
+ await rm4(paths.launchAgentPath, { force: true });
52621
52913
  removed = true;
52622
52914
  } catch (error51) {
52623
52915
  options.io.stderr.write(
@@ -52761,11 +53053,11 @@ function resolveNodePath() {
52761
53053
  }
52762
53054
  function buildServicePath(nodePath, homeDir) {
52763
53055
  const entries = [
52764
- path4.dirname(nodePath),
53056
+ path5.dirname(nodePath),
52765
53057
  "/opt/homebrew/bin",
52766
53058
  "/usr/local/bin",
52767
- path4.join(homeDir, ".local", "bin"),
52768
- path4.join(homeDir, "bin"),
53059
+ path5.join(homeDir, ".local", "bin"),
53060
+ path5.join(homeDir, "bin"),
52769
53061
  "/usr/bin",
52770
53062
  "/bin",
52771
53063
  "/usr/sbin",
@@ -52810,7 +53102,7 @@ function resolveBinPath() {
52810
53102
  return cliBrand.binName;
52811
53103
  }
52812
53104
  try {
52813
- return realpathSync(entry);
53105
+ return realpathSync2(entry);
52814
53106
  } catch {
52815
53107
  return entry;
52816
53108
  }
@@ -53473,7 +53765,7 @@ function isCliEntrypoint(moduleUrl, argvPath) {
53473
53765
  return false;
53474
53766
  }
53475
53767
  try {
53476
- return realpathSync2(fileURLToPath3(moduleUrl)) === realpathSync2(argvPath);
53768
+ return realpathSync3(fileURLToPath3(moduleUrl)) === realpathSync3(argvPath);
53477
53769
  } catch {
53478
53770
  return moduleUrl === pathToFileURL(argvPath).href;
53479
53771
  }