@ai-development-environment/control-agent 0.0.96 → 0.0.98

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.
Files changed (2) hide show
  1. package/dist/control-agent.js +1089 -120
  2. package/package.json +1 -1
@@ -3225,7 +3225,7 @@ var require_stream = __commonJS({
3225
3225
  };
3226
3226
  duplex._final = function(callback) {
3227
3227
  if (ws.readyState === ws.CONNECTING) {
3228
- ws.once("open", function open5() {
3228
+ ws.once("open", function open6() {
3229
3229
  duplex._final(callback);
3230
3230
  });
3231
3231
  return;
@@ -3246,7 +3246,7 @@ var require_stream = __commonJS({
3246
3246
  };
3247
3247
  duplex._write = function(chunk, encoding, callback) {
3248
3248
  if (ws.readyState === ws.CONNECTING) {
3249
- ws.once("open", function open5() {
3249
+ ws.once("open", function open6() {
3250
3250
  duplex._write(chunk, encoding, callback);
3251
3251
  });
3252
3252
  return;
@@ -4280,7 +4280,7 @@ async function saveConfig(config, path = configPath()) {
4280
4280
 
4281
4281
  // src/graphql-client.ts
4282
4282
  import { createReadStream } from "node:fs";
4283
- import { rm, stat } from "node:fs/promises";
4283
+ import { open, rm, stat } from "node:fs/promises";
4284
4284
  import { createHash } from "node:crypto";
4285
4285
  import { Readable } from "node:stream";
4286
4286
 
@@ -4988,6 +4988,39 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
4988
4988
  var wrapper_default = import_websocket.default;
4989
4989
 
4990
4990
  // src/graphql-client.ts
4991
+ async function writeArtifactTransferBytes(writer, bytes, position) {
4992
+ let written = 0;
4993
+ while (written < bytes.byteLength) {
4994
+ const remaining = bytes.byteLength - written;
4995
+ const { bytesWritten } = await writer.write(
4996
+ bytes,
4997
+ written,
4998
+ remaining,
4999
+ position + written
5000
+ );
5001
+ if (!Number.isSafeInteger(bytesWritten) || bytesWritten <= 0 || bytesWritten > remaining) {
5002
+ throw new Error("Artifact transfer file write made no progress");
5003
+ }
5004
+ written += bytesWritten;
5005
+ }
5006
+ }
5007
+ async function artifactTransferRetryDelay(attempt, signal) {
5008
+ await new Promise((resolve12, reject) => {
5009
+ const timeout = setTimeout(
5010
+ () => {
5011
+ signal.removeEventListener("abort", abort);
5012
+ resolve12();
5013
+ },
5014
+ Math.min(4e3, 250 * 2 ** attempt)
5015
+ );
5016
+ const abort = () => {
5017
+ clearTimeout(timeout);
5018
+ reject(signal.reason ?? new Error("Artifact transfer was cancelled"));
5019
+ };
5020
+ if (signal.aborted) abort();
5021
+ else signal.addEventListener("abort", abort, { once: true });
5022
+ });
5023
+ }
4991
5024
  var RUN_COMMAND_FIELDS = `
4992
5025
  id runId agentId sequence type payload status error
4993
5026
  run {
@@ -5257,6 +5290,205 @@ var AgentGraphQLClient = class {
5257
5290
  );
5258
5291
  }
5259
5292
  }
5293
+ artifactTransferHeaders(values = {}) {
5294
+ return mergeHeaders(this.headers, {
5295
+ ...this.credential ? { authorization: `Bearer ${this.credential}` } : {},
5296
+ ...values
5297
+ });
5298
+ }
5299
+ async uploadBuildArtifactTransfer(input) {
5300
+ const information = await stat(input.path);
5301
+ const base = `${this.server}/api/agent/build-artifact-transfers/${encodeURIComponent(input.transferId)}`;
5302
+ const metadata = await fetch(base, {
5303
+ method: "POST",
5304
+ headers: this.artifactTransferHeaders({
5305
+ "content-type": "application/json"
5306
+ }),
5307
+ body: JSON.stringify({
5308
+ uploadLength: information.size,
5309
+ checksum: input.checksum,
5310
+ filename: input.filename,
5311
+ contentType: input.contentType
5312
+ }),
5313
+ signal: input.signal
5314
+ });
5315
+ if (!metadata.ok) {
5316
+ throw new Error(
5317
+ `Artifact transfer initialization failed: HTTP ${metadata.status} ${await metadata.text()}`
5318
+ );
5319
+ }
5320
+ const confirmedOffset = async () => {
5321
+ const response = await fetch(`${base}/upload`, {
5322
+ method: "HEAD",
5323
+ headers: this.artifactTransferHeaders(),
5324
+ signal: input.signal
5325
+ });
5326
+ if (!response.ok) {
5327
+ throw new Error(
5328
+ `Could not inspect artifact upload offset: HTTP ${response.status}`
5329
+ );
5330
+ }
5331
+ const offset = Number(response.headers.get("upload-offset"));
5332
+ if (!Number.isSafeInteger(offset) || offset < 0 || offset > information.size) {
5333
+ throw new Error("Artifact relay returned an invalid upload offset");
5334
+ }
5335
+ return offset;
5336
+ };
5337
+ const handle = await open(input.path, "r");
5338
+ try {
5339
+ let offset = await confirmedOffset();
5340
+ const buffer = Buffer.allocUnsafe(16 * 1024 * 1024);
5341
+ while (offset < information.size) {
5342
+ if (input.signal.aborted) throw input.signal.reason;
5343
+ const length = Math.min(buffer.length, information.size - offset);
5344
+ const { bytesRead } = await handle.read(buffer, 0, length, offset);
5345
+ if (bytesRead !== length)
5346
+ throw new Error("Artifact changed while uploading");
5347
+ let attempt = 0;
5348
+ while (true) {
5349
+ let failure;
5350
+ try {
5351
+ const response = await fetch(`${base}/upload`, {
5352
+ method: "PATCH",
5353
+ headers: this.artifactTransferHeaders({
5354
+ "content-length": String(bytesRead),
5355
+ "content-type": "application/offset+octet-stream",
5356
+ "upload-offset": String(offset)
5357
+ }),
5358
+ body: buffer.subarray(0, bytesRead),
5359
+ signal: input.signal
5360
+ });
5361
+ if (response.ok) {
5362
+ const nextOffset = Number(response.headers.get("upload-offset"));
5363
+ if (Number.isSafeInteger(nextOffset) && nextOffset > offset && nextOffset <= information.size) {
5364
+ offset = nextOffset;
5365
+ break;
5366
+ }
5367
+ failure = new Error(
5368
+ "Artifact relay returned an invalid upload offset"
5369
+ );
5370
+ } else {
5371
+ failure = new Error(
5372
+ `Artifact chunk upload failed: HTTP ${response.status} ${await response.text()}`
5373
+ );
5374
+ }
5375
+ } catch (error) {
5376
+ if (input.signal.aborted) throw error;
5377
+ failure = error instanceof Error ? error : new Error(String(error));
5378
+ }
5379
+ if (++attempt >= 5) throw failure;
5380
+ await artifactTransferRetryDelay(attempt, input.signal);
5381
+ const resumedOffset = await confirmedOffset();
5382
+ if (resumedOffset !== offset) {
5383
+ offset = resumedOffset;
5384
+ break;
5385
+ }
5386
+ }
5387
+ }
5388
+ } finally {
5389
+ await handle.close();
5390
+ }
5391
+ const completion = await fetch(`${base}/complete`, {
5392
+ method: "POST",
5393
+ headers: this.artifactTransferHeaders(),
5394
+ signal: input.signal
5395
+ });
5396
+ if (!completion.ok) {
5397
+ throw new Error(
5398
+ `Artifact transfer completion failed: HTTP ${completion.status} ${await completion.text()}`
5399
+ );
5400
+ }
5401
+ }
5402
+ async downloadBuildArtifactTransfer(input) {
5403
+ const base = `${this.server}/api/agent/build-artifact-transfers/${encodeURIComponent(input.transferId)}`;
5404
+ let metadata;
5405
+ while (true) {
5406
+ const response = await fetch(base, {
5407
+ headers: this.artifactTransferHeaders(),
5408
+ signal: input.signal
5409
+ });
5410
+ if (!response.ok) {
5411
+ throw new Error(
5412
+ `Could not inspect artifact transfer: HTTP ${response.status} ${await response.text()}`
5413
+ );
5414
+ }
5415
+ metadata = await response.json();
5416
+ if (["READY", "DOWNLOADING", "SUCCEEDED"].includes(metadata.status))
5417
+ break;
5418
+ if (["FAILED", "CANCELLED", "EXPIRED"].includes(metadata.status)) {
5419
+ throw new Error(
5420
+ metadata.error || `Artifact transfer ${metadata.status.toLowerCase()}`
5421
+ );
5422
+ }
5423
+ await new Promise((resolve12) => setTimeout(resolve12, 1e3));
5424
+ }
5425
+ if (!Number.isSafeInteger(metadata.uploadLength) || !metadata.uploadLength || !metadata.checksum || !metadata.filename) {
5426
+ throw new Error("Artifact transfer metadata is incomplete");
5427
+ }
5428
+ let offset = 0;
5429
+ try {
5430
+ offset = (await stat(input.path)).size;
5431
+ } catch {
5432
+ }
5433
+ if (offset > metadata.uploadLength) {
5434
+ await rm(input.path, { force: true });
5435
+ offset = 0;
5436
+ }
5437
+ const handle = await open(input.path, offset === 0 ? "w" : "r+");
5438
+ try {
5439
+ while (offset < metadata.uploadLength) {
5440
+ const end = Math.min(
5441
+ metadata.uploadLength - 1,
5442
+ offset + 16 * 1024 * 1024 - 1
5443
+ );
5444
+ let bytes = null;
5445
+ let attempt = 0;
5446
+ while (!bytes) {
5447
+ let failure = new Error("Artifact range download failed");
5448
+ try {
5449
+ const response = await fetch(`${base}/download`, {
5450
+ headers: this.artifactTransferHeaders({
5451
+ range: `bytes=${offset}-${end}`
5452
+ }),
5453
+ signal: input.signal
5454
+ });
5455
+ if (response.status !== 206) {
5456
+ failure = new Error(
5457
+ `Artifact range download failed: HTTP ${response.status} ${await response.text()}`
5458
+ );
5459
+ } else {
5460
+ const received = Buffer.from(await response.arrayBuffer());
5461
+ if (received.length === end - offset + 1) bytes = received;
5462
+ else failure = new Error("Artifact range length did not match");
5463
+ }
5464
+ } catch (error) {
5465
+ if (input.signal.aborted) throw error;
5466
+ failure = error instanceof Error ? error : new Error(String(error));
5467
+ }
5468
+ if (bytes) break;
5469
+ if (++attempt >= 5) throw failure;
5470
+ await artifactTransferRetryDelay(attempt, input.signal);
5471
+ }
5472
+ await writeArtifactTransferBytes(handle, bytes, offset);
5473
+ offset += bytes.length;
5474
+ }
5475
+ await handle.sync();
5476
+ } finally {
5477
+ await handle.close();
5478
+ }
5479
+ const digest = createHash("sha256");
5480
+ for await (const chunk of createReadStream(input.path))
5481
+ digest.update(chunk);
5482
+ if (digest.digest("hex") !== metadata.checksum) {
5483
+ await rm(input.path, { force: true });
5484
+ throw new Error("Downloaded artifact checksum did not match");
5485
+ }
5486
+ return {
5487
+ filename: metadata.filename,
5488
+ contentType: metadata.contentType ?? "application/octet-stream",
5489
+ checksum: metadata.checksum
5490
+ };
5491
+ }
5260
5492
  completeJob(jobId, status2, result, error) {
5261
5493
  return this.request(
5262
5494
  `mutation CompleteJob($jobId: ID!, $status: AgentJobStatus!, $result: JSON, $error: String) {
@@ -7423,6 +7655,9 @@ var IOS_BUILD_JOB_KIND = "ios.build.run";
7423
7655
  var IOS_BUILD_DELETE_JOB_KIND = "ios.build.delete";
7424
7656
  var IOS_ARTIFACT_DOWNLOAD_JOB_KIND = "ios.artifact.download";
7425
7657
  var IOS_DEPLOY_JOB_KIND = "ios.build.deploy";
7658
+ var IOS_AGENT_RUN_DESTINATIONS_JOB_KIND = "ios.agentRunDestinations.inspect";
7659
+ var IOS_ARTIFACT_TRANSFER_UPLOAD_JOB_KIND = "ios.artifact.transfer.upload";
7660
+ var IOS_REMOTE_DEPLOY_JOB_KIND = "ios.build.remoteDeploy";
7426
7661
  var IOS_EXPORT_JOB_KIND = "ios.archive.export";
7427
7662
  var IOS_TEST_RESULTS_JOB_KIND = "ios.test-results.parse";
7428
7663
  var IOS_COVERAGE_REPORT_JOB_KIND = "ios.coverage.generate";
@@ -7436,6 +7671,9 @@ var IOS_BUILD_JOB_KINDS = [
7436
7671
  IOS_BUILD_DELETE_JOB_KIND,
7437
7672
  IOS_ARTIFACT_DOWNLOAD_JOB_KIND,
7438
7673
  IOS_DEPLOY_JOB_KIND,
7674
+ IOS_AGENT_RUN_DESTINATIONS_JOB_KIND,
7675
+ IOS_ARTIFACT_TRANSFER_UPLOAD_JOB_KIND,
7676
+ IOS_REMOTE_DEPLOY_JOB_KIND,
7439
7677
  IOS_EXPORT_JOB_KIND,
7440
7678
  IOS_TEST_RESULTS_JOB_KIND,
7441
7679
  IOS_COVERAGE_REPORT_JOB_KIND,
@@ -7476,6 +7714,31 @@ var APPROVED_BUILD_SETTING_OVERRIDES = [
7476
7714
  "MARKETING_VERSION",
7477
7715
  "CURRENT_PROJECT_VERSION"
7478
7716
  ];
7717
+ function genericBuildDestinations(action) {
7718
+ if (!GENERIC_BUILD_DESTINATION_ACTIONS.includes(action)) return [];
7719
+ const physical = {
7720
+ type: "PHYSICAL_DEVICE",
7721
+ id: "generic-ios",
7722
+ name: "Any Physical iOS Device",
7723
+ platform: "iOS",
7724
+ osVersion: null,
7725
+ state: null,
7726
+ generic: true
7727
+ };
7728
+ if (action === "ARCHIVE") return [physical];
7729
+ return [
7730
+ {
7731
+ type: "SIMULATOR",
7732
+ id: "generic-ios-simulator",
7733
+ name: "Any iOS Simulator",
7734
+ platform: "iOS Simulator",
7735
+ osVersion: null,
7736
+ state: null,
7737
+ generic: true
7738
+ },
7739
+ physical
7740
+ ];
7741
+ }
7479
7742
  var DEFAULT_BUILD_ADVANCED_SETTINGS = {
7480
7743
  packageResolution: "DEFAULT",
7481
7744
  disablePackageRepositoryCache: false,
@@ -7592,6 +7855,12 @@ function parseBuildDestination(value) {
7592
7855
  destination.generic,
7593
7856
  "build destination.generic"
7594
7857
  )
7858
+ },
7859
+ ...destination.available === void 0 ? {} : {
7860
+ available: booleanValue2(
7861
+ destination.available,
7862
+ "build destination.available"
7863
+ )
7595
7864
  }
7596
7865
  };
7597
7866
  }
@@ -7925,13 +8194,50 @@ function parseBuildArtifactDownloadPayload(value) {
7925
8194
  )
7926
8195
  };
7927
8196
  }
7928
- function parseBuildDeploymentPayload(value) {
7929
- const input = objectValue7(value, "build deployment payload");
8197
+ function parseBuildArtifactTransferUploadPayload(value) {
8198
+ const input = objectValue7(value, "build artifact transfer upload payload");
8199
+ const buildId = stringValue7(
8200
+ input.buildId,
8201
+ "build artifact transfer upload payload.buildId"
8202
+ );
8203
+ const artifactDirectory = safeAbsolutePath(
8204
+ input.artifactDirectory,
8205
+ "build artifact transfer upload payload.artifactDirectory"
8206
+ );
8207
+ if (artifactDirectory.split("/").at(-1) !== buildId) {
8208
+ throw new Error(
8209
+ "Build artifact transfer folder must end with the build ID"
8210
+ );
8211
+ }
8212
+ return {
8213
+ transferId: stringValue7(
8214
+ input.transferId,
8215
+ "build artifact transfer upload payload.transferId"
8216
+ ),
8217
+ buildId,
8218
+ artifactDirectory,
8219
+ artifactRelativePath: safeRelativePath2(
8220
+ input.artifactRelativePath,
8221
+ "build artifact transfer upload payload.artifactRelativePath"
8222
+ )
8223
+ };
8224
+ }
8225
+ function parseBuildAgentRunDestinationsPayload(value) {
8226
+ const input = objectValue7(value, "agent run destinations payload");
8227
+ return {
8228
+ destinationType: enumValue3(
8229
+ input.destinationType,
8230
+ BUILD_DESTINATION_TYPES,
8231
+ "agent run destinations payload.destinationType"
8232
+ )
8233
+ };
8234
+ }
8235
+ function parseDeployments(input) {
7930
8236
  if (!Array.isArray(input.deployments) || !input.deployments.length) {
7931
8237
  throw new Error("build deployment payload.deployments must not be empty");
7932
8238
  }
7933
- const deployments = input.deployments.map((value2, index) => {
7934
- const deployment = objectValue7(value2, `deployments[${index}]`);
8239
+ const deployments = input.deployments.map((value, index) => {
8240
+ const deployment = objectValue7(value, `deployments[${index}]`);
7935
8241
  return {
7936
8242
  id: stringValue7(deployment.id, `deployments[${index}].id`),
7937
8243
  destination: parseBuildDestination(deployment.destination)
@@ -7950,6 +8256,11 @@ function parseBuildDeploymentPayload(value) {
7950
8256
  if (new Set(deployments.map(({ destination }) => destination.type)).size > 1) {
7951
8257
  throw new Error("deployment destinations must use one destination type");
7952
8258
  }
8259
+ return deployments;
8260
+ }
8261
+ function parseBuildDeploymentPayload(value) {
8262
+ const input = objectValue7(value, "build deployment payload");
8263
+ const deployments = parseDeployments(input);
7953
8264
  return {
7954
8265
  ...worktreeIdentity(input),
7955
8266
  buildId: stringValue7(input.buildId, "build deployment payload.buildId"),
@@ -7968,6 +8279,24 @@ function parseBuildDeploymentPayload(value) {
7968
8279
  deployments
7969
8280
  };
7970
8281
  }
8282
+ function parseBuildRemoteDeploymentPayload(value) {
8283
+ const input = objectValue7(value, "remote build deployment payload");
8284
+ return {
8285
+ transferId: stringValue7(
8286
+ input.transferId,
8287
+ "remote build deployment payload.transferId"
8288
+ ),
8289
+ buildId: stringValue7(
8290
+ input.buildId,
8291
+ "remote build deployment payload.buildId"
8292
+ ),
8293
+ bundleIdentifier: stringValue7(
8294
+ input.bundleIdentifier,
8295
+ "remote build deployment payload.bundleIdentifier"
8296
+ ),
8297
+ deployments: parseDeployments(input)
8298
+ };
8299
+ }
7971
8300
  function parseBuildExportSettings(value) {
7972
8301
  const input = objectValue7(value, "export settings");
7973
8302
  const profiles = objectValue7(
@@ -8576,6 +8905,215 @@ function parseCliHealthJobPayload(value) {
8576
8905
  return { checks };
8577
8906
  }
8578
8907
 
8908
+ // ../agent-contract/src/tailscale.ts
8909
+ var TAILSCALE_SERVE_INSPECT_JOB_KIND = "tailscale.serve.inspect.v1";
8910
+ var TAILSCALE_SERVE_UPSERT_JOB_KIND = "tailscale.serve.upsert.v1";
8911
+ var TAILSCALE_SERVE_REMOVE_JOB_KIND = "tailscale.serve.remove.v1";
8912
+ var TAILSCALE_SERVE_JOB_KINDS = [
8913
+ TAILSCALE_SERVE_INSPECT_JOB_KIND,
8914
+ TAILSCALE_SERVE_UPSERT_JOB_KIND,
8915
+ TAILSCALE_SERVE_REMOVE_JOB_KIND
8916
+ ];
8917
+ var TAILSCALE_SERVE_PROTOCOLS = [
8918
+ "HTTP",
8919
+ "HTTPS",
8920
+ "TCP",
8921
+ "TLS_TERMINATED_TCP"
8922
+ ];
8923
+ var TAILSCALE_DESTINATION_PROTOCOLS = [
8924
+ "HTTP",
8925
+ "HTTPS",
8926
+ "HTTPS_INSECURE",
8927
+ "TCP"
8928
+ ];
8929
+ var TAILSCALE_PROXY_PROTOCOLS = ["NONE", "V1", "V2"];
8930
+ function objectValue13(value, name) {
8931
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
8932
+ throw new Error(`${name} must be an object`);
8933
+ }
8934
+ return value;
8935
+ }
8936
+ function exactKeys3(value, keys, name) {
8937
+ const unexpected = Object.keys(value).find((key) => !keys.includes(key));
8938
+ if (unexpected) throw new Error(`Unexpected ${name} field: ${unexpected}`);
8939
+ }
8940
+ function requiredString(value, name) {
8941
+ if (typeof value !== "string" || value.trim().length === 0 || value.includes("\0")) {
8942
+ throw new Error(`${name} must be a non-empty string`);
8943
+ }
8944
+ return value.trim();
8945
+ }
8946
+ function integerPort(value, name) {
8947
+ if (!Number.isInteger(value) || value < 1 || value > 65535) {
8948
+ throw new Error(`${name} must be an integer from 1 through 65535`);
8949
+ }
8950
+ return value;
8951
+ }
8952
+ function enumValue4(value, values, name) {
8953
+ if (typeof value !== "string" || !values.includes(value)) {
8954
+ throw new Error(`${name} is invalid`);
8955
+ }
8956
+ return value;
8957
+ }
8958
+ function normalizeTailscaleMountPath(value) {
8959
+ if (typeof value !== "string" || value.includes("\0") || value.includes("?")) {
8960
+ throw new Error("Tailscale mount path is invalid");
8961
+ }
8962
+ const path = value.trim() || "/";
8963
+ const prefixed = path.startsWith("/") ? path : `/${path}`;
8964
+ return prefixed === "/" ? prefixed : prefixed.replace(/\/+$/, "");
8965
+ }
8966
+ function normalizeDestinationPath(value) {
8967
+ if (typeof value !== "string" || value.includes("\0")) {
8968
+ throw new Error("Tailscale destination path is invalid");
8969
+ }
8970
+ const path = value.trim();
8971
+ if (!path) return "";
8972
+ return path.startsWith("/") ? path : `/${path}`;
8973
+ }
8974
+ function appCapabilities(value) {
8975
+ if (!Array.isArray(value) || value.length > 100) {
8976
+ throw new Error("Tailscale app capabilities must be an array");
8977
+ }
8978
+ const pattern = /^(?:[\p{L}\p{N}-]+\.)+[\p{L}\p{N}-]+\/[\p{L}\p{N}-]+(?:\/[\p{L}\p{N}-]+)*$/u;
8979
+ return [
8980
+ ...new Set(
8981
+ value.map((item, index) => {
8982
+ const capability = requiredString(
8983
+ item,
8984
+ `Tailscale app capabilities[${index}]`
8985
+ );
8986
+ if (!pattern.test(capability)) {
8987
+ throw new Error(
8988
+ `Tailscale app capabilities[${index}] must use domain/name format`
8989
+ );
8990
+ }
8991
+ return capability;
8992
+ })
8993
+ )
8994
+ ].sort();
8995
+ }
8996
+ function parseTailscaleServeRoute(value) {
8997
+ const route = objectValue13(value, "Tailscale Serve route");
8998
+ exactKeys3(
8999
+ route,
9000
+ [
9001
+ "protocol",
9002
+ "listenPort",
9003
+ "mountPath",
9004
+ "destination",
9005
+ "funnel",
9006
+ "appCapabilities",
9007
+ "proxyProtocol"
9008
+ ],
9009
+ "Tailscale Serve route"
9010
+ );
9011
+ const protocol = enumValue4(
9012
+ route.protocol,
9013
+ TAILSCALE_SERVE_PROTOCOLS,
9014
+ "Tailscale Serve protocol"
9015
+ );
9016
+ const listenPort = integerPort(route.listenPort, "Tailscale listen port");
9017
+ const destination = objectValue13(
9018
+ route.destination,
9019
+ "Tailscale Serve destination"
9020
+ );
9021
+ exactKeys3(
9022
+ destination,
9023
+ ["protocol", "port", "path"],
9024
+ "Tailscale Serve destination"
9025
+ );
9026
+ const destinationProtocol = enumValue4(
9027
+ destination.protocol,
9028
+ TAILSCALE_DESTINATION_PROTOCOLS,
9029
+ "Tailscale destination protocol"
9030
+ );
9031
+ const funnel = route.funnel;
9032
+ if (typeof funnel !== "boolean")
9033
+ throw new Error("Tailscale Funnel must be a boolean");
9034
+ const proxyProtocol = enumValue4(
9035
+ route.proxyProtocol,
9036
+ TAILSCALE_PROXY_PROTOCOLS,
9037
+ "Tailscale PROXY protocol"
9038
+ );
9039
+ const capabilities = appCapabilities(route.appCapabilities);
9040
+ const isWeb = protocol === "HTTP" || protocol === "HTTPS";
9041
+ if (isWeb && destinationProtocol === "TCP") {
9042
+ throw new Error("Web listeners require an HTTP(S) destination");
9043
+ }
9044
+ if (!isWeb && destinationProtocol !== "TCP") {
9045
+ throw new Error("TCP listeners require a TCP destination");
9046
+ }
9047
+ if (!isWeb && normalizeTailscaleMountPath(route.mountPath) !== "/") {
9048
+ throw new Error("TCP listeners do not support mount paths");
9049
+ }
9050
+ if (funnel && (protocol === "HTTP" || ![443, 8443, 1e4].includes(listenPort))) {
9051
+ throw new Error(
9052
+ "Funnel requires HTTPS or TLS TCP on port 443, 8443, or 10000"
9053
+ );
9054
+ }
9055
+ if (capabilities.length && (!isWeb || funnel)) {
9056
+ throw new Error(
9057
+ "App capabilities are supported only by private HTTP(S) listeners"
9058
+ );
9059
+ }
9060
+ if (proxyProtocol !== "NONE" && protocol !== "TCP") {
9061
+ throw new Error("PROXY protocol is supported only by TCP listeners");
9062
+ }
9063
+ return {
9064
+ protocol,
9065
+ listenPort,
9066
+ mountPath: isWeb ? normalizeTailscaleMountPath(route.mountPath) : "/",
9067
+ destination: {
9068
+ protocol: destinationProtocol,
9069
+ port: integerPort(destination.port, "Tailscale destination port"),
9070
+ path: isWeb ? normalizeDestinationPath(destination.path) : ""
9071
+ },
9072
+ funnel,
9073
+ appCapabilities: capabilities,
9074
+ proxyProtocol
9075
+ };
9076
+ }
9077
+ function tailscaleServeInspectPayload(value) {
9078
+ const payload = objectValue13(value, "Tailscale Serve inspect payload");
9079
+ exactKeys3(payload, ["operationId"], "Tailscale Serve inspect payload");
9080
+ return { operationId: requiredString(payload.operationId, "operationId") };
9081
+ }
9082
+ function mutationPayload(value, allowPrevious) {
9083
+ const payload = objectValue13(value, "Tailscale Serve mutation payload");
9084
+ exactKeys3(
9085
+ payload,
9086
+ [
9087
+ "operationId",
9088
+ "templateId",
9089
+ "revision",
9090
+ "route",
9091
+ ...allowPrevious ? ["previousRoute"] : []
9092
+ ],
9093
+ "Tailscale Serve mutation payload"
9094
+ );
9095
+ if (!Number.isInteger(payload.revision) || payload.revision < 1) {
9096
+ throw new Error("Tailscale template revision must be a positive integer");
9097
+ }
9098
+ return {
9099
+ operationId: requiredString(payload.operationId, "operationId"),
9100
+ templateId: requiredString(payload.templateId, "templateId"),
9101
+ revision: payload.revision,
9102
+ route: parseTailscaleServeRoute(payload.route)
9103
+ };
9104
+ }
9105
+ function tailscaleServeUpsertPayload(value) {
9106
+ const base = mutationPayload(value, true);
9107
+ const payload = value;
9108
+ return {
9109
+ ...base,
9110
+ previousRoute: payload.previousRoute === null ? null : parseTailscaleServeRoute(payload.previousRoute)
9111
+ };
9112
+ }
9113
+ function tailscaleServeRemovePayload(value) {
9114
+ return mutationPayload(value, false);
9115
+ }
9116
+
8579
9117
  // src/inventory.ts
8580
9118
  var AGENT_VERSION = process.env.CONTROL_AGENT_VERSION ?? "0.1.0";
8581
9119
  var PORTABLE_AGENT_CAPABILITIES = [
@@ -8599,9 +9137,11 @@ var MACOS_AGENT_CAPABILITIES = [
8599
9137
  ...IOS_BUILD_JOB_KINDS,
8600
9138
  ...SIGNING_ASSET_JOB_KINDS
8601
9139
  ];
9140
+ var UNIX_AGENT_CAPABILITIES = [...TAILSCALE_SERVE_JOB_KINDS];
8602
9141
  function capabilitiesForPlatform(targetPlatform) {
8603
9142
  return [
8604
9143
  ...PORTABLE_AGENT_CAPABILITIES,
9144
+ ...targetPlatform === "darwin" || targetPlatform === "linux" ? UNIX_AGENT_CAPABILITIES : [],
8605
9145
  ...targetPlatform === "darwin" ? MACOS_AGENT_CAPABILITIES : []
8606
9146
  ];
8607
9147
  }
@@ -9031,7 +9571,7 @@ import {
9031
9571
  cp,
9032
9572
  mkdir as mkdir2,
9033
9573
  mkdtemp as mkdtemp2,
9034
- open,
9574
+ open as open2,
9035
9575
  readdir,
9036
9576
  readFile as readFile2,
9037
9577
  realpath as realpath2,
@@ -9426,7 +9966,7 @@ function command(executable, args, timeoutMs, signal, cwd, env = process.env) {
9426
9966
  });
9427
9967
  }
9428
9968
  async function commandToFile(executable, args, timeoutMs, signal, cwd, env, destination) {
9429
- const output = await open(destination, "wx", 384);
9969
+ const output = await open2(destination, "wx", 384);
9430
9970
  try {
9431
9971
  return await captureCommand({
9432
9972
  command: executable,
@@ -9936,7 +10476,7 @@ var parseBuildSourceMetadata = async (payload, timeoutMs, signal) => {
9936
10476
  headSha: input.headSha
9937
10477
  };
9938
10478
  };
9939
- function simulatorDestinations(value) {
10479
+ function simulatorDestinations(value, includeUnavailable = false) {
9940
10480
  if (!value || typeof value !== "object" || Array.isArray(value)) return [];
9941
10481
  const devices = value.devices;
9942
10482
  if (!devices || typeof devices !== "object" || Array.isArray(devices))
@@ -9949,7 +10489,8 @@ function simulatorDestinations(value) {
9949
10489
  for (const raw of entries) {
9950
10490
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
9951
10491
  const device = raw;
9952
- if (device.isAvailable !== true || typeof device.udid !== "string")
10492
+ const available = device.isAvailable === true;
10493
+ if (!available && !includeUnavailable || typeof device.udid !== "string")
9953
10494
  continue;
9954
10495
  result.push({
9955
10496
  type: "SIMULATOR",
@@ -9957,7 +10498,8 @@ function simulatorDestinations(value) {
9957
10498
  name: typeof device.name === "string" ? device.name : device.udid,
9958
10499
  platform: "iOS Simulator",
9959
10500
  osVersion,
9960
- state: typeof device.state === "string" ? device.state : null
10501
+ state: typeof device.state === "string" ? device.state : available ? null : "Unavailable",
10502
+ available
9961
10503
  });
9962
10504
  }
9963
10505
  }
@@ -9977,7 +10519,7 @@ function nestedString(value, paths) {
9977
10519
  }
9978
10520
  return null;
9979
10521
  }
9980
- function physicalDestinations(value) {
10522
+ function physicalDestinations(value, includeUnavailable = false) {
9981
10523
  if (!value || typeof value !== "object" || Array.isArray(value)) return [];
9982
10524
  const result = value.result;
9983
10525
  const devices = result && typeof result === "object" && !Array.isArray(result) ? result.devices : null;
@@ -9998,7 +10540,8 @@ function physicalDestinations(value) {
9998
10540
  ["connectionProperties", "tunnelState"],
9999
10541
  ["deviceProperties", "connectionState"]
10000
10542
  ]);
10001
- if (state && /(disconnected|unavailable|not.?connected)/i.test(state)) {
10543
+ const available = !(state && /(disconnected|unavailable|not.?connected|offline)/i.test(state));
10544
+ if (!available && !includeUnavailable) {
10002
10545
  return [];
10003
10546
  }
10004
10547
  return [
@@ -10011,37 +10554,13 @@ function physicalDestinations(value) {
10011
10554
  ["deviceProperties", "osVersionNumber"],
10012
10555
  ["deviceProperties", "osVersion"]
10013
10556
  ]),
10014
- state
10557
+ state,
10558
+ available
10015
10559
  }
10016
10560
  ];
10017
10561
  });
10018
10562
  }
10019
- function genericBuildDestinations(action) {
10020
- if (!GENERIC_BUILD_DESTINATION_ACTIONS.includes(action)) return [];
10021
- const physical = {
10022
- type: "PHYSICAL_DEVICE",
10023
- id: "generic-ios",
10024
- name: "Any Physical iOS Device",
10025
- platform: "iOS",
10026
- osVersion: null,
10027
- state: null,
10028
- generic: true
10029
- };
10030
- if (action === "ARCHIVE") return [physical];
10031
- return [
10032
- {
10033
- type: "SIMULATOR",
10034
- id: "generic-ios-simulator",
10035
- name: "Any iOS Simulator",
10036
- platform: "iOS Simulator",
10037
- osVersion: null,
10038
- state: null,
10039
- generic: true
10040
- },
10041
- physical
10042
- ];
10043
- }
10044
- async function listPhysicalDevices(timeoutMs, signal) {
10563
+ async function listPhysicalDevices(timeoutMs, signal, includeUnavailable = false) {
10045
10564
  const directory = await mkdtemp2(join3(tmpdir2(), "ade-devices-"));
10046
10565
  const output = join3(directory, "devices.json");
10047
10566
  try {
@@ -10061,7 +10580,10 @@ async function listPhysicalDevices(timeoutMs, signal) {
10061
10580
  signal
10062
10581
  );
10063
10582
  if (result.exitCode !== 0) return [];
10064
- return physicalDestinations(JSON.parse(await readFile2(output, "utf8")));
10583
+ return physicalDestinations(
10584
+ JSON.parse(await readFile2(output, "utf8")),
10585
+ includeUnavailable
10586
+ );
10065
10587
  } finally {
10066
10588
  await rm3(directory, { recursive: true, force: true });
10067
10589
  }
@@ -10069,24 +10591,7 @@ async function listPhysicalDevices(timeoutMs, signal) {
10069
10591
  var inspectBuildDestinations = async (payload, timeoutMs, signal) => {
10070
10592
  const input = parseBuildDestinationsPayload(payload);
10071
10593
  const folder = await validateWorktree(input, timeoutMs, signal);
10072
- const absolutePath = await validateSource(folder, input.source);
10073
- const preflight = await command(
10074
- "xcrun",
10075
- [
10076
- "xcodebuild",
10077
- ...sourceArguments(input.source, absolutePath),
10078
- "-scheme",
10079
- input.scheme,
10080
- "-configuration",
10081
- input.configuration,
10082
- "-showBuildSettings",
10083
- "-json"
10084
- ],
10085
- Math.min(timeoutMs, 6e4),
10086
- signal,
10087
- folder
10088
- );
10089
- requireSuccess(preflight, "The saved scheme or configuration is unavailable");
10594
+ await validateSource(folder, input.source);
10090
10595
  const [simulators, physical] = await Promise.all([
10091
10596
  command(
10092
10597
  "xcrun",
@@ -10112,7 +10617,7 @@ var inspectBuildRunDestinations = async (payload, timeoutMs, signal) => {
10112
10617
  if (input.destinationType === "SIMULATOR") {
10113
10618
  const simulators = await command(
10114
10619
  "xcrun",
10115
- ["simctl", "list", "devices", "available", "-j"],
10620
+ ["simctl", "list", "devices", "-j"],
10116
10621
  Math.min(timeoutMs, 3e4),
10117
10622
  signal,
10118
10623
  folder
@@ -10120,12 +10625,33 @@ var inspectBuildRunDestinations = async (payload, timeoutMs, signal) => {
10120
10625
  requireSuccess(simulators, "Could not inspect available simulators");
10121
10626
  return {
10122
10627
  ...successfulProcess,
10123
- destinations: simulatorDestinations(JSON.parse(simulators.stdout))
10628
+ destinations: simulatorDestinations(JSON.parse(simulators.stdout), true)
10124
10629
  };
10125
10630
  }
10126
10631
  return {
10127
10632
  ...successfulProcess,
10128
- destinations: await listPhysicalDevices(timeoutMs, signal)
10633
+ destinations: await listPhysicalDevices(timeoutMs, signal, true)
10634
+ };
10635
+ };
10636
+ var inspectAgentBuildRunDestinations = async (payload, timeoutMs, signal) => {
10637
+ const input = parseBuildAgentRunDestinationsPayload(payload);
10638
+ if (input.destinationType === "SIMULATOR") {
10639
+ const simulators = await command(
10640
+ "xcrun",
10641
+ ["simctl", "list", "devices", "-j"],
10642
+ Math.min(timeoutMs, 3e4),
10643
+ signal,
10644
+ process.cwd()
10645
+ );
10646
+ requireSuccess(simulators, "Could not inspect available simulators");
10647
+ return {
10648
+ ...successfulProcess,
10649
+ destinations: simulatorDestinations(JSON.parse(simulators.stdout), true)
10650
+ };
10651
+ }
10652
+ return {
10653
+ ...successfulProcess,
10654
+ destinations: await listPhysicalDevices(timeoutMs, signal, true)
10129
10655
  };
10130
10656
  };
10131
10657
  function sensitiveValues(env) {
@@ -10897,8 +11423,8 @@ async function writeJsonReport(input, kind, timeoutMs, signal) {
10897
11423
  ),
10898
11424
  `Could not generate ${filename}`
10899
11425
  );
10900
- const serialized = await readFile2(temporary, "utf8");
10901
- const parsed = JSON.parse(serialized);
11426
+ const serialized2 = await readFile2(temporary, "utf8");
11427
+ const parsed = JSON.parse(serialized2);
10902
11428
  const normalized = kind === "TEST_RESULTS" ? normalizeTestResults(parsed) : normalizeCoverage(parsed);
10903
11429
  await rename2(temporary, destination);
10904
11430
  return {
@@ -11246,7 +11772,10 @@ async function captureArtifacts(input, folder, signal, includeProducts = true) {
11246
11772
  await artifact(input.artifactDirectory, "RUNNABLE_APP", destination, {
11247
11773
  bundleIdentifier: settings.PRODUCT_BUNDLE_IDENTIFIER ?? null,
11248
11774
  target: apps[0].target,
11249
- destinationType: input.destination.type
11775
+ destinationType: input.destination.type,
11776
+ architectures: (settings.ARCHS ?? settings.VALID_ARCHS ?? "").split(/\s+/).filter(Boolean),
11777
+ minimumOSVersion: settings.IPHONEOS_DEPLOYMENT_TARGET ?? null,
11778
+ sdkName: settings.SDK_NAME ?? null
11250
11779
  })
11251
11780
  );
11252
11781
  }
@@ -11325,6 +11854,7 @@ var runIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
11325
11854
  telemetry: input.telemetry ?? null
11326
11855
  };
11327
11856
  let buildResult = null;
11857
+ let preflightResult = null;
11328
11858
  let coverageChanges = [];
11329
11859
  let errorCode = null;
11330
11860
  let error = null;
@@ -11350,29 +11880,50 @@ var runIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
11350
11880
  } catch (progressError) {
11351
11881
  logger.emit("PREPARING", "SYSTEM", cleanError(progressError));
11352
11882
  }
11353
- for (const script of [...input.scripts].sort(
11354
- (a, b) => a.position - b.position
11355
- )) {
11356
- if (!script.preBuildScript) continue;
11357
- const execution = await runHook({
11358
- input,
11359
- folder,
11360
- script,
11361
- phase: "PRE_BUILD",
11362
- source: script.preBuildScript,
11363
- contextPath,
11364
- hookContext: baseHookContext,
11883
+ if (!signal.aborted) {
11884
+ preflightResult = await runLoggedCommand({
11885
+ command: "xcrun",
11886
+ args: xcodeBuildSettingsArguments(input, folder),
11887
+ cwd: folder,
11888
+ env: xcodeEnvironment(),
11889
+ timeoutMs: Math.min(timeoutMs, 6e4),
11890
+ signal,
11365
11891
  logger,
11366
- signal
11892
+ phase: "PREFLIGHT"
11367
11893
  });
11368
- scriptExecutions.push(execution);
11369
- if (execution.causedBuildFailure) {
11894
+ if (preflightResult.exitCode !== 0 && !preflightResult.cancelled) {
11370
11895
  failBuild = true;
11371
- errorCode = "SCRIPT_FAILED";
11372
- error = execution.error;
11373
- break;
11896
+ errorCode = preflightResult.timedOut ? "TIMEOUT" : classifyFailure(preflightResult.output);
11897
+ error = cleanError(
11898
+ preflightResult.output || "The saved scheme, configuration, or destination is unavailable"
11899
+ );
11900
+ }
11901
+ }
11902
+ if (!failBuild && !signal.aborted) {
11903
+ for (const script of [...input.scripts].sort(
11904
+ (a, b) => a.position - b.position
11905
+ )) {
11906
+ if (!script.preBuildScript) continue;
11907
+ const execution = await runHook({
11908
+ input,
11909
+ folder,
11910
+ script,
11911
+ phase: "PRE_BUILD",
11912
+ source: script.preBuildScript,
11913
+ contextPath,
11914
+ hookContext: baseHookContext,
11915
+ logger,
11916
+ signal
11917
+ });
11918
+ scriptExecutions.push(execution);
11919
+ if (execution.causedBuildFailure) {
11920
+ failBuild = true;
11921
+ errorCode = "SCRIPT_FAILED";
11922
+ error = execution.error;
11923
+ break;
11924
+ }
11925
+ if (signal.aborted) break;
11374
11926
  }
11375
- if (signal.aborted) break;
11376
11927
  }
11377
11928
  if (!failBuild && !signal.aborted) {
11378
11929
  try {
@@ -11430,7 +11981,7 @@ var runIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
11430
11981
  ...baseHookContext,
11431
11982
  buildFolder: input.artifactDirectory,
11432
11983
  failed: failBuild || !signal.aborted && buildResult !== null && buildResult.exitCode !== 0,
11433
- cancelled: signal.aborted || buildResult?.cancelled === true,
11984
+ cancelled: signal.aborted || preflightResult?.cancelled === true || buildResult?.cancelled === true,
11434
11985
  errorCode,
11435
11986
  error
11436
11987
  },
@@ -11466,7 +12017,7 @@ var runIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
11466
12017
  }
11467
12018
  const reports = [];
11468
12019
  const testAction = ["TEST", "TEST_WITHOUT_BUILDING"].includes(input.action);
11469
- const canGenerateReports = () => !signal.aborted && buildResult?.cancelled !== true && buildResult?.timedOut !== true;
12020
+ const canGenerateReports = () => !signal.aborted && buildResult !== null && !buildResult.cancelled && !buildResult.timedOut;
11470
12021
  if (canGenerateReports() && testAction && input.advancedSettings.parseTestResults) {
11471
12022
  const report = await writeJsonReport(
11472
12023
  input,
@@ -11510,8 +12061,8 @@ var runIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
11510
12061
  await artifact(input.artifactDirectory, "RAW_LOG", rawLog)
11511
12062
  );
11512
12063
  }
11513
- const cancelled = signal.aborted || buildResult?.cancelled === true;
11514
- const timedOut = buildResult?.timedOut === true;
12064
+ const cancelled = signal.aborted || preflightResult?.cancelled === true || buildResult?.cancelled === true;
12065
+ const timedOut = preflightResult?.timedOut === true || buildResult?.timedOut === true;
11515
12066
  const exitCode = cancelled ? null : failBuild || !buildResult ? 1 : buildResult.exitCode;
11516
12067
  const args = xcodeBuildArguments(input);
11517
12068
  const finalStateHash = await worktreeCodeStateHash(
@@ -11522,7 +12073,7 @@ var runIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
11522
12073
  const codeStateObservedAt = (/* @__PURE__ */ new Date()).toISOString();
11523
12074
  return {
11524
12075
  exitCode,
11525
- signal: buildResult?.signal ?? null,
12076
+ signal: buildResult?.signal ?? preflightResult?.signal ?? null,
11526
12077
  timedOut,
11527
12078
  cancelled,
11528
12079
  errorCode,
@@ -11633,6 +12184,67 @@ var downloadIosBuildArtifact = async (payload, timeoutMs, signal, onLog, context
11633
12184
  }
11634
12185
  }
11635
12186
  };
12187
+ var uploadTransferredIosBuildArtifact = async (payload, timeoutMs, signal, onLog, context) => {
12188
+ const input = parseBuildArtifactTransferUploadPayload(payload);
12189
+ if (!context?.uploadBuildArtifactTransfer) {
12190
+ throw new Error("This agent cannot upload transferred build artifacts");
12191
+ }
12192
+ const root = await realpath2(input.artifactDirectory);
12193
+ const target2 = await realpath2(
12194
+ containedPath2(root, input.artifactRelativePath)
12195
+ );
12196
+ const difference = relative3(root, target2);
12197
+ if (!difference || difference === ".." || difference.startsWith(`..${sep3}`) || isAbsolute3(difference)) {
12198
+ throw new Error("Build artifact resolves outside the build folder");
12199
+ }
12200
+ const information = await stat2(target2);
12201
+ let uploadPath = target2;
12202
+ let filename = basename2(target2);
12203
+ let contentType = "application/octet-stream";
12204
+ let temporaryArchive = null;
12205
+ try {
12206
+ if (information.isDirectory()) {
12207
+ temporaryArchive = join3(
12208
+ tmpdir2(),
12209
+ `ade-build-transfer-${input.transferId}-${randomUUID()}.tar.gz`
12210
+ );
12211
+ requireSuccess(
12212
+ await command(
12213
+ "tar",
12214
+ ["-czf", temporaryArchive, "-C", dirname2(target2), basename2(target2)],
12215
+ timeoutMs,
12216
+ signal
12217
+ ),
12218
+ "Could not package the runnable app"
12219
+ );
12220
+ uploadPath = temporaryArchive;
12221
+ filename = `${filename}.tar.gz`;
12222
+ contentType = "application/gzip";
12223
+ } else if (!information.isFile()) {
12224
+ throw new Error("Build artifact is not transferable");
12225
+ }
12226
+ const checksum = await fileChecksum(uploadPath);
12227
+ if (!checksum)
12228
+ throw new Error("Could not checksum the runnable app archive");
12229
+ await onLog({
12230
+ sequence: 0,
12231
+ stream: "SYSTEM",
12232
+ message: `Uploading ${filename} in resumable 16 MiB chunks`,
12233
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
12234
+ });
12235
+ await context.uploadBuildArtifactTransfer({
12236
+ transferId: input.transferId,
12237
+ path: uploadPath,
12238
+ filename,
12239
+ contentType,
12240
+ checksum,
12241
+ signal
12242
+ });
12243
+ return successfulProcess;
12244
+ } finally {
12245
+ if (temporaryArchive) await rm3(temporaryArchive, { force: true });
12246
+ }
12247
+ };
11636
12248
  async function runSimpleLogged(logger, options) {
11637
12249
  return runLoggedCommand({
11638
12250
  command: "xcrun",
@@ -11651,17 +12263,7 @@ async function runSimpleLogged(logger, options) {
11651
12263
  function simulatorAppArguments(destinationId) {
11652
12264
  return ["-a", "Simulator", "--args", "-CurrentDeviceUDID", destinationId];
11653
12265
  }
11654
- var deployIosBuild = async (payload, timeoutMs, signal, _onLog, context) => {
11655
- const input = parseBuildDeploymentPayload(payload);
11656
- const folder = await validateWorktree(
11657
- input,
11658
- Math.min(timeoutMs, 6e4),
11659
- signal
11660
- );
11661
- const appPath = containedPath2(
11662
- input.artifactDirectory,
11663
- input.artifactRelativePath
11664
- );
12266
+ async function deployPreparedIosBuild(input, folder, appPath, timeoutMs, signal, context) {
11665
12267
  if (!await pathExists(appPath) || !appPath.endsWith(".app")) {
11666
12268
  throw new Error("Runnable app artifact is missing");
11667
12269
  }
@@ -11851,6 +12453,99 @@ var deployIosBuild = async (payload, timeoutMs, signal, _onLog, context) => {
11851
12453
  } finally {
11852
12454
  await logger.close();
11853
12455
  }
12456
+ }
12457
+ var deployIosBuild = async (payload, timeoutMs, signal, _onLog, context) => {
12458
+ const input = parseBuildDeploymentPayload(payload);
12459
+ const folder = await validateWorktree(
12460
+ input,
12461
+ Math.min(timeoutMs, 6e4),
12462
+ signal
12463
+ );
12464
+ const appPath = containedPath2(
12465
+ input.artifactDirectory,
12466
+ input.artifactRelativePath
12467
+ );
12468
+ return deployPreparedIosBuild(
12469
+ input,
12470
+ folder,
12471
+ appPath,
12472
+ timeoutMs,
12473
+ signal,
12474
+ context
12475
+ );
12476
+ };
12477
+ async function createRemoteBuildStagingDirectory(buildId) {
12478
+ return realpath2(
12479
+ await mkdtemp2(join3(tmpdir2(), `ade-remote-build-${buildId}-`))
12480
+ );
12481
+ }
12482
+ var deployTransferredIosBuild = async (payload, timeoutMs, signal, onLog, context) => {
12483
+ const input = parseBuildRemoteDeploymentPayload(payload);
12484
+ if (!context?.downloadBuildArtifactTransfer) {
12485
+ throw new Error("This agent cannot download transferred build artifacts");
12486
+ }
12487
+ const staging = await createRemoteBuildStagingDirectory(input.buildId);
12488
+ const archive = join3(staging, "artifact.tar.gz");
12489
+ try {
12490
+ await onLog({
12491
+ sequence: 0,
12492
+ stream: "SYSTEM",
12493
+ message: "Waiting for the relayed build artifact",
12494
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
12495
+ });
12496
+ await context.downloadBuildArtifactTransfer({
12497
+ transferId: input.transferId,
12498
+ path: archive,
12499
+ signal
12500
+ });
12501
+ const listing = await command(
12502
+ "tar",
12503
+ ["-tzf", archive],
12504
+ Math.min(timeoutMs, 12e4),
12505
+ signal,
12506
+ staging
12507
+ );
12508
+ requireSuccess(listing, "Transferred build archive is malformed");
12509
+ const entries = listing.stdout.split("\n").map((entry) => entry.trim().replace(/^\.\//, "")).filter(Boolean);
12510
+ if (!entries.length || entries.some(
12511
+ (entry) => entry.startsWith("/") || entry.split("/").some((component) => component === "..")
12512
+ )) {
12513
+ throw new Error("Transferred build archive contains an unsafe path");
12514
+ }
12515
+ const roots = new Set(entries.map((entry) => entry.split("/")[0]));
12516
+ const root = [...roots][0];
12517
+ if (roots.size !== 1 || !root?.endsWith(".app")) {
12518
+ throw new Error("Transferred build archive must contain one app bundle");
12519
+ }
12520
+ requireSuccess(
12521
+ await command(
12522
+ "tar",
12523
+ ["-xzf", archive, "-C", staging],
12524
+ Math.min(timeoutMs, 12e4),
12525
+ signal,
12526
+ staging
12527
+ ),
12528
+ "Could not extract the transferred app"
12529
+ );
12530
+ const appPath = await realpath2(join3(staging, root));
12531
+ const pathDifference = relative3(staging, appPath);
12532
+ if (pathDifference === ".." || pathDifference.startsWith(`..${sep3}`) || isAbsolute3(pathDifference)) {
12533
+ throw new Error("Transferred app resolves outside its staging directory");
12534
+ }
12535
+ return await deployPreparedIosBuild(
12536
+ {
12537
+ ...input,
12538
+ artifactDirectory: staging
12539
+ },
12540
+ staging,
12541
+ appPath,
12542
+ timeoutMs,
12543
+ signal,
12544
+ context
12545
+ );
12546
+ } finally {
12547
+ await rm3(staging, { recursive: true, force: true });
12548
+ }
11854
12549
  };
11855
12550
  function exportPlist(settings) {
11856
12551
  const method = {
@@ -15174,7 +15869,7 @@ var runWorkflowGitCheckpoint = async (payloadValue, _timeoutMs, signal) => {
15174
15869
 
15175
15870
  // src/handlers/worktrees.ts
15176
15871
  import { createWriteStream as createWriteStream2, watch } from "node:fs";
15177
- import { mkdtemp as mkdtemp6, open as open2, readFile as readFile9, realpath as realpath9, rm as rm10, stat as stat10 } from "node:fs/promises";
15872
+ import { mkdtemp as mkdtemp6, open as open3, readFile as readFile9, realpath as realpath9, rm as rm10, stat as stat10 } from "node:fs/promises";
15178
15873
  import { tmpdir as tmpdir6 } from "node:os";
15179
15874
  import { basename as basename5, dirname as dirname8, isAbsolute as isAbsolute7, join as join14, relative as relative8 } from "node:path";
15180
15875
  import { spawn as spawn7 } from "node:child_process";
@@ -16438,7 +17133,7 @@ function requestedDiffPaths(input) {
16438
17133
  return [input.path];
16439
17134
  }
16440
17135
  async function readBoundedFile(path, limit) {
16441
- const file = await open2(path, "r");
17136
+ const file = await open3(path, "r");
16442
17137
  try {
16443
17138
  const metadata = await file.stat();
16444
17139
  if (!metadata.isFile()) throw new Error("Changed path is not a file");
@@ -18385,6 +19080,280 @@ var autoSyncWorktree = async (payload, timeoutMs, signal) => {
18385
19080
  };
18386
19081
  };
18387
19082
 
19083
+ // src/handlers/tailscale.ts
19084
+ import { accessSync as accessSync2, constants as constants2 } from "node:fs";
19085
+ var MAX_OUTPUT_BYTES2 = 2 * 1024 * 1024;
19086
+ var mutationQueues = /* @__PURE__ */ new Map();
19087
+ function objectValue14(value) {
19088
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
19089
+ }
19090
+ function resolveTailscaleExecutable(platform2 = process.platform, resolve12 = findExecutable, isExecutable2 = (path) => {
19091
+ accessSync2(path, constants2.X_OK);
19092
+ return true;
19093
+ }) {
19094
+ const resolved = resolve12("tailscale", {
19095
+ overrideVariable: "CONTROL_AGENT_TAILSCALE_EXECUTABLE"
19096
+ });
19097
+ if (resolved) return resolved;
19098
+ if (platform2 === "darwin") {
19099
+ for (const path of [
19100
+ "/Applications/Tailscale.app/Contents/MacOS/Tailscale",
19101
+ "/Applications/Tailscale.app/Contents/MacOS/tailscale"
19102
+ ]) {
19103
+ try {
19104
+ if (isExecutable2(path)) return path;
19105
+ } catch {
19106
+ }
19107
+ }
19108
+ }
19109
+ throw new Error(
19110
+ "Tailscale CLI was not found. Install Tailscale or set CONTROL_AGENT_TAILSCALE_EXECUTABLE."
19111
+ );
19112
+ }
19113
+ function parseTailscaleJson(value, label) {
19114
+ try {
19115
+ const parsed = JSON.parse(value);
19116
+ const object = objectValue14(parsed);
19117
+ if (!object) throw new Error("expected an object");
19118
+ return object;
19119
+ } catch (error) {
19120
+ throw new Error(
19121
+ `${label} returned malformed JSON: ${error instanceof Error ? error.message : String(error)}`
19122
+ );
19123
+ }
19124
+ }
19125
+ function tailscaleCommandFailure(command3, result) {
19126
+ const reason = result.timedOut ? "timed out" : result.cancelled ? "was cancelled" : result.stderr.trim() || `exited with status ${result.exitCode}`;
19127
+ return new Error(`${command3} failed: ${reason}`);
19128
+ }
19129
+ async function captureTailscale(executable, args, timeoutMs, signal) {
19130
+ const result = await captureCommand({
19131
+ command: executable,
19132
+ args,
19133
+ timeoutMs,
19134
+ signal,
19135
+ maxOutputBytes: MAX_OUTPUT_BYTES2
19136
+ });
19137
+ if (result.outputTruncated) {
19138
+ throw new Error(`tailscale ${args.join(" ")} exceeded the output limit`);
19139
+ }
19140
+ if (result.exitCode !== 0) {
19141
+ throw tailscaleCommandFailure(`tailscale ${args.join(" ")}`, result);
19142
+ }
19143
+ return result;
19144
+ }
19145
+ function backendIdentity(status2) {
19146
+ const self2 = objectValue14(status2.Self);
19147
+ const rawAddresses = Array.isArray(self2?.TailscaleIPs) ? self2.TailscaleIPs : status2.TailscaleIPs;
19148
+ const addresses = Array.isArray(rawAddresses) ? rawAddresses.filter((value) => typeof value === "string") : [];
19149
+ const dnsName = typeof self2?.DNSName === "string" ? self2.DNSName : null;
19150
+ return {
19151
+ dnsHostname: dnsName?.replace(/\.$/, "") ?? null,
19152
+ ipv4: addresses.filter((value) => value.includes(".")),
19153
+ ipv6: addresses.filter((value) => value.includes(":")),
19154
+ backendState: typeof status2.BackendState === "string" ? status2.BackendState : "Unknown"
19155
+ };
19156
+ }
19157
+ function destinationFromProxy(proxy) {
19158
+ const parsed = new URL(proxy);
19159
+ if (parsed.hostname !== "127.0.0.1") {
19160
+ throw new Error("Only 127.0.0.1 Tailscale Serve targets are managed");
19161
+ }
19162
+ const rawProtocol = parsed.protocol.replace(/:$/, "");
19163
+ const protocol = rawProtocol === "https+insecure" ? "HTTPS_INSECURE" : rawProtocol === "https" ? "HTTPS" : rawProtocol === "tcp" ? "TCP" : "HTTP";
19164
+ return {
19165
+ protocol,
19166
+ port: Number(parsed.port),
19167
+ path: protocol === "TCP" ? "" : parsed.pathname === "/" ? "" : parsed.pathname
19168
+ };
19169
+ }
19170
+ function portFromListener(value) {
19171
+ const match = value.match(/:(\d+)$/) ?? value.match(/^(\d+)$/);
19172
+ return match ? Number(match[1]) : null;
19173
+ }
19174
+ function normalizeTailscaleServeSnapshot(status2, serve, inspectedAt = (/* @__PURE__ */ new Date()).toISOString()) {
19175
+ const routes = [];
19176
+ const tcp = objectValue14(serve.TCP) ?? {};
19177
+ const web = objectValue14(serve.Web) ?? {};
19178
+ const funnel = objectValue14(serve.AllowFunnel) ?? {};
19179
+ for (const [listener, rawWeb] of Object.entries(web)) {
19180
+ const listenPort = portFromListener(listener);
19181
+ const webConfig = objectValue14(rawWeb);
19182
+ const handlers2 = objectValue14(webConfig?.Handlers);
19183
+ if (!listenPort || !handlers2) continue;
19184
+ const tcpConfig = objectValue14(tcp[String(listenPort)]);
19185
+ const protocol = tcpConfig?.HTTPS === true ? "HTTPS" : "HTTP";
19186
+ const isFunnel = Object.entries(funnel).some(
19187
+ ([key, enabled]) => enabled === true && portFromListener(key) === listenPort
19188
+ );
19189
+ for (const [mountPath, rawHandler] of Object.entries(handlers2)) {
19190
+ const handler = objectValue14(rawHandler);
19191
+ if (!handler || typeof handler.Proxy !== "string") continue;
19192
+ const capabilities = Array.isArray(handler.AcceptAppCaps) ? handler.AcceptAppCaps.filter(
19193
+ (value) => typeof value === "string"
19194
+ ) : [];
19195
+ try {
19196
+ routes.push(
19197
+ parseTailscaleServeRoute({
19198
+ protocol,
19199
+ listenPort,
19200
+ mountPath,
19201
+ destination: destinationFromProxy(handler.Proxy),
19202
+ funnel: isFunnel,
19203
+ appCapabilities: capabilities,
19204
+ proxyProtocol: "NONE"
19205
+ })
19206
+ );
19207
+ } catch {
19208
+ }
19209
+ }
19210
+ }
19211
+ for (const [listener, rawTcp] of Object.entries(tcp)) {
19212
+ const listenPort = portFromListener(listener);
19213
+ const config = objectValue14(rawTcp);
19214
+ if (!listenPort || !config || typeof config.TCPForward !== "string")
19215
+ continue;
19216
+ const destination = config.TCPForward.startsWith("tcp://") ? config.TCPForward : `tcp://${config.TCPForward}`;
19217
+ const proxy = config.ProxyProtocol;
19218
+ try {
19219
+ routes.push(
19220
+ parseTailscaleServeRoute({
19221
+ protocol: config.TerminateTLS ? "TLS_TERMINATED_TCP" : "TCP",
19222
+ listenPort,
19223
+ mountPath: "/",
19224
+ destination: destinationFromProxy(destination),
19225
+ funnel: Object.entries(funnel).some(
19226
+ ([key, enabled]) => enabled === true && portFromListener(key) === listenPort
19227
+ ),
19228
+ appCapabilities: [],
19229
+ proxyProtocol: proxy === 1 || proxy === "1" || proxy === "V1" ? "V1" : proxy === 2 || proxy === "2" || proxy === "V2" ? "V2" : "NONE"
19230
+ })
19231
+ );
19232
+ } catch {
19233
+ }
19234
+ }
19235
+ return {
19236
+ identity: backendIdentity(status2),
19237
+ routes: routes.sort(
19238
+ (first, second) => `${first.listenPort}:${first.mountPath}`.localeCompare(
19239
+ `${second.listenPort}:${second.mountPath}`
19240
+ )
19241
+ ),
19242
+ inspectedAt
19243
+ };
19244
+ }
19245
+ async function inspect(executable, timeoutMs, signal) {
19246
+ const status2 = await captureTailscale(
19247
+ executable,
19248
+ ["status", "--json"],
19249
+ timeoutMs,
19250
+ signal
19251
+ );
19252
+ const serve = await captureTailscale(
19253
+ executable,
19254
+ ["serve", "status", "--json"],
19255
+ timeoutMs,
19256
+ signal
19257
+ );
19258
+ return normalizeTailscaleServeSnapshot(
19259
+ parseTailscaleJson(status2.stdout, "tailscale status --json"),
19260
+ parseTailscaleJson(serve.stdout, "tailscale serve status --json")
19261
+ );
19262
+ }
19263
+ function listenerArguments(route) {
19264
+ const flag = route.protocol === "TLS_TERMINATED_TCP" ? "tls-terminated-tcp" : route.protocol.toLowerCase();
19265
+ return [
19266
+ `--${flag}=${route.listenPort}`,
19267
+ ...route.protocol === "HTTP" || route.protocol === "HTTPS" ? [`--set-path=${route.mountPath}`] : []
19268
+ ];
19269
+ }
19270
+ function routeTarget(route) {
19271
+ const scheme = route.destination.protocol === "HTTPS_INSECURE" ? "https+insecure" : route.destination.protocol.toLowerCase();
19272
+ return `${scheme}://127.0.0.1:${route.destination.port}${route.destination.path}`;
19273
+ }
19274
+ function tailscaleUpsertArguments(route) {
19275
+ const normalized = parseTailscaleServeRoute(route);
19276
+ return [
19277
+ normalized.funnel ? "funnel" : "serve",
19278
+ "--bg",
19279
+ "--yes",
19280
+ ...listenerArguments(normalized),
19281
+ ...normalized.appCapabilities.length ? [`--accept-app-caps=${normalized.appCapabilities.join(",")}`] : [],
19282
+ ...normalized.proxyProtocol === "NONE" ? [] : [`--proxy-protocol=${normalized.proxyProtocol === "V1" ? "1" : "2"}`],
19283
+ routeTarget(normalized)
19284
+ ];
19285
+ }
19286
+ function tailscaleRemoveArguments(route) {
19287
+ const normalized = parseTailscaleServeRoute(route);
19288
+ return [
19289
+ normalized.funnel ? "funnel" : "serve",
19290
+ ...listenerArguments(normalized),
19291
+ "off"
19292
+ ];
19293
+ }
19294
+ function tailscaleUpsertCommandSequence(route, previousRoute) {
19295
+ return [
19296
+ ...previousRoute && JSON.stringify(previousRoute) !== JSON.stringify(route) ? [tailscaleRemoveArguments(previousRoute)] : [],
19297
+ tailscaleUpsertArguments(route)
19298
+ ];
19299
+ }
19300
+ async function serialized(agentId, work) {
19301
+ const previous = mutationQueues.get(agentId) ?? Promise.resolve();
19302
+ let release2;
19303
+ const current = new Promise((resolve12) => {
19304
+ release2 = resolve12;
19305
+ });
19306
+ const tail = previous.then(() => current);
19307
+ mutationQueues.set(agentId, tail);
19308
+ await previous;
19309
+ try {
19310
+ return await work();
19311
+ } finally {
19312
+ release2();
19313
+ if (mutationQueues.get(agentId) === tail) mutationQueues.delete(agentId);
19314
+ }
19315
+ }
19316
+ function success2(snapshot) {
19317
+ return {
19318
+ exitCode: 0,
19319
+ signal: null,
19320
+ timedOut: false,
19321
+ cancelled: false,
19322
+ snapshot
19323
+ };
19324
+ }
19325
+ var inspectTailscaleServe = async (rawPayload, timeoutMs, signal) => {
19326
+ tailscaleServeInspectPayload(rawPayload);
19327
+ const executable = resolveTailscaleExecutable();
19328
+ return success2(await inspect(executable, timeoutMs, signal));
19329
+ };
19330
+ var upsertTailscaleServe = async (rawPayload, timeoutMs, signal, _onLog, context) => {
19331
+ const payload = tailscaleServeUpsertPayload(rawPayload);
19332
+ const executable = resolveTailscaleExecutable();
19333
+ return serialized(context?.agentId ?? "default", async () => {
19334
+ for (const args of tailscaleUpsertCommandSequence(
19335
+ payload.route,
19336
+ payload.previousRoute
19337
+ )) {
19338
+ await captureTailscale(executable, args, timeoutMs, signal);
19339
+ }
19340
+ return success2(await inspect(executable, timeoutMs, signal));
19341
+ });
19342
+ };
19343
+ var removeTailscaleServe = async (rawPayload, timeoutMs, signal, _onLog, context) => {
19344
+ const payload = tailscaleServeRemovePayload(rawPayload);
19345
+ const executable = resolveTailscaleExecutable();
19346
+ return serialized(context?.agentId ?? "default", async () => {
19347
+ await captureTailscale(
19348
+ executable,
19349
+ tailscaleRemoveArguments(payload.route),
19350
+ timeoutMs,
19351
+ signal
19352
+ );
19353
+ return success2(await inspect(executable, timeoutMs, signal));
19354
+ });
19355
+ };
19356
+
18388
19357
  // src/handlers/index.ts
18389
19358
  var handlers = {
18390
19359
  [CLI_HEALTH_JOB_KIND]: runCliHealth,
@@ -18423,10 +19392,13 @@ var handlers = {
18423
19392
  [IOS_SOURCE_PARSE_JOB_KIND]: parseBuildSourceMetadata,
18424
19393
  [IOS_DESTINATIONS_JOB_KIND]: inspectBuildDestinations,
18425
19394
  [IOS_RUN_DESTINATIONS_JOB_KIND]: inspectBuildRunDestinations,
19395
+ [IOS_AGENT_RUN_DESTINATIONS_JOB_KIND]: inspectAgentBuildRunDestinations,
18426
19396
  [IOS_BUILD_JOB_KIND]: runIosBuild,
18427
19397
  [IOS_BUILD_DELETE_JOB_KIND]: deleteIosBuild,
18428
19398
  [IOS_ARTIFACT_DOWNLOAD_JOB_KIND]: downloadIosBuildArtifact,
19399
+ [IOS_ARTIFACT_TRANSFER_UPLOAD_JOB_KIND]: uploadTransferredIosBuildArtifact,
18429
19400
  [IOS_DEPLOY_JOB_KIND]: deployIosBuild,
19401
+ [IOS_REMOTE_DEPLOY_JOB_KIND]: deployTransferredIosBuild,
18430
19402
  [IOS_EXPORT_JOB_KIND]: exportIosArchive,
18431
19403
  [IOS_TEST_RESULTS_JOB_KIND]: generateIosBuildReport,
18432
19404
  [IOS_COVERAGE_REPORT_JOB_KIND]: generateIosBuildReport,
@@ -18437,7 +19409,10 @@ var handlers = {
18437
19409
  [SIGNING_PROFILE_INSTALL_JOB_KIND]: installSigningProfile,
18438
19410
  [SIGNING_PROFILE_DELETE_JOB_KIND]: deleteSigningProfile,
18439
19411
  [SIGNING_IDENTITY_IMPORT_JOB_KIND]: importSigningIdentity,
18440
- [SIGNING_IDENTITY_DELETE_JOB_KIND]: deleteSigningIdentity
19412
+ [SIGNING_IDENTITY_DELETE_JOB_KIND]: deleteSigningIdentity,
19413
+ [TAILSCALE_SERVE_INSPECT_JOB_KIND]: inspectTailscaleServe,
19414
+ [TAILSCALE_SERVE_UPSERT_JOB_KIND]: upsertTailscaleServe,
19415
+ [TAILSCALE_SERVE_REMOVE_JOB_KIND]: removeTailscaleServe
18441
19416
  };
18442
19417
 
18443
19418
  // src/repository-coordinator.ts
@@ -18520,6 +19495,8 @@ var JobExecutor = class {
18520
19495
  reportBuildProgress: (input) => this.client.reportBuildProgress(input),
18521
19496
  appendBuildLogChunks: (buildId, chunks) => this.client.appendBuildLogChunks(buildId, chunks),
18522
19497
  uploadBuildArtifact: (input) => this.client.uploadBuildArtifact(input),
19498
+ uploadBuildArtifactTransfer: (input) => this.client.uploadBuildArtifactTransfer(input),
19499
+ downloadBuildArtifactTransfer: (input) => this.client.downloadBuildArtifactTransfer(input),
18523
19500
  claimSigningSecretTransfer: (transferId) => this.client.claimSigningSecretTransfer(transferId),
18524
19501
  claimWorkflowJobSecrets: () => this.client.claimWorkflowJobSecrets(claimed.id),
18525
19502
  appendCommandOutput: (attemptId, chunks) => this.client.appendCommandOutput(claimed.id, attemptId, chunks)
@@ -52441,8 +53418,8 @@ var ClaudeAdapter = class {
52441
53418
  dir: worktree.folder,
52442
53419
  limit: 500
52443
53420
  });
52444
- const serialized = JSON.stringify(history);
52445
- if (serialized.includes('"permissionMode":"plan"')) kind = "PLAN";
53421
+ const serialized2 = JSON.stringify(history);
53422
+ if (serialized2.includes('"permissionMode":"plan"')) kind = "PLAN";
52446
53423
  finalOutput = firstString([...history].reverse());
52447
53424
  this.hydrationCache.set(session.sessionId, {
52448
53425
  lastModified: lastModifiedKey,
@@ -53008,8 +53985,8 @@ var CodexAdapter = class {
53008
53985
  })
53009
53986
  );
53010
53987
  const hydrated = asRecord(detail.thread);
53011
- const serialized = JSON.stringify(hydrated.turns ?? []);
53012
- if (serialized.includes('"type":"plan"')) kind = "PLAN";
53988
+ const serialized2 = JSON.stringify(hydrated.turns ?? []);
53989
+ if (serialized2.includes('"type":"plan"')) kind = "PLAN";
53013
53990
  finalOutput = firstString(
53014
53991
  Array.isArray(hydrated.turns) ? [...hydrated.turns].reverse() : []
53015
53992
  );
@@ -60376,15 +61353,6 @@ var defaultDependencies = {
60376
61353
  signal.addEventListener("abort", onAbort, { once: true });
60377
61354
  })
60378
61355
  };
60379
- function assertLoopbackServer(server) {
60380
- const hostname2 = new URL(server).hostname.toLowerCase();
60381
- const isIpv4Loopback = /^127(?:\.\d{1,3}){3}$/.test(hostname2);
60382
- if (hostname2 !== "localhost" && hostname2 !== "[::1]" && !isIpv4Loopback) {
60383
- throw new Error(
60384
- `Development auto-enrollment is restricted to loopback servers; received ${hostname2}`
60385
- );
60386
- }
60387
- }
60388
61356
  async function waitForServer(client2, timeoutMs, signal, dependencies) {
60389
61357
  const deadline = Date.now() + timeoutMs;
60390
61358
  let lastError;
@@ -60398,17 +61366,18 @@ async function waitForServer(client2, timeoutMs, signal, dependencies) {
60398
61366
  }
60399
61367
  }
60400
61368
  const detail = lastError instanceof Error ? `: ${lastError.message}` : "";
60401
- throw new Error(`Timed out waiting for the local control plane${detail}`);
61369
+ throw new Error(
61370
+ `Timed out waiting for the development control plane${detail}`
61371
+ );
60402
61372
  }
60403
61373
  async function prepareDevelopmentAgent(options, signal, dependencies = defaultDependencies) {
60404
61374
  const server = normalizeServer(options.server);
60405
- assertLoopbackServer(server);
60406
61375
  const websocketServer = options.websocketServer ?? defaultWebSocketServer(server);
60407
61376
  const configFile = options.configFile ?? developmentConfigPath();
60408
61377
  const inventory = dependencies.inventory();
60409
61378
  const name = options.name ?? `${inventory.hostname}-dev`;
60410
61379
  const anonymousClient = dependencies.createClient(server);
60411
- console.log(`Waiting for local control plane at ${server} ...`);
61380
+ console.log(`Waiting for development control plane at ${server} ...`);
60412
61381
  await waitForServer(
60413
61382
  anonymousClient,
60414
61383
  options.waitTimeoutMs ?? DEFAULT_SERVER_WAIT_MS,