orkestrator 2.34.4-beta → 2.34.5-beta

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/client.js CHANGED
@@ -147,13 +147,16 @@ __export(exports_standalone_service, {
147
147
  STANDALONE_MAX_BOOT_ATTEMPTS: () => STANDALONE_MAX_BOOT_ATTEMPTS,
148
148
  STANDALONE_PACKAGE: () => STANDALONE_PACKAGE,
149
149
  activateRelease: () => activateRelease,
150
+ effectiveServiceArguments: () => effectiveServiceArguments,
150
151
  installLauncher: () => installLauncher,
151
152
  installRelease: () => installRelease,
152
153
  isNewerVersion: () => isNewerVersion,
153
154
  linkedVersion: () => linkedVersion,
155
+ nextRollbackTarget: () => nextRollbackTarget,
154
156
  readLauncherState: () => readLauncherState,
155
157
  readReleaseManifest: () => readReleaseManifest,
156
158
  readServiceConfig: () => readServiceConfig,
159
+ readStartupFailure: () => readStartupFailure,
157
160
  readText: () => readText,
158
161
  releaseDirectory: () => releaseDirectory,
159
162
  releaseEntry: () => releaseEntry,
@@ -202,9 +205,31 @@ function readServiceConfig(layout) {
202
205
  bun: value.bun,
203
206
  bunVersion: typeof value.bunVersion === "string" ? value.bunVersion : "",
204
207
  args: value.args.filter((arg) => typeof arg === "string"),
208
+ ...typeof value.rollbackTarget?.version === "string" && Array.isArray(value.rollbackTarget.args) ? {
209
+ rollbackTarget: {
210
+ version: value.rollbackTarget.version,
211
+ args: value.rollbackTarget.args.filter((arg) => typeof arg === "string")
212
+ }
213
+ } : {},
214
+ ...typeof value.installGeneration === "string" ? { installGeneration: value.installGeneration } : {},
205
215
  installedAt: typeof value.installedAt === "string" ? value.installedAt : ""
206
216
  };
207
217
  }
218
+ function nextRollbackTarget(layout, config, before, version) {
219
+ if (!config || !before)
220
+ return;
221
+ const target = config.rollbackTarget;
222
+ if (before === version)
223
+ return target?.version === version ? undefined : target;
224
+ return {
225
+ version: before,
226
+ args: effectiveServiceArguments(config, before, linkedVersion(layout.previous))
227
+ };
228
+ }
229
+ function effectiveServiceArguments(config, current, previous) {
230
+ const target = config?.rollbackTarget;
231
+ return target && target.version === current && previous === current ? target.args : config?.args ?? [];
232
+ }
208
233
  async function writeServiceConfig(layout, config) {
209
234
  await mkdir(layout.root, { recursive: true });
210
235
  await writeAtomically(layout.serviceFile, `${JSON.stringify(config, null, 2)}
@@ -213,13 +238,30 @@ async function writeServiceConfig(layout, config) {
213
238
  function readLauncherState(layout) {
214
239
  const value = readJson(layout.stateFile);
215
240
  const rollback = value?.rollback;
241
+ const bootFailure = value?.bootFailure;
216
242
  return {
217
243
  version: 1,
244
+ ...typeof value?.installGeneration === "string" ? { installGeneration: value.installGeneration } : {},
218
245
  lastReadyVersion: typeof value?.lastReadyVersion === "string" ? value.lastReadyVersion : null,
219
246
  bootAttempts: value?.bootAttempts && typeof value.bootAttempts === "object" ? value.bootAttempts : {},
220
- rollback: rollback && typeof rollback.failedVersion === "string" && typeof rollback.restoredVersion === "string" && typeof rollback.at === "string" ? rollback : null
247
+ rollback: rollback && typeof rollback.failedVersion === "string" && typeof rollback.restoredVersion === "string" && typeof rollback.at === "string" ? rollback : null,
248
+ bootFailure: bootFailure && typeof bootFailure.version === "string" && typeof bootFailure.exits === "number" && typeof bootFailure.at === "string" ? bootFailure : null
221
249
  };
222
250
  }
251
+ function readStartupFailure(layout, version, sinceMs, installGeneration) {
252
+ const state = readLauncherState(layout);
253
+ const rollback = state.rollback;
254
+ if (rollback?.failedVersion === version && (installGeneration === undefined || rollback.installGeneration === installGeneration) && Date.parse(rollback.at) >= sinceMs) {
255
+ return { restoredVersion: rollback.restoredVersion };
256
+ }
257
+ const failure = state.bootFailure;
258
+ if (failure?.version !== version || installGeneration !== undefined && failure.installGeneration !== installGeneration || failure.exits < STANDALONE_MAX_BOOT_ATTEMPTS || Date.parse(failure.at) < sinceMs) {
259
+ return null;
260
+ }
261
+ const previous = linkedVersion(layout.previous);
262
+ const rollbackComing = previous && previous !== version && state.lastReadyVersion !== version;
263
+ return rollbackComing ? null : { restoredVersion: null };
264
+ }
223
265
  async function writeAtomically(file, contents) {
224
266
  const temporary = `${file}.${process.pid}.${Date.now()}.tmp`;
225
267
  await writeFile(temporary, contents, { mode: 384 });
@@ -3457,7 +3499,7 @@ var require_crc32 = __commonJS(function(exports, module) {
3457
3499
  });
3458
3500
 
3459
3501
  // ../../node_modules/.bun/yauzl@3.4.0/node_modules/yauzl/index.js
3460
- function open(path, options2, callback) {
3502
+ function open2(path, options2, callback) {
3461
3503
  if (typeof options2 === "function") {
3462
3504
  callback = options2;
3463
3505
  options2 = null;
@@ -3773,7 +3815,7 @@ var init_yauzl = __esm(() => {
3773
3815
  PassThrough = __require("stream").PassThrough;
3774
3816
  Writable = __require("stream").Writable;
3775
3817
  crc32 = typeof zlib.crc32 === "function" ? zlib.crc32 : require_crc32();
3776
- $open = open;
3818
+ $open = open2;
3777
3819
  $validateFileName = validateFileName;
3778
3820
  util.inherits(ZipFile, EventEmitter);
3779
3821
  ZipFile.prototype.close = function() {
@@ -4391,10 +4433,10 @@ function pinnedToolchainArtifacts(platform = process.platform, architecture = pr
4391
4433
  var PINNED_TOOLCHAIN_VERSIONS, PINNED_SUPPORT_TOOL_VERSIONS, PINNED_TOOL_VERSIONS, GITHUB_RELEASE_HOSTS, NPM_REGISTRY_HOSTS, GROK_DOWNLOAD_HOSTS, CODEX_RELEASE_BASE, PI_RELEASE_BASE, GH_RELEASE_BASE, OPENCODE_RELEASE_BASE, CODEX_CODE_MODE_HOST_FILE_NAME = "codex-code-mode-host", PINNED_TOOLCHAIN_ARTIFACTS;
4392
4434
  var init_manifest = __esm(() => {
4393
4435
  PINNED_TOOLCHAIN_VERSIONS = {
4394
- claude: "2.1.309",
4395
- codex: "0.164.0",
4396
- grok: "1.0.67",
4397
- opencode: "1.19.4",
4436
+ claude: "2.1.314",
4437
+ codex: "0.166.0",
4438
+ grok: "1.0.70",
4439
+ opencode: "1.19.5",
4398
4440
  pi: "1.4.0"
4399
4441
  };
4400
4442
  PINNED_SUPPORT_TOOL_VERSIONS = {
@@ -4425,23 +4467,23 @@ var init_manifest = __esm(() => {
4425
4467
  format: "tar.gz",
4426
4468
  url: `${CODEX_RELEASE_BASE}/codex-aarch64-apple-darwin.tar.gz`,
4427
4469
  entryPath: "codex-aarch64-apple-darwin",
4428
- size: 101767166,
4429
- sha256: "aa8299c169d80111aac05543ad2621c9bdbedb9bc252c905fe3f5a5baf9a4142",
4470
+ size: 107610108,
4471
+ sha256: "c48824462fc57681eafb0925d97b11b589010475613904f570b42fea53e240d5",
4430
4472
  allowedHosts: GITHUB_RELEASE_HOSTS
4431
4473
  },
4432
4474
  companions: [
4433
4475
  codexCodeModeHost("aarch64-apple-darwin", {
4434
- size: 24347400,
4435
- sha256: "7bc3ca697567bc884a76233999c46cb22b023b09f2d4d4ab3518f1354c64e415"
4476
+ size: 24769716,
4477
+ sha256: "7abd50fe5f033495a7b693c45fb82dc57093a18f1c4ba3e8043577de46c5c7c5"
4436
4478
  }, {
4437
- size: 68543072,
4438
- sha256: "416721e33af8822041dc650793512b3e58d43e0be4f3a5c6187eb4dfaad3148a"
4479
+ size: 69182864,
4480
+ sha256: "fd37051bfdce0b65cf22a7077e265720a7bd6709d5e400dcbc26197f3041400a"
4439
4481
  })
4440
4482
  ],
4441
4483
  executable: {
4442
4484
  fileName: "codex",
4443
- size: 260293200,
4444
- sha256: "d8990e3ee7acfbc26f91265b12a1a83f0fee40d0e0f8f1f40a37d4ec21f499cf"
4485
+ size: 268972064,
4486
+ sha256: "39acf979729dd290f9a24ee38b2914e036c91b65952872e28ae2a6b88a7db7c1"
4445
4487
  }
4446
4488
  },
4447
4489
  {
@@ -4453,23 +4495,23 @@ var init_manifest = __esm(() => {
4453
4495
  format: "tar.gz",
4454
4496
  url: `${CODEX_RELEASE_BASE}/codex-x86_64-apple-darwin.tar.gz`,
4455
4497
  entryPath: "codex-x86_64-apple-darwin",
4456
- size: 110491441,
4457
- sha256: "de6bbc013c56337f6ddd3f26dfbfd62aad5924cb68cd5ef6430538922bfa0e75",
4498
+ size: 117027024,
4499
+ sha256: "db0c38713c36199b12e7c8d1629386c35a4ce8f99e1c9ce72f1725a74df67b57",
4458
4500
  allowedHosts: GITHUB_RELEASE_HOSTS
4459
4501
  },
4460
4502
  companions: [
4461
4503
  codexCodeModeHost("x86_64-apple-darwin", {
4462
- size: 26213130,
4463
- sha256: "50f93d5413647488940aa23db9b1c822ee86f2c45fa37af34993e992c9848211"
4504
+ size: 26748407,
4505
+ sha256: "b35c775d5e5f8d60d05e0de7154e5bbbf320b6d0e28a3237d42cc8a742ccaf65"
4464
4506
  }, {
4465
- size: 72251296,
4466
- sha256: "067f9c14f67b02bdb984d4fc4740da14cd397da7ee60b4506eb378921afe3e7d"
4507
+ size: 73183424,
4508
+ sha256: "9d96de017ecec30ec3f8689f489584151c167c255a787dc6e297b6edc9ebe08c"
4467
4509
  })
4468
4510
  ],
4469
4511
  executable: {
4470
4512
  fileName: "codex",
4471
- size: 279923568,
4472
- sha256: "d7e19691e0c1945612475ee8ab32e59ead55472e976b5cea1682d827d34dbba1"
4513
+ size: 289445104,
4514
+ sha256: "1361e18268866b7f9f5e8703fabcea0a2a990a439eedcce03940abb4ba9eaaeb"
4473
4515
  }
4474
4516
  },
4475
4517
  {
@@ -4481,23 +4523,23 @@ var init_manifest = __esm(() => {
4481
4523
  format: "tar.gz",
4482
4524
  url: `${CODEX_RELEASE_BASE}/codex-aarch64-unknown-linux-musl.tar.gz`,
4483
4525
  entryPath: "codex-aarch64-unknown-linux-musl",
4484
- size: 106977873,
4485
- sha256: "29a507a2a3fd8f6cd94b96b762d281d299d15d7902dee6132d314b78cf9108eb",
4526
+ size: 113382496,
4527
+ sha256: "94eb707605ec2646b2e05b7edf7c4e7b3ccf02b16237c34ae9beb8cfb7f810a9",
4486
4528
  allowedHosts: GITHUB_RELEASE_HOSTS
4487
4529
  },
4488
4530
  companions: [
4489
4531
  codexCodeModeHost("aarch64-unknown-linux-musl", {
4490
- size: 26642339,
4491
- sha256: "0e0bdde24d5b31b05519693635baa9c4f2dc4fe7eee31c67b92155f190dd32d2"
4532
+ size: 27023046,
4533
+ sha256: "ac98d1f878e353a16cba711fd47bfef2f6c814109bdd819ee2b6b6d6a19701e7"
4492
4534
  }, {
4493
- size: 70117728,
4494
- sha256: "7d28b996b825b13ea058af67655b619a26a125d61d4fb2985694a9c4e77d2e21"
4535
+ size: 70838624,
4536
+ sha256: "587436a943a59c18cabdbe83f07800d237b60ddd2e0f54d05b23d8b37a0b36f4"
4495
4537
  })
4496
4538
  ],
4497
4539
  executable: {
4498
4540
  fileName: "codex",
4499
- size: 268045528,
4500
- sha256: "8813c8d8a4cea2eeba271b72ab22f49628b184b24ab18dbef06b4ce41e9a8d39"
4541
+ size: 277549176,
4542
+ sha256: "f2e6c980c937e734644bed1b4a9a018d3e63a0ccbe71aafc85c86a8c6e4b462a"
4501
4543
  }
4502
4544
  },
4503
4545
  {
@@ -4509,23 +4551,23 @@ var init_manifest = __esm(() => {
4509
4551
  format: "tar.gz",
4510
4552
  url: `${CODEX_RELEASE_BASE}/codex-x86_64-unknown-linux-musl.tar.gz`,
4511
4553
  entryPath: "codex-x86_64-unknown-linux-musl",
4512
- size: 114979035,
4513
- sha256: "90dd0b03b28afcb7fa733a4f73b413db2916a47347efd43fb669cc1c774ee8e4",
4554
+ size: 121764138,
4555
+ sha256: "a0bf572d2f93f056c6712a6da348e96078dc84f3885fd7be67807ef389cb3978",
4514
4556
  allowedHosts: GITHUB_RELEASE_HOSTS
4515
4557
  },
4516
4558
  companions: [
4517
4559
  codexCodeModeHost("x86_64-unknown-linux-musl", {
4518
- size: 28137475,
4519
- sha256: "feade7026436baa4e82e898050c7ef82a8adbd98163dc504771275b5ef224f2f"
4560
+ size: 28565521,
4561
+ sha256: "7588334fd47bc4003d970b70dfd4d1e4578491b1e9baa72976a4c2d7e97590da"
4520
4562
  }, {
4521
- size: 76997904,
4522
- sha256: "8ee4f472e92ac226a189e98998633b2d07f989f208515750764db660369f2926"
4563
+ size: 77997328,
4564
+ sha256: "657f4e902726e05867c937b583049d5997e057f8cd0ab40450633c9c9ef8d630"
4523
4565
  })
4524
4566
  ],
4525
4567
  executable: {
4526
4568
  fileName: "codex",
4527
- size: 308139304,
4528
- sha256: "b93f6811d3f653f0fb83be4050d857fb9abbb111dfa2ce160563327daff7f306"
4569
+ size: 319346888,
4570
+ sha256: "b8a11fedf24b2dfb59a70c9e52b030f9a508dcd49cd2dd0d41956f4c785156b8"
4529
4571
  }
4530
4572
  },
4531
4573
  {
@@ -4537,14 +4579,14 @@ var init_manifest = __esm(() => {
4537
4579
  format: "zip",
4538
4580
  url: `${OPENCODE_RELEASE_BASE}/opencode-darwin-arm64.zip`,
4539
4581
  entryPath: "opencode",
4540
- size: 64291659,
4541
- sha256: "c400a2f8c8ab7e99478a7f4dd1a466b57bf33888e520d85c55780a5464c4b689",
4582
+ size: 64278924,
4583
+ sha256: "c1c9e62521192192ef4c3ed1355ada2144055bddb61682217e997b03c55203c1",
4542
4584
  allowedHosts: GITHUB_RELEASE_HOSTS
4543
4585
  },
4544
4586
  executable: {
4545
4587
  fileName: "opencode",
4546
- size: 180803872,
4547
- sha256: "417af53923f3d529759ab85ce3e8dd09116f742efcd332de591d2da6ebae696b",
4588
+ size: 180672544,
4589
+ sha256: "feaafead8cd563af0c2dcb386b0757e4355e327ebb6bd1aaad5440fe2f83596c",
4548
4590
  repairInvalidMacSignature: true
4549
4591
  }
4550
4592
  },
@@ -4557,14 +4599,14 @@ var init_manifest = __esm(() => {
4557
4599
  format: "zip",
4558
4600
  url: `${OPENCODE_RELEASE_BASE}/opencode-darwin-x64.zip`,
4559
4601
  entryPath: "opencode",
4560
- size: 69113309,
4561
- sha256: "123ebd72609c4f8dbae0485cdac39b9aedfeb6576b2eef64f2e73152b1de82f4",
4602
+ size: 69100888,
4603
+ sha256: "b0212efa6db347187225a17fa3a31c6b261bf4c9f1589a290b0ca902d720d250",
4562
4604
  allowedHosts: GITHUB_RELEASE_HOSTS
4563
4605
  },
4564
4606
  executable: {
4565
4607
  fileName: "opencode",
4566
- size: 194103008,
4567
- sha256: "6d6a980039553a6b4efe2221fdbd5e8945abf3c2365d97fcab097713a327228f",
4608
+ size: 193954400,
4609
+ sha256: "4b15c8aab1844936e9e1d1327213355d94c820de8c7084fad88d3812a067d3d0",
4568
4610
  repairInvalidMacSignature: true
4569
4611
  }
4570
4612
  },
@@ -4577,14 +4619,14 @@ var init_manifest = __esm(() => {
4577
4619
  format: "tar.gz",
4578
4620
  url: `${OPENCODE_RELEASE_BASE}/opencode-linux-arm64.tar.gz`,
4579
4621
  entryPath: "opencode",
4580
- size: 78526339,
4581
- sha256: "8568230ccd847dc93e95b9f6894379077d3e014a16b48040743573e77c23e85f",
4622
+ size: 78513091,
4623
+ sha256: "fae5be262d693b7c5e562856cc473e59e741772936db0c0600ac0d5ed5bd088b",
4582
4624
  allowedHosts: GITHUB_RELEASE_HOSTS
4583
4625
  },
4584
4626
  executable: {
4585
4627
  fileName: "opencode",
4586
- size: 211340280,
4587
- sha256: "eba0da50d9cc22f2dd062c32e0e7e58d7ece301aa50aabdad1e9dba4ffbf25b4"
4628
+ size: 211209208,
4629
+ sha256: "dab85b46f4ea4813da5367997f99a60aab64a2118d58ebf552980e792747436c"
4588
4630
  }
4589
4631
  },
4590
4632
  {
@@ -4596,14 +4638,14 @@ var init_manifest = __esm(() => {
4596
4638
  format: "tar.gz",
4597
4639
  url: `${OPENCODE_RELEASE_BASE}/opencode-linux-x64.tar.gz`,
4598
4640
  entryPath: "opencode",
4599
- size: 79667571,
4600
- sha256: "849bd6b3d959a1d2ef200ed1634a51c79a45ca236aa26665d75debd694805cce",
4641
+ size: 79651896,
4642
+ sha256: "588310a336fa3b9df36b3f0225b26f679d453efaad0cf3d66c3651ef3ec1647a",
4601
4643
  allowedHosts: GITHUB_RELEASE_HOSTS
4602
4644
  },
4603
4645
  executable: {
4604
4646
  fileName: "opencode",
4605
- size: 214522032,
4606
- sha256: "538fcb1b6408c8d4608822acee19f7ff5b3a3eb3383c0633c4f06170857312a7"
4647
+ size: 214386864,
4648
+ sha256: "7df4e4136301f16589b0f30bf91a61380fca5f8ef22dd36570f5ebe1d4ec802c"
4607
4649
  }
4608
4650
  },
4609
4651
  {
@@ -4615,14 +4657,14 @@ var init_manifest = __esm(() => {
4615
4657
  format: "tar.gz",
4616
4658
  url: claudeArchiveUrl("darwin-arm64"),
4617
4659
  entryPath: "package/claude",
4618
- size: 122689402,
4619
- sha256: "0e8ce1ddb8566129661bb7698db69db89d904e23b464afc6e85d5bd8879b9a51",
4660
+ size: 124539940,
4661
+ sha256: "e4524422963f1c79a4894b5ecb70e123cc45a3dd3a0915f312f6135f70aa570c",
4620
4662
  allowedHosts: NPM_REGISTRY_HOSTS
4621
4663
  },
4622
4664
  executable: {
4623
4665
  fileName: "claude",
4624
- size: 275923296,
4625
- sha256: "5eadc0009cdcdbc0d06e58110d7b2baf88ced6088b1c33a60a21217fe410619c"
4666
+ size: 278848096,
4667
+ sha256: "21d6e7b9a6f07b4cfcb7df13760b3b72aa25e084e13a5354413382987ba832ed"
4626
4668
  }
4627
4669
  },
4628
4670
  {
@@ -4634,14 +4676,14 @@ var init_manifest = __esm(() => {
4634
4676
  format: "tar.gz",
4635
4677
  url: claudeArchiveUrl("darwin-x64"),
4636
4678
  entryPath: "package/claude",
4637
- size: 127778018,
4638
- sha256: "58f2fcea83ddc59839353a55dafc2a8cd084b8acb9bf7131ff7db48a28604c33",
4679
+ size: 129924753,
4680
+ sha256: "f0041d107ad1a613e82fa4d8149e44390c975c3488ca34812fa27ccc9a92b8d6",
4639
4681
  allowedHosts: NPM_REGISTRY_HOSTS
4640
4682
  },
4641
4683
  executable: {
4642
4684
  fileName: "claude",
4643
- size: 287157296,
4644
- sha256: "200e0d775ae55c040db3bb8db8de3e22efcddbd7e0abff5ab50dd10f6853425d"
4685
+ size: 290710016,
4686
+ sha256: "404779a308a358ecec8b25c8bccf0ce79a94cf261d12cb74065518d189c174f8"
4645
4687
  }
4646
4688
  },
4647
4689
  {
@@ -4653,14 +4695,14 @@ var init_manifest = __esm(() => {
4653
4695
  format: "tar.gz",
4654
4696
  url: claudeArchiveUrl("linux-arm64"),
4655
4697
  entryPath: "package/claude",
4656
- size: 133084470,
4657
- sha256: "3108f1bf04723cd654cf98269eb861827a15d9256db13ebf88a09d3b497926b2",
4698
+ size: 136687968,
4699
+ sha256: "5a8087f6b0df5796326bb8e58c6e89637c58f28a16aea45434a7e914e854b0dc",
4658
4700
  allowedHosts: NPM_REGISTRY_HOSTS
4659
4701
  },
4660
4702
  executable: {
4661
4703
  fileName: "claude",
4662
- size: 293331128,
4663
- sha256: "9796dd26899337f687739a0b39a7353b639f062a3436a7b941fcf19a1b757a9b"
4704
+ size: 297067016,
4705
+ sha256: "a66c0a8f5d0c87a439953d797985192bbcbaefbce2e0d7b2eaa8df30ef0f3ef2"
4664
4706
  }
4665
4707
  },
4666
4708
  {
@@ -4672,14 +4714,14 @@ var init_manifest = __esm(() => {
4672
4714
  format: "tar.gz",
4673
4715
  url: claudeArchiveUrl("linux-x64"),
4674
4716
  entryPath: "package/claude",
4675
- size: 132508222,
4676
- sha256: "e960c4437f6f99648437784db43e04ad5f12711aecd710864f96876f94c2fc1a",
4717
+ size: 137217467,
4718
+ sha256: "c6b690e52ba2d038c4c8d2566b2ec35acef4995c4321f7d57fa464a7088c2b78",
4677
4719
  allowedHosts: NPM_REGISTRY_HOSTS
4678
4720
  },
4679
4721
  executable: {
4680
4722
  fileName: "claude",
4681
- size: 293469552,
4682
- sha256: "4d704a8aecbe038affa3000f498ae17c1b4f753add8facb8764120affc99b658"
4723
+ size: 298229464,
4724
+ sha256: "4487042b4c4f5d88d4568f68ae98aae0b1ffae58864cf57fb868641bc5f84594"
4683
4725
  }
4684
4726
  },
4685
4727
  {
@@ -4691,14 +4733,14 @@ var init_manifest = __esm(() => {
4691
4733
  format: "raw",
4692
4734
  url: `https://storage.googleapis.com/grok-build-public-artifacts/cli/grok-${PINNED_TOOLCHAIN_VERSIONS.grok}-macos-aarch64`,
4693
4735
  entryPath: "",
4694
- size: 177770992,
4695
- sha256: "036a47ce2987adae04de827f7803bf0cc460bf169b640db168f18ec7acb734db",
4736
+ size: 179063008,
4737
+ sha256: "5c8a688251bf61e35370b828b6a353b0a20fcc3f64000de8a91d426039f29cec",
4696
4738
  allowedHosts: GROK_DOWNLOAD_HOSTS
4697
4739
  },
4698
4740
  executable: {
4699
4741
  fileName: "grok",
4700
- size: 177770992,
4701
- sha256: "036a47ce2987adae04de827f7803bf0cc460bf169b640db168f18ec7acb734db"
4742
+ size: 179063008,
4743
+ sha256: "5c8a688251bf61e35370b828b6a353b0a20fcc3f64000de8a91d426039f29cec"
4702
4744
  }
4703
4745
  },
4704
4746
  {
@@ -4710,14 +4752,14 @@ var init_manifest = __esm(() => {
4710
4752
  format: "raw",
4711
4753
  url: `https://storage.googleapis.com/grok-build-public-artifacts/cli/grok-${PINNED_TOOLCHAIN_VERSIONS.grok}-macos-x86_64`,
4712
4754
  entryPath: "",
4713
- size: 192143296,
4714
- sha256: "24a951871bfb7b3f6036fc1e1cd2442c664e2b2eeba58de9e8f47de4c633a03e",
4755
+ size: 193578304,
4756
+ sha256: "d9ecd462f1f3c3b40a7a131944987ee7ed1c38069b7009cc4107398ab767f484",
4715
4757
  allowedHosts: GROK_DOWNLOAD_HOSTS
4716
4758
  },
4717
4759
  executable: {
4718
4760
  fileName: "grok",
4719
- size: 192143296,
4720
- sha256: "24a951871bfb7b3f6036fc1e1cd2442c664e2b2eeba58de9e8f47de4c633a03e"
4761
+ size: 193578304,
4762
+ sha256: "d9ecd462f1f3c3b40a7a131944987ee7ed1c38069b7009cc4107398ab767f484"
4721
4763
  }
4722
4764
  },
4723
4765
  {
@@ -4729,14 +4771,14 @@ var init_manifest = __esm(() => {
4729
4771
  format: "raw",
4730
4772
  url: `https://storage.googleapis.com/grok-build-public-artifacts/cli/grok-${PINNED_TOOLCHAIN_VERSIONS.grok}-linux-aarch64`,
4731
4773
  entryPath: "",
4732
- size: 165548280,
4733
- sha256: "089a0acc2c1b570efcd791c5cd0f8aa074f6e8ab82c00ab78af0b934acdb6386",
4774
+ size: 166639048,
4775
+ sha256: "3aa36883574b263088e5addffe241f55430e8daaee1a0c4934b02180007a9761",
4734
4776
  allowedHosts: GROK_DOWNLOAD_HOSTS
4735
4777
  },
4736
4778
  executable: {
4737
4779
  fileName: "grok",
4738
- size: 165548280,
4739
- sha256: "089a0acc2c1b570efcd791c5cd0f8aa074f6e8ab82c00ab78af0b934acdb6386"
4780
+ size: 166639048,
4781
+ sha256: "3aa36883574b263088e5addffe241f55430e8daaee1a0c4934b02180007a9761"
4740
4782
  }
4741
4783
  },
4742
4784
  {
@@ -4748,14 +4790,14 @@ var init_manifest = __esm(() => {
4748
4790
  format: "raw",
4749
4791
  url: `https://storage.googleapis.com/grok-build-public-artifacts/cli/grok-${PINNED_TOOLCHAIN_VERSIONS.grok}-linux-x86_64`,
4750
4792
  entryPath: "",
4751
- size: 194169184,
4752
- sha256: "ce33b1a29e37901f56fe59916cda7a1149e6fd377118f918e90d89f9abb52a51",
4793
+ size: 195700192,
4794
+ sha256: "22d281c5964c5444147328096ba2a221f3f0874c6858e27c825656a80f6e58e9",
4753
4795
  allowedHosts: GROK_DOWNLOAD_HOSTS
4754
4796
  },
4755
4797
  executable: {
4756
4798
  fileName: "grok",
4757
- size: 194169184,
4758
- sha256: "ce33b1a29e37901f56fe59916cda7a1149e6fd377118f918e90d89f9abb52a51"
4799
+ size: 195700192,
4800
+ sha256: "22d281c5964c5444147328096ba2a221f3f0874c6858e27c825656a80f6e58e9"
4759
4801
  }
4760
4802
  },
4761
4803
  {
@@ -4893,7 +4935,7 @@ var init_manifest = __esm(() => {
4893
4935
 
4894
4936
  // ../toolchain/src/manager.ts
4895
4937
  import { spawn } from "child_process";
4896
- import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
4938
+ import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
4897
4939
  import { createReadStream, createWriteStream } from "fs";
4898
4940
  import {
4899
4941
  chmod,
@@ -4901,13 +4943,13 @@ import {
4901
4943
  lstat,
4902
4944
  mkdir as mkdir3,
4903
4945
  mkdtemp as mkdtemp2,
4904
- open as open2,
4946
+ open as open3,
4905
4947
  readFile as readFile2,
4906
4948
  readdir as readdir3,
4907
4949
  readlink,
4908
4950
  rename as rename2,
4909
4951
  rm as rm3,
4910
- stat as stat2,
4952
+ stat as stat3,
4911
4953
  symlink as symlink2,
4912
4954
  utimes
4913
4955
  } from "fs/promises";
@@ -5214,7 +5256,7 @@ async function acquireInstallLock(rootDir, onProgress, timings, openLockFile, ow
5214
5256
  let announcedWait = false;
5215
5257
  while (Date.now() - startedAt < timings.lockWaitTimeoutMs) {
5216
5258
  const owner = {
5217
- token: randomUUID4(),
5259
+ token: randomUUID5(),
5218
5260
  pid: process.pid,
5219
5261
  createdAt: new Date().toISOString()
5220
5262
  };
@@ -5260,7 +5302,7 @@ async function acquireInstallLock(rootDir, onProgress, timings, openLockFile, ow
5260
5302
  continue;
5261
5303
  }
5262
5304
  }
5263
- const lockStat = await stat2(lockPath).catch(() => null);
5305
+ const lockStat = await stat3(lockPath).catch(() => null);
5264
5306
  if (!observedOwner && lockStat && Date.now() - lockStat.mtimeMs > timings.lockStaleAfterMs) {
5265
5307
  const currentOwner = await readInstallLockOwner(lockPath);
5266
5308
  if (!currentOwner) {
@@ -5307,7 +5349,7 @@ async function downloadArchive(label, archive, archivePath, fetchImpl, onBytes,
5307
5349
  if (contentLength !== null && Number(contentLength) !== archive.size) {
5308
5350
  throw new Error(`${label} archive size header did not match the pinned manifest`);
5309
5351
  }
5310
- handle = await open2(archivePath, "wx", 384);
5352
+ handle = await open3(archivePath, "wx", 384);
5311
5353
  const reader = response.body.getReader();
5312
5354
  const hash = createHash4("sha256");
5313
5355
  let received = 0;
@@ -5583,7 +5625,7 @@ async function repairInvalidMacSignature(executablePath, artifact, timeoutMs, sp
5583
5625
  await runCodesign(["--sign", "-", "--force", executablePath], `${artifact.name} could not be ad-hoc signed after source verification`, timeoutMs, spawnProcess);
5584
5626
  }
5585
5627
  async function prepareRepairableExecutable(sourcePath, executablePath, artifact, skipExecutableProbeForTests, processTimeoutMs, spawnProcess) {
5586
- const temporaryPath = path17.join(path17.dirname(executablePath), `.repair-${artifact.executable.fileName}-${randomUUID4()}.tmp`);
5628
+ const temporaryPath = path17.join(path17.dirname(executablePath), `.repair-${artifact.executable.fileName}-${randomUUID5()}.tmp`);
5587
5629
  try {
5588
5630
  await copyFile2(sourcePath, temporaryPath);
5589
5631
  await chmod(temporaryPath, 448);
@@ -5743,7 +5785,7 @@ async function acquireVersionLease(rootDir, artifact, openLeaseFile) {
5743
5785
  return;
5744
5786
  processVersionLeases.delete(leaseKey);
5745
5787
  const owner = {
5746
- token: randomUUID4(),
5788
+ token: randomUUID5(),
5747
5789
  pid: process.pid,
5748
5790
  createdAt: new Date().toISOString()
5749
5791
  };
@@ -5799,7 +5841,7 @@ async function pruneSupersededVersions(rootDir, artifacts, retainSupersededMs, o
5799
5841
  await Promise.all(entries.filter((entry) => entry.isDirectory() && !keep.has(entry.name)).map(async (entry) => {
5800
5842
  const versionDir = path17.join(toolDir, entry.name);
5801
5843
  try {
5802
- if ((await stat2(versionDir)).mtimeMs > staleBefore)
5844
+ if ((await stat3(versionDir)).mtimeMs > staleBefore)
5803
5845
  return;
5804
5846
  if (await versionHasLiveLease(rootDir, name, entry.name, ownerProcessExists))
5805
5847
  return;
@@ -5855,7 +5897,7 @@ async function activateExecutables(rootDir, artifacts) {
5855
5897
  const executables = {};
5856
5898
  const activate = async (linkName, target) => {
5857
5899
  const activePath = path17.join(binDir, linkName);
5858
- const temporaryLink = path17.join(binDir, `.${linkName}-${randomUUID4()}.tmp`);
5900
+ const temporaryLink = path17.join(binDir, `.${linkName}-${randomUUID5()}.tmp`);
5859
5901
  const existingTarget = await readlink(activePath).catch(() => null);
5860
5902
  if (existingTarget !== target) {
5861
5903
  await symlink2(target, temporaryLink, "file");
@@ -5921,7 +5963,7 @@ async function ensurePinnedToolchains(options2) {
5921
5963
  phase: "waiting",
5922
5964
  completedTools: 0,
5923
5965
  message
5924
- }), timings, options2.openLockFileForTests ?? open2, ownerProcessExists);
5966
+ }), timings, options2.openLockFileForTests ?? open3, ownerProcessExists);
5925
5967
  try {
5926
5968
  await cleanStagingDirectories(rootDir);
5927
5969
  const statuses = await Promise.all(artifacts.map((artifact) => installationStatus(rootDir, artifact)));
@@ -5995,7 +6037,7 @@ async function ensurePinnedToolchains(options2) {
5995
6037
  const result = await activateExecutables(rootDir, artifacts);
5996
6038
  await Promise.all(artifacts.map((artifact) => touchArtifactDirectory(rootDir, artifact)));
5997
6039
  if (!options2.skipVersionLeaseForTests) {
5998
- await Promise.all(artifacts.map((artifact) => acquireVersionLease(rootDir, artifact, options2.openLeaseFileForTests ?? open2)));
6040
+ await Promise.all(artifacts.map((artifact) => acquireVersionLease(rootDir, artifact, options2.openLeaseFileForTests ?? open3)));
5999
6041
  }
6000
6042
  await options2.onActivated?.(result);
6001
6043
  await pruneSupersededVersions(rootDir, artifacts, timings.retainSupersededMs, ownerProcessExists, options2.removeSupersededVersionForTests ?? rm3);
@@ -6072,7 +6114,7 @@ var init_platform_selection = __esm(() => {
6072
6114
  });
6073
6115
 
6074
6116
  // ../toolchain/src/install.ts
6075
- import { randomUUID as randomUUID5 } from "crypto";
6117
+ import { randomUUID as randomUUID6 } from "crypto";
6076
6118
  import { lstat as lstat2, mkdir as mkdir4, readdir as readdir4, readlink as readlink2, rename as rename3, rm as rm4, symlink as symlink3 } from "fs/promises";
6077
6119
  import path19 from "path";
6078
6120
  async function resolveToolchainSelection(options2) {
@@ -6150,7 +6192,7 @@ async function pointCurrentAt(rootDir, binDir) {
6150
6192
  }
6151
6193
  if (existing && await readlink2(link) === target)
6152
6194
  return link;
6153
- const temporary = path19.join(binRoot, `.${CURRENT_TOOLCHAIN_LINK}-${randomUUID5()}.tmp`);
6195
+ const temporary = path19.join(binRoot, `.${CURRENT_TOOLCHAIN_LINK}-${randomUUID6()}.tmp`);
6154
6196
  await symlink3(target, temporary, "dir");
6155
6197
  try {
6156
6198
  await rename3(temporary, link);
@@ -6443,7 +6485,7 @@ var PUBLIC_ACTIONS = {
6443
6485
  effects: [],
6444
6486
  observation: "immediate",
6445
6487
  milestone: "A",
6446
- summary: "List native-agent sessions of one environment without reading transcripts."
6488
+ summary: "List native-agent sessions of one environment or Coordinator conversation without reading transcripts."
6447
6489
  },
6448
6490
  "session.get": {
6449
6491
  version: 1,
@@ -6488,10 +6530,10 @@ var PUBLIC_ACTIONS = {
6488
6530
  "session.stop": {
6489
6531
  version: 1,
6490
6532
  mutation: true,
6491
- effects: ["provider-session"],
6533
+ effects: ["metadata", "provider-session", "filesystem", "process"],
6492
6534
  observation: "receipt",
6493
6535
  milestone: "C",
6494
- summary: "Stop the current turn of one session, optionally bound to an expected run."
6536
+ summary: "Stop the current turn and pause its goal, optionally bound to an expected run."
6495
6537
  },
6496
6538
  "session.steer": {
6497
6539
  version: 1,
@@ -6565,6 +6607,46 @@ var PUBLIC_ACTIONS = {
6565
6607
  milestone: "C",
6566
6608
  summary: "Pause or resume one pending interaction's countdown where its deadline is enforced."
6567
6609
  },
6610
+ "session.goal.set": {
6611
+ version: 1,
6612
+ mutation: true,
6613
+ effects: ["provider-session", "filesystem", "process"],
6614
+ observation: "receipt",
6615
+ milestone: "D",
6616
+ summary: "Give one agent tab a durable objective: the backend sends turns until a check proves it, the agent needs a person, or a limit stops it."
6617
+ },
6618
+ "session.goal.get": {
6619
+ version: 1,
6620
+ mutation: false,
6621
+ effects: [],
6622
+ observation: "immediate",
6623
+ milestone: "D",
6624
+ summary: "Read one session's goal and its recent history, without starting anything; check output on request."
6625
+ },
6626
+ "session.goal.pause": {
6627
+ version: 1,
6628
+ mutation: true,
6629
+ effects: ["metadata", "provider-session", "filesystem", "process"],
6630
+ observation: "receipt",
6631
+ milestone: "D",
6632
+ summary: "Pause a running goal; no further turn starts until it is resumed."
6633
+ },
6634
+ "session.goal.resume": {
6635
+ version: 1,
6636
+ mutation: true,
6637
+ effects: ["provider-session", "filesystem", "process"],
6638
+ observation: "receipt",
6639
+ milestone: "D",
6640
+ summary: "Resume a stopped goal, optionally with a higher iteration cap or token budget."
6641
+ },
6642
+ "session.goal.clear": {
6643
+ version: 1,
6644
+ mutation: true,
6645
+ effects: ["metadata", "provider-session", "filesystem", "process"],
6646
+ observation: "receipt",
6647
+ milestone: "D",
6648
+ summary: "Remove a session's goal in any status; it stays readable in the goal history."
6649
+ },
6568
6650
  "session.transcript": {
6569
6651
  version: 1,
6570
6652
  mutation: false,
@@ -6733,6 +6815,158 @@ var PUBLIC_ACTIONS = {
6733
6815
  milestone: "D",
6734
6816
  summary: "Start the Resolve Conflicts agent session in an environment (the Resolve tab): merge the target branch, resolve and validate conflicts, commit and push."
6735
6817
  },
6818
+ "pr.get": {
6819
+ version: 1,
6820
+ mutation: false,
6821
+ effects: [],
6822
+ observation: "immediate",
6823
+ milestone: "D",
6824
+ summary: "Read the pull request attached to an environment's branch: attachment, URL, state, draft, base and head branches, head SHA, mergeability, checks, reviewers, labels and conversation comments."
6825
+ },
6826
+ "pr.list": {
6827
+ version: 1,
6828
+ mutation: false,
6829
+ effects: [],
6830
+ observation: "immediate",
6831
+ milestone: "D",
6832
+ summary: "List a project's pull requests from GitHub, by filter and search text."
6833
+ },
6834
+ "pr.timeline": {
6835
+ version: 1,
6836
+ mutation: false,
6837
+ effects: [],
6838
+ observation: "immediate",
6839
+ milestone: "D",
6840
+ summary: "Read the timeline of an environment's pull request: commits, comments, reviews and events, oldest first."
6841
+ },
6842
+ "pr.files": {
6843
+ version: 1,
6844
+ mutation: false,
6845
+ effects: [],
6846
+ observation: "immediate",
6847
+ milestone: "D",
6848
+ summary: "Read the changed files and review threads of an environment's pull request; with path, that file's diff hunks."
6849
+ },
6850
+ "pr.options": {
6851
+ version: 1,
6852
+ mutation: false,
6853
+ effects: [],
6854
+ observation: "immediate",
6855
+ milestone: "D",
6856
+ summary: "Read the labels and assignable users of the repository of an environment's pull request, for pr.label and pr.reviewers."
6857
+ },
6858
+ "pr.merge": {
6859
+ version: 1,
6860
+ mutation: true,
6861
+ effects: ["external-repository", "filesystem", "process"],
6862
+ observation: "receipt",
6863
+ milestone: "D",
6864
+ summary: "Merge an environment's pull request into its target branch (the Merge button, squash by default): a draft is marked ready first and the head branch is deleted unless deleteBranch is false. With cleanup, the Merge and Cleanup button: also delete the environment and its worktree once the merge is confirmed."
6865
+ },
6866
+ "pr.close": {
6867
+ version: 1,
6868
+ mutation: true,
6869
+ effects: ["external-repository"],
6870
+ observation: "receipt",
6871
+ milestone: "D",
6872
+ summary: "Close an environment's pull request without merging; pr.reopen undoes it."
6873
+ },
6874
+ "pr.reopen": {
6875
+ version: 1,
6876
+ mutation: true,
6877
+ effects: ["external-repository"],
6878
+ observation: "receipt",
6879
+ milestone: "D",
6880
+ summary: "Reopen an environment's closed pull request."
6881
+ },
6882
+ "pr.ready": {
6883
+ version: 1,
6884
+ mutation: true,
6885
+ effects: ["external-repository"],
6886
+ observation: "receipt",
6887
+ milestone: "D",
6888
+ summary: "Mark an environment's draft pull request ready for review."
6889
+ },
6890
+ "pr.draft": {
6891
+ version: 1,
6892
+ mutation: true,
6893
+ effects: ["external-repository"],
6894
+ observation: "receipt",
6895
+ milestone: "D",
6896
+ summary: "Convert an environment's pull request back to a draft."
6897
+ },
6898
+ "pr.comment": {
6899
+ version: 1,
6900
+ mutation: true,
6901
+ effects: ["external-repository"],
6902
+ observation: "receipt",
6903
+ milestone: "D",
6904
+ summary: "Add a conversation comment to an environment's pull request."
6905
+ },
6906
+ "pr.comment.edit": {
6907
+ version: 1,
6908
+ mutation: true,
6909
+ effects: ["external-repository"],
6910
+ observation: "receipt",
6911
+ milestone: "D",
6912
+ summary: "Edit a conversation comment on an environment's pull request; the GitHub user must be allowed to edit it."
6913
+ },
6914
+ "pr.review": {
6915
+ version: 1,
6916
+ mutation: true,
6917
+ effects: ["external-repository"],
6918
+ observation: "receipt",
6919
+ milestone: "D",
6920
+ summary: "Submit a review of an environment's pull request: approve, request changes or comment, with optional inline comments."
6921
+ },
6922
+ "pr.review.comment": {
6923
+ version: 1,
6924
+ mutation: true,
6925
+ effects: ["external-repository"],
6926
+ observation: "receipt",
6927
+ milestone: "D",
6928
+ summary: "Add one inline review comment to a line of an environment's pull request."
6929
+ },
6930
+ "pr.thread.reply": {
6931
+ version: 1,
6932
+ mutation: true,
6933
+ effects: ["external-repository"],
6934
+ observation: "receipt",
6935
+ milestone: "D",
6936
+ summary: "Reply to a review thread on an environment's pull request (threadId from pr.files)."
6937
+ },
6938
+ "pr.thread.resolve": {
6939
+ version: 1,
6940
+ mutation: true,
6941
+ effects: ["external-repository"],
6942
+ observation: "receipt",
6943
+ milestone: "D",
6944
+ summary: "Resolve or unresolve a review thread on an environment's pull request (threadId from pr.files)."
6945
+ },
6946
+ "pr.file.viewed": {
6947
+ version: 1,
6948
+ mutation: true,
6949
+ effects: ["external-repository"],
6950
+ observation: "receipt",
6951
+ milestone: "D",
6952
+ summary: "Mark a changed file of an environment's pull request as viewed or not viewed for the GitHub user."
6953
+ },
6954
+ "pr.reviewers": {
6955
+ version: 1,
6956
+ mutation: true,
6957
+ effects: ["external-repository"],
6958
+ observation: "receipt",
6959
+ milestone: "D",
6960
+ summary: "Request or remove reviewers on an environment's pull request."
6961
+ },
6962
+ "pr.label": {
6963
+ version: 1,
6964
+ mutation: true,
6965
+ effects: ["external-repository"],
6966
+ observation: "receipt",
6967
+ milestone: "D",
6968
+ summary: "Add or remove one label on an environment's pull request."
6969
+ },
6736
6970
  "attention.list": {
6737
6971
  version: 1,
6738
6972
  mutation: false,
@@ -7003,7 +7237,12 @@ var PUBLIC_EXIT = Object.freeze({
7003
7237
  deadline: 5,
7004
7238
  interactionRequired: 6,
7005
7239
  unknownDispatch: 7,
7006
- conflict: 8
7240
+ conflict: 8,
7241
+ goalClaimed: 10,
7242
+ goalBlocked: 11,
7243
+ goalLimited: 12,
7244
+ goalPaused: 13,
7245
+ goalGone: 14
7007
7246
  });
7008
7247
  var PUBLIC_SIGNAL_EXIT = Object.freeze({ SIGINT: 130, SIGTERM: 143 });
7009
7248
  var PUBLIC_ERROR_EXIT = {
@@ -7055,7 +7294,12 @@ var PUBLIC_ERROR_EXIT = {
7055
7294
  "session-idle": PUBLIC_EXIT.conflict,
7056
7295
  "interaction-stale": PUBLIC_EXIT.conflict,
7057
7296
  "dispatch-parked": PUBLIC_EXIT.conflict,
7058
- "node-unsupported": PUBLIC_EXIT.conflict
7297
+ "node-unsupported": PUBLIC_EXIT.conflict,
7298
+ "goal-claimed": PUBLIC_EXIT.goalClaimed,
7299
+ "goal-blocked": PUBLIC_EXIT.goalBlocked,
7300
+ "goal-limited": PUBLIC_EXIT.goalLimited,
7301
+ "goal-paused": PUBLIC_EXIT.goalPaused,
7302
+ "goal-gone": PUBLIC_EXIT.goalGone
7059
7303
  };
7060
7304
  function isPublicErrorCode(value) {
7061
7305
  return typeof value === "string" && Object.hasOwn(PUBLIC_ERROR_EXIT, value);
@@ -7550,7 +7794,7 @@ function splitFlag(token) {
7550
7794
  }
7551
7795
 
7552
7796
  // src/client/fix.ts
7553
- import { existsSync as existsSync3, realpathSync as realpathSync2 } from "fs";
7797
+ import { existsSync as existsSync3, realpathSync as realpathSync3 } from "fs";
7554
7798
  import { mkdtemp, readdir as readdir2, rmdir } from "fs/promises";
7555
7799
  import os4 from "os";
7556
7800
  import path6 from "path";
@@ -7562,16 +7806,19 @@ var COORDINATOR_CONTROL_TOOL_NAMES = Object.freeze([
7562
7806
  "add_ticket_comment",
7563
7807
  "address_multi_review",
7564
7808
  "adopt_workflow",
7809
+ "answer_worker_question",
7565
7810
  "cancel_build_pipeline",
7566
7811
  "cancel_graph_run",
7567
7812
  "cancel_multi_review",
7568
7813
  "cancel_schedule",
7814
+ "clear_worker_goal",
7569
7815
  "compare_project_files",
7570
7816
  "create_pr",
7571
7817
  "create_schedule",
7572
7818
  "create_ticket",
7573
7819
  "custom_fix_multi_review",
7574
7820
  "edit_worker_tab",
7821
+ "fork_worker",
7575
7822
  "get_build_pipeline",
7576
7823
  "get_environment",
7577
7824
  "get_feature_plan",
@@ -7584,6 +7831,9 @@ var COORDINATOR_CONTROL_TOOL_NAMES = Object.freeze([
7584
7831
  "get_tab_state",
7585
7832
  "get_tab_transcript",
7586
7833
  "get_ticket",
7834
+ "get_usage_limits",
7835
+ "get_worker_changes",
7836
+ "get_worker_goal",
7587
7837
  "launch_environment",
7588
7838
  "launch_feature_plan",
7589
7839
  "launch_graph",
@@ -7598,14 +7848,18 @@ var COORDINATOR_CONTROL_TOOL_NAMES = Object.freeze([
7598
7848
  "list_node_environments",
7599
7849
  "list_nodes",
7600
7850
  "list_projects",
7851
+ "list_provider_accounts",
7601
7852
  "list_schedules",
7602
7853
  "list_tabs",
7603
7854
  "list_tickets",
7855
+ "list_worker_commands",
7604
7856
  "list_workflows",
7605
7857
  "open_multi_review",
7606
7858
  "open_multi_review_fix",
7607
7859
  "open_worker_browser",
7608
7860
  "pause_build_pipeline",
7861
+ "pause_schedule",
7862
+ "pause_worker_goal",
7609
7863
  "propose_agents_md_update",
7610
7864
  "read_messages",
7611
7865
  "read_node_environment",
@@ -7614,18 +7868,25 @@ var COORDINATOR_CONTROL_TOOL_NAMES = Object.freeze([
7614
7868
  "request_project_file_update",
7615
7869
  "request_project_on_node",
7616
7870
  "request_root_changes",
7871
+ "resolve_conflicts",
7617
7872
  "resume_build_pipeline",
7873
+ "resume_schedule",
7874
+ "run_worker_command",
7618
7875
  "send_message",
7619
7876
  "send_node_message",
7620
7877
  "send_prompt_to_node_tab",
7621
7878
  "send_prompt_to_tab",
7879
+ "set_worker_goal",
7622
7880
  "start_build_pipeline",
7623
7881
  "start_environment",
7624
7882
  "start_multi_review",
7625
7883
  "start_node_environment",
7626
7884
  "stop_environment",
7627
7885
  "stop_node_environment",
7886
+ "stop_worker_turn",
7887
+ "switch_provider_account",
7628
7888
  "transfer_files",
7889
+ "update_schedule",
7629
7890
  "update_ticket"
7630
7891
  ]);
7631
7892
 
@@ -8066,12 +8327,13 @@ async function writePrivateFile(file, contents) {
8066
8327
  }
8067
8328
 
8068
8329
  // ../../apps/backend/src/data-dir.ts
8330
+ import { realpathSync as realpathSync2 } from "fs";
8069
8331
  import os3 from "os";
8070
8332
  import path5 from "path";
8071
8333
  // ../../apps/backend/package.json
8072
8334
  var package_default = {
8073
8335
  name: "@orkestrator/backend",
8074
- version: "2.34.4-beta",
8336
+ version: "2.34.5-beta",
8075
8337
  private: true,
8076
8338
  type: "module",
8077
8339
  exports: {
@@ -8091,7 +8353,7 @@ var package_default = {
8091
8353
  dependencies: {
8092
8354
  "@modelcontextprotocol/node": "2.1.1",
8093
8355
  "@modelcontextprotocol/server": "2.3.1",
8094
- "@opencode-ai/sdk": "1.19.4",
8356
+ "@opencode-ai/sdk": "1.19.5",
8095
8357
  "@orkestrator/protocol": "workspace:*",
8096
8358
  "@orkestrator/toolchain": "workspace:*",
8097
8359
  "@xterm/addon-serialize": "0.14.0",
@@ -8191,6 +8453,16 @@ function defaultDataDir(platform = process.platform, env = process.env, home = o
8191
8453
  return path5.join(home, "Library", "Application Support", APP_SLUG);
8192
8454
  return path5.join(env.XDG_CONFIG_HOME ?? path5.join(home, ".config"), APP_SLUG);
8193
8455
  }
8456
+ function isDefaultDataDir(dataDir, platform = process.platform, env = process.env, home = os3.homedir()) {
8457
+ const canonical = (value) => {
8458
+ try {
8459
+ return realpathSync2(value);
8460
+ } catch {
8461
+ return path5.resolve(value);
8462
+ }
8463
+ };
8464
+ return canonical(dataDir) === canonical(defaultDataDir(platform, env, home));
8465
+ }
8194
8466
 
8195
8467
  // src/client/fix.ts
8196
8468
  var FIX_ISSUE_REPOSITORY = BUG_REPORT_ISSUE_REPOSITORY;
@@ -8292,10 +8564,10 @@ function resolveFixAgent(name, options2) {
8292
8564
  if (onPath) {
8293
8565
  let isManaged = false;
8294
8566
  try {
8295
- const root = realpathSync2(toolchainRootDir(options2.dataDir));
8296
- const target = realpathSync2(onPath);
8567
+ const root = realpathSync3(toolchainRootDir(options2.dataDir));
8568
+ const target = realpathSync3(onPath);
8297
8569
  const relative = path6.relative(root, target);
8298
- isManaged = !relative.startsWith(`..${path6.sep}`) && relative !== ".." && !path6.isAbsolute(relative) || existsSync3(managed) && target === realpathSync2(managed);
8570
+ isManaged = !relative.startsWith(`..${path6.sep}`) && relative !== ".." && !path6.isAbsolute(relative) || existsSync3(managed) && target === realpathSync3(managed);
8299
8571
  } catch {}
8300
8572
  return { agent, executable: onPath, source: isManaged ? "managed" : "path" };
8301
8573
  }
@@ -8305,13 +8577,13 @@ function resolveFixAgent(name, options2) {
8305
8577
  }
8306
8578
  async function diagnosticConnection(dataDir, env) {
8307
8579
  try {
8308
- const descriptor = realpathSync2(path6.join(dataDir, "backend-instance.json"));
8580
+ const descriptor = realpathSync3(path6.join(dataDir, "backend-instance.json"));
8309
8581
  const config = await new CliConfigStore(defaultCliConfigDir(env)).read();
8310
8582
  for (const [name, connection] of Object.entries(config.connections)) {
8311
8583
  if (connection.kind !== "descriptor")
8312
8584
  continue;
8313
8585
  try {
8314
- if (realpathSync2(connection.descriptorPath) === descriptor)
8586
+ if (realpathSync3(connection.descriptorPath) === descriptor)
8315
8587
  return name;
8316
8588
  } catch {}
8317
8589
  }
@@ -8538,6 +8810,63 @@ async function observe(check, options2) {
8538
8810
  }
8539
8811
  }
8540
8812
 
8813
+ // ../protocol/src/native-agent-goal.ts
8814
+ init_agent_platforms();
8815
+ function goalIsRunning(status) {
8816
+ return status === "active" || status === "checking" || status === "verifying";
8817
+ }
8818
+ var NATIVE_AGENT_GOAL_LIMITS = Object.freeze({
8819
+ objectiveMaxChars: 4000,
8820
+ checkMaxBytes: 4 * 1024,
8821
+ reportMessageMaxBytes: 2 * 1024,
8822
+ checkOutputMaxBytes: 8 * 1024,
8823
+ statusDetailMaxBytes: 1024,
8824
+ maxIterationsDefault: 20,
8825
+ maxIterationsLimit: 100,
8826
+ noProgressLimit: 3,
8827
+ checkTimeoutDefaultMs: 10 * 60000,
8828
+ checkTimeoutMaxMs: 30 * 60000,
8829
+ verifierModelMaxChars: 200,
8830
+ verifierTimeoutDefaultMs: 30 * 60000,
8831
+ verdictSummaryMaxBytes: 2 * 1024,
8832
+ verdictReasonsMax: 16,
8833
+ verdictReasonMaxBytes: 512,
8834
+ historyLimit: 5
8835
+ });
8836
+ var AGENT_GOAL_TOOL_NAMES = Object.freeze(["report_goal"]);
8837
+ var GOAL_QUALIFIED_PLATFORMS = Object.freeze([
8838
+ "claude",
8839
+ "codex",
8840
+ "opencode",
8841
+ "cursor",
8842
+ "grok",
8843
+ "pi"
8844
+ ]);
8845
+ var GOAL_TOKEN_BUDGET_PLATFORMS = Object.freeze([
8846
+ "claude",
8847
+ "codex",
8848
+ "opencode",
8849
+ "cursor",
8850
+ "grok",
8851
+ "pi"
8852
+ ]);
8853
+ function parseVerifierFlag(text) {
8854
+ const colon = text.indexOf(":");
8855
+ const agent = colon < 0 ? text : text.slice(0, colon);
8856
+ const model = colon < 0 ? undefined : text.slice(colon + 1);
8857
+ if (!isAgentPlatform(agent)) {
8858
+ return { error: "--verifier takes an agent, optionally with a model: --verifier codex:o3." };
8859
+ }
8860
+ if (model === undefined)
8861
+ return { agent };
8862
+ if (!model || model.length > NATIVE_AGENT_GOAL_LIMITS.verifierModelMaxChars) {
8863
+ return {
8864
+ error: `The verifier's model name must be 1 to ${NATIVE_AGENT_GOAL_LIMITS.verifierModelMaxChars} characters.`
8865
+ };
8866
+ }
8867
+ return { agent, model };
8868
+ }
8869
+
8541
8870
  // ../protocol/src/public-api-wait.ts
8542
8871
  function isTerminalPublicReceipt(receipt) {
8543
8872
  return !["admitted", "running", "unknown"].includes(receipt.state);
@@ -8592,6 +8921,34 @@ function terminalReceiptErrorCode(receipt) {
8592
8921
  return "run-unknown";
8593
8922
  return receipt.error?.code ?? "operation-failed";
8594
8923
  }
8924
+ var GOAL_END_CODES = {
8925
+ claimed: "goal-claimed",
8926
+ blocked: "goal-blocked",
8927
+ limited: "goal-limited",
8928
+ paused: "goal-paused"
8929
+ };
8930
+ function classifyGoalForWait(read, goalId, options2 = {}) {
8931
+ const current = read.goal?.goalId === goalId ? read.goal : null;
8932
+ if (!current) {
8933
+ const past = read.history.findLast((goal) => goal.goalId === goalId);
8934
+ if (past?.status === "complete")
8935
+ return { kind: "complete", goal: past };
8936
+ if (past?.status === "claimed")
8937
+ return { kind: "stopped", code: "goal-claimed", goal: past };
8938
+ return past ? { kind: "gone", goal: past } : { kind: "gone" };
8939
+ }
8940
+ if (goalIsRunning(current.status)) {
8941
+ const needsAnswer = read.activity === "waiting" || (read.pendingInteractionCount ?? 0) > 0;
8942
+ return needsAnswer && !options2.continueOnInteraction ? { kind: "interaction", goal: current } : { kind: "pending", goal: current };
8943
+ }
8944
+ if (current.status === "complete")
8945
+ return { kind: "complete", goal: current };
8946
+ return {
8947
+ kind: "stopped",
8948
+ code: GOAL_END_CODES[current.status],
8949
+ goal: current
8950
+ };
8951
+ }
8595
8952
 
8596
8953
  // src/client/commands/common.ts
8597
8954
  var REQUEST_ID_OPTION = {
@@ -9862,6 +10219,7 @@ var PROJECTION_FIELD_SET = new Set([
9862
10219
  "queue",
9863
10220
  "asyncQuestionResponses",
9864
10221
  "contextUsage",
10222
+ "goal",
9865
10223
  "rateLimits",
9866
10224
  "runtime",
9867
10225
  "runtimeHealthAuthoritative",
@@ -9886,6 +10244,7 @@ var OPTIONAL_PROJECTION_FIELD_SET = new Set([
9886
10244
  "queue",
9887
10245
  "asyncQuestionResponses",
9888
10246
  "contextUsage",
10247
+ "goal",
9889
10248
  "rateLimits",
9890
10249
  "runtime",
9891
10250
  "runtimeHealthAuthoritative",
@@ -10079,6 +10438,11 @@ var VERIFICATION_VERDICT_SCHEMA = Object.freeze({
10079
10438
  properties: VERIFICATION_VERDICT_PROPERTIES
10080
10439
  });
10081
10440
  var MAX_BUILD_PIPELINE_ITERATIONS = 10;
10441
+ var SETUP_BUILD_PHASES = new Set([
10442
+ "creating-environment",
10443
+ "starting-environment",
10444
+ "waiting-for-setup"
10445
+ ]);
10082
10446
  var BUILD_PHASES = new Set([
10083
10447
  "creating-environment",
10084
10448
  "starting-environment",
@@ -10114,6 +10478,15 @@ var FAILURE_KINDS = new Set([
10114
10478
  var INTERACTION_PAYLOAD_ENCODER = new TextEncoder;
10115
10479
  var INTERACTION_PROVIDERS = new Set(AGENT_INTERACTION_PROVIDERS);
10116
10480
  var INTERACTION_KINDS = new Set(AGENT_INTERACTION_KINDS);
10481
+ var VALIDATION_RESULT_STATUSES = new Set([
10482
+ "pending",
10483
+ "queued",
10484
+ "running",
10485
+ "passed",
10486
+ "failed",
10487
+ "skipped",
10488
+ "incomplete"
10489
+ ]);
10117
10490
 
10118
10491
  // ../protocol/src/public-api-inputs-build.ts
10119
10492
  init_agent_platforms();
@@ -10401,7 +10774,11 @@ function sessionLines(session) {
10401
10774
  ["activity", session.activity],
10402
10775
  ["latest request", session.latestRequestId],
10403
10776
  ["pending interactions", session.pendingInteractionCount],
10404
- ["recoverable dispatch", session.recoverableDispatch]
10777
+ ["recoverable dispatch", session.recoverableDispatch],
10778
+ [
10779
+ "goal",
10780
+ session.goal ? `${session.goal.status} (${session.goal.iterations}/${session.goal.maxIterations})` : undefined
10781
+ ]
10405
10782
  ]);
10406
10783
  }
10407
10784
  function receiptLines(receipt) {
@@ -15001,6 +15378,15 @@ function parseEndpoints(value) {
15001
15378
  return { role: entry.role, url: entry.url };
15002
15379
  });
15003
15380
  }
15381
+ function parseControlMcp(value) {
15382
+ if (!isRecord3(value) || typeof value.running !== "boolean" || value.url !== undefined && !isHttpUrl(value.url)) {
15383
+ throw new Error("Backend instance descriptor field is invalid: controlMcp");
15384
+ }
15385
+ return {
15386
+ running: value.running,
15387
+ ...value.url !== undefined ? { url: value.url } : {}
15388
+ };
15389
+ }
15004
15390
  function parseBackendInstanceDescriptor(value) {
15005
15391
  if (!isRecord3(value))
15006
15392
  throw new Error("Backend instance descriptor must be an object");
@@ -15023,6 +15409,7 @@ function parseBackendInstanceDescriptor(value) {
15023
15409
  throw new Error("Backend instance descriptor field is invalid: browserUrl");
15024
15410
  }
15025
15411
  const endpoints = value.endpoints === undefined ? undefined : parseEndpoints(value.endpoints);
15412
+ const controlMcp = value.controlMcp === undefined ? undefined : parseControlMcp(value.controlMcp);
15026
15413
  if (!Number.isSafeInteger(value.publicApiSchemaVersion) || value.publicApiSchemaVersion < 1) {
15027
15414
  throw new Error("Backend instance descriptor field is invalid: publicApiSchemaVersion");
15028
15415
  }
@@ -15041,6 +15428,7 @@ function parseBackendInstanceDescriptor(value) {
15041
15428
  url: value.url,
15042
15429
  ...value.browserUrl !== undefined ? { browserUrl: value.browserUrl } : {},
15043
15430
  ...endpoints !== undefined ? { endpoints } : {},
15431
+ ...controlMcp !== undefined ? { controlMcp } : {},
15044
15432
  authFile,
15045
15433
  dataDir,
15046
15434
  appVersion: text("appVersion", 64),
@@ -17120,7 +17508,11 @@ var SCHEDULED_JOB_LIMITS = Object.freeze({
17120
17508
  busyRetryDelayMs: 30000,
17121
17509
  fireStaleMs: 90000,
17122
17510
  fireHeartbeatMs: 20000,
17123
- maxFireAttempts: 3
17511
+ maxFireAttempts: 3,
17512
+ maxSelfTimerDelayMs: 7 * 24 * 60 * 60000,
17513
+ maxPendingSelfTimers: 20,
17514
+ selfTimerGrantWindowMs: 2 * 60 * 60000,
17515
+ userInstructionMaxChars: 500
17124
17516
  });
17125
17517
  function describeScheduleTrigger(trigger, timeZone) {
17126
17518
  switch (trigger.kind) {
@@ -17701,12 +18093,19 @@ var mailCommands = [
17701
18093
  ];
17702
18094
 
17703
18095
  // src/client/commands/service.ts
17704
- import { spawnSync } from "child_process";
18096
+ import { randomUUID as randomUUID4 } from "crypto";
18097
+ import { spawnSync as spawnSync2 } from "child_process";
17705
18098
  import { existsSync as existsSync6 } from "fs";
17706
- import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
18099
+ import { mkdir as mkdir2, rm as rm2, stat as stat2, writeFile as writeFile2 } from "fs/promises";
18100
+ import { createServer } from "net";
17707
18101
  import path16 from "path";
17708
18102
 
18103
+ // ../../apps/backend/src/fixed-ports.ts
18104
+ var DEFAULT_GATEWAY_PORT = 34121;
18105
+ var DEFAULT_CONTROL_MCP_PORT = 34122;
18106
+
17709
18107
  // src/client/service-connection.ts
18108
+ import { open } from "fs/promises";
17710
18109
  import path13 from "path";
17711
18110
  async function currentDescriptorGeneration(dataDir) {
17712
18111
  try {
@@ -17715,20 +18114,49 @@ async function currentDescriptorGeneration(dataDir) {
17715
18114
  return null;
17716
18115
  }
17717
18116
  }
18117
+ var LOG_EXCERPT_LINES = 20;
18118
+ var LOG_EXCERPT_MAX_LINE = 500;
18119
+ var LOG_EXCERPT_MAX_BYTES = 256 * 1024;
18120
+ async function startupLogExcerpt(logFile, offset, version) {
18121
+ let text;
18122
+ try {
18123
+ const file = await open(logFile);
18124
+ try {
18125
+ const { size } = await file.stat();
18126
+ const start = Math.max(offset, size - LOG_EXCERPT_MAX_BYTES);
18127
+ const buffer = Buffer.alloc(Math.max(size - start, 0));
18128
+ await file.read(buffer, 0, buffer.length, start);
18129
+ text = buffer.toString("utf8");
18130
+ } finally {
18131
+ await file.close();
18132
+ }
18133
+ } catch {
18134
+ return [];
18135
+ }
18136
+ const lines = text.split(`
18137
+ `);
18138
+ const exit = lines.findLastIndex((line) => line.includes(` ${version} exited (`));
18139
+ const rollback = lines.findIndex((line) => line.includes(` ${version} did not start after `) && line.includes("; rolling back to "));
18140
+ const nextStart = lines.findIndex((line) => /^\[launcher\] \S+ starting /.test(line) && !line.endsWith(` starting ${version}`));
18141
+ const end = exit >= 0 ? exit : Math.min(...[rollback, nextStart, lines.length].filter((index) => index >= 0));
18142
+ return lines.slice(0, end).filter((line) => line.trim() !== "").slice(-LOG_EXCERPT_LINES).map((line) => line.slice(0, LOG_EXCERPT_MAX_LINE));
18143
+ }
17718
18144
  async function awaitServiceConnection(dataDir, previousGeneration, options2) {
17719
18145
  const file = path13.join(dataDir, BACKEND_INSTANCE_DESCRIPTOR_FILE);
17720
18146
  try {
17721
18147
  const descriptor = await observe(async () => {
17722
18148
  try {
17723
18149
  const value = await readDescriptor(file);
17724
- const fresh = value.generation !== previousGeneration && path13.resolve(value.dataDir) === path13.resolve(dataDir) && processIsAlive(value.pid);
17725
- return fresh ? { done: true, value } : { done: false };
17726
- } catch {
17727
- return { done: false };
17728
- }
18150
+ const fresh = value.generation !== previousGeneration && path13.resolve(value.dataDir) === path13.resolve(dataDir) && (options2.expectedVersion === undefined || value.appVersion === options2.expectedVersion) && processIsAlive(value.pid);
18151
+ if (fresh)
18152
+ return { done: true, value };
18153
+ } catch {}
18154
+ await options2.assertStarting?.();
18155
+ return { done: false };
17729
18156
  }, options2);
17730
18157
  const connection = {
17731
18158
  browserUrl: descriptor.browserUrl ?? (isLoopbackHost(new URL(descriptor.url).hostname) || descriptor.endpoints?.some((endpoint) => endpoint.url === descriptor.url) ? null : descriptor.url),
18159
+ clientUrls: (descriptor.endpoints ?? []).filter((endpoint) => endpoint.role === "client").map((endpoint) => endpoint.url),
17732
18160
  authFile: descriptor.authFile,
17733
18161
  token: null
17734
18162
  };
@@ -17752,6 +18180,7 @@ async function awaitServiceConnection(dataDir, previousGeneration, options2) {
17752
18180
  }
17753
18181
 
17754
18182
  // src/client/service-setup.ts
18183
+ import { spawnSync } from "child_process";
17755
18184
  import { existsSync as existsSync4 } from "fs";
17756
18185
  import os6 from "os";
17757
18186
  var HOSTED_WEB_ORIGINS = "https://orkestrator.dev,https://www.orkestrator.dev";
@@ -17764,7 +18193,24 @@ var systemSetupProbe = {
17764
18193
  return MACOS_TAILSCALE_APP_CLI;
17765
18194
  }
17766
18195
  return typeof Bun === "undefined" ? null : Bun.which("tailscale");
17767
- }
18196
+ },
18197
+ tailscaleOperatorNeeded: (cli) => {
18198
+ if (process.platform !== "linux")
18199
+ return null;
18200
+ const user = os6.userInfo();
18201
+ if (user.uid === 0)
18202
+ return null;
18203
+ const prefs = spawnSync(cli, ["debug", "prefs"], { encoding: "utf8", timeout: 1e4 });
18204
+ if (prefs.status !== 0)
18205
+ return null;
18206
+ try {
18207
+ const { OperatorUser } = JSON.parse(prefs.stdout);
18208
+ return OperatorUser === user.username ? null : user.username;
18209
+ } catch {
18210
+ return null;
18211
+ }
18212
+ },
18213
+ grantTailscaleOperator: (cli, user) => spawnSync("sudo", [cli, "set", `--operator=${user}`], { stdio: "inherit" }).status === 0
17768
18214
  };
17769
18215
  function isTailscaleIPv4(address) {
17770
18216
  const [first, second] = address.split(".").map(Number);
@@ -17808,9 +18254,44 @@ async function confirm(io, prompt, fallback) {
17808
18254
  `);
17809
18255
  }
17810
18256
  }
18257
+ async function askGatewayPort(io) {
18258
+ io.stderr(`
18259
+ This is not the default data directory, so ports ${DEFAULT_GATEWAY_PORT} and ${DEFAULT_CONTROL_MCP_PORT} stay with the main install. Without a port of its own, this backend takes a free one that changes on each start.
18260
+ `);
18261
+ for (;; ) {
18262
+ const answer = await ask(io, "Gateway port for a stable address (empty for a free one): ");
18263
+ if (answer === "")
18264
+ return null;
18265
+ const port = Number(answer);
18266
+ if (/^\d+$/.test(answer) && port >= 1 && port <= 65535 && port !== DEFAULT_GATEWAY_PORT && port !== DEFAULT_CONTROL_MCP_PORT) {
18267
+ return String(port);
18268
+ }
18269
+ io.stderr(`Enter a port from 1 to 65535, other than ${DEFAULT_GATEWAY_PORT} and ${DEFAULT_CONTROL_MCP_PORT}.
18270
+ `);
18271
+ }
18272
+ }
17811
18273
  function quote(arg) {
17812
18274
  return /^[\w@%+=:,./-]+$/.test(arg) ? arg : `'${arg.replaceAll("'", `'\\''`)}'`;
17813
18275
  }
18276
+ function operatorMessage(cli, user) {
18277
+ return [
18278
+ `
18279
+ Tailscale only lets root or its operator configure Serve, and ${user} is neither.`,
18280
+ "The backend cannot start with --tailscale-serve until this has been run once:",
18281
+ ` sudo ${quote(cli)} set --operator=${quote(user)}
18282
+ `
18283
+ ].join(`
18284
+ `);
18285
+ }
18286
+ function warnIfTailscaleOperatorNeeded(io, serveArgs, probe = systemSetupProbe) {
18287
+ if (!serveArgs.includes("--tailscale-serve"))
18288
+ return;
18289
+ const binFlag = serveArgs.indexOf("--tailscale-bin");
18290
+ const cli = (binFlag >= 0 ? serveArgs[binFlag + 1] : undefined) ?? io.env.ORKESTRATOR_TAILSCALE_BIN ?? probe.findTailscaleCli();
18291
+ const user = cli ? probe.tailscaleOperatorNeeded(cli) : null;
18292
+ if (cli && user)
18293
+ io.stderr(operatorMessage(cli, user));
18294
+ }
17814
18295
  async function askServeArguments(io, options2) {
17815
18296
  const source = io;
17816
18297
  io = {
@@ -17843,16 +18324,30 @@ async function askServeArguments(io, options2) {
17843
18324
  This machine has no Tailscale address yet. Connect Tailscale before the service starts, or it will not serve.
17844
18325
  `);
17845
18326
  }
18327
+ if (access === "tailnet" && options2.secondBackend) {
18328
+ const port = await askGatewayPort(io);
18329
+ if (port)
18330
+ args.push("--port", port);
18331
+ }
17846
18332
  if (access === "serve") {
17847
18333
  args.push("--tailscale-serve");
17848
- if (!probe.findTailscaleCli()) {
18334
+ let cli = probe.findTailscaleCli();
18335
+ if (!cli) {
17849
18336
  io.stderr(`
17850
18337
  The Tailscale CLI was not found, and Tailscale Serve needs it.
17851
18338
  `);
17852
- const cli = await ask(io, "Path to the tailscale executable (empty to keep `tailscale`): ");
18339
+ cli = await ask(io, "Path to the tailscale executable (empty to keep `tailscale`): ");
17853
18340
  if (cli !== "")
17854
18341
  args.push("--tailscale-bin", cli);
17855
18342
  }
18343
+ const operator = cli ? probe.tailscaleOperatorNeeded(cli) : null;
18344
+ if (cli && operator) {
18345
+ io.stderr(operatorMessage(cli, operator));
18346
+ const granted = !options2.dryRun && await confirm(io, "Run it now with sudo?", true) && probe.grantTailscaleOperator(cli, operator);
18347
+ if (!granted && !options2.dryRun)
18348
+ io.stderr(`Continuing without it.
18349
+ `);
18350
+ }
17856
18351
  if (await confirm(io, `
17857
18352
  Allow the hosted web client at orkestrator.dev?`, true)) {
17858
18353
  args.push("--allowed-origins", HOSTED_WEB_ORIGINS);
@@ -17934,6 +18429,8 @@ ${args}
17934
18429
  }
17935
18430
  const name = `orkestrator-backend${tag}.service`;
17936
18431
  const configHome = options2.env.XDG_CONFIG_HOME ?? path14.join(home, ".config");
18432
+ const xdgEnvironment = options2.env.XDG_CONFIG_HOME === undefined ? "" : `Environment=${systemdWord(`XDG_CONFIG_HOME=${options2.env.XDG_CONFIG_HOME}`)}
18433
+ `;
17937
18434
  return {
17938
18435
  manager: "systemd",
17939
18436
  name,
@@ -17947,7 +18444,7 @@ Type=simple
17947
18444
  ExecStart=${[options2.bun, options2.launcher, options2.dataDir].map(systemdWord).join(" ")}
17948
18445
  Environment=${systemdWord(`PATH=${pathValue}`)}
17949
18446
  Environment=NODE_ENV=production
17950
- Restart=always
18447
+ ${xdgEnvironment}Restart=always
17951
18448
  RestartSec=10
17952
18449
  StandardOutput=append:${options2.logFile}
17953
18450
  StandardError=append:${options2.logFile}
@@ -18032,8 +18529,43 @@ function unitFor(context, dataDir, bun) {
18032
18529
  };
18033
18530
  });
18034
18531
  }
18532
+ async function startupFailedError(version, failure, logFile, logOffset) {
18533
+ const excerpt = await startupLogExcerpt(logFile, logOffset, version);
18534
+ return new CliError("operation-failed", [
18535
+ failure.restoredVersion ? `orkestrator@${version} did not start, so the service went back to ${failure.restoredVersion}.` : `orkestrator@${version} did not start, and the service keeps retrying it.`,
18536
+ ...excerpt.length > 0 ? ["", ...excerpt, ""] : [],
18537
+ `Fix the cause, then run this command again. Full log: ${logFile}`
18538
+ ].join(`
18539
+ `), { retryable: true });
18540
+ }
18541
+ async function freeServicePort(allocate = probeServicePort) {
18542
+ for (let attempt = 0;attempt < 32; attempt += 1) {
18543
+ const port = await allocate();
18544
+ if (validServicePort(String(port)))
18545
+ return String(port);
18546
+ }
18547
+ throw new Error("Unable to allocate an unreserved service port");
18548
+ }
18549
+ function validServicePort(value) {
18550
+ const port = Number(value);
18551
+ return value !== undefined && String(port) === value && Number.isInteger(port) && port > 0 && port <= 65535 && port !== DEFAULT_GATEWAY_PORT && port !== DEFAULT_CONTROL_MCP_PORT ? value : undefined;
18552
+ }
18553
+ async function probeServicePort() {
18554
+ return new Promise((resolve, reject) => {
18555
+ const probe = createServer();
18556
+ probe.once("error", reject);
18557
+ probe.listen(0, "127.0.0.1", () => {
18558
+ const address = probe.address();
18559
+ probe.close(() => resolve(typeof address === "object" && address ? address.port : 0));
18560
+ });
18561
+ });
18562
+ }
18563
+ function portArgument(args) {
18564
+ const index = args?.indexOf("--port") ?? -1;
18565
+ return index >= 0 ? args?.[index + 1] : undefined;
18566
+ }
18035
18567
  function runManager(argv, allowFailure = false) {
18036
- const result = spawnSync(argv[0], argv.slice(1), { encoding: "utf8", timeout: 60000 });
18568
+ const result = spawnSync2(argv[0], argv.slice(1), { encoding: "utf8", timeout: 60000 });
18037
18569
  const ok = result.status === 0;
18038
18570
  if (!ok && !allowFailure) {
18039
18571
  throw new CliError("operation-failed", `${argv.join(" ")} failed: ${(result.stderr || result.stdout || "").trim().slice(0, 300)}`);
@@ -18055,10 +18587,19 @@ var serviceCommands = [
18055
18587
  "With none, in a terminal, it asks how clients will connect (Tailscale Serve, the",
18056
18588
  "tailnet address or SSH) and prints the equivalent command; --yes skips the questions.",
18057
18589
  "The Bun running this command is the Bun the service uses; reinstall to change it.",
18590
+ "",
18591
+ "Only the default data directory's backend uses the fixed gateway port. With another",
18592
+ "--data-dir and no `--port`, the backend takes a free port that changes on each start,",
18593
+ "so pass `-- --port PORT` for a stable address. --tailscale-serve gets a free port",
18594
+ "chosen once, here, with strict binding so it cannot move to a fallback port.",
18058
18595
  "On Linux, run `loginctl enable-linger $USER` so the service runs without a login.",
18059
18596
  "",
18060
18597
  "Once the service is running, the address it serves and its gateway token are printed.",
18061
- "The token is shown only in this terminal: never in --output json or the service log."
18598
+ "The token is shown only in this terminal: never in --output json or the service log.",
18599
+ "If the release cannot start, the command fails with what the service logged.",
18600
+ "",
18601
+ "On Linux, --tailscale-serve needs `sudo tailscale set --operator=$USER` once; the",
18602
+ "questions offer to run it, and a scripted install warns when it is missing."
18062
18603
  ].join(`
18063
18604
  `),
18064
18605
  positionals: [],
@@ -18115,13 +18656,30 @@ var serviceCommands = [
18115
18656
  if (readyWaitMs > PUBLIC_API_LIMITS.waitMaxMs) {
18116
18657
  throw new CliError("invalid-input", "--timeout may be at most 24h");
18117
18658
  }
18118
- const serveArgs = interactive ? await askServeArguments(context.io, {
18659
+ const secondBackend = !isDefaultDataDir(dataDir, process.platform, context.io.env);
18660
+ const requestedArgs = interactive ? await askServeArguments(context.io, {
18119
18661
  installCommand: installCommandLine(parsed),
18120
- signal: context.signal
18662
+ signal: context.signal,
18663
+ dryRun: parsed.options["dry-run"] === true,
18664
+ secondBackend
18121
18665
  }) : parsed.rest;
18666
+ if (!interactive)
18667
+ warnIfTailscaleOperatorNeeded(context.io, requestedArgs);
18122
18668
  const bun = process.execPath;
18123
18669
  const bunVersion = typeof Bun === "undefined" ? "" : Bun.version;
18124
18670
  const { service, layout, unit } = await unitFor(context, dataDir, bun);
18671
+ const pinsPort = secondBackend && requestedArgs.includes("--tailscale-serve") && !requestedArgs.includes("--port");
18672
+ const serveArgs = pinsPort && parsed.options["dry-run"] !== true ? [
18673
+ ...requestedArgs,
18674
+ "--port",
18675
+ validServicePort(portArgument(service.readServiceConfig(layout)?.args)) ?? await freeServicePort(),
18676
+ "--strict-gateway-port"
18677
+ ] : requestedArgs;
18678
+ const portNote = pinsPort ? [
18679
+ "This is not the default data directory, so its gateway keeps a free port chosen at install instead of the fixed one."
18680
+ ] : secondBackend && !requestedArgs.includes("--port") ? [
18681
+ "This is not the default data directory, so its gateway takes a free port that changes on each start. For a stable address, reinstall with `-- --port PORT`."
18682
+ ] : [];
18125
18683
  const summary = {
18126
18684
  dataDir,
18127
18685
  version,
@@ -18143,6 +18701,7 @@ var serviceCommands = [
18143
18701
  ["unit", `${unit.manager} ${unit.name} \u2192 ${unit.file}`],
18144
18702
  ["serve arguments", serveArgs.join(" ") || "(none)"]
18145
18703
  ]),
18704
+ ...portNote,
18146
18705
  "",
18147
18706
  "Dry run: nothing was installed."
18148
18707
  ]
@@ -18158,13 +18717,17 @@ var serviceCommands = [
18158
18717
  throw new CliError("operation-failed", `Could not install orkestrator@${version}: ${error instanceof Error ? error.message : String(error)}`, { retryable: true });
18159
18718
  }
18160
18719
  await service.installLauncher(layout, releaseDir);
18720
+ const rollbackTarget = service.nextRollbackTarget(layout, service.readServiceConfig(layout), service.linkedVersion(layout.current), version);
18161
18721
  await service.activateRelease(layout, version);
18722
+ const installGeneration = randomUUID4();
18162
18723
  await service.writeServiceConfig(layout, {
18724
+ installGeneration,
18163
18725
  version: 1,
18164
18726
  dataDir,
18165
18727
  bun,
18166
18728
  bunVersion,
18167
18729
  args: serveArgs,
18730
+ ...rollbackTarget ? { rollbackTarget } : {},
18168
18731
  installedAt: new Date().toISOString()
18169
18732
  });
18170
18733
  await mkdir2(layout.logs, { recursive: true });
@@ -18175,6 +18738,8 @@ var serviceCommands = [
18175
18738
  let connection = null;
18176
18739
  if (started) {
18177
18740
  const previousGeneration = await currentDescriptorGeneration(dataDir);
18741
+ const loadedAt = Date.now();
18742
+ const logOffset = await stat2(logFile).then((file) => file.size, () => 0);
18178
18743
  const commands = serviceManagerCommands(unit);
18179
18744
  commands.load.forEach((argv, index) => runManager(argv, unit.manager === "launchd" && index === 0));
18180
18745
  if (readyWaitMs > 0) {
@@ -18182,7 +18747,13 @@ var serviceCommands = [
18182
18747
  `);
18183
18748
  connection = await awaitServiceConnection(dataDir, previousGeneration, {
18184
18749
  timeoutMs: readyWaitMs,
18185
- signal: context.signal
18750
+ signal: context.signal,
18751
+ expectedVersion: version,
18752
+ assertStarting: async () => {
18753
+ const failure = service.readStartupFailure(layout, version, loadedAt, installGeneration);
18754
+ if (failure)
18755
+ throw await startupFailedError(version, failure, logFile, logOffset);
18756
+ }
18186
18757
  });
18187
18758
  }
18188
18759
  }
@@ -18193,6 +18764,7 @@ var serviceCommands = [
18193
18764
  started,
18194
18765
  ready: connection !== null,
18195
18766
  browserUrl: connection?.browserUrl ?? null,
18767
+ clientUrls: connection?.clientUrls ?? [],
18196
18768
  authFile: connection?.authFile ?? null,
18197
18769
  ...connection?.credentialError ? { credentialError: connection.credentialError } : {},
18198
18770
  ...summary
@@ -18209,6 +18781,7 @@ var serviceCommands = [
18209
18781
  "",
18210
18782
  ...keyValues([
18211
18783
  ["address", connection.browserUrl ?? "(no browser listener; see the log)"],
18784
+ ...connection.clientUrls.length > 0 ? [["local listeners", connection.clientUrls.join(", ")]] : [],
18212
18785
  ["auth file", connection.authFile],
18213
18786
  ...context.io.stdoutIsTty && context.global.output === "human" && connection.token ? [["token", connection.token]] : []
18214
18787
  ]),
@@ -18217,6 +18790,7 @@ var serviceCommands = [
18217
18790
  "",
18218
18791
  `The backend has not reported ready yet. Follow ${logFile}; its address and token file are logged there.`
18219
18792
  ] : [],
18793
+ ...portNote,
18220
18794
  ...unit.manager === "systemd" ? ["Run `loginctl enable-linger $USER` so it keeps running after you log out."] : []
18221
18795
  ]
18222
18796
  };
@@ -18269,15 +18843,17 @@ var serviceCommands = [
18269
18843
  const state = service.readLauncherState(layout);
18270
18844
  const installed = existsSync6(unit.file);
18271
18845
  const running = installed ? runManager(serviceManagerCommands(unit).status, true).ok : false;
18846
+ const current = service.linkedVersion(layout.current);
18847
+ const previous = service.linkedVersion(layout.previous);
18272
18848
  const result = {
18273
18849
  dataDir,
18274
18850
  installed,
18275
18851
  running,
18276
18852
  unit: { manager: unit.manager, name: unit.name, file: unit.file },
18277
- current: service.linkedVersion(layout.current),
18278
- previous: service.linkedVersion(layout.previous),
18853
+ current,
18854
+ previous,
18279
18855
  bun: config?.bun ?? null,
18280
- args: config?.args ?? [],
18856
+ args: service.effectiveServiceArguments(config, current, previous),
18281
18857
  lastReadyVersion: state.lastReadyVersion,
18282
18858
  rollback: state.rollback
18283
18859
  };
@@ -18850,18 +19426,357 @@ var runCommands = [
18850
19426
  }
18851
19427
  ];
18852
19428
 
18853
- // src/client/commands/ticket.ts
18854
- var STATUSES = ["backlog", "in-progress", "review", "done"];
18855
- var KINDS3 = ["feature", "work-brief"];
18856
- var PROJECT_OPTION3 = {
18857
- name: "project",
18858
- kind: "string",
18859
- valueName: "PROJECT_ID",
18860
- description: "Project ID (project list)."
18861
- };
18862
- var STATUS_OPTION = {
18863
- name: "status",
18864
- kind: "string",
19429
+ // src/client/commands/session-goal.ts
19430
+ var WAIT_OPTIONS2 = [
19431
+ {
19432
+ name: "wait",
19433
+ kind: "boolean",
19434
+ description: "Observe the goal until it stops. Exit 0 for complete, 10 claimed, 11 blocked, 12 limited, 13 paused, 14 cleared or replaced."
19435
+ },
19436
+ TIMEOUT_OPTION,
19437
+ {
19438
+ name: "continue-on-interaction",
19439
+ kind: "boolean",
19440
+ description: "While waiting, keep waiting when a turn needs an answer (another client may answer)."
19441
+ }
19442
+ ];
19443
+ var LIMIT_OPTIONS = [
19444
+ {
19445
+ name: "max-iterations",
19446
+ kind: "integer",
19447
+ valueName: "N",
19448
+ description: `Turns the backend may send, at most ${NATIVE_AGENT_GOAL_LIMITS.maxIterationsLimit}.`
19449
+ },
19450
+ {
19451
+ name: "budget",
19452
+ kind: "integer",
19453
+ valueName: "TOKENS",
19454
+ description: "Tokens the goal may spend; refused where the provider reports no usage."
19455
+ }
19456
+ ];
19457
+ var LIMIT_FIELDS = { budget: "tokenBudget" };
19458
+ function limitsInput(options2) {
19459
+ return {
19460
+ ...options2["max-iterations"] !== undefined ? { maxIterations: options2["max-iterations"] } : {},
19461
+ ...options2.budget !== undefined ? { tokenBudget: options2.budget } : {}
19462
+ };
19463
+ }
19464
+ function goalLines(read) {
19465
+ const goal = read.goal;
19466
+ if (!goal)
19467
+ return ["no goal"];
19468
+ return keyValues([
19469
+ ["goal", goal.goalId],
19470
+ ["status", goal.status],
19471
+ ["reason", read.description],
19472
+ ["iterations", `${goal.iterations}/${goal.maxIterations}`],
19473
+ [
19474
+ "tokens",
19475
+ goal.tokenBudget !== undefined ? `${goal.tokensUsed ?? 0}/${goal.tokenBudget}` : undefined
19476
+ ],
19477
+ ["check", goal.check],
19478
+ [
19479
+ "verifier",
19480
+ goal.verifier ? `${goal.verifier.agent}${goal.verifier.model ? `:${goal.verifier.model}` : ""}` : undefined
19481
+ ],
19482
+ ["last check", goal.lastCheck ? goal.lastCheck.passed ? "passed" : "failed" : undefined],
19483
+ ["objective", goal.objective]
19484
+ ]);
19485
+ }
19486
+ function goalDetails(sessionId, goal) {
19487
+ return {
19488
+ sessionId,
19489
+ goalId: goal.goalId,
19490
+ status: goal.status,
19491
+ ...goal.statusReason ? { statusReason: goal.statusReason } : {},
19492
+ iterations: goal.iterations,
19493
+ maxIterations: goal.maxIterations
19494
+ };
19495
+ }
19496
+ var STOPPED_MESSAGES = {
19497
+ "goal-claimed": "The agent says the goal is done, and nothing verified it",
19498
+ "goal-blocked": "The goal is blocked: the agent says it needs a person",
19499
+ "goal-limited": "The goal reached its iteration cap or token budget",
19500
+ "goal-paused": "The goal is paused"
19501
+ };
19502
+ async function waitForGoal(context, session, sessionId, goalId, timeoutMs, options2) {
19503
+ let latest;
19504
+ const receiptOption = options2.receipt ? { receipt: options2.receipt } : {};
19505
+ try {
19506
+ return await observe(async () => {
19507
+ const read = await session.read("session.goal.get", {
19508
+ sessionId,
19509
+ ...options2.includeCheckOutput ? { includeCheckOutput: true } : {}
19510
+ });
19511
+ const verdict = classifyGoalForWait(read, goalId, {
19512
+ continueOnInteraction: options2.continueOnInteraction
19513
+ });
19514
+ if (verdict.kind === "pending") {
19515
+ latest = verdict.goal;
19516
+ return { done: false };
19517
+ }
19518
+ if (verdict.kind === "complete")
19519
+ return {
19520
+ done: true,
19521
+ value: {
19522
+ ...read,
19523
+ goal: verdict.goal,
19524
+ description: "Complete",
19525
+ ...read.goal?.goalId !== goalId ? { checkOutput: undefined } : {}
19526
+ }
19527
+ };
19528
+ if (verdict.kind === "interaction") {
19529
+ throw new CliError("interaction-required", "The goal's turn is waiting for an answer to a pending interaction", {
19530
+ ...receiptOption,
19531
+ details: {
19532
+ ...goalDetails(sessionId, verdict.goal),
19533
+ pendingInteractionCount: read.pendingInteractionCount
19534
+ }
19535
+ });
19536
+ }
19537
+ if (verdict.kind === "gone") {
19538
+ throw new CliError("goal-gone", "The goal was cleared or replaced before it ended", {
19539
+ ...receiptOption,
19540
+ details: {
19541
+ sessionId,
19542
+ goalId,
19543
+ ...verdict.goal ? { status: verdict.goal.status } : {}
19544
+ }
19545
+ });
19546
+ }
19547
+ throw new CliError(verdict.code, STOPPED_MESSAGES[verdict.code], {
19548
+ ...receiptOption,
19549
+ details: goalDetails(sessionId, verdict.goal)
19550
+ });
19551
+ }, { timeoutMs, signal: context.signal });
19552
+ } catch (error) {
19553
+ if (!(error instanceof ObservationStopped))
19554
+ throw error;
19555
+ const details = { sessionId, goalId, ...latest ? goalDetails(sessionId, latest) : {} };
19556
+ if (error.reason === "signal") {
19557
+ throw new CliError("observation-interrupted", "Stopped observing the goal; it continues in the backend", { ...receiptOption, details: { ...details, signal: signalName(context.signal) } });
19558
+ }
19559
+ throw new CliError("deadline-exceeded", "Timed out observing the goal; it continues in the backend. `session goal get --wait` observes it again", { ...receiptOption, details });
19560
+ }
19561
+ }
19562
+ function waitOutcome(base, read) {
19563
+ return {
19564
+ ...base,
19565
+ result: read,
19566
+ human: goalLines(read),
19567
+ ids: read.goal ? [read.goal.goalId] : []
19568
+ };
19569
+ }
19570
+ async function runLoopMutation(context, parsed, action, input) {
19571
+ const options2 = parsed.options;
19572
+ const sessionId = parsed.positionals.session;
19573
+ validateWaitOptions(options2);
19574
+ if (options2.wait !== true) {
19575
+ const { session, result, receipt, warnings } = await submit(context, action, input, options2);
19576
+ return {
19577
+ action,
19578
+ result,
19579
+ receipt,
19580
+ warnings,
19581
+ connection: session.identity,
19582
+ ids: result.goal ? [result.goal.goalId] : [],
19583
+ human: result.goal ? keyValues([
19584
+ ["goal", result.goal.goalId],
19585
+ ["status", result.goal.status],
19586
+ ["iterations", `${result.goal.iterations}/${result.goal.maxIterations}`]
19587
+ ]) : receipt ? receiptLines(receipt) : []
19588
+ };
19589
+ }
19590
+ const budget = options2.timeout ?? DEFAULT_WAIT_MS;
19591
+ const started = Date.now();
19592
+ const submitted = await submitForWait(context, action, input, options2, budget);
19593
+ const { session, warnings } = submitted;
19594
+ let receipt = submitted.receipt;
19595
+ let mutation = submitted.result;
19596
+ if (!mutation && receipt) {
19597
+ receipt = await waitForOperation(context, session, receipt, budget - (Date.now() - started));
19598
+ const failure = receiptFailure(receipt);
19599
+ if (failure)
19600
+ throw failure;
19601
+ mutation = receipt.result;
19602
+ }
19603
+ const goalId = mutation?.goal?.goalId;
19604
+ if (!goalId)
19605
+ throw new CliError("response-invalid", `${action} returned no goal`);
19606
+ const read = await waitForGoal(context, session, sessionId, goalId, Math.max(0, budget - (Date.now() - started)), {
19607
+ continueOnInteraction: options2["continue-on-interaction"] === true,
19608
+ ...receipt ? { receipt } : {}
19609
+ });
19610
+ return waitOutcome({ action, result: read, receipt, warnings, connection: session.identity }, read);
19611
+ }
19612
+ var sessionGoalCommands = [
19613
+ {
19614
+ path: ["session", "goal", "set"],
19615
+ action: "session.goal.set",
19616
+ summary: "Give a session a goal: the backend sends turns until a check proves it or it stops.",
19617
+ description: "Pass the objective with --objective, --objective-file or --objective-stdin. Without --check or --verifier, a done report ends the goal as claimed: nothing verified it, and --wait exits 10. A session holds one goal, and a running goal is refused (exit 8) until it is cleared. A goal never changes the permission mode, so an approval still parks a turn (--wait exits 6).",
19618
+ positionals: [{ name: "session", required: true }],
19619
+ options: [
19620
+ {
19621
+ name: "objective-file",
19622
+ kind: "string",
19623
+ valueName: "PATH",
19624
+ description: "Read the objective from a local file."
19625
+ },
19626
+ { name: "objective-stdin", kind: "boolean", description: "Read the objective from stdin." },
19627
+ { name: "objective", kind: "string", valueName: "TEXT", description: "Inline objective." },
19628
+ {
19629
+ name: "check",
19630
+ kind: "string",
19631
+ valueName: "COMMAND",
19632
+ description: "One-line shell command run in the workspace on a done report; exit 0 completes the goal."
19633
+ },
19634
+ {
19635
+ name: "verifier",
19636
+ kind: "string",
19637
+ valueName: "AGENT[:MODEL]",
19638
+ description: "A fresh read-only session on that agent judges the work on a done report; with --check, both must pass."
19639
+ },
19640
+ ...LIMIT_OPTIONS,
19641
+ ...WAIT_OPTIONS2,
19642
+ REQUEST_ID_OPTION
19643
+ ],
19644
+ inputFields: { ...LIMIT_FIELDS, "objective-file": "objective", "objective-stdin": "objective" },
19645
+ idOutput: "goal ID",
19646
+ examples: [
19647
+ 'orkestrator session goal set ses_\u2026 --objective "Make the tests pass" --check "bun test" --max-iterations 10 --wait --timeout 2h'
19648
+ ],
19649
+ async run(context, parsed) {
19650
+ const options2 = parsed.options;
19651
+ validateWaitOptions(options2);
19652
+ const verifier = options2.verifier === undefined ? undefined : parseVerifierFlag(String(options2.verifier));
19653
+ if (verifier && "error" in verifier)
19654
+ throw new CliError("invalid-input", verifier.error);
19655
+ const objective = await readTextSource(context.io, {
19656
+ file: options2["objective-file"],
19657
+ stdin: options2["objective-stdin"],
19658
+ inline: options2.objective,
19659
+ label: "Objective",
19660
+ maxBytes: NATIVE_AGENT_GOAL_LIMITS.objectiveMaxChars * 4,
19661
+ names: {
19662
+ file: "--objective-file",
19663
+ stdin: "--objective-stdin",
19664
+ inline: "--objective"
19665
+ },
19666
+ required: true
19667
+ });
19668
+ return runLoopMutation(context, parsed, "session.goal.set", {
19669
+ sessionId: parsed.positionals.session,
19670
+ objective,
19671
+ ...options2.check !== undefined ? { check: options2.check } : {},
19672
+ ...verifier ? { verifier } : {},
19673
+ ...limitsInput(options2)
19674
+ });
19675
+ }
19676
+ },
19677
+ {
19678
+ path: ["session", "goal", "get"],
19679
+ action: "session.goal.get",
19680
+ summary: "Show a session's goal: status, iterations, limits and the last check.",
19681
+ description: "Reads the session record and starts nothing. With no goal the result's goal is null and the exit is 0, unless --wait is given, which exits 14. --wait observes the goal the session holds now, so it picks up a goal after an earlier --wait timed out.",
19682
+ positionals: [{ name: "session", required: true }],
19683
+ options: [
19684
+ {
19685
+ name: "check-output",
19686
+ kind: "boolean",
19687
+ description: "Include the last check's output tail. It is untrusted command output."
19688
+ },
19689
+ ...WAIT_OPTIONS2
19690
+ ],
19691
+ inputFields: { "check-output": "includeCheckOutput" },
19692
+ idOutput: "goal ID",
19693
+ async run(context, parsed) {
19694
+ const options2 = parsed.options;
19695
+ validateWaitOptions(options2);
19696
+ const sessionId = parsed.positionals.session;
19697
+ const session = await context.session();
19698
+ const first = await session.read("session.goal.get", {
19699
+ sessionId,
19700
+ ...options2["check-output"] === true ? { includeCheckOutput: true } : {}
19701
+ });
19702
+ const base = { action: "session.goal.get", connection: session.identity };
19703
+ if (options2.wait !== true)
19704
+ return waitOutcome({ ...base, result: first }, first);
19705
+ const goalId = first.goal?.goalId;
19706
+ if (!goalId) {
19707
+ throw new CliError("goal-gone", "The session has no goal to wait for", {
19708
+ details: { sessionId }
19709
+ });
19710
+ }
19711
+ const read = await waitForGoal(context, session, sessionId, goalId, options2.timeout ?? DEFAULT_WAIT_MS, {
19712
+ continueOnInteraction: options2["continue-on-interaction"] === true,
19713
+ includeCheckOutput: options2["check-output"] === true
19714
+ });
19715
+ return waitOutcome({ ...base, result: read }, read);
19716
+ }
19717
+ },
19718
+ {
19719
+ path: ["session", "goal", "pause"],
19720
+ action: "session.goal.pause",
19721
+ summary: "Pause a running goal; no further turn starts until it is resumed.",
19722
+ description: "A turn already running finishes. Use `session stop` to stop it too. Refused (exit 8) when no goal is running.",
19723
+ positionals: [{ name: "session", required: true }],
19724
+ options: [REQUEST_ID_OPTION],
19725
+ idOutput: "goal ID",
19726
+ run: (context, parsed) => runGoalControl(context, parsed, "session.goal.pause")
19727
+ },
19728
+ {
19729
+ path: ["session", "goal", "resume"],
19730
+ action: "session.goal.resume",
19731
+ summary: "Resume a stopped goal, optionally with a higher limit.",
19732
+ description: "A goal stopped by its limit needs a higher --max-iterations or --budget. An ended goal (claimed, complete) cannot resume; set a new one. Refused with exit 8.",
19733
+ positionals: [{ name: "session", required: true }],
19734
+ options: [...LIMIT_OPTIONS, ...WAIT_OPTIONS2, REQUEST_ID_OPTION],
19735
+ inputFields: LIMIT_FIELDS,
19736
+ idOutput: "goal ID",
19737
+ run: (context, parsed) => runLoopMutation(context, parsed, "session.goal.resume", {
19738
+ sessionId: parsed.positionals.session,
19739
+ ...limitsInput(parsed.options)
19740
+ })
19741
+ },
19742
+ {
19743
+ path: ["session", "goal", "clear"],
19744
+ action: "session.goal.clear",
19745
+ summary: "Remove a session's goal, in any status.",
19746
+ description: "What the agent did stays, and the goal stays readable in the goal history. A session with no goal stays that way and exits 0.",
19747
+ positionals: [{ name: "session", required: true }],
19748
+ options: [REQUEST_ID_OPTION],
19749
+ idOutput: "goal ID",
19750
+ run: (context, parsed) => runGoalControl(context, parsed, "session.goal.clear")
19751
+ }
19752
+ ];
19753
+ async function runGoalControl(context, parsed, action) {
19754
+ const { session, result, receipt, warnings } = await submit(context, action, { sessionId: parsed.positionals.session }, parsed.options);
19755
+ return {
19756
+ action,
19757
+ result,
19758
+ receipt,
19759
+ warnings,
19760
+ connection: session.identity,
19761
+ ids: result.goal ? [result.goal.goalId] : [],
19762
+ human: action === "session.goal.clear" ? [result.cleared ? "cleared" : "no goal"] : [
19763
+ `paused${result.goal ? ` at ${result.goal.iterations}/${result.goal.maxIterations}` : ""}`
19764
+ ]
19765
+ };
19766
+ }
19767
+
19768
+ // src/client/commands/ticket.ts
19769
+ var STATUSES = ["backlog", "in-progress", "review", "done"];
19770
+ var KINDS3 = ["feature", "work-brief"];
19771
+ var PROJECT_OPTION3 = {
19772
+ name: "project",
19773
+ kind: "string",
19774
+ valueName: "PROJECT_ID",
19775
+ description: "Project ID (project list)."
19776
+ };
19777
+ var STATUS_OPTION = {
19778
+ name: "status",
19779
+ kind: "string",
18865
19780
  valueName: "COLUMN",
18866
19781
  description: `Column: ${STATUSES.join(", ")}.`
18867
19782
  };
@@ -19267,6 +20182,742 @@ var toolchainCommands = [
19267
20182
  }
19268
20183
  ];
19269
20184
 
20185
+ // ../protocol/src/public-api-inputs-pr.ts
20186
+ var PUBLIC_PR_BODY_MAX_CHARS = 65536;
20187
+ var PUBLIC_PR_REVIEW_COMMENTS_MAX = 200;
20188
+ var PUBLIC_PR_REVIEWERS_MAX = 100;
20189
+ var PUBLIC_PR_MERGE_METHODS = Object.freeze(["squash", "merge", "rebase"]);
20190
+ var PUBLIC_PR_LIST_FILTERS = Object.freeze([
20191
+ "open",
20192
+ "mine",
20193
+ "review-requested",
20194
+ "closed"
20195
+ ]);
20196
+ var PUBLIC_PR_REVIEW_EVENTS = Object.freeze([
20197
+ "APPROVE",
20198
+ "REQUEST_CHANGES",
20199
+ "COMMENT"
20200
+ ]);
20201
+ var PUBLIC_PR_SIDES = Object.freeze(["LEFT", "RIGHT"]);
20202
+ function schema4(properties, required = []) {
20203
+ return { type: "object", properties, required, additionalProperties: false };
20204
+ }
20205
+ var environmentId = {
20206
+ type: "string",
20207
+ minLength: 1,
20208
+ maxLength: PUBLIC_API_LIMITS.idMaxChars,
20209
+ description: "Environment ID, from environment.list. Its attached pull request is the target."
20210
+ };
20211
+ var projectId = {
20212
+ type: "string",
20213
+ minLength: 1,
20214
+ maxLength: PUBLIC_API_LIMITS.idMaxChars,
20215
+ description: "Project ID, from project.list."
20216
+ };
20217
+ var body = {
20218
+ type: "string",
20219
+ minLength: 1,
20220
+ maxLength: PUBLIC_PR_BODY_MAX_CHARS,
20221
+ description: "Markdown text, posted as the GitHub user configured in Settings."
20222
+ };
20223
+ var filePath = {
20224
+ type: "string",
20225
+ minLength: 1,
20226
+ maxLength: PUBLIC_API_LIMITS.pathMaxChars,
20227
+ description: "Repository-relative path of a changed file, from pr.files."
20228
+ };
20229
+ var threadId = {
20230
+ type: "string",
20231
+ minLength: 1,
20232
+ maxLength: PUBLIC_API_LIMITS.idMaxChars,
20233
+ description: "Review thread ID, from pr.files."
20234
+ };
20235
+ var commitSha = {
20236
+ type: "string",
20237
+ minLength: 40,
20238
+ maxLength: 40
20239
+ };
20240
+ var line = {
20241
+ type: "integer",
20242
+ minimum: 1,
20243
+ description: "Line in the new file (RIGHT) or the old file (LEFT)."
20244
+ };
20245
+ var side = {
20246
+ type: "string",
20247
+ enum: PUBLIC_PR_SIDES,
20248
+ description: "RIGHT: the new file. LEFT: the old file."
20249
+ };
20250
+ var startLine = {
20251
+ type: "integer",
20252
+ minimum: 1,
20253
+ description: "First line of a multi-line comment; below line."
20254
+ };
20255
+ var reviewerList = {
20256
+ type: "array",
20257
+ maxItems: PUBLIC_PR_REVIEWERS_MAX,
20258
+ items: { type: "string", minLength: 1, maxLength: PUBLIC_API_LIMITS.nameMaxChars }
20259
+ };
20260
+ var environmentOnly = schema4({ environmentId }, ["environmentId"]);
20261
+ var environmentExamples = [{ environmentId: "env-1" }];
20262
+ var PR_ACTION_INPUTS = Object.freeze({
20263
+ "pr.get": {
20264
+ capability: "sessions.read",
20265
+ confirmation: "never",
20266
+ input: environmentOnly,
20267
+ examples: environmentExamples
20268
+ },
20269
+ "pr.list": {
20270
+ capability: "sessions.read",
20271
+ confirmation: "never",
20272
+ input: schema4({
20273
+ projectId,
20274
+ filter: {
20275
+ type: "string",
20276
+ enum: PUBLIC_PR_LIST_FILTERS,
20277
+ description: "Which pull requests (default open)."
20278
+ },
20279
+ query: {
20280
+ type: "string",
20281
+ minLength: 1,
20282
+ maxLength: 256,
20283
+ description: "Search text, as in the pull request list's search box."
20284
+ }
20285
+ }, ["projectId"]),
20286
+ examples: [{ projectId: "project-1" }, { projectId: "project-1", filter: "closed" }]
20287
+ },
20288
+ "pr.timeline": {
20289
+ capability: "sessions.read",
20290
+ confirmation: "never",
20291
+ input: environmentOnly,
20292
+ examples: environmentExamples
20293
+ },
20294
+ "pr.files": {
20295
+ capability: "sessions.read",
20296
+ confirmation: "never",
20297
+ input: schema4({
20298
+ environmentId,
20299
+ path: {
20300
+ ...filePath,
20301
+ description: "Return only this file, with its diff hunks. Omitted: every file without hunks, to keep the result small."
20302
+ }
20303
+ }, ["environmentId"]),
20304
+ examples: [{ environmentId: "env-1" }, { environmentId: "env-1", path: "src/index.ts" }]
20305
+ },
20306
+ "pr.options": {
20307
+ capability: "sessions.read",
20308
+ confirmation: "never",
20309
+ input: environmentOnly,
20310
+ examples: environmentExamples
20311
+ },
20312
+ "pr.merge": {
20313
+ capability: "environments.destructive",
20314
+ confirmation: "always",
20315
+ input: schema4({
20316
+ environmentId,
20317
+ method: {
20318
+ type: "string",
20319
+ enum: PUBLIC_PR_MERGE_METHODS,
20320
+ description: "How to merge (default squash, as on the Merge button)."
20321
+ },
20322
+ deleteBranch: {
20323
+ type: "boolean",
20324
+ description: "Delete the head branch after merging (default true). A project's home checkout keeps its branch."
20325
+ },
20326
+ cleanup: {
20327
+ type: "boolean",
20328
+ description: "Merge and Cleanup: delete this environment and its worktree once the merge is confirmed. Cannot be combined with deleteBranch false."
20329
+ },
20330
+ expectedPrUrl: {
20331
+ type: "string",
20332
+ description: "Refuse if the attached PR or the explicit cleanup/branch effects changed since confirmation."
20333
+ },
20334
+ expectedHeadSha: {
20335
+ ...commitSha,
20336
+ description: "headSha from pr.get. The merge is refused if the branch has moved since, so what merges is what was read."
20337
+ }
20338
+ }, ["environmentId"]),
20339
+ examples: [
20340
+ { environmentId: "env-1" },
20341
+ { environmentId: "env-1", method: "rebase", deleteBranch: false },
20342
+ { environmentId: "env-1", cleanup: true },
20343
+ { environmentId: "env-1", expectedHeadSha: "0123456789abcdef0123456789abcdef01234567" }
20344
+ ]
20345
+ },
20346
+ "pr.close": {
20347
+ capability: "sessions.control",
20348
+ confirmation: "always",
20349
+ input: environmentOnly,
20350
+ examples: environmentExamples
20351
+ },
20352
+ "pr.reopen": {
20353
+ capability: "sessions.control",
20354
+ confirmation: "voice",
20355
+ input: environmentOnly,
20356
+ examples: environmentExamples
20357
+ },
20358
+ "pr.ready": {
20359
+ capability: "sessions.control",
20360
+ confirmation: "voice",
20361
+ input: environmentOnly,
20362
+ examples: environmentExamples
20363
+ },
20364
+ "pr.draft": {
20365
+ capability: "sessions.control",
20366
+ confirmation: "voice",
20367
+ input: environmentOnly,
20368
+ examples: environmentExamples
20369
+ },
20370
+ "pr.comment": {
20371
+ capability: "sessions.control",
20372
+ confirmation: "voice",
20373
+ input: schema4({ environmentId, body }, ["environmentId", "body"]),
20374
+ examples: [{ environmentId: "env-1", body: "Merging once CI is green." }]
20375
+ },
20376
+ "pr.comment.edit": {
20377
+ capability: "sessions.control",
20378
+ confirmation: "voice",
20379
+ input: schema4({
20380
+ environmentId,
20381
+ commentId: {
20382
+ type: "integer",
20383
+ minimum: 1,
20384
+ description: "Comment ID, from pr.get or a pr.comment result."
20385
+ },
20386
+ body
20387
+ }, ["environmentId", "commentId", "body"]),
20388
+ examples: [{ environmentId: "env-1", commentId: 123, body: "Merged after the fix." }]
20389
+ },
20390
+ "pr.review": {
20391
+ capability: "sessions.control",
20392
+ confirmation: "always",
20393
+ input: schema4({
20394
+ environmentId,
20395
+ event: {
20396
+ type: "string",
20397
+ enum: PUBLIC_PR_REVIEW_EVENTS,
20398
+ description: "The verdict. REQUEST_CHANGES needs body; COMMENT needs body or comments. GitHub refuses approving your own pull request."
20399
+ },
20400
+ body: { ...body, description: "Review summary." },
20401
+ commitId: {
20402
+ ...commitSha,
20403
+ description: "The commit reviewed (headSha from pr.get). Omitted: the current head."
20404
+ },
20405
+ comments: {
20406
+ type: "array",
20407
+ maxItems: PUBLIC_PR_REVIEW_COMMENTS_MAX,
20408
+ description: "Inline comments submitted with the review.",
20409
+ items: {
20410
+ type: "object",
20411
+ properties: { path: filePath, line, side, startLine, body },
20412
+ required: ["path", "line", "side", "body"],
20413
+ additionalProperties: false
20414
+ }
20415
+ }
20416
+ }, ["environmentId", "event"]),
20417
+ examples: [
20418
+ { environmentId: "env-1", event: "APPROVE" },
20419
+ {
20420
+ environmentId: "env-1",
20421
+ event: "COMMENT",
20422
+ comments: [{ path: "src/index.ts", line: 12, side: "RIGHT", body: "Is this bounded?" }]
20423
+ }
20424
+ ]
20425
+ },
20426
+ "pr.review.comment": {
20427
+ capability: "sessions.control",
20428
+ confirmation: "voice",
20429
+ input: schema4({
20430
+ environmentId,
20431
+ path: filePath,
20432
+ line,
20433
+ side,
20434
+ startLine,
20435
+ body,
20436
+ commitId: {
20437
+ ...commitSha,
20438
+ description: "The commit commented on. Omitted: the pull request's current head."
20439
+ }
20440
+ }, ["environmentId", "path", "line", "side", "body"]),
20441
+ examples: [
20442
+ { environmentId: "env-1", path: "src/index.ts", line: 12, side: "RIGHT", body: "Why?" }
20443
+ ]
20444
+ },
20445
+ "pr.thread.reply": {
20446
+ capability: "sessions.control",
20447
+ confirmation: "voice",
20448
+ input: schema4({ environmentId, threadId, body }, ["environmentId", "threadId", "body"]),
20449
+ examples: [{ environmentId: "env-1", threadId: "PRRT_1", body: "Fixed." }]
20450
+ },
20451
+ "pr.thread.resolve": {
20452
+ capability: "sessions.control",
20453
+ confirmation: "voice",
20454
+ input: schema4({
20455
+ environmentId,
20456
+ threadId,
20457
+ resolved: { type: "boolean", description: "False to unresolve (default true)." }
20458
+ }, ["environmentId", "threadId"]),
20459
+ examples: [
20460
+ { environmentId: "env-1", threadId: "PRRT_1" },
20461
+ { environmentId: "env-1", threadId: "PRRT_1", resolved: false }
20462
+ ]
20463
+ },
20464
+ "pr.file.viewed": {
20465
+ capability: "sessions.control",
20466
+ confirmation: "never",
20467
+ input: schema4({
20468
+ environmentId,
20469
+ path: filePath,
20470
+ viewed: { type: "boolean", description: "False to clear it (default true)." }
20471
+ }, ["environmentId", "path"]),
20472
+ examples: [{ environmentId: "env-1", path: "src/index.ts" }]
20473
+ },
20474
+ "pr.reviewers": {
20475
+ capability: "sessions.control",
20476
+ confirmation: "voice",
20477
+ input: schema4({
20478
+ environmentId,
20479
+ add: {
20480
+ ...reviewerList,
20481
+ description: "Logins or org/team-slug entries to request a review from."
20482
+ },
20483
+ remove: {
20484
+ ...reviewerList,
20485
+ description: "Logins or org/team-slug entries whose request to remove."
20486
+ }
20487
+ }, ["environmentId"]),
20488
+ examples: [{ environmentId: "env-1", add: ["octocat"] }]
20489
+ },
20490
+ "pr.label": {
20491
+ capability: "sessions.control",
20492
+ confirmation: "voice",
20493
+ input: schema4({
20494
+ environmentId,
20495
+ name: {
20496
+ type: "string",
20497
+ minLength: 1,
20498
+ maxLength: 100,
20499
+ description: "Label name, from pr.options."
20500
+ },
20501
+ present: { type: "boolean", description: "False to remove the label (default true)." }
20502
+ }, ["environmentId", "name"]),
20503
+ examples: [{ environmentId: "env-1", name: "bug" }]
20504
+ }
20505
+ });
20506
+
20507
+ // src/client/commands/pull-requests.ts
20508
+ var ENVIRONMENT = { name: "environment", required: true, description: "Its pull request." };
20509
+ var YES_OPTION = {
20510
+ name: "yes",
20511
+ kind: "boolean",
20512
+ description: "Confirm this change on GitHub. Required."
20513
+ };
20514
+ var BODY_OPTIONS = [
20515
+ { name: "body", kind: "string", valueName: "TEXT", description: "Inline Markdown text." },
20516
+ {
20517
+ name: "body-file",
20518
+ kind: "string",
20519
+ valueName: "PATH",
20520
+ description: "Read the text from a local file."
20521
+ }
20522
+ ];
20523
+ var INLINE_OPTIONS = [
20524
+ { name: "path", kind: "string", valueName: "PATH", description: "Changed file." },
20525
+ { name: "line", kind: "string", valueName: "N", description: "Line of the comment." },
20526
+ { name: "side", kind: "string", valueName: "SIDE", description: "RIGHT (new) or LEFT (old)." },
20527
+ { name: "start-line", kind: "string", valueName: "N", description: "First line of a range." }
20528
+ ];
20529
+ function requireYes(parsed, what) {
20530
+ if (parsed.options.yes !== true) {
20531
+ throw new CliError("invalid-input", `${what}; pass --yes to confirm`);
20532
+ }
20533
+ }
20534
+ function requiredOption(parsed, name) {
20535
+ const value = stringOption(parsed.options, name);
20536
+ if (!value)
20537
+ throw new CliError("invalid-input", `--${name} is required`);
20538
+ return value;
20539
+ }
20540
+ function integerOption(parsed, name) {
20541
+ const value = stringOption(parsed.options, name);
20542
+ if (value === undefined)
20543
+ return;
20544
+ const number = Number(value);
20545
+ if (!Number.isSafeInteger(number) || number < 1) {
20546
+ throw new CliError("invalid-input", `--${name} must be a positive integer`);
20547
+ }
20548
+ return number;
20549
+ }
20550
+ function requiredInteger(parsed, name) {
20551
+ const number = integerOption(parsed, name);
20552
+ if (number === undefined)
20553
+ throw new CliError("invalid-input", `--${name} is required`);
20554
+ return number;
20555
+ }
20556
+ async function bodyText(context, parsed, required) {
20557
+ const body = await readTextSource(context.io, {
20558
+ file: parsed.options["body-file"],
20559
+ inline: parsed.options.body,
20560
+ label: "Body",
20561
+ maxBytes: PUBLIC_PR_BODY_MAX_CHARS * 4,
20562
+ names: { file: "--body-file", stdin: "", inline: "--body" },
20563
+ required
20564
+ });
20565
+ if (body !== undefined && body.length > PUBLIC_PR_BODY_MAX_CHARS) {
20566
+ throw new CliError("input-too-large", `Body is longer than ${PUBLIC_PR_BODY_MAX_CHARS} characters`);
20567
+ }
20568
+ return body;
20569
+ }
20570
+ function repeatedList(value) {
20571
+ return typeof value === "string" ? value.split(",").map((entry) => entry.trim()).filter(Boolean) : [];
20572
+ }
20573
+ function changeHuman(result) {
20574
+ return keyValues(Object.entries(result).filter(([key, value]) => key !== "environmentId" && typeof value !== "object"));
20575
+ }
20576
+ function change(name, action, summary, options2, input, extras = {}) {
20577
+ return {
20578
+ path: ["pr", name],
20579
+ action,
20580
+ summary,
20581
+ positionals: [ENVIRONMENT],
20582
+ options: [...options2, REQUEST_ID_OPTION],
20583
+ idOutput: "pull request URL",
20584
+ async run(context, parsed) {
20585
+ const environmentId = parsed.positionals.environment;
20586
+ const { session, result, receipt, warnings } = await submit(context, action, { environmentId, ...await input(context, parsed) }, parsed.options);
20587
+ return {
20588
+ action,
20589
+ result,
20590
+ receipt,
20591
+ warnings,
20592
+ connection: session.identity,
20593
+ ids: typeof result.url === "string" ? [result.url] : [],
20594
+ human: changeHuman(result)
20595
+ };
20596
+ },
20597
+ ...extras
20598
+ };
20599
+ }
20600
+ function read(name, action, summary, options2, input, human) {
20601
+ return {
20602
+ path: ["pr", name],
20603
+ action,
20604
+ summary,
20605
+ positionals: [ENVIRONMENT],
20606
+ options: options2,
20607
+ async run(context, parsed) {
20608
+ const session = await context.session();
20609
+ const result = await session.read(action, {
20610
+ environmentId: parsed.positionals.environment,
20611
+ ...input(parsed)
20612
+ });
20613
+ return { action, result, connection: session.identity, human: human(result) };
20614
+ }
20615
+ };
20616
+ }
20617
+ function pullRequestLines(result) {
20618
+ const pullRequest = result.pullRequest;
20619
+ if (!pullRequest) {
20620
+ return keyValues([
20621
+ ["attachment", result.attachment],
20622
+ ["url", result.url],
20623
+ ["error", result.error]
20624
+ ]);
20625
+ }
20626
+ return keyValues([
20627
+ ["attachment", result.attachment],
20628
+ ["number", pullRequest.number],
20629
+ ["title", pullRequest.title],
20630
+ ["url", pullRequest.htmlUrl],
20631
+ ["state", pullRequest.isDraft ? `${pullRequest.state} (draft)` : pullRequest.state],
20632
+ ["branch", `${pullRequest.headRef} into ${pullRequest.baseRef}`],
20633
+ ["head", pullRequest.headSha],
20634
+ ["mergeability", pullRequest.mergeability],
20635
+ ["checks", pullRequest.checksState]
20636
+ ]);
20637
+ }
20638
+ function timelineLines(result) {
20639
+ const items = result.items;
20640
+ const lines = items.flatMap((item) => {
20641
+ if (item.kind === "comment") {
20642
+ return [
20643
+ `comment \xB7 ${item.comment.author?.login ?? "unknown"} \xB7 ${item.comment.createdAt}`,
20644
+ item.comment.body
20645
+ ];
20646
+ }
20647
+ if (item.kind === "event") {
20648
+ return [
20649
+ `event \xB7 ${item.actor?.login ?? "unknown"} \xB7 ${item.createdAt}`,
20650
+ `${item.event}${item.detail ? `: ${item.detail}` : ""}`
20651
+ ];
20652
+ }
20653
+ return [
20654
+ `${item.kind} \xB7 ${item.author?.login ?? (item.kind === "commit" ? item.authorName : undefined) ?? "unknown"} \xB7 ${item.createdAt}`,
20655
+ item.kind === "commit" ? `${item.sha} ${item.message}` : `${item.state}${item.body ? `: ${item.body}` : ""}`
20656
+ ];
20657
+ });
20658
+ if (!items.length)
20659
+ lines.push("No timeline entries.");
20660
+ if (result.truncated)
20661
+ lines.push("Timeline truncated; more entries are available on GitHub.");
20662
+ return lines;
20663
+ }
20664
+ function fileLines(result) {
20665
+ const lines = keyValues([
20666
+ ["number", result.number],
20667
+ ["head", result.headSha]
20668
+ ]);
20669
+ if (Array.isArray(result.files)) {
20670
+ lines.push(...table(["FILE", "STATUS", "+", "-"], result.files.map((file) => [
20671
+ file.path,
20672
+ file.status,
20673
+ String(file.additions),
20674
+ String(file.deletions)
20675
+ ])));
20676
+ } else {
20677
+ const file = result.file;
20678
+ lines.push(`${file.path} (${file.status}, +${file.additions} -${file.deletions})`);
20679
+ if (!file.patchAvailable)
20680
+ lines.push("Patch unavailable; view this file on GitHub.");
20681
+ else
20682
+ for (const hunk of file.hunks) {
20683
+ lines.push(hunk.header, ...hunk.lines.map((line) => `${line.type === "add" ? "+" : line.type === "remove" ? "-" : line.type === "no-newline" ? "\\" : " "}${line.text}`));
20684
+ }
20685
+ }
20686
+ for (const thread of result.threads ?? []) {
20687
+ lines.push(`Thread ${thread.id} \xB7 ${thread.path}:${thread.line ?? "outdated"} \xB7 ${thread.isResolved ? "resolved" : "unresolved"}${thread.isOutdated ? " \xB7 outdated" : ""}`);
20688
+ for (const comment of thread.comments)
20689
+ lines.push(`${comment.author?.login ?? "unknown"} \xB7 ${comment.createdAt}: ${comment.body}`);
20690
+ if (thread.commentsTruncated)
20691
+ lines.push("Thread comments truncated; see GitHub for more.");
20692
+ }
20693
+ if (result.truncated)
20694
+ lines.push("File list truncated; more files are available on GitHub.");
20695
+ return lines;
20696
+ }
20697
+ async function reviewComments(context, parsed) {
20698
+ const file = parsed.options["comments-file"];
20699
+ if (file === undefined)
20700
+ return;
20701
+ const text = await readTextSource(context.io, {
20702
+ file,
20703
+ label: "Review comments",
20704
+ maxBytes: PUBLIC_PR_BODY_MAX_CHARS * 4,
20705
+ names: { file: "--comments-file", stdin: "" },
20706
+ required: true
20707
+ });
20708
+ let comments;
20709
+ try {
20710
+ comments = JSON.parse(text);
20711
+ } catch {
20712
+ throw new CliError("invalid-input", "--comments-file must contain a JSON array of inline comments");
20713
+ }
20714
+ if (!Array.isArray(comments) || comments.length > PUBLIC_PR_REVIEW_COMMENTS_MAX)
20715
+ throw new CliError("invalid-input", `--comments-file must contain at most ${PUBLIC_PR_REVIEW_COMMENTS_MAX} inline comments in a JSON array`);
20716
+ return comments;
20717
+ }
20718
+ var pullRequestCommands = [
20719
+ read("get", "pr.get", "Read the pull request attached to an environment's branch.", [], () => ({}), pullRequestLines),
20720
+ {
20721
+ path: ["pr", "list"],
20722
+ action: "pr.list",
20723
+ summary: "List a project's pull requests from GitHub.",
20724
+ positionals: [{ name: "project", required: true }],
20725
+ options: [
20726
+ {
20727
+ name: "filter",
20728
+ kind: "string",
20729
+ valueName: "FILTER",
20730
+ description: "open (default), mine, review-requested or closed."
20731
+ },
20732
+ { name: "query", kind: "string", valueName: "TEXT", description: "Search text." }
20733
+ ],
20734
+ idOutput: "pull request URLs",
20735
+ async run(context, parsed) {
20736
+ const session = await context.session();
20737
+ const filter = stringOption(parsed.options, "filter");
20738
+ const query = stringOption(parsed.options, "query");
20739
+ const result = await session.read("pr.list", {
20740
+ projectId: parsed.positionals.project,
20741
+ ...filter !== undefined ? { filter: oneOf(filter, PUBLIC_PR_LIST_FILTERS, "--filter") } : {},
20742
+ ...query !== undefined ? { query } : {}
20743
+ });
20744
+ return {
20745
+ action: "pr.list",
20746
+ result,
20747
+ connection: session.identity,
20748
+ ids: result.pullRequests.map((pullRequest) => pullRequest.htmlUrl),
20749
+ human: table(["NUMBER", "STATE", "TITLE"], result.pullRequests.map((pullRequest) => [
20750
+ String(pullRequest.number),
20751
+ pullRequest.state,
20752
+ pullRequest.title
20753
+ ]))
20754
+ };
20755
+ }
20756
+ },
20757
+ read("timeline", "pr.timeline", "Read a pull request's commits, comments, reviews and events.", [], () => ({}), timelineLines),
20758
+ read("files", "pr.files", "Read a pull request's changed files and review threads; with --path, one file's diff.", [
20759
+ {
20760
+ name: "path",
20761
+ kind: "string",
20762
+ valueName: "PATH",
20763
+ description: "Only this file, with hunks."
20764
+ }
20765
+ ], (parsed) => {
20766
+ const path = stringOption(parsed.options, "path");
20767
+ return path !== undefined ? { path } : {};
20768
+ }, fileLines),
20769
+ read("options", "pr.options", "Read the repository's labels and assignable users.", [], () => ({}), (result) => keyValues([
20770
+ [
20771
+ "labels",
20772
+ result.labels.map((label) => label.name).join(", ")
20773
+ ],
20774
+ [
20775
+ "assignable users",
20776
+ result.assignableUsers.map((user) => user.login).join(", ")
20777
+ ]
20778
+ ])),
20779
+ change("merge", "pr.merge", "Merge the environment's pull request into its target branch (squash by default).", [
20780
+ {
20781
+ name: "method",
20782
+ kind: "string",
20783
+ valueName: "METHOD",
20784
+ description: "squash (default), merge or rebase."
20785
+ },
20786
+ { name: "keep-branch", kind: "boolean", description: "Keep the head branch." },
20787
+ {
20788
+ name: "cleanup",
20789
+ kind: "boolean",
20790
+ description: "Merge and Cleanup: also delete the environment once merged."
20791
+ },
20792
+ {
20793
+ name: "expected-head-sha",
20794
+ kind: "string",
20795
+ valueName: "SHA",
20796
+ description: "Refuse if the branch has moved from this commit."
20797
+ },
20798
+ YES_OPTION
20799
+ ], (_context, parsed) => {
20800
+ requireYes(parsed, "Merging cannot be undone");
20801
+ const method = stringOption(parsed.options, "method");
20802
+ const expectedHeadSha = stringOption(parsed.options, "expected-head-sha");
20803
+ return {
20804
+ ...method !== undefined ? { method: oneOf(method, PUBLIC_PR_MERGE_METHODS, "--method") } : {},
20805
+ ...parsed.options["keep-branch"] === true ? { deleteBranch: false } : {},
20806
+ ...parsed.options.cleanup === true ? { cleanup: true } : {},
20807
+ ...expectedHeadSha !== undefined ? { expectedHeadSha } : {}
20808
+ };
20809
+ }, { inputFields: { "keep-branch": "deleteBranch", yes: null } }),
20810
+ change("close", "pr.close", "Close the environment's pull request without merging.", [YES_OPTION], (_context, parsed) => {
20811
+ requireYes(parsed, "This closes the pull request");
20812
+ return {};
20813
+ }, { inputFields: { yes: null } }),
20814
+ change("reopen", "pr.reopen", "Reopen the environment's closed pull request.", [], () => ({})),
20815
+ change("ready", "pr.ready", "Mark the environment's draft pull request ready.", [], () => ({})),
20816
+ change("draft", "pr.draft", "Convert the environment's pull request to a draft.", [], () => ({})),
20817
+ change("comment", "pr.comment", "Add a conversation comment to the environment's pull request.", BODY_OPTIONS, async (context, parsed) => ({ body: await bodyText(context, parsed, true) }), { inputFields: { "body-file": "body" } }),
20818
+ change("comment-edit", "pr.comment.edit", "Edit a conversation comment on the environment's pull request.", [
20819
+ { name: "comment-id", kind: "string", valueName: "ID", description: "From pr get." },
20820
+ ...BODY_OPTIONS
20821
+ ], async (context, parsed) => ({
20822
+ commentId: requiredInteger(parsed, "comment-id"),
20823
+ body: await bodyText(context, parsed, true)
20824
+ }), { inputFields: { "body-file": "body" } }),
20825
+ change("review", "pr.review", "Submit a review: approve, request changes or comment.", [
20826
+ {
20827
+ name: "event",
20828
+ kind: "string",
20829
+ valueName: "EVENT",
20830
+ description: "APPROVE, REQUEST_CHANGES or COMMENT."
20831
+ },
20832
+ ...BODY_OPTIONS,
20833
+ { name: "commit-id", kind: "string", valueName: "SHA", description: "The commit reviewed." },
20834
+ {
20835
+ name: "comments-file",
20836
+ kind: "string",
20837
+ valueName: "PATH",
20838
+ description: "JSON array of inline comments (path, line, side, body and optional startLine)."
20839
+ },
20840
+ YES_OPTION
20841
+ ], async (context, parsed) => {
20842
+ requireYes(parsed, "This submits a review as your GitHub user");
20843
+ const body = await bodyText(context, parsed, false);
20844
+ const commitId = stringOption(parsed.options, "commit-id");
20845
+ const comments = await reviewComments(context, parsed);
20846
+ return {
20847
+ ...comments !== undefined ? { comments } : {},
20848
+ event: oneOf(parsed.options.event, PUBLIC_PR_REVIEW_EVENTS, "--event"),
20849
+ ...body !== undefined ? { body } : {},
20850
+ ...commitId !== undefined ? { commitId } : {}
20851
+ };
20852
+ }, { inputFields: { "body-file": "body", "comments-file": "comments", yes: null } }),
20853
+ change("review-comment", "pr.review.comment", "Add one inline review comment to the environment's pull request.", [
20854
+ ...INLINE_OPTIONS,
20855
+ ...BODY_OPTIONS,
20856
+ {
20857
+ name: "commit-id",
20858
+ kind: "string",
20859
+ valueName: "SHA",
20860
+ description: "Omitted: the current head."
20861
+ }
20862
+ ], async (context, parsed) => {
20863
+ const startLine = integerOption(parsed, "start-line");
20864
+ const commitId = stringOption(parsed.options, "commit-id");
20865
+ return {
20866
+ path: requiredOption(parsed, "path"),
20867
+ line: requiredInteger(parsed, "line"),
20868
+ side: oneOf(parsed.options.side, PUBLIC_PR_SIDES, "--side"),
20869
+ ...startLine !== undefined ? { startLine } : {},
20870
+ body: await bodyText(context, parsed, true),
20871
+ ...commitId !== undefined ? { commitId } : {}
20872
+ };
20873
+ }, { inputFields: { "body-file": "body" } }),
20874
+ change("thread-reply", "pr.thread.reply", "Reply to a review thread on the environment's pull request.", [
20875
+ { name: "thread-id", kind: "string", valueName: "ID", description: "From pr files." },
20876
+ ...BODY_OPTIONS
20877
+ ], async (context, parsed) => ({
20878
+ threadId: requiredOption(parsed, "thread-id"),
20879
+ body: await bodyText(context, parsed, true)
20880
+ }), { inputFields: { "body-file": "body" } }),
20881
+ change("thread-resolve", "pr.thread.resolve", "Resolve a review thread on the environment's pull request.", [
20882
+ { name: "thread-id", kind: "string", valueName: "ID", description: "From pr files." },
20883
+ { name: "unresolve", kind: "boolean", description: "Unresolve it instead." }
20884
+ ], (_context, parsed) => ({
20885
+ threadId: requiredOption(parsed, "thread-id"),
20886
+ ...parsed.options.unresolve === true ? { resolved: false } : {}
20887
+ }), { inputFields: { unresolve: "resolved" } }),
20888
+ change("file-viewed", "pr.file.viewed", "Mark a changed file as viewed on the environment's pull request.", [
20889
+ { name: "path", kind: "string", valueName: "PATH", description: "Changed file." },
20890
+ { name: "unviewed", kind: "boolean", description: "Clear it instead." }
20891
+ ], (_context, parsed) => ({
20892
+ path: requiredOption(parsed, "path"),
20893
+ ...parsed.options.unviewed === true ? { viewed: false } : {}
20894
+ }), { inputFields: { unviewed: "viewed" } }),
20895
+ change("reviewers", "pr.reviewers", "Request or remove reviewers on the environment's pull request.", [
20896
+ {
20897
+ name: "add",
20898
+ kind: "string",
20899
+ valueName: "LIST",
20900
+ description: "Comma-separated logins or org/team-slugs."
20901
+ },
20902
+ {
20903
+ name: "remove",
20904
+ kind: "string",
20905
+ valueName: "LIST",
20906
+ description: "Comma-separated logins or org/team-slugs."
20907
+ }
20908
+ ], (_context, parsed) => ({
20909
+ add: repeatedList(parsed.options.add),
20910
+ remove: repeatedList(parsed.options.remove)
20911
+ })),
20912
+ change("label", "pr.label", "Add or remove one label on the environment's pull request.", [
20913
+ { name: "name", kind: "string", valueName: "LABEL", description: "Label name." },
20914
+ { name: "remove", kind: "boolean", description: "Remove it instead." }
20915
+ ], (_context, parsed) => ({
20916
+ name: requiredOption(parsed, "name"),
20917
+ ...parsed.options.remove === true ? { present: false } : {}
20918
+ }), { inputFields: { remove: "present" } })
20919
+ ];
20920
+
19270
20921
  // src/client/help.ts
19271
20922
  var GROUP_SUMMARIES = {
19272
20923
  connection: "Select and verify the backend this client talks to.",
@@ -19628,8 +21279,10 @@ var CLIENT_COMMANDS = [
19628
21279
  ...projectCommands,
19629
21280
  ...environmentCommands,
19630
21281
  ...sessionCommands,
21282
+ ...sessionGoalCommands,
19631
21283
  ...changesCommands,
19632
21284
  ...workflowCommands,
21285
+ ...pullRequestCommands,
19633
21286
  ...buildCommands,
19634
21287
  ...graphCommands,
19635
21288
  ...attentionCommands,