@rosthq/cli 0.7.28 → 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(),
@@ -43312,6 +43319,82 @@ var softwareGithubIssueIntakeMetadataSchema = external_exports.object({
43312
43319
  labels: external_exports.array(external_exports.string().trim().min(1).max(80)).max(50).default([]),
43313
43320
  requested_automation_mode: softwareAutomationModeSchema.optional()
43314
43321
  }).strict();
43322
+ var softwareVercelDeploymentStatusSchema = external_exports.enum(["queued", "initializing", "building", "ready", "error", "canceled", "unknown"]);
43323
+ var softwareVercelConnectionConnectInputSchema = external_exports.object({
43324
+ account_id: external_exports.string().trim().min(1).max(200),
43325
+ account_name: external_exports.string().trim().min(1).max(255).nullable().optional(),
43326
+ team_id: external_exports.string().trim().min(1).max(200).nullable().optional(),
43327
+ team_slug: external_exports.string().trim().min(1).max(255).nullable().optional(),
43328
+ user_id: external_exports.string().trim().min(1).max(200).nullable().optional(),
43329
+ configuration_id: external_exports.string().trim().min(1).max(200).nullable().optional(),
43330
+ access_token_vault_ref: external_exports.string().trim().min(12).max(700),
43331
+ scopes: external_exports.array(external_exports.string().trim().min(1).max(120)).max(40).default([])
43332
+ }).strict();
43333
+ var softwareVercelConnectionConnectOutputSchema = external_exports.object({
43334
+ vercel_connection_id: uuid10,
43335
+ account_id: external_exports.string(),
43336
+ team_id: external_exports.string().nullable()
43337
+ });
43338
+ var softwareVercelProjectLinkInputSchema = external_exports.object({
43339
+ software_project_id: uuid10,
43340
+ vercel_connection_id: uuid10,
43341
+ vercel_project_id: external_exports.string().trim().min(1).max(200),
43342
+ vercel_project_name: external_exports.string().trim().min(1).max(255),
43343
+ framework: external_exports.string().trim().min(1).max(120).nullable().optional(),
43344
+ root_directory: external_exports.string().trim().min(1).max(500).nullable().optional(),
43345
+ production_branch: external_exports.string().trim().min(1).max(255).nullable().optional(),
43346
+ git_repository: external_exports.record(external_exports.string().min(1), external_exports.unknown()).default({})
43347
+ }).strict();
43348
+ var softwareVercelProjectLinkOutputSchema = external_exports.object({
43349
+ vercel_project_link_id: uuid10,
43350
+ software_project_id: uuid10,
43351
+ vercel_project_id: external_exports.string()
43352
+ });
43353
+ var softwareVercelConnectionViewSchema = external_exports.object({
43354
+ id: uuid10,
43355
+ account_id: external_exports.string(),
43356
+ account_name: external_exports.string().nullable(),
43357
+ team_id: external_exports.string().nullable(),
43358
+ team_slug: external_exports.string().nullable(),
43359
+ user_id: external_exports.string().nullable(),
43360
+ configuration_id: external_exports.string().nullable(),
43361
+ status: external_exports.enum(["active", "revoked"]),
43362
+ connected_at: external_exports.string()
43363
+ });
43364
+ var softwareVercelProjectLinkViewSchema = external_exports.object({
43365
+ id: uuid10,
43366
+ software_project_id: uuid10,
43367
+ software_project_name: external_exports.string(),
43368
+ software_project_slug: external_exports.string(),
43369
+ vercel_connection_id: uuid10,
43370
+ vercel_project_id: external_exports.string(),
43371
+ vercel_project_name: external_exports.string(),
43372
+ framework: external_exports.string().nullable(),
43373
+ root_directory: external_exports.string().nullable(),
43374
+ production_branch: external_exports.string().nullable(),
43375
+ status: external_exports.enum(["active", "revoked"])
43376
+ });
43377
+ var softwareVercelProjectListInputSchema = external_exports.object({}).strict();
43378
+ var softwareVercelProjectListOutputSchema = external_exports.object({
43379
+ connections: external_exports.array(softwareVercelConnectionViewSchema),
43380
+ project_links: external_exports.array(softwareVercelProjectLinkViewSchema)
43381
+ });
43382
+ var softwareVercelDeployPreviewInputSchema = external_exports.object({
43383
+ software_project_id: uuid10,
43384
+ commit_sha: external_exports.string().trim().regex(/^[A-Fa-f0-9]{7,64}$/),
43385
+ git_ref: external_exports.string().trim().min(1).max(255),
43386
+ build_request_id: uuid10.optional(),
43387
+ phase_run_id: uuid10.optional(),
43388
+ seat_id: uuid10.optional(),
43389
+ required_secret_keys: external_exports.array(external_exports.string().trim().min(1).max(200)).max(25).default([])
43390
+ }).strict();
43391
+ var softwareVercelDeployPreviewOutputSchema = external_exports.object({
43392
+ vercel_deployment_record_id: uuid10,
43393
+ deployment_id: external_exports.string(),
43394
+ deployment_url: external_exports.string().nullable(),
43395
+ commit_sha: external_exports.string(),
43396
+ status: softwareVercelDeploymentStatusSchema
43397
+ });
43315
43398
 
43316
43399
  // ../../packages/protocol/src/sync-brief.ts
43317
43400
  var uuidSchema14 = external_exports.string().uuid();
@@ -44556,7 +44639,7 @@ No orphan agents. No raw secrets in prompts, logs, or tool arguments. No durable
44556
44639
  order: 41,
44557
44640
  title: "Add agents to your Responsibility Graph",
44558
44641
  summary: "The visual journey for adding an agent seat: where to start, choosing a mode, placing the seat, naming a Steward, setup, the safety gates, and go-live.",
44559
- version: "2026-07-04.1",
44642
+ version: "2026-07-04.2",
44560
44643
  public: true,
44561
44644
  audiences: ["human", "in_app_agent"],
44562
44645
  stages: ["staffing"],
@@ -44578,7 +44661,7 @@ A seat is a function, not a person. Adding an agent means staffing a seat with a
44578
44661
  You can open agent creation from three places:
44579
44662
 
44580
44663
  - **Responsibility Graph.** Hover or focus a seat node and use its **Add agent** action to add an agent seat under that seat. The graph toolbar's **Add agent** action adds an agent and then asks where it should report. On an empty or small graph, **Add first agent** starts the same flow with the founder or owner seat as the default Steward.
44581
- - **Left sidebar.** The **Agents** item in the Define section opens the agents surface. **Add agent** at the top of that surface is the primary action.
44664
+ - **Left sidebar.** The **Agents** item opens the agents surface. **Add agent** at the top of that surface is the primary action.
44582
44665
  - **Agents surface.** The unified agents page has **Templates**, **Custom**, and **Existing** modes. A template's **Use template** action enters the same wizard with that template selected.
44583
44666
 
44584
44667
  Starting from a selected graph seat carries the parent seat and a default Steward candidate into the wizard so you do not re-pick them.
@@ -45049,7 +45132,7 @@ External connectors are being rolled out provider by provider, conservatively (r
45049
45132
  order: 48,
45050
45133
  title: "CLI and MCP installation guide",
45051
45134
  summary: "Install the public CLI, register remote token-backed MCP clients, and find the full command and tool catalog.",
45052
- version: "2026-07-04.19",
45135
+ version: "2026-07-05.1",
45053
45136
  public: true,
45054
45137
  audiences: ["human", "cli", "mcp", "in_app_agent"],
45055
45138
  stages: ["company_setup", "staffing"],
@@ -45497,7 +45580,7 @@ These ergonomic wrappers (including the \`{{cli}} agent\` group) require **{{cli
45497
45580
 
45498
45581
  **Generated verbs (0.6.0+).** Every registry command now has a first-class \`{{cli}} <namespace> <action>\` verb, projected directly from the command's input schema \u2014 so namespaces that previously had no ergonomic wrapper (for example \`{{cli}} measurable create\`, \`{{cli}} work_order enqueue\`, \`{{cli}} mcp_token create\`) are usable without dropping to the raw \`command <id>\` path. Flags follow the schema field names (\`--seat-id\`, \`--name\`, \u2026); read a command's exact input offline with \`{{cli}} <namespace> <action> --schema\` before calling it, and pass a nested payload with \`--input '<json>'\` when a field is a complex object. \`--json\` still selects machine output, \`--seat <id>\` still sets seat scope, and \`{{cli}} command <id> --json '<body>'\` remains the universal fallback.
45499
45582
 
45500
- The signed-in app exposes the same Skill command surface under **Skills** in the left sidebar. Use \`/skills\` to filter the tenant library, \`/skills/new\` to build canonical \`SKILL.md\` frontmatter with \`tool.catalog\` dependencies, \`/skills/import\` for bounded GitHub or upload imports, and each Skill detail page to review validation, publish, check Seat dependencies, and assign a published version. During \`/agents/new\`, \`agent_setup.get\` can return \`next_action: "choose_skills"\`; selected Skills are proposed immutable-version assignments and required Skill tools block sign-off/go-live until tool configuration is ready.
45583
+ The signed-in app exposes the same Skill command surface at **Skills**, linked from the Agents page (the nav collapse moved it out of the primary sidebar). Use \`/skills\` to filter the tenant library, \`/skills/new\` to build canonical \`SKILL.md\` frontmatter with \`tool.catalog\` dependencies, \`/skills/import\` for bounded GitHub or upload imports, and each Skill detail page to review validation, publish, check Seat dependencies, and assign a published version. During \`/agents/new\`, \`agent_setup.get\` can return \`next_action: "choose_skills"\`; selected Skills are proposed immutable-version assignments and required Skill tools block sign-off/go-live until tool configuration is ready.
45501
45584
 
45502
45585
  | Command | Command ids | Purpose | Scope | Safe example |
45503
45586
  |---|---|---|---|---|
@@ -45528,7 +45611,7 @@ The signed-in app exposes the same Skill command surface under **Skills** in the
45528
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\` |
45529
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\` |
45530
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\` |
45531
- | \`{{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"\` |
45532
45615
 
45533
45616
  Skills wrapper help:
45534
45617
 
@@ -45769,7 +45852,7 @@ Several rows here are seat-operating commands (\`task.create\`, the \`signal.*\`
45769
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. |
45770
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 \`{}\`. |
45771
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. |
45772
- | \`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>", ...}\`). |
45773
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. |
45774
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. |
45775
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. |
@@ -45795,6 +45878,8 @@ Several rows here are seat-operating commands (\`task.create\`, the \`signal.*\`
45795
45878
  | \`rost_set_a_forge_github_automation_policy\` | \`software_factory.github.policy.upsert\` | Set the server-side GitHub automation policy: allowed actions, automation-mode ceiling, and operations requiring humans. The allowed-actions and human-gated-actions lists are stored server-side as structured JSON arrays. Owner-only and human-gated. | Tenant-admin | Call with \`{"software_project_id":"<project-id>","automation_mode_ceiling":"pr_only","allowed_actions":["webhook_intake","installation_token"],"requires_human_for":["deploy_production"]}\`. |
45796
45879
  | \`rost_create_a_forge_github_installation_token\` | \`software_factory.github.installation_token.create\` | Mint a short-lived GitHub App installation token for one bound repository after policy evaluation. The token is returned once and never stored. | Tenant | Call with \`{"software_project_id":"<project-id>","repository_id":789,"requested_actions":["read"],"requested_automation_mode":"plan_only"}\`. |
45797
45880
  | \`rost_list_forge_github_installations\` | \`software_factory.github.installation.list\` | List connected GitHub App installations and each repository's bind state (connected or active) with the Forge project it maps to. Read-only. | Tenant | Call with \`{}\`; a \`connected\` repository is verified but not yet bound to a project. |
45881
+ | \`rost_list_forge_vercel_links\` | \`software_factory.vercel.project.list\` | List connected Vercel accounts/teams and linked existing Vercel projects. Read-only; no OAuth tokens or vault refs are returned. | Tenant | Call with \`{}\`; tenant admins connect/link from \`/forge/settings/vercel\`. |
45882
+ | \`rost_create_a_forge_vercel_preview_deployment\` | \`software_factory.vercel.deploy_preview\` | Create a Vercel preview deployment for a linked project after Forge deploy-preview authority, the Vercel OAuth vault-ref connection, and required preview secret/config grants are verified. | Tenant | Call with \`{"software_project_id":"<project-id>","commit_sha":"<sha>","git_ref":"feature/example","seat_id":"<seat-id>"}\`; returns deployment metadata, not tokens. |
45798
45883
  | \`rost_list_forge_runner_capacity\` | \`software_factory.capacity.list\` | List Forge runner capacity observations (advisory scheduling input only, never an authority input). Read-only; requires the Forge add-on. | Tenant | Call with \`{}\` or \`{"runner_id":"<runner-id>"}\`. |
45799
45884
 
45800
45885
  The web Forge control room at \`/forge\` uses the same command path for project selection, build request creation/list/detail reads, request pause/cancel/resume controls, runner capacity, configuration status, and Developer Team install. A request detail page shows phase history, open gates, plan progress, loop/time/cost ceilings, and latest plan-review questions from \`software_factory.request.show\`; answering a clarification, approving a plan, rejecting it, pausing, canceling, or resuming calls the same Forge commands exposed over CLI/MCP. Lifecycle controls deliberately stage pending confirmations before changing runner state. The install button also stages a pending confirmation before durable seats, Skills, authority grants, or live agent activation are applied; the install page states plainly that approving activates the 6 agents at a pull-request-only, observe-first posture and surfaces the company autonomy ceiling.
@@ -45809,6 +45894,12 @@ Tenant GitHub App connect and bind are intentionally not CLI or MCP mutation too
45809
45894
 
45810
45895
  The Forge GitHub App itself is org-owned. App private key, webhook secret, and client secret values are platform-owned ROST operator credentials stored through the vault's reserved ROST system namespace, not through a customer tenant namespace or a tenant UUID deployment variable. Manifest conversion and manual secret storage also require the app-level \`GITHUB_APP_OPERATOR_EMAILS\` allowlist, which should contain only approved ROST platform operator emails. Deployment config carries only refs (\`GITHUB_APP_PRIVATE_KEY_REF\`, \`GITHUB_APP_WEBHOOK_SECRET_REF\`, \`GITHUB_APP_CLIENT_SECRET_REF\`); raw secret env vars are local/legacy compatibility only. The dogfood app is \`ROST Forge\` under \`agenticoperatingsystem\` with App ID \`4208495\` and Client ID \`Iv23liImH7qlBvXYQ0Wx\`. The base app omits \`administration\` and \`workflows\`; elevated repo creation or workflow mutation needs a separate human-approved power path.
45811
45896
 
45897
+ #### Forge Vercel access
45898
+
45899
+ Forge Vercel access is through a tenant-bound OAuth connection, never a browser-visible token. Tenant admins start at \`/forge/settings/vercel\`; the callback exchanges the short-lived Vercel code server-side, stores the access token in the vault, and records only account/team metadata plus the vault-ref pointer via \`software_factory.vercel.connection.connect\`. Existing Vercel projects are linked through \`software_factory.vercel.project.link\`; greenfield project creation is intentionally absent from this slice and requires a future explicit authority flag.
45900
+
45901
+ Preview deployment creation is a governed command. \`software_factory.vercel.deploy_preview\` requires an active Vercel project link, a Vercel OAuth vault-ref connection, active \`deploy_preview\` Forge authority for the requesting seat when a seat is supplied, and any requested preview secret/config grants. It writes the deployment id, URL, commit SHA, checks, status, and error metadata into \`software_vercel_deployments\`; build-request deployments also write a Forge artifact and event. Raw OAuth tokens and vault refs are never returned.
45902
+
45812
45903
  ### Seat-scoped operating tools
45813
45904
 
45814
45905
  Seat-scoped MCP tokens expose the operating protocol below. The server still checks the seat's permission manifest, Charter, task ownership, and tenant boundary. These are the **seat-token equivalents** of the CLI \`{{cli}} task\` / \`{{cli}} signal\` / \`{{cli}} friction\` wrappers and the tenant-admin task tools above: \`rost_get_tasks\`, \`{{cli}} task list\`, and \`task.list\` are the same operation reached three different ways. The command-id column makes the mapping explicit.
@@ -46813,7 +46904,7 @@ Do not claim that a live charge happened unless Stripe test/live evidence proves
46813
46904
  order: 72,
46814
46905
  title: "Settings guide",
46815
46906
  summary: "How to use Settings as the control plane for company access, channels, providers, tokens, and operating defaults.",
46816
- version: "2026-07-04.2",
46907
+ version: "2026-07-04.3",
46817
46908
  public: true,
46818
46909
  audiences: ["human", "cli", "mcp", "in_app_agent"],
46819
46910
  stages: ["company_setup", "staffing"],
@@ -46836,9 +46927,9 @@ Start with members and invites, then provider and channel connections, then MCP
46836
46927
 
46837
46928
  ## Navigating Settings
46838
46929
 
46839
- Settings is a left-rail master\u2013detail layout: a grouped rail (General, Team & Access, Billing, AI & Agents, Connections) selects one section at a time, shown in the detail pane. The active section is a URL search param, so \`/settings?section=<id>\` is linkable and survives a refresh. The Connections section is the workspace-level provider surface: Google, Slack, QuickBooks, local runners, seat-scoped agent tokens, and stored secrets appear as provider cards with status, used-for chips, actions, and the shared Access language (**Off / Read / Draft / Act with approval**). Detailed management forms for tokens, channels, runners, provider links, and stored vault entries remain reachable from those cards.
46930
+ Settings is reached from the footer gear in the left sidebar (the nav collapse moved it out of the primary item list). It is a left-rail master\u2013detail layout: a grouped rail (General, Team & Access, Billing, AI & Agents, Connections, Advanced) selects one section at a time, shown in the detail pane. The active section is a URL search param, so \`/settings?section=<id>\` is linkable and survives a refresh. The Connections section is the workspace-level provider surface: Google, Slack, QuickBooks, local runners, seat-scoped agent tokens, and stored secrets appear as provider cards with status, used-for chips, actions, and the shared Access language (**Off / Read / Draft / Act with approval**). Detailed management forms for tokens, channels, runners, provider links, and stored vault entries remain reachable from those cards.
46840
46931
 
46841
- Legacy hash deep-links still resolve \u2014 \`/settings#integrations\`, \`#channels\`, and \`#vault\` redirect to \`?section=connections\`; other hashes such as \`#members\` and \`#guardrails\` redirect to their matching section. \`#runners\` is the one exception: it keeps resolving to the dedicated Connected machines management table (not the Connections overview), since System Health and seat pages deep-link there expecting the per-runner revoke actions. Documented shortcut links resolve to the right section instead of a dead end: \`/settings/integrations\` and \`/readiness\` open the Connections section (connector readiness lives inside it, not on a standalone page), and \`/settings/policy\` opens the Product learning section. After you save a change, the app keeps you on the section you edited rather than returning to the top.
46932
+ Legacy hash deep-links still resolve \u2014 \`/settings#integrations\`, \`#channels\`, and \`#vault\` redirect to \`?section=connections\`; other hashes such as \`#members\` and \`#guardrails\` redirect to their matching section. \`#runners\` is the one exception: it keeps resolving to the dedicated Connected machines management table (not the Connections overview), since System Health and seat pages deep-link there expecting the per-runner revoke actions. Documented shortcut links resolve to the right section instead of a dead end: \`/settings/integrations\` and \`/readiness\` open the Connections section (connector readiness lives inside it, not on a standalone page), \`/settings/policy\` opens the Product learning section, and MCP/CLI access tokens live in the Agent access tokens section (not a separate \`/mcp\` page \u2014 \`/mcp\` is the MCP protocol endpoint itself, not a browsable settings screen). The Advanced group links out to \`/migration/ninety\`, the one settings-adjacent page with no home in the grouped rail. After you save a change, the app keeps you on the section you edited rather than returning to the top.
46842
46933
 
46843
46934
  ## What belongs in Settings
46844
46935
 
@@ -47092,7 +47183,7 @@ The runner should not store raw credentials in local notes, prompts, or logs. It
47092
47183
  order: 76,
47093
47184
  title: "Stock agents guide",
47094
47185
  summary: "How to use stock agent templates as starting points while preserving seat-specific Charters and human approval.",
47095
- version: "2026-07-01.1",
47186
+ version: "2026-07-04.1",
47096
47187
  public: true,
47097
47188
  audiences: ["human", "cli", "mcp", "in_app_agent"],
47098
47189
  stages: ["staffing"],
@@ -47111,7 +47202,7 @@ Stock agents are templates, not finished employees. Use them to accelerate setup
47111
47202
 
47112
47203
  ## Where to create agents
47113
47204
 
47114
- Add agents from the unified agents surface. In the app, open it from the **Agents** item in the left sidebar's Define section, or from an **Add agent** action on the Responsibility Graph. The first screen asks for a creation mode \u2014 use a template, design a custom agent, or connect an existing agent \u2014 then walks the same conservative path for every mode: seat placement, Steward, draft Charter, manifest sign-off, sandbox dry run, and human go-live. Each step is command-backed, so the same workflow runs from the app, the CLI, and MCP.
47205
+ Add agents from the unified agents surface. In the app, open it from the **Agents** item in the left sidebar, or from an **Add agent** action on the Responsibility Graph. The first screen asks for a creation mode \u2014 use a template, design a custom agent, or connect an existing agent \u2014 then walks the same conservative path for every mode: seat placement, Steward, draft Charter, manifest sign-off, sandbox dry run, and human go-live. Each step is command-backed, so the same workflow runs from the app, the CLI, and MCP.
47115
47206
 
47116
47207
  ## Template review
47117
47208
 
@@ -49005,7 +49096,7 @@ var COMMAND_MANIFEST = [
49005
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." },
49006
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." },
49007
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." },
49008
- { "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." },
49009
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." },
49010
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." },
49011
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." },
@@ -49015,6 +49106,10 @@ var COMMAND_MANIFEST = [
49015
49106
  { "id": "software_factory.secret.request", "namespace": "software_factory", "action": "secret.request", "title": "Request a Forge secret grant", "description": "An agent or user REQUESTS access to a project secret by key (draft-and-confirm \u2014 no value, no grant). Records the ask as a durable event for a human to grant. Never accepts a secret value (invariant #5). Entitlement-gated.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "software_project_id", "flag": "software-project-id", "type": "string", "required": true }, { "name": "environment", "flag": "environment", "type": "enum", "required": true, "enumValues": ["development", "preview", "production"] }, { "name": "seat_id", "flag": "seat-id", "type": "string", "required": true }, { "name": "action", "flag": "action", "type": "string", "required": true }, { "name": "key_name", "flag": "key-name", "type": "string", "required": true }, { "name": "reason", "flag": "reason", "type": "string", "required": false }], "hasComplexInput": false, "help": "Request access to a project secret by key; persists the request and creates a human approval task. Never accepts a secret value." },
49016
49107
  { "id": "software_factory.secret.revoke", "namespace": "software_factory", "action": "secret.revoke", "title": "Revoke a Forge secret grant", "description": "Revoke a Forge secret grant (teardown \u2014 a compromised seat's access can be cut immediately, ADR-0018 \xA76). Owner-only, human-gated. Entitlement-gated.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "grant_id", "flag": "grant-id", "type": "string", "required": true }], "hasComplexInput": false, "help": "Revoke a Forge secret grant (teardown \u2014 cut a compromised seat's access immediately). Owner-only and human-gated." },
49017
49108
  { "id": "software_factory.secret.use", "namespace": "software_factory", "action": "secret.use", "title": "Verify Forge secret broker access", "description": "Verify a scoped secret broker grant without returning or opening the raw value. Runtime vault access happens only in the runner-side broker helper; this command writes a redacted authorization log and creates a request task for missing grants.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "software_project_id", "flag": "software-project-id", "type": "string", "required": true }, { "name": "environment", "flag": "environment", "type": "enum", "required": true, "enumValues": ["development", "preview", "production"] }, { "name": "seat_id", "flag": "seat-id", "type": "string", "required": true }, { "name": "action", "flag": "action", "type": "string", "required": true }, { "name": "key_name", "flag": "key-name", "type": "string", "required": true }, { "name": "operation", "flag": "operation", "type": "enum", "required": false, "enumValues": ["use", "vercel_env_sync", "test_execution"] }], "hasComplexInput": false, "help": "Verify scoped broker access without returning or opening the raw value; missing grants create or reuse a request task and all attempts write redacted access logs." },
49109
+ { "id": "software_factory.vercel.connection.connect", "namespace": "software_factory", "action": "vercel.connection.connect", "title": "Connect a Forge Vercel account", "description": "Record a Vercel OAuth account/team connection. The access token is stored only as a vault ref; this command persists bounded account metadata. Owner-only and human-gated.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": false, "fields": [{ "name": "account_id", "flag": "account-id", "type": "string", "required": true }, { "name": "account_name", "flag": "account-name", "type": "string", "required": false }, { "name": "team_id", "flag": "team-id", "type": "string", "required": false }, { "name": "team_slug", "flag": "team-slug", "type": "string", "required": false }, { "name": "user_id", "flag": "user-id", "type": "string", "required": false }, { "name": "configuration_id", "flag": "configuration-id", "type": "string", "required": false }, { "name": "access_token_vault_ref", "flag": "access-token-vault-ref", "type": "string", "required": true }, { "name": "scopes", "flag": "scopes", "type": "array", "required": false, "itemType": "string" }], "hasComplexInput": false, "help": "Record a Vercel OAuth account/team connection from the Forge Vercel settings flow. Stores only a vault-ref pointer to the token. Owner-only and human-gated." },
49110
+ { "id": "software_factory.vercel.deploy_preview", "namespace": "software_factory", "action": "vercel.deploy_preview", "title": "Create a Forge Vercel preview deployment", "description": "Create a Vercel preview deployment for a linked existing project after Forge authority, Vercel connection, and required secret/config grants are verified. Records deployment metadata as artifacts and audit rows.", "requiredScope": "tenant", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "software_project_id", "flag": "software-project-id", "type": "string", "required": true }, { "name": "commit_sha", "flag": "commit-sha", "type": "string", "required": true }, { "name": "git_ref", "flag": "git-ref", "type": "string", "required": true }, { "name": "build_request_id", "flag": "build-request-id", "type": "string", "required": false }, { "name": "phase_run_id", "flag": "phase-run-id", "type": "string", "required": false }, { "name": "seat_id", "flag": "seat-id", "type": "string", "required": false }, { "name": "required_secret_keys", "flag": "required-secret-keys", "type": "array", "required": false, "itemType": "string" }], "hasComplexInput": false, "help": "Create a Vercel preview deployment for a linked project after Forge authority, Vercel connection, and required secret/config grants are verified." },
49111
+ { "id": "software_factory.vercel.project.link", "namespace": "software_factory", "action": "vercel.project.link", "title": "Link a Forge project to Vercel", "description": "Link a Forge project to an existing Vercel project. Greenfield Vercel project creation is intentionally out of scope and remains behind a future authority flag.", "requiredScope": "tenant_admin", "confirmation": "human_required", "exposeOverMcp": false, "fields": [{ "name": "software_project_id", "flag": "software-project-id", "type": "string", "required": true }, { "name": "vercel_connection_id", "flag": "vercel-connection-id", "type": "string", "required": true }, { "name": "vercel_project_id", "flag": "vercel-project-id", "type": "string", "required": true }, { "name": "vercel_project_name", "flag": "vercel-project-name", "type": "string", "required": true }, { "name": "framework", "flag": "framework", "type": "string", "required": false }, { "name": "root_directory", "flag": "root-directory", "type": "string", "required": false }, { "name": "production_branch", "flag": "production-branch", "type": "string", "required": false }], "hasComplexInput": true, "help": "Link an existing Vercel project to a Forge software project. Owner-only and human-gated; greenfield project creation remains a future explicit authority." },
49112
+ { "id": "software_factory.vercel.project.list", "namespace": "software_factory", "action": "vercel.project.list", "title": "List Forge Vercel links", "description": "List Vercel account/team connections and existing Vercel project links for this tenant. Read-only; token vault refs are never returned.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [], "hasComplexInput": false, "help": "List Vercel account/team connections and linked Vercel projects. Read-only; no OAuth tokens or vault refs are returned." },
49018
49113
  { "id": "staffing.assign", "namespace": "staffing", "action": "assign", "title": "Staff seat", "description": "Compatibility command for the DER-626 MCP staffing tool.", "requiredScope": "seat", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "seat_id", "flag": "seat-id", "type": "string", "required": true }], "hasComplexInput": true, "help": "Staff a seat only after the function, Charter, and human accountability are clear." },
49019
49114
  { "id": "staffing.assign_agent_dry_run", "namespace": "staffing", "action": "assign_agent_dry_run", "title": "Assign dry-run agent to seat", "description": "Assign an agent occupancy while the agent remains in dry-run.", "requiredScope": "seat", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "seat_id", "flag": "seat-id", "type": "string", "required": true }, { "name": "agent_id", "flag": "agent-id", "type": "string", "required": true }], "hasComplexInput": false, "help": "Start agent staffing with dry runs and a named human Steward." },
49020
49115
  { "id": "staffing.assign_user", "namespace": "staffing", "action": "assign_user", "title": "Assign user to seat", "description": "Assign a human user occupancy to a seat.", "requiredScope": "seat", "confirmation": "human_required", "exposeOverMcp": true, "fields": [{ "name": "seat_id", "flag": "seat-id", "type": "string", "required": true }, { "name": "user_id", "flag": "user-id", "type": "string", "required": true }], "hasComplexInput": false, "help": "Assign humans to seats after the seat accountability is clear." },
@@ -49231,9 +49326,9 @@ function coerceScalar(field3, raw) {
49231
49326
  }
49232
49327
  }
49233
49328
  function rawValueForField(entry, field3, parsed, aliasFlags) {
49234
- const canonical = parsed.values.get(field3.flag);
49235
- if (canonical !== void 0) {
49236
- return canonical;
49329
+ const canonical2 = parsed.values.get(field3.flag);
49330
+ if (canonical2 !== void 0) {
49331
+ return canonical2;
49237
49332
  }
49238
49333
  for (const [aliasFlag, targetField] of aliasFlags) {
49239
49334
  if (targetField === field3.name) {
@@ -51567,11 +51662,247 @@ function parseSemver2(value) {
51567
51662
  // src/runner-serve.ts
51568
51663
  import { execFile as execFileCallback3, spawn } from "node:child_process";
51569
51664
  import { createHash } from "node:crypto";
51570
- import { chmod, mkdir as mkdir2, readFile as readFile4, rename, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
51571
- import { cpus, hostname as hostname3, platform, totalmem, tmpdir } from "node:os";
51572
- 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";
51573
51668
  import { setTimeout as sleep2 } from "node:timers/promises";
51574
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
51575
51906
  var execFile3 = promisify3(execFileCallback3);
51576
51907
  var RUNNER_WORK_ORDER_TOOLS = [
51577
51908
  "mcp__rost__rost_get_context",
@@ -51597,6 +51928,20 @@ ${usage()}
51597
51928
  }
51598
51929
  options.io.stdout.write(`runner serve: ${options.appUrl} name=${config2.name} execute=${config2.execute}
51599
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
+ }
51600
51945
  let state;
51601
51946
  try {
51602
51947
  state = await pairIfNeeded(options.client ?? null, fetchImpl, options.appUrl, config2, options.io);
@@ -51682,7 +52027,7 @@ function detectTelemetry(capabilities, env, activeSessions) {
51682
52027
  }
51683
52028
  function parseServeArgs(args, appUrl2, env) {
51684
52029
  let name = env.RUNNER_NAME ?? `headless-${hostname3()}`;
51685
- 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`);
51686
52031
  let heartbeatMs = Number(env.RUNNER_HEARTBEAT_MS ?? 3e4);
51687
52032
  let interactiveClaimMs = Number(env.RUNNER_INTERACTIVE_CLAIM_MS ?? 1e3);
51688
52033
  let execute2 = env.RUNNER_EXECUTE === "1";
@@ -51724,7 +52069,29 @@ function parseServeArgs(args, appUrl2, env) {
51724
52069
  throw new Error("--interactive-claim-ms must be an integer >= 250.");
51725
52070
  }
51726
52071
  interactiveClaimMs = Math.min(interactiveClaimMs, heartbeatMs);
51727
- 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
+ };
51728
52095
  }
51729
52096
  function parseRuntime(value) {
51730
52097
  if (value === "auto" || value === "claude" || value === "codex") {
@@ -51844,7 +52211,7 @@ function isRuntimeSessionMap(value) {
51844
52211
  });
51845
52212
  }
51846
52213
  async function saveState(filePath, state) {
51847
- await mkdir2(path3.dirname(filePath), { recursive: true, mode: 448 });
52214
+ await mkdir2(path4.dirname(filePath), { recursive: true, mode: 448 });
51848
52215
  const tempPath = `${filePath}.${process.pid}.${createHash("sha1").update(`${Date.now()}:${Math.random()}`).digest("hex").slice(0, 8)}.tmp`;
51849
52216
  try {
51850
52217
  await writeFile2(tempPath, `${JSON.stringify(state)}
@@ -51852,7 +52219,7 @@ async function saveState(filePath, state) {
51852
52219
  await chmod(tempPath, 384);
51853
52220
  await rename(tempPath, filePath);
51854
52221
  } catch (error51) {
51855
- await rm2(tempPath, { force: true });
52222
+ await rm3(tempPath, { force: true });
51856
52223
  throw error51;
51857
52224
  }
51858
52225
  }
@@ -51938,7 +52305,7 @@ async function executeClaimedUnit(fetchImpl, appUrl2, state, config2, io, runtim
51938
52305
  throw new Error(`${claimedUnitLabel(input.kind)} start failed ${started.status}: ${redactForLog(JSON.stringify(started.json))}`);
51939
52306
  }
51940
52307
  const turnRuntime = runtimeForClaimedUnit(input.item, runtime, input.kind);
51941
- 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) : {
51942
52309
  ok: false,
51943
52310
  summary: `Runner runtime ${runtime} cannot execute requested ${String(input.item.brain ?? "unknown")} turn.`
51944
52311
  };
@@ -51978,14 +52345,14 @@ function runtimeForClaimedUnit(item, fallback, kind) {
51978
52345
  }
51979
52346
  return fallback === "claude" ? "claude" : null;
51980
52347
  }
51981
- function runLocalTurn(appUrl2, workOrder, runtime, kind, state) {
51982
- 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);
51983
52350
  }
51984
- function runClaudeTurn(appUrl2, workOrder, kind, state) {
51985
- return spawnRunnerTurn(appUrl2, workOrder, "claude", kind, state);
52351
+ function runClaudeTurn(appUrl2, workOrder, kind, state, sandbox) {
52352
+ return spawnRunnerTurn(appUrl2, workOrder, "claude", kind, sandbox, state);
51986
52353
  }
51987
- function runCodexTurn(appUrl2, workOrder, kind) {
51988
- return spawnRunnerTurn(appUrl2, workOrder, "codex", kind);
52354
+ function runCodexTurn(appUrl2, workOrder, kind, sandbox) {
52355
+ return spawnRunnerTurn(appUrl2, workOrder, "codex", kind, sandbox);
51989
52356
  }
51990
52357
  function buildTurnCommand(runtime, prompt, configPath, kind, resumeSessionId) {
51991
52358
  if (runtime === "codex") {
@@ -52010,12 +52377,12 @@ function buildTurnCommand(runtime, prompt, configPath, kind, resumeSessionId) {
52010
52377
  ]
52011
52378
  };
52012
52379
  }
52013
- async function spawnRunnerTurn(appUrl2, workOrder, runtime, kind, state) {
52380
+ async function spawnRunnerTurn(appUrl2, workOrder, runtime, kind, sandbox, state) {
52014
52381
  const mcp = typeof workOrder.mcp === "object" && workOrder.mcp !== null ? workOrder.mcp : {};
52015
52382
  const token = typeof mcp.token === "string" ? mcp.token : "";
52016
52383
  const url2 = typeof mcp.url === "string" ? `${appUrl2.replace(/\/+$/, "")}${mcp.url}` : `${appUrl2.replace(/\/+$/, "")}/mcp`;
52017
52384
  const mcpConfig = JSON.stringify({ mcpServers: { rost: { type: "http", url: url2, headers: { Authorization: `Bearer ${token}` } } } });
52018
- 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`);
52019
52386
  const context = {
52020
52387
  work_order_id: kind === "work_order" ? workOrder.id : null,
52021
52388
  turn_execution_id: kind === "turn_execution" ? workOrder.id : null,
@@ -52045,7 +52412,17 @@ async function spawnRunnerTurn(appUrl2, workOrder, runtime, kind, state) {
52045
52412
  `, { mode: 384 });
52046
52413
  await chmod(configPath, 384);
52047
52414
  const resumeSessionId = runtime === "claude" ? getClaudeResumeSessionId(state, workOrder) : void 0;
52048
- 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);
52049
52426
  const expectSessionEnvelope = runtime === "claude" && kind === "turn_execution";
52050
52427
  return new Promise((resolve2) => {
52051
52428
  let settled = false;
@@ -52054,7 +52431,10 @@ async function spawnRunnerTurn(appUrl2, workOrder, runtime, kind, state) {
52054
52431
  return;
52055
52432
  }
52056
52433
  settled = true;
52057
- void rm2(configPath, { force: true });
52434
+ void rm3(configPath, { force: true });
52435
+ if (sandboxProfilePath) {
52436
+ void rm3(sandboxProfilePath, { force: true });
52437
+ }
52058
52438
  resolve2(resumeSessionId ? { ...result, resumedFromSessionId: resumeSessionId } : result);
52059
52439
  };
52060
52440
  const child = spawn(command, args, {
@@ -52187,10 +52567,10 @@ function usage() {
52187
52567
 
52188
52568
  // src/runner-service.ts
52189
52569
  import { execFile as execFileCallback4 } from "node:child_process";
52190
- import { realpathSync } from "node:fs";
52191
- import { access as access2, mkdir as mkdir3, readFile as readFile5, rm as rm3, writeFile as writeFile3 } from "node:fs/promises";
52192
- import { homedir as homedir2 } from "node:os";
52193
- 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";
52194
52574
  import { promisify as promisify4 } from "node:util";
52195
52575
  var execFile4 = promisify4(execFileCallback4);
52196
52576
  var SERVICE_VERBS = ["install-service", "start", "stop", "restart", "status", "logs", "uninstall"];
@@ -52207,9 +52587,9 @@ function isRunnerServiceInvocation(args) {
52207
52587
  function runnerServicePaths(homeDir, runnerName) {
52208
52588
  const safeName = safeRunnerName(runnerName);
52209
52589
  return {
52210
- launchAgentPath: path4.join(homeDir, "Library", "LaunchAgents", `${serviceLabel(safeName)}.plist`),
52211
- stateFile: path4.join(homeDir, "Library", "Application Support", cliBrand.name, `runner-${safeName}.json`),
52212
- 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)
52213
52593
  };
52214
52594
  }
52215
52595
  function renderLaunchdPlist(config2) {
@@ -52249,9 +52629,9 @@ ${programArgsXml}
52249
52629
  <key>RunAtLoad</key>
52250
52630
  <true/>
52251
52631
  <key>StandardOutPath</key>
52252
- <string>${escapePlist(path4.join(config2.logDir, "stdout.log"))}</string>
52632
+ <string>${escapePlist(path5.join(config2.logDir, "stdout.log"))}</string>
52253
52633
  <key>StandardErrorPath</key>
52254
- <string>${escapePlist(path4.join(config2.logDir, "stderr.log"))}</string>
52634
+ <string>${escapePlist(path5.join(config2.logDir, "stderr.log"))}</string>
52255
52635
  </dict>
52256
52636
  </plist>
52257
52637
  `;
@@ -52286,7 +52666,7 @@ ${serviceUsage()}
52286
52666
  userCode: parsed.userCode,
52287
52667
  nodePath: options.nodePath ?? resolveNodePath(),
52288
52668
  binPath: options.binPath ?? resolveBinPath(),
52289
- homeDir: options.homeDir ?? homedir2(),
52669
+ homeDir: options.homeDir ?? homedir3(),
52290
52670
  platform: options.platform ?? process.platform,
52291
52671
  exec: options.exec ?? defaultExec,
52292
52672
  uid: options.uid ?? (process.getuid ? process.getuid() : 0)
@@ -52318,8 +52698,8 @@ async function installService(options) {
52318
52698
  const paths = runnerServicePaths(options.homeDir, safeName);
52319
52699
  const label = serviceLabel(safeName);
52320
52700
  await mkdir3(paths.logDir, { recursive: true });
52321
- await mkdir3(path4.dirname(paths.stateFile), { recursive: true });
52322
- 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 });
52323
52703
  if (options.userCode !== void 0) {
52324
52704
  const paired = await pairRunnerOnce(options, safeName, paths.stateFile, options.userCode);
52325
52705
  if (!paired) {
@@ -52497,8 +52877,8 @@ async function logsService(options) {
52497
52877
  const safeName = safeRunnerName(options.runnerName);
52498
52878
  const paths = runnerServicePaths(options.homeDir, safeName);
52499
52879
  const label = serviceLabel(safeName);
52500
- const stdoutLog = path4.join(paths.logDir, "stdout.log");
52501
- 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");
52502
52882
  options.io.stdout.write(
52503
52883
  `${[
52504
52884
  `runner service logs for ${label}:`,
@@ -52529,7 +52909,7 @@ async function uninstallService(options) {
52529
52909
  await bootoutQuietly(options.exec, options.uid, paths.launchAgentPath);
52530
52910
  let removed = false;
52531
52911
  try {
52532
- await rm3(paths.launchAgentPath, { force: true });
52912
+ await rm4(paths.launchAgentPath, { force: true });
52533
52913
  removed = true;
52534
52914
  } catch (error51) {
52535
52915
  options.io.stderr.write(
@@ -52673,11 +53053,11 @@ function resolveNodePath() {
52673
53053
  }
52674
53054
  function buildServicePath(nodePath, homeDir) {
52675
53055
  const entries = [
52676
- path4.dirname(nodePath),
53056
+ path5.dirname(nodePath),
52677
53057
  "/opt/homebrew/bin",
52678
53058
  "/usr/local/bin",
52679
- path4.join(homeDir, ".local", "bin"),
52680
- path4.join(homeDir, "bin"),
53059
+ path5.join(homeDir, ".local", "bin"),
53060
+ path5.join(homeDir, "bin"),
52681
53061
  "/usr/bin",
52682
53062
  "/bin",
52683
53063
  "/usr/sbin",
@@ -52722,7 +53102,7 @@ function resolveBinPath() {
52722
53102
  return cliBrand.binName;
52723
53103
  }
52724
53104
  try {
52725
- return realpathSync(entry);
53105
+ return realpathSync2(entry);
52726
53106
  } catch {
52727
53107
  return entry;
52728
53108
  }
@@ -53385,7 +53765,7 @@ function isCliEntrypoint(moduleUrl, argvPath) {
53385
53765
  return false;
53386
53766
  }
53387
53767
  try {
53388
- return realpathSync2(fileURLToPath3(moduleUrl)) === realpathSync2(argvPath);
53768
+ return realpathSync3(fileURLToPath3(moduleUrl)) === realpathSync3(argvPath);
53389
53769
  } catch {
53390
53770
  return moduleUrl === pathToFileURL(argvPath).href;
53391
53771
  }