orkestrator 2.34.4-beta → 2.35.0-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,26 @@ 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",
7888
+ "set_account_auto_switch",
7628
7889
  "transfer_files",
7890
+ "update_schedule",
7629
7891
  "update_ticket"
7630
7892
  ]);
7631
7893
 
@@ -8066,12 +8328,13 @@ async function writePrivateFile(file, contents) {
8066
8328
  }
8067
8329
 
8068
8330
  // ../../apps/backend/src/data-dir.ts
8331
+ import { realpathSync as realpathSync2 } from "fs";
8069
8332
  import os3 from "os";
8070
8333
  import path5 from "path";
8071
8334
  // ../../apps/backend/package.json
8072
8335
  var package_default = {
8073
8336
  name: "@orkestrator/backend",
8074
- version: "2.34.4-beta",
8337
+ version: "2.35.0-beta",
8075
8338
  private: true,
8076
8339
  type: "module",
8077
8340
  exports: {
@@ -8091,7 +8354,7 @@ var package_default = {
8091
8354
  dependencies: {
8092
8355
  "@modelcontextprotocol/node": "2.1.1",
8093
8356
  "@modelcontextprotocol/server": "2.3.1",
8094
- "@opencode-ai/sdk": "1.19.4",
8357
+ "@opencode-ai/sdk": "1.19.5",
8095
8358
  "@orkestrator/protocol": "workspace:*",
8096
8359
  "@orkestrator/toolchain": "workspace:*",
8097
8360
  "@xterm/addon-serialize": "0.14.0",
@@ -8191,6 +8454,16 @@ function defaultDataDir(platform = process.platform, env = process.env, home = o
8191
8454
  return path5.join(home, "Library", "Application Support", APP_SLUG);
8192
8455
  return path5.join(env.XDG_CONFIG_HOME ?? path5.join(home, ".config"), APP_SLUG);
8193
8456
  }
8457
+ function isDefaultDataDir(dataDir, platform = process.platform, env = process.env, home = os3.homedir()) {
8458
+ const canonical = (value) => {
8459
+ try {
8460
+ return realpathSync2(value);
8461
+ } catch {
8462
+ return path5.resolve(value);
8463
+ }
8464
+ };
8465
+ return canonical(dataDir) === canonical(defaultDataDir(platform, env, home));
8466
+ }
8194
8467
 
8195
8468
  // src/client/fix.ts
8196
8469
  var FIX_ISSUE_REPOSITORY = BUG_REPORT_ISSUE_REPOSITORY;
@@ -8292,10 +8565,10 @@ function resolveFixAgent(name, options2) {
8292
8565
  if (onPath) {
8293
8566
  let isManaged = false;
8294
8567
  try {
8295
- const root = realpathSync2(toolchainRootDir(options2.dataDir));
8296
- const target = realpathSync2(onPath);
8568
+ const root = realpathSync3(toolchainRootDir(options2.dataDir));
8569
+ const target = realpathSync3(onPath);
8297
8570
  const relative = path6.relative(root, target);
8298
- isManaged = !relative.startsWith(`..${path6.sep}`) && relative !== ".." && !path6.isAbsolute(relative) || existsSync3(managed) && target === realpathSync2(managed);
8571
+ isManaged = !relative.startsWith(`..${path6.sep}`) && relative !== ".." && !path6.isAbsolute(relative) || existsSync3(managed) && target === realpathSync3(managed);
8299
8572
  } catch {}
8300
8573
  return { agent, executable: onPath, source: isManaged ? "managed" : "path" };
8301
8574
  }
@@ -8305,13 +8578,13 @@ function resolveFixAgent(name, options2) {
8305
8578
  }
8306
8579
  async function diagnosticConnection(dataDir, env) {
8307
8580
  try {
8308
- const descriptor = realpathSync2(path6.join(dataDir, "backend-instance.json"));
8581
+ const descriptor = realpathSync3(path6.join(dataDir, "backend-instance.json"));
8309
8582
  const config = await new CliConfigStore(defaultCliConfigDir(env)).read();
8310
8583
  for (const [name, connection] of Object.entries(config.connections)) {
8311
8584
  if (connection.kind !== "descriptor")
8312
8585
  continue;
8313
8586
  try {
8314
- if (realpathSync2(connection.descriptorPath) === descriptor)
8587
+ if (realpathSync3(connection.descriptorPath) === descriptor)
8315
8588
  return name;
8316
8589
  } catch {}
8317
8590
  }
@@ -8538,6 +8811,63 @@ async function observe(check, options2) {
8538
8811
  }
8539
8812
  }
8540
8813
 
8814
+ // ../protocol/src/native-agent-goal.ts
8815
+ init_agent_platforms();
8816
+ function goalIsRunning(status) {
8817
+ return status === "active" || status === "checking" || status === "verifying";
8818
+ }
8819
+ var NATIVE_AGENT_GOAL_LIMITS = Object.freeze({
8820
+ objectiveMaxChars: 4000,
8821
+ checkMaxBytes: 4 * 1024,
8822
+ reportMessageMaxBytes: 2 * 1024,
8823
+ checkOutputMaxBytes: 8 * 1024,
8824
+ statusDetailMaxBytes: 1024,
8825
+ maxIterationsDefault: 20,
8826
+ maxIterationsLimit: 100,
8827
+ noProgressLimit: 3,
8828
+ checkTimeoutDefaultMs: 10 * 60000,
8829
+ checkTimeoutMaxMs: 30 * 60000,
8830
+ verifierModelMaxChars: 200,
8831
+ verifierTimeoutDefaultMs: 30 * 60000,
8832
+ verdictSummaryMaxBytes: 2 * 1024,
8833
+ verdictReasonsMax: 16,
8834
+ verdictReasonMaxBytes: 512,
8835
+ historyLimit: 5
8836
+ });
8837
+ var AGENT_GOAL_TOOL_NAMES = Object.freeze(["report_goal"]);
8838
+ var GOAL_QUALIFIED_PLATFORMS = Object.freeze([
8839
+ "claude",
8840
+ "codex",
8841
+ "opencode",
8842
+ "cursor",
8843
+ "grok",
8844
+ "pi"
8845
+ ]);
8846
+ var GOAL_TOKEN_BUDGET_PLATFORMS = Object.freeze([
8847
+ "claude",
8848
+ "codex",
8849
+ "opencode",
8850
+ "cursor",
8851
+ "grok",
8852
+ "pi"
8853
+ ]);
8854
+ function parseVerifierFlag(text) {
8855
+ const colon = text.indexOf(":");
8856
+ const agent = colon < 0 ? text : text.slice(0, colon);
8857
+ const model = colon < 0 ? undefined : text.slice(colon + 1);
8858
+ if (!isAgentPlatform(agent)) {
8859
+ return { error: "--verifier takes an agent, optionally with a model: --verifier codex:o3." };
8860
+ }
8861
+ if (model === undefined)
8862
+ return { agent };
8863
+ if (!model || model.length > NATIVE_AGENT_GOAL_LIMITS.verifierModelMaxChars) {
8864
+ return {
8865
+ error: `The verifier's model name must be 1 to ${NATIVE_AGENT_GOAL_LIMITS.verifierModelMaxChars} characters.`
8866
+ };
8867
+ }
8868
+ return { agent, model };
8869
+ }
8870
+
8541
8871
  // ../protocol/src/public-api-wait.ts
8542
8872
  function isTerminalPublicReceipt(receipt) {
8543
8873
  return !["admitted", "running", "unknown"].includes(receipt.state);
@@ -8592,6 +8922,34 @@ function terminalReceiptErrorCode(receipt) {
8592
8922
  return "run-unknown";
8593
8923
  return receipt.error?.code ?? "operation-failed";
8594
8924
  }
8925
+ var GOAL_END_CODES = {
8926
+ claimed: "goal-claimed",
8927
+ blocked: "goal-blocked",
8928
+ limited: "goal-limited",
8929
+ paused: "goal-paused"
8930
+ };
8931
+ function classifyGoalForWait(read, goalId, options2 = {}) {
8932
+ const current = read.goal?.goalId === goalId ? read.goal : null;
8933
+ if (!current) {
8934
+ const past = read.history.findLast((goal) => goal.goalId === goalId);
8935
+ if (past?.status === "complete")
8936
+ return { kind: "complete", goal: past };
8937
+ if (past?.status === "claimed")
8938
+ return { kind: "stopped", code: "goal-claimed", goal: past };
8939
+ return past ? { kind: "gone", goal: past } : { kind: "gone" };
8940
+ }
8941
+ if (goalIsRunning(current.status)) {
8942
+ const needsAnswer = read.activity === "waiting" || (read.pendingInteractionCount ?? 0) > 0;
8943
+ return needsAnswer && !options2.continueOnInteraction ? { kind: "interaction", goal: current } : { kind: "pending", goal: current };
8944
+ }
8945
+ if (current.status === "complete")
8946
+ return { kind: "complete", goal: current };
8947
+ return {
8948
+ kind: "stopped",
8949
+ code: GOAL_END_CODES[current.status],
8950
+ goal: current
8951
+ };
8952
+ }
8595
8953
 
8596
8954
  // src/client/commands/common.ts
8597
8955
  var REQUEST_ID_OPTION = {
@@ -8766,6 +9124,15 @@ async function waitForOperation(context, session, initial, timeoutMs, options2 =
8766
9124
  }
8767
9125
  async function waitForEnvironment(context, session, environmentId, condition, receipt, timeoutMs) {
8768
9126
  let latestReceipt = receipt;
9127
+ const deadline = Date.now() + timeoutMs;
9128
+ const remaining = () => {
9129
+ if (context.signal?.aborted)
9130
+ throw new ObservationStopped("signal", signalName(context.signal));
9131
+ const budget = deadline - Date.now();
9132
+ if (budget <= 0)
9133
+ throw new ObservationStopped("deadline");
9134
+ return budget;
9135
+ };
8769
9136
  const conditionMet = (environment) => {
8770
9137
  if (condition === "deleted")
8771
9138
  return environment === null;
@@ -8779,8 +9146,10 @@ async function waitForEnvironment(context, session, environmentId, condition, re
8779
9146
  };
8780
9147
  try {
8781
9148
  return await observe(async () => {
9149
+ remaining();
8782
9150
  if (latestReceipt && !isTerminalReceipt(latestReceipt)) {
8783
- latestReceipt = await readOperation(session, latestReceipt.operationId);
9151
+ latestReceipt = await readOperation(session, latestReceipt.operationId, remaining());
9152
+ remaining();
8784
9153
  }
8785
9154
  if (latestReceipt && isTerminalReceipt(latestReceipt)) {
8786
9155
  const failure = receiptFailure(latestReceipt);
@@ -8789,15 +9158,15 @@ async function waitForEnvironment(context, session, environmentId, condition, re
8789
9158
  }
8790
9159
  let environment;
8791
9160
  try {
8792
- environment = await session.read("environment.get", {
8793
- environmentId
8794
- });
9161
+ const snapshot = await session.readWithReceipt("environment.get", { environmentId }, remaining());
9162
+ environment = snapshot.result;
8795
9163
  } catch (error) {
8796
9164
  if (error instanceof CliError && error.code === "not-found")
8797
9165
  environment = null;
8798
9166
  else
8799
9167
  throw error;
8800
9168
  }
9169
+ remaining();
8801
9170
  if (condition !== "deleted" && !environment) {
8802
9171
  throw new CliError("not-found", "The environment no longer exists", latestReceipt ? { receipt: latestReceipt } : {});
8803
9172
  }
@@ -8816,8 +9185,9 @@ async function waitForEnvironment(context, session, environmentId, condition, re
8816
9185
  return { done: false };
8817
9186
  }, { timeoutMs, signal: context.signal });
8818
9187
  } catch (error) {
8819
- if (error instanceof ObservationStopped) {
8820
- throw stoppedError(error, latestReceipt?.action ?? "environment.get", latestReceipt, `the environment (${condition})`);
9188
+ const stopped = context.signal?.aborted ? new ObservationStopped("signal", signalName(context.signal)) : Date.now() >= deadline && error instanceof CliError && error.code === "connection-failed" ? new ObservationStopped("deadline") : error;
9189
+ if (stopped instanceof ObservationStopped) {
9190
+ throw stoppedError(stopped, latestReceipt?.action ?? "environment.get", latestReceipt, `the environment (${condition})`);
8821
9191
  }
8822
9192
  throw error;
8823
9193
  }
@@ -9862,6 +10232,7 @@ var PROJECTION_FIELD_SET = new Set([
9862
10232
  "queue",
9863
10233
  "asyncQuestionResponses",
9864
10234
  "contextUsage",
10235
+ "goal",
9865
10236
  "rateLimits",
9866
10237
  "runtime",
9867
10238
  "runtimeHealthAuthoritative",
@@ -9886,6 +10257,7 @@ var OPTIONAL_PROJECTION_FIELD_SET = new Set([
9886
10257
  "queue",
9887
10258
  "asyncQuestionResponses",
9888
10259
  "contextUsage",
10260
+ "goal",
9889
10261
  "rateLimits",
9890
10262
  "runtime",
9891
10263
  "runtimeHealthAuthoritative",
@@ -9910,6 +10282,35 @@ var REVIEWER_STATUSES = new Set([
9910
10282
  "cancelled"
9911
10283
  ]);
9912
10284
 
10285
+ // ../protocol/src/build-pipeline-key-information.ts
10286
+ var KEY_INFORMATION_LIMITS = Object.freeze({
10287
+ proseLength: 4000,
10288
+ itemLength: 600,
10289
+ listItems: 30,
10290
+ decisions: 12,
10291
+ decisionLength: 400,
10292
+ options: 6,
10293
+ optionLength: 300,
10294
+ prosCons: 6,
10295
+ rationaleLength: 1000,
10296
+ tests: 40,
10297
+ testNameLength: 400,
10298
+ issues: 60,
10299
+ issueLength: 400,
10300
+ severityLength: 40,
10301
+ resolutionLength: 1000,
10302
+ verifications: 20,
10303
+ summaryLength: 2000,
10304
+ checks: 30,
10305
+ checkLength: 400,
10306
+ updates: 30,
10307
+ maxRound: 1000
10308
+ });
10309
+ var KEY_INFORMATION_TOOL_NAMES = Object.freeze([
10310
+ "get_key_information",
10311
+ "update_key_information"
10312
+ ]);
10313
+
9913
10314
  // ../protocol/src/ticket-sections.ts
9914
10315
  var ACCEPTANCE_CRITERION_STATUSES = ["passed", "failed", "not-checked"];
9915
10316
  var TICKET_SECTION_DEFINITIONS = [
@@ -10079,6 +10480,11 @@ var VERIFICATION_VERDICT_SCHEMA = Object.freeze({
10079
10480
  properties: VERIFICATION_VERDICT_PROPERTIES
10080
10481
  });
10081
10482
  var MAX_BUILD_PIPELINE_ITERATIONS = 10;
10483
+ var SETUP_BUILD_PHASES = new Set([
10484
+ "creating-environment",
10485
+ "starting-environment",
10486
+ "waiting-for-setup"
10487
+ ]);
10082
10488
  var BUILD_PHASES = new Set([
10083
10489
  "creating-environment",
10084
10490
  "starting-environment",
@@ -10114,6 +10520,15 @@ var FAILURE_KINDS = new Set([
10114
10520
  var INTERACTION_PAYLOAD_ENCODER = new TextEncoder;
10115
10521
  var INTERACTION_PROVIDERS = new Set(AGENT_INTERACTION_PROVIDERS);
10116
10522
  var INTERACTION_KINDS = new Set(AGENT_INTERACTION_KINDS);
10523
+ var VALIDATION_RESULT_STATUSES = new Set([
10524
+ "pending",
10525
+ "queued",
10526
+ "running",
10527
+ "passed",
10528
+ "failed",
10529
+ "skipped",
10530
+ "incomplete"
10531
+ ]);
10117
10532
 
10118
10533
  // ../protocol/src/public-api-inputs-build.ts
10119
10534
  init_agent_platforms();
@@ -10401,7 +10816,11 @@ function sessionLines(session) {
10401
10816
  ["activity", session.activity],
10402
10817
  ["latest request", session.latestRequestId],
10403
10818
  ["pending interactions", session.pendingInteractionCount],
10404
- ["recoverable dispatch", session.recoverableDispatch]
10819
+ ["recoverable dispatch", session.recoverableDispatch],
10820
+ [
10821
+ "goal",
10822
+ session.goal ? `${session.goal.status} (${session.goal.iterations}/${session.goal.maxIterations})` : undefined
10823
+ ]
10405
10824
  ]);
10406
10825
  }
10407
10826
  function receiptLines(receipt) {
@@ -15001,6 +15420,15 @@ function parseEndpoints(value) {
15001
15420
  return { role: entry.role, url: entry.url };
15002
15421
  });
15003
15422
  }
15423
+ function parseControlMcp(value) {
15424
+ if (!isRecord3(value) || typeof value.running !== "boolean" || value.url !== undefined && !isHttpUrl(value.url)) {
15425
+ throw new Error("Backend instance descriptor field is invalid: controlMcp");
15426
+ }
15427
+ return {
15428
+ running: value.running,
15429
+ ...value.url !== undefined ? { url: value.url } : {}
15430
+ };
15431
+ }
15004
15432
  function parseBackendInstanceDescriptor(value) {
15005
15433
  if (!isRecord3(value))
15006
15434
  throw new Error("Backend instance descriptor must be an object");
@@ -15023,6 +15451,7 @@ function parseBackendInstanceDescriptor(value) {
15023
15451
  throw new Error("Backend instance descriptor field is invalid: browserUrl");
15024
15452
  }
15025
15453
  const endpoints = value.endpoints === undefined ? undefined : parseEndpoints(value.endpoints);
15454
+ const controlMcp = value.controlMcp === undefined ? undefined : parseControlMcp(value.controlMcp);
15026
15455
  if (!Number.isSafeInteger(value.publicApiSchemaVersion) || value.publicApiSchemaVersion < 1) {
15027
15456
  throw new Error("Backend instance descriptor field is invalid: publicApiSchemaVersion");
15028
15457
  }
@@ -15041,6 +15470,7 @@ function parseBackendInstanceDescriptor(value) {
15041
15470
  url: value.url,
15042
15471
  ...value.browserUrl !== undefined ? { browserUrl: value.browserUrl } : {},
15043
15472
  ...endpoints !== undefined ? { endpoints } : {},
15473
+ ...controlMcp !== undefined ? { controlMcp } : {},
15044
15474
  authFile,
15045
15475
  dataDir,
15046
15476
  appVersion: text("appVersion", 64),
@@ -16277,17 +16707,24 @@ function waitOption(conditions) {
16277
16707
  description: `Observe until the environment is ${conditions.join(" or ")} (bounded by --timeout).`
16278
16708
  };
16279
16709
  }
16710
+ function remainingBudget(budget, recoveryDeadline) {
16711
+ return recoveryDeadline === undefined ? budget : Math.max(0, recoveryDeadline - Date.now());
16712
+ }
16280
16713
  async function lifecycle(context, parsed, action, conditions, extraInput = {}) {
16281
16714
  const environmentId = String(parsed.positionals.environment);
16282
16715
  const wait = parsed.options.wait === undefined ? undefined : oneOf(parsed.options.wait, conditions, "--wait");
16283
16716
  if (parsed.options.timeout !== undefined && wait === undefined) {
16284
16717
  throw new CliError("invalid-input", "--timeout needs --wait");
16285
16718
  }
16286
- const { session, result, receipt, warnings } = await submit(context, action, { environmentId, ...extraInput }, parsed.options);
16719
+ const input = { environmentId, ...extraInput };
16720
+ const budget = parsed.options.timeout ?? DEFAULT_WAIT_MS;
16721
+ const submitted = wait ? await submitForWait(context, action, input, parsed.options, budget) : await submit(context, action, input, parsed.options);
16722
+ const { session, receipt, warnings } = submitted;
16723
+ const result = submitted.result ?? receipt?.result;
16287
16724
  let finalReceipt = receipt;
16288
16725
  let environment;
16289
16726
  if (wait) {
16290
- const waited = await waitForEnvironment(context, session, environmentId, wait, receipt, parsed.options.timeout ?? DEFAULT_WAIT_MS);
16727
+ const waited = await waitForEnvironment(context, session, environmentId, wait, receipt, remainingBudget(budget, "deadline" in submitted ? submitted.deadline : undefined));
16291
16728
  finalReceipt = waited.receipt ?? finalReceipt;
16292
16729
  environment = waited.environment;
16293
16730
  }
@@ -16713,14 +17150,18 @@ var environmentCommands = [
16713
17150
  if (options2["exit-code"] === true && options2.wait !== true) {
16714
17151
  throw new CliError("invalid-input", "--exit-code needs --wait");
16715
17152
  }
16716
- const { session, receipt, result, warnings } = await submit(context, "environment.exec", {
17153
+ const input = {
16717
17154
  environmentId: parsed.positionals.environment,
16718
17155
  argv,
16719
17156
  ...options2.cwd !== undefined ? { cwd: options2.cwd } : {},
16720
17157
  ...Object.keys(env).length > 0 ? { env } : {},
16721
17158
  ...stdinBase64 !== undefined ? { stdinBase64 } : {},
16722
17159
  ...options2["exec-timeout"] !== undefined ? { timeoutMs: options2["exec-timeout"] } : {}
16723
- }, options2);
17160
+ };
17161
+ const budget = options2.timeout ?? DEFAULT_WAIT_MS;
17162
+ const submitted = options2.wait === true ? await submitForWait(context, "environment.exec", input, options2, budget) : await submit(context, "environment.exec", input, options2);
17163
+ const { session, receipt, warnings } = submitted;
17164
+ const result = submitted.result ?? receipt?.result;
16724
17165
  if (!receipt)
16725
17166
  throw new CliError("response-invalid", "environment.exec returned no receipt");
16726
17167
  if (options2.wait !== true) {
@@ -16734,7 +17175,7 @@ var environmentCommands = [
16734
17175
  human: receiptLines(receipt)
16735
17176
  };
16736
17177
  }
16737
- const final = await waitForOperation(context, session, receipt, options2.timeout ?? DEFAULT_WAIT_MS);
17178
+ const final = await waitForOperation(context, session, receipt, remainingBudget(budget, "deadline" in submitted ? submitted.deadline : undefined));
16738
17179
  const human = [];
16739
17180
  if (context.global.output === "human") {
16740
17181
  for (const stream of ["stdout", "stderr"]) {
@@ -17120,7 +17561,11 @@ var SCHEDULED_JOB_LIMITS = Object.freeze({
17120
17561
  busyRetryDelayMs: 30000,
17121
17562
  fireStaleMs: 90000,
17122
17563
  fireHeartbeatMs: 20000,
17123
- maxFireAttempts: 3
17564
+ maxFireAttempts: 3,
17565
+ maxSelfTimerDelayMs: 7 * 24 * 60 * 60000,
17566
+ maxPendingSelfTimers: 20,
17567
+ selfTimerGrantWindowMs: 2 * 60 * 60000,
17568
+ userInstructionMaxChars: 500
17124
17569
  });
17125
17570
  function describeScheduleTrigger(trigger, timeZone) {
17126
17571
  switch (trigger.kind) {
@@ -17701,12 +18146,19 @@ var mailCommands = [
17701
18146
  ];
17702
18147
 
17703
18148
  // src/client/commands/service.ts
17704
- import { spawnSync } from "child_process";
18149
+ import { randomUUID as randomUUID4 } from "crypto";
18150
+ import { spawnSync as spawnSync2 } from "child_process";
17705
18151
  import { existsSync as existsSync6 } from "fs";
17706
- import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
18152
+ import { mkdir as mkdir2, rm as rm2, stat as stat2, writeFile as writeFile2 } from "fs/promises";
18153
+ import { createServer } from "net";
17707
18154
  import path16 from "path";
17708
18155
 
18156
+ // ../../apps/backend/src/fixed-ports.ts
18157
+ var DEFAULT_GATEWAY_PORT = 34121;
18158
+ var DEFAULT_CONTROL_MCP_PORT = 34122;
18159
+
17709
18160
  // src/client/service-connection.ts
18161
+ import { open } from "fs/promises";
17710
18162
  import path13 from "path";
17711
18163
  async function currentDescriptorGeneration(dataDir) {
17712
18164
  try {
@@ -17715,20 +18167,49 @@ async function currentDescriptorGeneration(dataDir) {
17715
18167
  return null;
17716
18168
  }
17717
18169
  }
18170
+ var LOG_EXCERPT_LINES = 20;
18171
+ var LOG_EXCERPT_MAX_LINE = 500;
18172
+ var LOG_EXCERPT_MAX_BYTES = 256 * 1024;
18173
+ async function startupLogExcerpt(logFile, offset, version) {
18174
+ let text;
18175
+ try {
18176
+ const file = await open(logFile);
18177
+ try {
18178
+ const { size } = await file.stat();
18179
+ const start = Math.max(offset, size - LOG_EXCERPT_MAX_BYTES);
18180
+ const buffer = Buffer.alloc(Math.max(size - start, 0));
18181
+ await file.read(buffer, 0, buffer.length, start);
18182
+ text = buffer.toString("utf8");
18183
+ } finally {
18184
+ await file.close();
18185
+ }
18186
+ } catch {
18187
+ return [];
18188
+ }
18189
+ const lines = text.split(`
18190
+ `);
18191
+ const exit = lines.findLastIndex((line) => line.includes(` ${version} exited (`));
18192
+ const rollback = lines.findIndex((line) => line.includes(` ${version} did not start after `) && line.includes("; rolling back to "));
18193
+ const nextStart = lines.findIndex((line) => /^\[launcher\] \S+ starting /.test(line) && !line.endsWith(` starting ${version}`));
18194
+ const end = exit >= 0 ? exit : Math.min(...[rollback, nextStart, lines.length].filter((index) => index >= 0));
18195
+ return lines.slice(0, end).filter((line) => line.trim() !== "").slice(-LOG_EXCERPT_LINES).map((line) => line.slice(0, LOG_EXCERPT_MAX_LINE));
18196
+ }
17718
18197
  async function awaitServiceConnection(dataDir, previousGeneration, options2) {
17719
18198
  const file = path13.join(dataDir, BACKEND_INSTANCE_DESCRIPTOR_FILE);
17720
18199
  try {
17721
18200
  const descriptor = await observe(async () => {
17722
18201
  try {
17723
18202
  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
- }
18203
+ const fresh = value.generation !== previousGeneration && path13.resolve(value.dataDir) === path13.resolve(dataDir) && (options2.expectedVersion === undefined || value.appVersion === options2.expectedVersion) && processIsAlive(value.pid);
18204
+ if (fresh)
18205
+ return { done: true, value };
18206
+ } catch {}
18207
+ await options2.assertStarting?.();
18208
+ return { done: false };
17729
18209
  }, options2);
17730
18210
  const connection = {
17731
18211
  browserUrl: descriptor.browserUrl ?? (isLoopbackHost(new URL(descriptor.url).hostname) || descriptor.endpoints?.some((endpoint) => endpoint.url === descriptor.url) ? null : descriptor.url),
18212
+ clientUrls: (descriptor.endpoints ?? []).filter((endpoint) => endpoint.role === "client").map((endpoint) => endpoint.url),
17732
18213
  authFile: descriptor.authFile,
17733
18214
  token: null
17734
18215
  };
@@ -17752,6 +18233,7 @@ async function awaitServiceConnection(dataDir, previousGeneration, options2) {
17752
18233
  }
17753
18234
 
17754
18235
  // src/client/service-setup.ts
18236
+ import { spawnSync } from "child_process";
17755
18237
  import { existsSync as existsSync4 } from "fs";
17756
18238
  import os6 from "os";
17757
18239
  var HOSTED_WEB_ORIGINS = "https://orkestrator.dev,https://www.orkestrator.dev";
@@ -17764,7 +18246,24 @@ var systemSetupProbe = {
17764
18246
  return MACOS_TAILSCALE_APP_CLI;
17765
18247
  }
17766
18248
  return typeof Bun === "undefined" ? null : Bun.which("tailscale");
17767
- }
18249
+ },
18250
+ tailscaleOperatorNeeded: (cli) => {
18251
+ if (process.platform !== "linux")
18252
+ return null;
18253
+ const user = os6.userInfo();
18254
+ if (user.uid === 0)
18255
+ return null;
18256
+ const prefs = spawnSync(cli, ["debug", "prefs"], { encoding: "utf8", timeout: 1e4 });
18257
+ if (prefs.status !== 0)
18258
+ return null;
18259
+ try {
18260
+ const { OperatorUser } = JSON.parse(prefs.stdout);
18261
+ return OperatorUser === user.username ? null : user.username;
18262
+ } catch {
18263
+ return null;
18264
+ }
18265
+ },
18266
+ grantTailscaleOperator: (cli, user) => spawnSync("sudo", [cli, "set", `--operator=${user}`], { stdio: "inherit" }).status === 0
17768
18267
  };
17769
18268
  function isTailscaleIPv4(address) {
17770
18269
  const [first, second] = address.split(".").map(Number);
@@ -17808,9 +18307,44 @@ async function confirm(io, prompt, fallback) {
17808
18307
  `);
17809
18308
  }
17810
18309
  }
18310
+ async function askGatewayPort(io) {
18311
+ io.stderr(`
18312
+ 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.
18313
+ `);
18314
+ for (;; ) {
18315
+ const answer = await ask(io, "Gateway port for a stable address (empty for a free one): ");
18316
+ if (answer === "")
18317
+ return null;
18318
+ const port = Number(answer);
18319
+ if (/^\d+$/.test(answer) && port >= 1 && port <= 65535 && port !== DEFAULT_GATEWAY_PORT && port !== DEFAULT_CONTROL_MCP_PORT) {
18320
+ return String(port);
18321
+ }
18322
+ io.stderr(`Enter a port from 1 to 65535, other than ${DEFAULT_GATEWAY_PORT} and ${DEFAULT_CONTROL_MCP_PORT}.
18323
+ `);
18324
+ }
18325
+ }
17811
18326
  function quote(arg) {
17812
18327
  return /^[\w@%+=:,./-]+$/.test(arg) ? arg : `'${arg.replaceAll("'", `'\\''`)}'`;
17813
18328
  }
18329
+ function operatorMessage(cli, user) {
18330
+ return [
18331
+ `
18332
+ Tailscale only lets root or its operator configure Serve, and ${user} is neither.`,
18333
+ "The backend cannot start with --tailscale-serve until this has been run once:",
18334
+ ` sudo ${quote(cli)} set --operator=${quote(user)}
18335
+ `
18336
+ ].join(`
18337
+ `);
18338
+ }
18339
+ function warnIfTailscaleOperatorNeeded(io, serveArgs, probe = systemSetupProbe) {
18340
+ if (!serveArgs.includes("--tailscale-serve"))
18341
+ return;
18342
+ const binFlag = serveArgs.indexOf("--tailscale-bin");
18343
+ const cli = (binFlag >= 0 ? serveArgs[binFlag + 1] : undefined) ?? io.env.ORKESTRATOR_TAILSCALE_BIN ?? probe.findTailscaleCli();
18344
+ const user = cli ? probe.tailscaleOperatorNeeded(cli) : null;
18345
+ if (cli && user)
18346
+ io.stderr(operatorMessage(cli, user));
18347
+ }
17814
18348
  async function askServeArguments(io, options2) {
17815
18349
  const source = io;
17816
18350
  io = {
@@ -17843,16 +18377,30 @@ async function askServeArguments(io, options2) {
17843
18377
  This machine has no Tailscale address yet. Connect Tailscale before the service starts, or it will not serve.
17844
18378
  `);
17845
18379
  }
18380
+ if (access === "tailnet" && options2.secondBackend) {
18381
+ const port = await askGatewayPort(io);
18382
+ if (port)
18383
+ args.push("--port", port);
18384
+ }
17846
18385
  if (access === "serve") {
17847
18386
  args.push("--tailscale-serve");
17848
- if (!probe.findTailscaleCli()) {
18387
+ let cli = probe.findTailscaleCli();
18388
+ if (!cli) {
17849
18389
  io.stderr(`
17850
18390
  The Tailscale CLI was not found, and Tailscale Serve needs it.
17851
18391
  `);
17852
- const cli = await ask(io, "Path to the tailscale executable (empty to keep `tailscale`): ");
18392
+ cli = await ask(io, "Path to the tailscale executable (empty to keep `tailscale`): ");
17853
18393
  if (cli !== "")
17854
18394
  args.push("--tailscale-bin", cli);
17855
18395
  }
18396
+ const operator = cli ? probe.tailscaleOperatorNeeded(cli) : null;
18397
+ if (cli && operator) {
18398
+ io.stderr(operatorMessage(cli, operator));
18399
+ const granted = !options2.dryRun && await confirm(io, "Run it now with sudo?", true) && probe.grantTailscaleOperator(cli, operator);
18400
+ if (!granted && !options2.dryRun)
18401
+ io.stderr(`Continuing without it.
18402
+ `);
18403
+ }
17856
18404
  if (await confirm(io, `
17857
18405
  Allow the hosted web client at orkestrator.dev?`, true)) {
17858
18406
  args.push("--allowed-origins", HOSTED_WEB_ORIGINS);
@@ -17934,6 +18482,8 @@ ${args}
17934
18482
  }
17935
18483
  const name = `orkestrator-backend${tag}.service`;
17936
18484
  const configHome = options2.env.XDG_CONFIG_HOME ?? path14.join(home, ".config");
18485
+ const xdgEnvironment = options2.env.XDG_CONFIG_HOME === undefined ? "" : `Environment=${systemdWord(`XDG_CONFIG_HOME=${options2.env.XDG_CONFIG_HOME}`)}
18486
+ `;
17937
18487
  return {
17938
18488
  manager: "systemd",
17939
18489
  name,
@@ -17947,7 +18497,7 @@ Type=simple
17947
18497
  ExecStart=${[options2.bun, options2.launcher, options2.dataDir].map(systemdWord).join(" ")}
17948
18498
  Environment=${systemdWord(`PATH=${pathValue}`)}
17949
18499
  Environment=NODE_ENV=production
17950
- Restart=always
18500
+ ${xdgEnvironment}Restart=always
17951
18501
  RestartSec=10
17952
18502
  StandardOutput=append:${options2.logFile}
17953
18503
  StandardError=append:${options2.logFile}
@@ -18032,8 +18582,43 @@ function unitFor(context, dataDir, bun) {
18032
18582
  };
18033
18583
  });
18034
18584
  }
18585
+ async function startupFailedError(version, failure, logFile, logOffset) {
18586
+ const excerpt = await startupLogExcerpt(logFile, logOffset, version);
18587
+ return new CliError("operation-failed", [
18588
+ 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.`,
18589
+ ...excerpt.length > 0 ? ["", ...excerpt, ""] : [],
18590
+ `Fix the cause, then run this command again. Full log: ${logFile}`
18591
+ ].join(`
18592
+ `), { retryable: true });
18593
+ }
18594
+ async function freeServicePort(allocate = probeServicePort) {
18595
+ for (let attempt = 0;attempt < 32; attempt += 1) {
18596
+ const port = await allocate();
18597
+ if (validServicePort(String(port)))
18598
+ return String(port);
18599
+ }
18600
+ throw new Error("Unable to allocate an unreserved service port");
18601
+ }
18602
+ function validServicePort(value) {
18603
+ const port = Number(value);
18604
+ return value !== undefined && String(port) === value && Number.isInteger(port) && port > 0 && port <= 65535 && port !== DEFAULT_GATEWAY_PORT && port !== DEFAULT_CONTROL_MCP_PORT ? value : undefined;
18605
+ }
18606
+ async function probeServicePort() {
18607
+ return new Promise((resolve, reject) => {
18608
+ const probe = createServer();
18609
+ probe.once("error", reject);
18610
+ probe.listen(0, "127.0.0.1", () => {
18611
+ const address = probe.address();
18612
+ probe.close(() => resolve(typeof address === "object" && address ? address.port : 0));
18613
+ });
18614
+ });
18615
+ }
18616
+ function portArgument(args) {
18617
+ const index = args?.indexOf("--port") ?? -1;
18618
+ return index >= 0 ? args?.[index + 1] : undefined;
18619
+ }
18035
18620
  function runManager(argv, allowFailure = false) {
18036
- const result = spawnSync(argv[0], argv.slice(1), { encoding: "utf8", timeout: 60000 });
18621
+ const result = spawnSync2(argv[0], argv.slice(1), { encoding: "utf8", timeout: 60000 });
18037
18622
  const ok = result.status === 0;
18038
18623
  if (!ok && !allowFailure) {
18039
18624
  throw new CliError("operation-failed", `${argv.join(" ")} failed: ${(result.stderr || result.stdout || "").trim().slice(0, 300)}`);
@@ -18055,10 +18640,19 @@ var serviceCommands = [
18055
18640
  "With none, in a terminal, it asks how clients will connect (Tailscale Serve, the",
18056
18641
  "tailnet address or SSH) and prints the equivalent command; --yes skips the questions.",
18057
18642
  "The Bun running this command is the Bun the service uses; reinstall to change it.",
18643
+ "",
18644
+ "Only the default data directory's backend uses the fixed gateway port. With another",
18645
+ "--data-dir and no `--port`, the backend takes a free port that changes on each start,",
18646
+ "so pass `-- --port PORT` for a stable address. --tailscale-serve gets a free port",
18647
+ "chosen once, here, with strict binding so it cannot move to a fallback port.",
18058
18648
  "On Linux, run `loginctl enable-linger $USER` so the service runs without a login.",
18059
18649
  "",
18060
18650
  "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."
18651
+ "The token is shown only in this terminal: never in --output json or the service log.",
18652
+ "If the release cannot start, the command fails with what the service logged.",
18653
+ "",
18654
+ "On Linux, --tailscale-serve needs `sudo tailscale set --operator=$USER` once; the",
18655
+ "questions offer to run it, and a scripted install warns when it is missing."
18062
18656
  ].join(`
18063
18657
  `),
18064
18658
  positionals: [],
@@ -18115,13 +18709,30 @@ var serviceCommands = [
18115
18709
  if (readyWaitMs > PUBLIC_API_LIMITS.waitMaxMs) {
18116
18710
  throw new CliError("invalid-input", "--timeout may be at most 24h");
18117
18711
  }
18118
- const serveArgs = interactive ? await askServeArguments(context.io, {
18712
+ const secondBackend = !isDefaultDataDir(dataDir, process.platform, context.io.env);
18713
+ const requestedArgs = interactive ? await askServeArguments(context.io, {
18119
18714
  installCommand: installCommandLine(parsed),
18120
- signal: context.signal
18715
+ signal: context.signal,
18716
+ dryRun: parsed.options["dry-run"] === true,
18717
+ secondBackend
18121
18718
  }) : parsed.rest;
18719
+ if (!interactive)
18720
+ warnIfTailscaleOperatorNeeded(context.io, requestedArgs);
18122
18721
  const bun = process.execPath;
18123
18722
  const bunVersion = typeof Bun === "undefined" ? "" : Bun.version;
18124
18723
  const { service, layout, unit } = await unitFor(context, dataDir, bun);
18724
+ const pinsPort = secondBackend && requestedArgs.includes("--tailscale-serve") && !requestedArgs.includes("--port");
18725
+ const serveArgs = pinsPort && parsed.options["dry-run"] !== true ? [
18726
+ ...requestedArgs,
18727
+ "--port",
18728
+ validServicePort(portArgument(service.readServiceConfig(layout)?.args)) ?? await freeServicePort(),
18729
+ "--strict-gateway-port"
18730
+ ] : requestedArgs;
18731
+ const portNote = pinsPort ? [
18732
+ "This is not the default data directory, so its gateway keeps a free port chosen at install instead of the fixed one."
18733
+ ] : secondBackend && !requestedArgs.includes("--port") ? [
18734
+ "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`."
18735
+ ] : [];
18125
18736
  const summary = {
18126
18737
  dataDir,
18127
18738
  version,
@@ -18143,6 +18754,7 @@ var serviceCommands = [
18143
18754
  ["unit", `${unit.manager} ${unit.name} \u2192 ${unit.file}`],
18144
18755
  ["serve arguments", serveArgs.join(" ") || "(none)"]
18145
18756
  ]),
18757
+ ...portNote,
18146
18758
  "",
18147
18759
  "Dry run: nothing was installed."
18148
18760
  ]
@@ -18158,13 +18770,17 @@ var serviceCommands = [
18158
18770
  throw new CliError("operation-failed", `Could not install orkestrator@${version}: ${error instanceof Error ? error.message : String(error)}`, { retryable: true });
18159
18771
  }
18160
18772
  await service.installLauncher(layout, releaseDir);
18773
+ const rollbackTarget = service.nextRollbackTarget(layout, service.readServiceConfig(layout), service.linkedVersion(layout.current), version);
18161
18774
  await service.activateRelease(layout, version);
18775
+ const installGeneration = randomUUID4();
18162
18776
  await service.writeServiceConfig(layout, {
18777
+ installGeneration,
18163
18778
  version: 1,
18164
18779
  dataDir,
18165
18780
  bun,
18166
18781
  bunVersion,
18167
18782
  args: serveArgs,
18783
+ ...rollbackTarget ? { rollbackTarget } : {},
18168
18784
  installedAt: new Date().toISOString()
18169
18785
  });
18170
18786
  await mkdir2(layout.logs, { recursive: true });
@@ -18175,6 +18791,8 @@ var serviceCommands = [
18175
18791
  let connection = null;
18176
18792
  if (started) {
18177
18793
  const previousGeneration = await currentDescriptorGeneration(dataDir);
18794
+ const loadedAt = Date.now();
18795
+ const logOffset = await stat2(logFile).then((file) => file.size, () => 0);
18178
18796
  const commands = serviceManagerCommands(unit);
18179
18797
  commands.load.forEach((argv, index) => runManager(argv, unit.manager === "launchd" && index === 0));
18180
18798
  if (readyWaitMs > 0) {
@@ -18182,7 +18800,13 @@ var serviceCommands = [
18182
18800
  `);
18183
18801
  connection = await awaitServiceConnection(dataDir, previousGeneration, {
18184
18802
  timeoutMs: readyWaitMs,
18185
- signal: context.signal
18803
+ signal: context.signal,
18804
+ expectedVersion: version,
18805
+ assertStarting: async () => {
18806
+ const failure = service.readStartupFailure(layout, version, loadedAt, installGeneration);
18807
+ if (failure)
18808
+ throw await startupFailedError(version, failure, logFile, logOffset);
18809
+ }
18186
18810
  });
18187
18811
  }
18188
18812
  }
@@ -18193,6 +18817,7 @@ var serviceCommands = [
18193
18817
  started,
18194
18818
  ready: connection !== null,
18195
18819
  browserUrl: connection?.browserUrl ?? null,
18820
+ clientUrls: connection?.clientUrls ?? [],
18196
18821
  authFile: connection?.authFile ?? null,
18197
18822
  ...connection?.credentialError ? { credentialError: connection.credentialError } : {},
18198
18823
  ...summary
@@ -18209,6 +18834,7 @@ var serviceCommands = [
18209
18834
  "",
18210
18835
  ...keyValues([
18211
18836
  ["address", connection.browserUrl ?? "(no browser listener; see the log)"],
18837
+ ...connection.clientUrls.length > 0 ? [["local listeners", connection.clientUrls.join(", ")]] : [],
18212
18838
  ["auth file", connection.authFile],
18213
18839
  ...context.io.stdoutIsTty && context.global.output === "human" && connection.token ? [["token", connection.token]] : []
18214
18840
  ]),
@@ -18217,6 +18843,7 @@ var serviceCommands = [
18217
18843
  "",
18218
18844
  `The backend has not reported ready yet. Follow ${logFile}; its address and token file are logged there.`
18219
18845
  ] : [],
18846
+ ...portNote,
18220
18847
  ...unit.manager === "systemd" ? ["Run `loginctl enable-linger $USER` so it keeps running after you log out."] : []
18221
18848
  ]
18222
18849
  };
@@ -18269,15 +18896,17 @@ var serviceCommands = [
18269
18896
  const state = service.readLauncherState(layout);
18270
18897
  const installed = existsSync6(unit.file);
18271
18898
  const running = installed ? runManager(serviceManagerCommands(unit).status, true).ok : false;
18899
+ const current = service.linkedVersion(layout.current);
18900
+ const previous = service.linkedVersion(layout.previous);
18272
18901
  const result = {
18273
18902
  dataDir,
18274
18903
  installed,
18275
18904
  running,
18276
18905
  unit: { manager: unit.manager, name: unit.name, file: unit.file },
18277
- current: service.linkedVersion(layout.current),
18278
- previous: service.linkedVersion(layout.previous),
18906
+ current,
18907
+ previous,
18279
18908
  bun: config?.bun ?? null,
18280
- args: config?.args ?? [],
18909
+ args: service.effectiveServiceArguments(config, current, previous),
18281
18910
  lastReadyVersion: state.lastReadyVersion,
18282
18911
  rollback: state.rollback
18283
18912
  };
@@ -18850,6 +19479,345 @@ var runCommands = [
18850
19479
  }
18851
19480
  ];
18852
19481
 
19482
+ // src/client/commands/session-goal.ts
19483
+ var WAIT_OPTIONS2 = [
19484
+ {
19485
+ name: "wait",
19486
+ kind: "boolean",
19487
+ description: "Observe the goal until it stops. Exit 0 for complete, 10 claimed, 11 blocked, 12 limited, 13 paused, 14 cleared or replaced."
19488
+ },
19489
+ TIMEOUT_OPTION,
19490
+ {
19491
+ name: "continue-on-interaction",
19492
+ kind: "boolean",
19493
+ description: "While waiting, keep waiting when a turn needs an answer (another client may answer)."
19494
+ }
19495
+ ];
19496
+ var LIMIT_OPTIONS = [
19497
+ {
19498
+ name: "max-iterations",
19499
+ kind: "integer",
19500
+ valueName: "N",
19501
+ description: `Turns the backend may send, at most ${NATIVE_AGENT_GOAL_LIMITS.maxIterationsLimit}.`
19502
+ },
19503
+ {
19504
+ name: "budget",
19505
+ kind: "integer",
19506
+ valueName: "TOKENS",
19507
+ description: "Tokens the goal may spend; refused where the provider reports no usage."
19508
+ }
19509
+ ];
19510
+ var LIMIT_FIELDS = { budget: "tokenBudget" };
19511
+ function limitsInput(options2) {
19512
+ return {
19513
+ ...options2["max-iterations"] !== undefined ? { maxIterations: options2["max-iterations"] } : {},
19514
+ ...options2.budget !== undefined ? { tokenBudget: options2.budget } : {}
19515
+ };
19516
+ }
19517
+ function goalLines(read) {
19518
+ const goal = read.goal;
19519
+ if (!goal)
19520
+ return ["no goal"];
19521
+ return keyValues([
19522
+ ["goal", goal.goalId],
19523
+ ["status", goal.status],
19524
+ ["reason", read.description],
19525
+ ["iterations", `${goal.iterations}/${goal.maxIterations}`],
19526
+ [
19527
+ "tokens",
19528
+ goal.tokenBudget !== undefined ? `${goal.tokensUsed ?? 0}/${goal.tokenBudget}` : undefined
19529
+ ],
19530
+ ["check", goal.check],
19531
+ [
19532
+ "verifier",
19533
+ goal.verifier ? `${goal.verifier.agent}${goal.verifier.model ? `:${goal.verifier.model}` : ""}` : undefined
19534
+ ],
19535
+ ["last check", goal.lastCheck ? goal.lastCheck.passed ? "passed" : "failed" : undefined],
19536
+ ["objective", goal.objective]
19537
+ ]);
19538
+ }
19539
+ function goalDetails(sessionId, goal) {
19540
+ return {
19541
+ sessionId,
19542
+ goalId: goal.goalId,
19543
+ status: goal.status,
19544
+ ...goal.statusReason ? { statusReason: goal.statusReason } : {},
19545
+ iterations: goal.iterations,
19546
+ maxIterations: goal.maxIterations
19547
+ };
19548
+ }
19549
+ var STOPPED_MESSAGES = {
19550
+ "goal-claimed": "The agent says the goal is done, and nothing verified it",
19551
+ "goal-blocked": "The goal is blocked: the agent says it needs a person",
19552
+ "goal-limited": "The goal reached its iteration cap or token budget",
19553
+ "goal-paused": "The goal is paused"
19554
+ };
19555
+ async function waitForGoal(context, session, sessionId, goalId, timeoutMs, options2) {
19556
+ let latest;
19557
+ const receiptOption = options2.receipt ? { receipt: options2.receipt } : {};
19558
+ try {
19559
+ return await observe(async () => {
19560
+ const read = await session.read("session.goal.get", {
19561
+ sessionId,
19562
+ ...options2.includeCheckOutput ? { includeCheckOutput: true } : {}
19563
+ });
19564
+ const verdict = classifyGoalForWait(read, goalId, {
19565
+ continueOnInteraction: options2.continueOnInteraction
19566
+ });
19567
+ if (verdict.kind === "pending") {
19568
+ latest = verdict.goal;
19569
+ return { done: false };
19570
+ }
19571
+ if (verdict.kind === "complete")
19572
+ return {
19573
+ done: true,
19574
+ value: {
19575
+ ...read,
19576
+ goal: verdict.goal,
19577
+ description: "Complete",
19578
+ ...read.goal?.goalId !== goalId ? { checkOutput: undefined } : {}
19579
+ }
19580
+ };
19581
+ if (verdict.kind === "interaction") {
19582
+ throw new CliError("interaction-required", "The goal's turn is waiting for an answer to a pending interaction", {
19583
+ ...receiptOption,
19584
+ details: {
19585
+ ...goalDetails(sessionId, verdict.goal),
19586
+ pendingInteractionCount: read.pendingInteractionCount
19587
+ }
19588
+ });
19589
+ }
19590
+ if (verdict.kind === "gone") {
19591
+ throw new CliError("goal-gone", "The goal was cleared or replaced before it ended", {
19592
+ ...receiptOption,
19593
+ details: {
19594
+ sessionId,
19595
+ goalId,
19596
+ ...verdict.goal ? { status: verdict.goal.status } : {}
19597
+ }
19598
+ });
19599
+ }
19600
+ throw new CliError(verdict.code, STOPPED_MESSAGES[verdict.code], {
19601
+ ...receiptOption,
19602
+ details: goalDetails(sessionId, verdict.goal)
19603
+ });
19604
+ }, { timeoutMs, signal: context.signal });
19605
+ } catch (error) {
19606
+ if (!(error instanceof ObservationStopped))
19607
+ throw error;
19608
+ const details = { sessionId, goalId, ...latest ? goalDetails(sessionId, latest) : {} };
19609
+ if (error.reason === "signal") {
19610
+ throw new CliError("observation-interrupted", "Stopped observing the goal; it continues in the backend", { ...receiptOption, details: { ...details, signal: signalName(context.signal) } });
19611
+ }
19612
+ throw new CliError("deadline-exceeded", "Timed out observing the goal; it continues in the backend. `session goal get --wait` observes it again", { ...receiptOption, details });
19613
+ }
19614
+ }
19615
+ function waitOutcome(base, read) {
19616
+ return {
19617
+ ...base,
19618
+ result: read,
19619
+ human: goalLines(read),
19620
+ ids: read.goal ? [read.goal.goalId] : []
19621
+ };
19622
+ }
19623
+ async function runLoopMutation(context, parsed, action, input) {
19624
+ const options2 = parsed.options;
19625
+ const sessionId = parsed.positionals.session;
19626
+ validateWaitOptions(options2);
19627
+ if (options2.wait !== true) {
19628
+ const { session, result, receipt, warnings } = await submit(context, action, input, options2);
19629
+ return {
19630
+ action,
19631
+ result,
19632
+ receipt,
19633
+ warnings,
19634
+ connection: session.identity,
19635
+ ids: result.goal ? [result.goal.goalId] : [],
19636
+ human: result.goal ? keyValues([
19637
+ ["goal", result.goal.goalId],
19638
+ ["status", result.goal.status],
19639
+ ["iterations", `${result.goal.iterations}/${result.goal.maxIterations}`]
19640
+ ]) : receipt ? receiptLines(receipt) : []
19641
+ };
19642
+ }
19643
+ const budget = options2.timeout ?? DEFAULT_WAIT_MS;
19644
+ const started = Date.now();
19645
+ const submitted = await submitForWait(context, action, input, options2, budget);
19646
+ const { session, warnings } = submitted;
19647
+ let receipt = submitted.receipt;
19648
+ let mutation = submitted.result;
19649
+ if (!mutation && receipt) {
19650
+ receipt = await waitForOperation(context, session, receipt, budget - (Date.now() - started));
19651
+ const failure = receiptFailure(receipt);
19652
+ if (failure)
19653
+ throw failure;
19654
+ mutation = receipt.result;
19655
+ }
19656
+ const goalId = mutation?.goal?.goalId;
19657
+ if (!goalId)
19658
+ throw new CliError("response-invalid", `${action} returned no goal`);
19659
+ const read = await waitForGoal(context, session, sessionId, goalId, Math.max(0, budget - (Date.now() - started)), {
19660
+ continueOnInteraction: options2["continue-on-interaction"] === true,
19661
+ ...receipt ? { receipt } : {}
19662
+ });
19663
+ return waitOutcome({ action, result: read, receipt, warnings, connection: session.identity }, read);
19664
+ }
19665
+ var sessionGoalCommands = [
19666
+ {
19667
+ path: ["session", "goal", "set"],
19668
+ action: "session.goal.set",
19669
+ summary: "Give a session a goal: the backend sends turns until a check proves it or it stops.",
19670
+ 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).",
19671
+ positionals: [{ name: "session", required: true }],
19672
+ options: [
19673
+ {
19674
+ name: "objective-file",
19675
+ kind: "string",
19676
+ valueName: "PATH",
19677
+ description: "Read the objective from a local file."
19678
+ },
19679
+ { name: "objective-stdin", kind: "boolean", description: "Read the objective from stdin." },
19680
+ { name: "objective", kind: "string", valueName: "TEXT", description: "Inline objective." },
19681
+ {
19682
+ name: "check",
19683
+ kind: "string",
19684
+ valueName: "COMMAND",
19685
+ description: "One-line shell command run in the workspace on a done report; exit 0 completes the goal."
19686
+ },
19687
+ {
19688
+ name: "verifier",
19689
+ kind: "string",
19690
+ valueName: "AGENT[:MODEL]",
19691
+ description: "A fresh read-only session on that agent judges the work on a done report; with --check, both must pass."
19692
+ },
19693
+ ...LIMIT_OPTIONS,
19694
+ ...WAIT_OPTIONS2,
19695
+ REQUEST_ID_OPTION
19696
+ ],
19697
+ inputFields: { ...LIMIT_FIELDS, "objective-file": "objective", "objective-stdin": "objective" },
19698
+ idOutput: "goal ID",
19699
+ examples: [
19700
+ 'orkestrator session goal set ses_\u2026 --objective "Make the tests pass" --check "bun test" --max-iterations 10 --wait --timeout 2h'
19701
+ ],
19702
+ async run(context, parsed) {
19703
+ const options2 = parsed.options;
19704
+ validateWaitOptions(options2);
19705
+ const verifier = options2.verifier === undefined ? undefined : parseVerifierFlag(String(options2.verifier));
19706
+ if (verifier && "error" in verifier)
19707
+ throw new CliError("invalid-input", verifier.error);
19708
+ const objective = await readTextSource(context.io, {
19709
+ file: options2["objective-file"],
19710
+ stdin: options2["objective-stdin"],
19711
+ inline: options2.objective,
19712
+ label: "Objective",
19713
+ maxBytes: NATIVE_AGENT_GOAL_LIMITS.objectiveMaxChars * 4,
19714
+ names: {
19715
+ file: "--objective-file",
19716
+ stdin: "--objective-stdin",
19717
+ inline: "--objective"
19718
+ },
19719
+ required: true
19720
+ });
19721
+ return runLoopMutation(context, parsed, "session.goal.set", {
19722
+ sessionId: parsed.positionals.session,
19723
+ objective,
19724
+ ...options2.check !== undefined ? { check: options2.check } : {},
19725
+ ...verifier ? { verifier } : {},
19726
+ ...limitsInput(options2)
19727
+ });
19728
+ }
19729
+ },
19730
+ {
19731
+ path: ["session", "goal", "get"],
19732
+ action: "session.goal.get",
19733
+ summary: "Show a session's goal: status, iterations, limits and the last check.",
19734
+ 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.",
19735
+ positionals: [{ name: "session", required: true }],
19736
+ options: [
19737
+ {
19738
+ name: "check-output",
19739
+ kind: "boolean",
19740
+ description: "Include the last check's output tail. It is untrusted command output."
19741
+ },
19742
+ ...WAIT_OPTIONS2
19743
+ ],
19744
+ inputFields: { "check-output": "includeCheckOutput" },
19745
+ idOutput: "goal ID",
19746
+ async run(context, parsed) {
19747
+ const options2 = parsed.options;
19748
+ validateWaitOptions(options2);
19749
+ const sessionId = parsed.positionals.session;
19750
+ const session = await context.session();
19751
+ const first = await session.read("session.goal.get", {
19752
+ sessionId,
19753
+ ...options2["check-output"] === true ? { includeCheckOutput: true } : {}
19754
+ });
19755
+ const base = { action: "session.goal.get", connection: session.identity };
19756
+ if (options2.wait !== true)
19757
+ return waitOutcome({ ...base, result: first }, first);
19758
+ const goalId = first.goal?.goalId;
19759
+ if (!goalId) {
19760
+ throw new CliError("goal-gone", "The session has no goal to wait for", {
19761
+ details: { sessionId }
19762
+ });
19763
+ }
19764
+ const read = await waitForGoal(context, session, sessionId, goalId, options2.timeout ?? DEFAULT_WAIT_MS, {
19765
+ continueOnInteraction: options2["continue-on-interaction"] === true,
19766
+ includeCheckOutput: options2["check-output"] === true
19767
+ });
19768
+ return waitOutcome({ ...base, result: read }, read);
19769
+ }
19770
+ },
19771
+ {
19772
+ path: ["session", "goal", "pause"],
19773
+ action: "session.goal.pause",
19774
+ summary: "Pause a running goal; no further turn starts until it is resumed.",
19775
+ description: "A turn already running finishes. Use `session stop` to stop it too. Refused (exit 8) when no goal is running.",
19776
+ positionals: [{ name: "session", required: true }],
19777
+ options: [REQUEST_ID_OPTION],
19778
+ idOutput: "goal ID",
19779
+ run: (context, parsed) => runGoalControl(context, parsed, "session.goal.pause")
19780
+ },
19781
+ {
19782
+ path: ["session", "goal", "resume"],
19783
+ action: "session.goal.resume",
19784
+ summary: "Resume a stopped goal, optionally with a higher limit.",
19785
+ 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.",
19786
+ positionals: [{ name: "session", required: true }],
19787
+ options: [...LIMIT_OPTIONS, ...WAIT_OPTIONS2, REQUEST_ID_OPTION],
19788
+ inputFields: LIMIT_FIELDS,
19789
+ idOutput: "goal ID",
19790
+ run: (context, parsed) => runLoopMutation(context, parsed, "session.goal.resume", {
19791
+ sessionId: parsed.positionals.session,
19792
+ ...limitsInput(parsed.options)
19793
+ })
19794
+ },
19795
+ {
19796
+ path: ["session", "goal", "clear"],
19797
+ action: "session.goal.clear",
19798
+ summary: "Remove a session's goal, in any status.",
19799
+ 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.",
19800
+ positionals: [{ name: "session", required: true }],
19801
+ options: [REQUEST_ID_OPTION],
19802
+ idOutput: "goal ID",
19803
+ run: (context, parsed) => runGoalControl(context, parsed, "session.goal.clear")
19804
+ }
19805
+ ];
19806
+ async function runGoalControl(context, parsed, action) {
19807
+ const { session, result, receipt, warnings } = await submit(context, action, { sessionId: parsed.positionals.session }, parsed.options);
19808
+ return {
19809
+ action,
19810
+ result,
19811
+ receipt,
19812
+ warnings,
19813
+ connection: session.identity,
19814
+ ids: result.goal ? [result.goal.goalId] : [],
19815
+ human: action === "session.goal.clear" ? [result.cleared ? "cleared" : "no goal"] : [
19816
+ `paused${result.goal ? ` at ${result.goal.iterations}/${result.goal.maxIterations}` : ""}`
19817
+ ]
19818
+ };
19819
+ }
19820
+
18853
19821
  // src/client/commands/ticket.ts
18854
19822
  var STATUSES = ["backlog", "in-progress", "review", "done"];
18855
19823
  var KINDS3 = ["feature", "work-brief"];
@@ -19267,6 +20235,742 @@ var toolchainCommands = [
19267
20235
  }
19268
20236
  ];
19269
20237
 
20238
+ // ../protocol/src/public-api-inputs-pr.ts
20239
+ var PUBLIC_PR_BODY_MAX_CHARS = 65536;
20240
+ var PUBLIC_PR_REVIEW_COMMENTS_MAX = 200;
20241
+ var PUBLIC_PR_REVIEWERS_MAX = 100;
20242
+ var PUBLIC_PR_MERGE_METHODS = Object.freeze(["squash", "merge", "rebase"]);
20243
+ var PUBLIC_PR_LIST_FILTERS = Object.freeze([
20244
+ "open",
20245
+ "mine",
20246
+ "review-requested",
20247
+ "closed"
20248
+ ]);
20249
+ var PUBLIC_PR_REVIEW_EVENTS = Object.freeze([
20250
+ "APPROVE",
20251
+ "REQUEST_CHANGES",
20252
+ "COMMENT"
20253
+ ]);
20254
+ var PUBLIC_PR_SIDES = Object.freeze(["LEFT", "RIGHT"]);
20255
+ function schema4(properties, required = []) {
20256
+ return { type: "object", properties, required, additionalProperties: false };
20257
+ }
20258
+ var environmentId = {
20259
+ type: "string",
20260
+ minLength: 1,
20261
+ maxLength: PUBLIC_API_LIMITS.idMaxChars,
20262
+ description: "Environment ID, from environment.list. Its attached pull request is the target."
20263
+ };
20264
+ var projectId = {
20265
+ type: "string",
20266
+ minLength: 1,
20267
+ maxLength: PUBLIC_API_LIMITS.idMaxChars,
20268
+ description: "Project ID, from project.list."
20269
+ };
20270
+ var body = {
20271
+ type: "string",
20272
+ minLength: 1,
20273
+ maxLength: PUBLIC_PR_BODY_MAX_CHARS,
20274
+ description: "Markdown text, posted as the GitHub user configured in Settings."
20275
+ };
20276
+ var filePath = {
20277
+ type: "string",
20278
+ minLength: 1,
20279
+ maxLength: PUBLIC_API_LIMITS.pathMaxChars,
20280
+ description: "Repository-relative path of a changed file, from pr.files."
20281
+ };
20282
+ var threadId = {
20283
+ type: "string",
20284
+ minLength: 1,
20285
+ maxLength: PUBLIC_API_LIMITS.idMaxChars,
20286
+ description: "Review thread ID, from pr.files."
20287
+ };
20288
+ var commitSha = {
20289
+ type: "string",
20290
+ minLength: 40,
20291
+ maxLength: 40
20292
+ };
20293
+ var line = {
20294
+ type: "integer",
20295
+ minimum: 1,
20296
+ description: "Line in the new file (RIGHT) or the old file (LEFT)."
20297
+ };
20298
+ var side = {
20299
+ type: "string",
20300
+ enum: PUBLIC_PR_SIDES,
20301
+ description: "RIGHT: the new file. LEFT: the old file."
20302
+ };
20303
+ var startLine = {
20304
+ type: "integer",
20305
+ minimum: 1,
20306
+ description: "First line of a multi-line comment; below line."
20307
+ };
20308
+ var reviewerList = {
20309
+ type: "array",
20310
+ maxItems: PUBLIC_PR_REVIEWERS_MAX,
20311
+ items: { type: "string", minLength: 1, maxLength: PUBLIC_API_LIMITS.nameMaxChars }
20312
+ };
20313
+ var environmentOnly = schema4({ environmentId }, ["environmentId"]);
20314
+ var environmentExamples = [{ environmentId: "env-1" }];
20315
+ var PR_ACTION_INPUTS = Object.freeze({
20316
+ "pr.get": {
20317
+ capability: "sessions.read",
20318
+ confirmation: "never",
20319
+ input: environmentOnly,
20320
+ examples: environmentExamples
20321
+ },
20322
+ "pr.list": {
20323
+ capability: "sessions.read",
20324
+ confirmation: "never",
20325
+ input: schema4({
20326
+ projectId,
20327
+ filter: {
20328
+ type: "string",
20329
+ enum: PUBLIC_PR_LIST_FILTERS,
20330
+ description: "Which pull requests (default open)."
20331
+ },
20332
+ query: {
20333
+ type: "string",
20334
+ minLength: 1,
20335
+ maxLength: 256,
20336
+ description: "Search text, as in the pull request list's search box."
20337
+ }
20338
+ }, ["projectId"]),
20339
+ examples: [{ projectId: "project-1" }, { projectId: "project-1", filter: "closed" }]
20340
+ },
20341
+ "pr.timeline": {
20342
+ capability: "sessions.read",
20343
+ confirmation: "never",
20344
+ input: environmentOnly,
20345
+ examples: environmentExamples
20346
+ },
20347
+ "pr.files": {
20348
+ capability: "sessions.read",
20349
+ confirmation: "never",
20350
+ input: schema4({
20351
+ environmentId,
20352
+ path: {
20353
+ ...filePath,
20354
+ description: "Return only this file, with its diff hunks. Omitted: every file without hunks, to keep the result small."
20355
+ }
20356
+ }, ["environmentId"]),
20357
+ examples: [{ environmentId: "env-1" }, { environmentId: "env-1", path: "src/index.ts" }]
20358
+ },
20359
+ "pr.options": {
20360
+ capability: "sessions.read",
20361
+ confirmation: "never",
20362
+ input: environmentOnly,
20363
+ examples: environmentExamples
20364
+ },
20365
+ "pr.merge": {
20366
+ capability: "environments.destructive",
20367
+ confirmation: "always",
20368
+ input: schema4({
20369
+ environmentId,
20370
+ method: {
20371
+ type: "string",
20372
+ enum: PUBLIC_PR_MERGE_METHODS,
20373
+ description: "How to merge (default squash, as on the Merge button)."
20374
+ },
20375
+ deleteBranch: {
20376
+ type: "boolean",
20377
+ description: "Delete the head branch after merging (default true). A project's home checkout keeps its branch."
20378
+ },
20379
+ cleanup: {
20380
+ type: "boolean",
20381
+ description: "Merge and Cleanup: delete this environment and its worktree once the merge is confirmed. Cannot be combined with deleteBranch false."
20382
+ },
20383
+ expectedPrUrl: {
20384
+ type: "string",
20385
+ description: "Refuse if the attached PR or the explicit cleanup/branch effects changed since confirmation."
20386
+ },
20387
+ expectedHeadSha: {
20388
+ ...commitSha,
20389
+ description: "headSha from pr.get. The merge is refused if the branch has moved since, so what merges is what was read."
20390
+ }
20391
+ }, ["environmentId"]),
20392
+ examples: [
20393
+ { environmentId: "env-1" },
20394
+ { environmentId: "env-1", method: "rebase", deleteBranch: false },
20395
+ { environmentId: "env-1", cleanup: true },
20396
+ { environmentId: "env-1", expectedHeadSha: "0123456789abcdef0123456789abcdef01234567" }
20397
+ ]
20398
+ },
20399
+ "pr.close": {
20400
+ capability: "sessions.control",
20401
+ confirmation: "always",
20402
+ input: environmentOnly,
20403
+ examples: environmentExamples
20404
+ },
20405
+ "pr.reopen": {
20406
+ capability: "sessions.control",
20407
+ confirmation: "voice",
20408
+ input: environmentOnly,
20409
+ examples: environmentExamples
20410
+ },
20411
+ "pr.ready": {
20412
+ capability: "sessions.control",
20413
+ confirmation: "voice",
20414
+ input: environmentOnly,
20415
+ examples: environmentExamples
20416
+ },
20417
+ "pr.draft": {
20418
+ capability: "sessions.control",
20419
+ confirmation: "voice",
20420
+ input: environmentOnly,
20421
+ examples: environmentExamples
20422
+ },
20423
+ "pr.comment": {
20424
+ capability: "sessions.control",
20425
+ confirmation: "voice",
20426
+ input: schema4({ environmentId, body }, ["environmentId", "body"]),
20427
+ examples: [{ environmentId: "env-1", body: "Merging once CI is green." }]
20428
+ },
20429
+ "pr.comment.edit": {
20430
+ capability: "sessions.control",
20431
+ confirmation: "voice",
20432
+ input: schema4({
20433
+ environmentId,
20434
+ commentId: {
20435
+ type: "integer",
20436
+ minimum: 1,
20437
+ description: "Comment ID, from pr.get or a pr.comment result."
20438
+ },
20439
+ body
20440
+ }, ["environmentId", "commentId", "body"]),
20441
+ examples: [{ environmentId: "env-1", commentId: 123, body: "Merged after the fix." }]
20442
+ },
20443
+ "pr.review": {
20444
+ capability: "sessions.control",
20445
+ confirmation: "always",
20446
+ input: schema4({
20447
+ environmentId,
20448
+ event: {
20449
+ type: "string",
20450
+ enum: PUBLIC_PR_REVIEW_EVENTS,
20451
+ description: "The verdict. REQUEST_CHANGES needs body; COMMENT needs body or comments. GitHub refuses approving your own pull request."
20452
+ },
20453
+ body: { ...body, description: "Review summary." },
20454
+ commitId: {
20455
+ ...commitSha,
20456
+ description: "The commit reviewed (headSha from pr.get). Omitted: the current head."
20457
+ },
20458
+ comments: {
20459
+ type: "array",
20460
+ maxItems: PUBLIC_PR_REVIEW_COMMENTS_MAX,
20461
+ description: "Inline comments submitted with the review.",
20462
+ items: {
20463
+ type: "object",
20464
+ properties: { path: filePath, line, side, startLine, body },
20465
+ required: ["path", "line", "side", "body"],
20466
+ additionalProperties: false
20467
+ }
20468
+ }
20469
+ }, ["environmentId", "event"]),
20470
+ examples: [
20471
+ { environmentId: "env-1", event: "APPROVE" },
20472
+ {
20473
+ environmentId: "env-1",
20474
+ event: "COMMENT",
20475
+ comments: [{ path: "src/index.ts", line: 12, side: "RIGHT", body: "Is this bounded?" }]
20476
+ }
20477
+ ]
20478
+ },
20479
+ "pr.review.comment": {
20480
+ capability: "sessions.control",
20481
+ confirmation: "voice",
20482
+ input: schema4({
20483
+ environmentId,
20484
+ path: filePath,
20485
+ line,
20486
+ side,
20487
+ startLine,
20488
+ body,
20489
+ commitId: {
20490
+ ...commitSha,
20491
+ description: "The commit commented on. Omitted: the pull request's current head."
20492
+ }
20493
+ }, ["environmentId", "path", "line", "side", "body"]),
20494
+ examples: [
20495
+ { environmentId: "env-1", path: "src/index.ts", line: 12, side: "RIGHT", body: "Why?" }
20496
+ ]
20497
+ },
20498
+ "pr.thread.reply": {
20499
+ capability: "sessions.control",
20500
+ confirmation: "voice",
20501
+ input: schema4({ environmentId, threadId, body }, ["environmentId", "threadId", "body"]),
20502
+ examples: [{ environmentId: "env-1", threadId: "PRRT_1", body: "Fixed." }]
20503
+ },
20504
+ "pr.thread.resolve": {
20505
+ capability: "sessions.control",
20506
+ confirmation: "voice",
20507
+ input: schema4({
20508
+ environmentId,
20509
+ threadId,
20510
+ resolved: { type: "boolean", description: "False to unresolve (default true)." }
20511
+ }, ["environmentId", "threadId"]),
20512
+ examples: [
20513
+ { environmentId: "env-1", threadId: "PRRT_1" },
20514
+ { environmentId: "env-1", threadId: "PRRT_1", resolved: false }
20515
+ ]
20516
+ },
20517
+ "pr.file.viewed": {
20518
+ capability: "sessions.control",
20519
+ confirmation: "never",
20520
+ input: schema4({
20521
+ environmentId,
20522
+ path: filePath,
20523
+ viewed: { type: "boolean", description: "False to clear it (default true)." }
20524
+ }, ["environmentId", "path"]),
20525
+ examples: [{ environmentId: "env-1", path: "src/index.ts" }]
20526
+ },
20527
+ "pr.reviewers": {
20528
+ capability: "sessions.control",
20529
+ confirmation: "voice",
20530
+ input: schema4({
20531
+ environmentId,
20532
+ add: {
20533
+ ...reviewerList,
20534
+ description: "Logins or org/team-slug entries to request a review from."
20535
+ },
20536
+ remove: {
20537
+ ...reviewerList,
20538
+ description: "Logins or org/team-slug entries whose request to remove."
20539
+ }
20540
+ }, ["environmentId"]),
20541
+ examples: [{ environmentId: "env-1", add: ["octocat"] }]
20542
+ },
20543
+ "pr.label": {
20544
+ capability: "sessions.control",
20545
+ confirmation: "voice",
20546
+ input: schema4({
20547
+ environmentId,
20548
+ name: {
20549
+ type: "string",
20550
+ minLength: 1,
20551
+ maxLength: 100,
20552
+ description: "Label name, from pr.options."
20553
+ },
20554
+ present: { type: "boolean", description: "False to remove the label (default true)." }
20555
+ }, ["environmentId", "name"]),
20556
+ examples: [{ environmentId: "env-1", name: "bug" }]
20557
+ }
20558
+ });
20559
+
20560
+ // src/client/commands/pull-requests.ts
20561
+ var ENVIRONMENT = { name: "environment", required: true, description: "Its pull request." };
20562
+ var YES_OPTION = {
20563
+ name: "yes",
20564
+ kind: "boolean",
20565
+ description: "Confirm this change on GitHub. Required."
20566
+ };
20567
+ var BODY_OPTIONS = [
20568
+ { name: "body", kind: "string", valueName: "TEXT", description: "Inline Markdown text." },
20569
+ {
20570
+ name: "body-file",
20571
+ kind: "string",
20572
+ valueName: "PATH",
20573
+ description: "Read the text from a local file."
20574
+ }
20575
+ ];
20576
+ var INLINE_OPTIONS = [
20577
+ { name: "path", kind: "string", valueName: "PATH", description: "Changed file." },
20578
+ { name: "line", kind: "string", valueName: "N", description: "Line of the comment." },
20579
+ { name: "side", kind: "string", valueName: "SIDE", description: "RIGHT (new) or LEFT (old)." },
20580
+ { name: "start-line", kind: "string", valueName: "N", description: "First line of a range." }
20581
+ ];
20582
+ function requireYes(parsed, what) {
20583
+ if (parsed.options.yes !== true) {
20584
+ throw new CliError("invalid-input", `${what}; pass --yes to confirm`);
20585
+ }
20586
+ }
20587
+ function requiredOption(parsed, name) {
20588
+ const value = stringOption(parsed.options, name);
20589
+ if (!value)
20590
+ throw new CliError("invalid-input", `--${name} is required`);
20591
+ return value;
20592
+ }
20593
+ function integerOption(parsed, name) {
20594
+ const value = stringOption(parsed.options, name);
20595
+ if (value === undefined)
20596
+ return;
20597
+ const number = Number(value);
20598
+ if (!Number.isSafeInteger(number) || number < 1) {
20599
+ throw new CliError("invalid-input", `--${name} must be a positive integer`);
20600
+ }
20601
+ return number;
20602
+ }
20603
+ function requiredInteger(parsed, name) {
20604
+ const number = integerOption(parsed, name);
20605
+ if (number === undefined)
20606
+ throw new CliError("invalid-input", `--${name} is required`);
20607
+ return number;
20608
+ }
20609
+ async function bodyText(context, parsed, required) {
20610
+ const body = await readTextSource(context.io, {
20611
+ file: parsed.options["body-file"],
20612
+ inline: parsed.options.body,
20613
+ label: "Body",
20614
+ maxBytes: PUBLIC_PR_BODY_MAX_CHARS * 4,
20615
+ names: { file: "--body-file", stdin: "", inline: "--body" },
20616
+ required
20617
+ });
20618
+ if (body !== undefined && body.length > PUBLIC_PR_BODY_MAX_CHARS) {
20619
+ throw new CliError("input-too-large", `Body is longer than ${PUBLIC_PR_BODY_MAX_CHARS} characters`);
20620
+ }
20621
+ return body;
20622
+ }
20623
+ function repeatedList(value) {
20624
+ return typeof value === "string" ? value.split(",").map((entry) => entry.trim()).filter(Boolean) : [];
20625
+ }
20626
+ function changeHuman(result) {
20627
+ return keyValues(Object.entries(result).filter(([key, value]) => key !== "environmentId" && typeof value !== "object"));
20628
+ }
20629
+ function change(name, action, summary, options2, input, extras = {}) {
20630
+ return {
20631
+ path: ["pr", name],
20632
+ action,
20633
+ summary,
20634
+ positionals: [ENVIRONMENT],
20635
+ options: [...options2, REQUEST_ID_OPTION],
20636
+ idOutput: "pull request URL",
20637
+ async run(context, parsed) {
20638
+ const environmentId = parsed.positionals.environment;
20639
+ const { session, result, receipt, warnings } = await submit(context, action, { environmentId, ...await input(context, parsed) }, parsed.options);
20640
+ return {
20641
+ action,
20642
+ result,
20643
+ receipt,
20644
+ warnings,
20645
+ connection: session.identity,
20646
+ ids: typeof result.url === "string" ? [result.url] : [],
20647
+ human: changeHuman(result)
20648
+ };
20649
+ },
20650
+ ...extras
20651
+ };
20652
+ }
20653
+ function read(name, action, summary, options2, input, human) {
20654
+ return {
20655
+ path: ["pr", name],
20656
+ action,
20657
+ summary,
20658
+ positionals: [ENVIRONMENT],
20659
+ options: options2,
20660
+ async run(context, parsed) {
20661
+ const session = await context.session();
20662
+ const result = await session.read(action, {
20663
+ environmentId: parsed.positionals.environment,
20664
+ ...input(parsed)
20665
+ });
20666
+ return { action, result, connection: session.identity, human: human(result) };
20667
+ }
20668
+ };
20669
+ }
20670
+ function pullRequestLines(result) {
20671
+ const pullRequest = result.pullRequest;
20672
+ if (!pullRequest) {
20673
+ return keyValues([
20674
+ ["attachment", result.attachment],
20675
+ ["url", result.url],
20676
+ ["error", result.error]
20677
+ ]);
20678
+ }
20679
+ return keyValues([
20680
+ ["attachment", result.attachment],
20681
+ ["number", pullRequest.number],
20682
+ ["title", pullRequest.title],
20683
+ ["url", pullRequest.htmlUrl],
20684
+ ["state", pullRequest.isDraft ? `${pullRequest.state} (draft)` : pullRequest.state],
20685
+ ["branch", `${pullRequest.headRef} into ${pullRequest.baseRef}`],
20686
+ ["head", pullRequest.headSha],
20687
+ ["mergeability", pullRequest.mergeability],
20688
+ ["checks", pullRequest.checksState]
20689
+ ]);
20690
+ }
20691
+ function timelineLines(result) {
20692
+ const items = result.items;
20693
+ const lines = items.flatMap((item) => {
20694
+ if (item.kind === "comment") {
20695
+ return [
20696
+ `comment \xB7 ${item.comment.author?.login ?? "unknown"} \xB7 ${item.comment.createdAt}`,
20697
+ item.comment.body
20698
+ ];
20699
+ }
20700
+ if (item.kind === "event") {
20701
+ return [
20702
+ `event \xB7 ${item.actor?.login ?? "unknown"} \xB7 ${item.createdAt}`,
20703
+ `${item.event}${item.detail ? `: ${item.detail}` : ""}`
20704
+ ];
20705
+ }
20706
+ return [
20707
+ `${item.kind} \xB7 ${item.author?.login ?? (item.kind === "commit" ? item.authorName : undefined) ?? "unknown"} \xB7 ${item.createdAt}`,
20708
+ item.kind === "commit" ? `${item.sha} ${item.message}` : `${item.state}${item.body ? `: ${item.body}` : ""}`
20709
+ ];
20710
+ });
20711
+ if (!items.length)
20712
+ lines.push("No timeline entries.");
20713
+ if (result.truncated)
20714
+ lines.push("Timeline truncated; more entries are available on GitHub.");
20715
+ return lines;
20716
+ }
20717
+ function fileLines(result) {
20718
+ const lines = keyValues([
20719
+ ["number", result.number],
20720
+ ["head", result.headSha]
20721
+ ]);
20722
+ if (Array.isArray(result.files)) {
20723
+ lines.push(...table(["FILE", "STATUS", "+", "-"], result.files.map((file) => [
20724
+ file.path,
20725
+ file.status,
20726
+ String(file.additions),
20727
+ String(file.deletions)
20728
+ ])));
20729
+ } else {
20730
+ const file = result.file;
20731
+ lines.push(`${file.path} (${file.status}, +${file.additions} -${file.deletions})`);
20732
+ if (!file.patchAvailable)
20733
+ lines.push("Patch unavailable; view this file on GitHub.");
20734
+ else
20735
+ for (const hunk of file.hunks) {
20736
+ lines.push(hunk.header, ...hunk.lines.map((line) => `${line.type === "add" ? "+" : line.type === "remove" ? "-" : line.type === "no-newline" ? "\\" : " "}${line.text}`));
20737
+ }
20738
+ }
20739
+ for (const thread of result.threads ?? []) {
20740
+ lines.push(`Thread ${thread.id} \xB7 ${thread.path}:${thread.line ?? "outdated"} \xB7 ${thread.isResolved ? "resolved" : "unresolved"}${thread.isOutdated ? " \xB7 outdated" : ""}`);
20741
+ for (const comment of thread.comments)
20742
+ lines.push(`${comment.author?.login ?? "unknown"} \xB7 ${comment.createdAt}: ${comment.body}`);
20743
+ if (thread.commentsTruncated)
20744
+ lines.push("Thread comments truncated; see GitHub for more.");
20745
+ }
20746
+ if (result.truncated)
20747
+ lines.push("File list truncated; more files are available on GitHub.");
20748
+ return lines;
20749
+ }
20750
+ async function reviewComments(context, parsed) {
20751
+ const file = parsed.options["comments-file"];
20752
+ if (file === undefined)
20753
+ return;
20754
+ const text = await readTextSource(context.io, {
20755
+ file,
20756
+ label: "Review comments",
20757
+ maxBytes: PUBLIC_PR_BODY_MAX_CHARS * 4,
20758
+ names: { file: "--comments-file", stdin: "" },
20759
+ required: true
20760
+ });
20761
+ let comments;
20762
+ try {
20763
+ comments = JSON.parse(text);
20764
+ } catch {
20765
+ throw new CliError("invalid-input", "--comments-file must contain a JSON array of inline comments");
20766
+ }
20767
+ if (!Array.isArray(comments) || comments.length > PUBLIC_PR_REVIEW_COMMENTS_MAX)
20768
+ throw new CliError("invalid-input", `--comments-file must contain at most ${PUBLIC_PR_REVIEW_COMMENTS_MAX} inline comments in a JSON array`);
20769
+ return comments;
20770
+ }
20771
+ var pullRequestCommands = [
20772
+ read("get", "pr.get", "Read the pull request attached to an environment's branch.", [], () => ({}), pullRequestLines),
20773
+ {
20774
+ path: ["pr", "list"],
20775
+ action: "pr.list",
20776
+ summary: "List a project's pull requests from GitHub.",
20777
+ positionals: [{ name: "project", required: true }],
20778
+ options: [
20779
+ {
20780
+ name: "filter",
20781
+ kind: "string",
20782
+ valueName: "FILTER",
20783
+ description: "open (default), mine, review-requested or closed."
20784
+ },
20785
+ { name: "query", kind: "string", valueName: "TEXT", description: "Search text." }
20786
+ ],
20787
+ idOutput: "pull request URLs",
20788
+ async run(context, parsed) {
20789
+ const session = await context.session();
20790
+ const filter = stringOption(parsed.options, "filter");
20791
+ const query = stringOption(parsed.options, "query");
20792
+ const result = await session.read("pr.list", {
20793
+ projectId: parsed.positionals.project,
20794
+ ...filter !== undefined ? { filter: oneOf(filter, PUBLIC_PR_LIST_FILTERS, "--filter") } : {},
20795
+ ...query !== undefined ? { query } : {}
20796
+ });
20797
+ return {
20798
+ action: "pr.list",
20799
+ result,
20800
+ connection: session.identity,
20801
+ ids: result.pullRequests.map((pullRequest) => pullRequest.htmlUrl),
20802
+ human: table(["NUMBER", "STATE", "TITLE"], result.pullRequests.map((pullRequest) => [
20803
+ String(pullRequest.number),
20804
+ pullRequest.state,
20805
+ pullRequest.title
20806
+ ]))
20807
+ };
20808
+ }
20809
+ },
20810
+ read("timeline", "pr.timeline", "Read a pull request's commits, comments, reviews and events.", [], () => ({}), timelineLines),
20811
+ read("files", "pr.files", "Read a pull request's changed files and review threads; with --path, one file's diff.", [
20812
+ {
20813
+ name: "path",
20814
+ kind: "string",
20815
+ valueName: "PATH",
20816
+ description: "Only this file, with hunks."
20817
+ }
20818
+ ], (parsed) => {
20819
+ const path = stringOption(parsed.options, "path");
20820
+ return path !== undefined ? { path } : {};
20821
+ }, fileLines),
20822
+ read("options", "pr.options", "Read the repository's labels and assignable users.", [], () => ({}), (result) => keyValues([
20823
+ [
20824
+ "labels",
20825
+ result.labels.map((label) => label.name).join(", ")
20826
+ ],
20827
+ [
20828
+ "assignable users",
20829
+ result.assignableUsers.map((user) => user.login).join(", ")
20830
+ ]
20831
+ ])),
20832
+ change("merge", "pr.merge", "Merge the environment's pull request into its target branch (squash by default).", [
20833
+ {
20834
+ name: "method",
20835
+ kind: "string",
20836
+ valueName: "METHOD",
20837
+ description: "squash (default), merge or rebase."
20838
+ },
20839
+ { name: "keep-branch", kind: "boolean", description: "Keep the head branch." },
20840
+ {
20841
+ name: "cleanup",
20842
+ kind: "boolean",
20843
+ description: "Merge and Cleanup: also delete the environment once merged."
20844
+ },
20845
+ {
20846
+ name: "expected-head-sha",
20847
+ kind: "string",
20848
+ valueName: "SHA",
20849
+ description: "Refuse if the branch has moved from this commit."
20850
+ },
20851
+ YES_OPTION
20852
+ ], (_context, parsed) => {
20853
+ requireYes(parsed, "Merging cannot be undone");
20854
+ const method = stringOption(parsed.options, "method");
20855
+ const expectedHeadSha = stringOption(parsed.options, "expected-head-sha");
20856
+ return {
20857
+ ...method !== undefined ? { method: oneOf(method, PUBLIC_PR_MERGE_METHODS, "--method") } : {},
20858
+ ...parsed.options["keep-branch"] === true ? { deleteBranch: false } : {},
20859
+ ...parsed.options.cleanup === true ? { cleanup: true } : {},
20860
+ ...expectedHeadSha !== undefined ? { expectedHeadSha } : {}
20861
+ };
20862
+ }, { inputFields: { "keep-branch": "deleteBranch", yes: null } }),
20863
+ change("close", "pr.close", "Close the environment's pull request without merging.", [YES_OPTION], (_context, parsed) => {
20864
+ requireYes(parsed, "This closes the pull request");
20865
+ return {};
20866
+ }, { inputFields: { yes: null } }),
20867
+ change("reopen", "pr.reopen", "Reopen the environment's closed pull request.", [], () => ({})),
20868
+ change("ready", "pr.ready", "Mark the environment's draft pull request ready.", [], () => ({})),
20869
+ change("draft", "pr.draft", "Convert the environment's pull request to a draft.", [], () => ({})),
20870
+ 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" } }),
20871
+ change("comment-edit", "pr.comment.edit", "Edit a conversation comment on the environment's pull request.", [
20872
+ { name: "comment-id", kind: "string", valueName: "ID", description: "From pr get." },
20873
+ ...BODY_OPTIONS
20874
+ ], async (context, parsed) => ({
20875
+ commentId: requiredInteger(parsed, "comment-id"),
20876
+ body: await bodyText(context, parsed, true)
20877
+ }), { inputFields: { "body-file": "body" } }),
20878
+ change("review", "pr.review", "Submit a review: approve, request changes or comment.", [
20879
+ {
20880
+ name: "event",
20881
+ kind: "string",
20882
+ valueName: "EVENT",
20883
+ description: "APPROVE, REQUEST_CHANGES or COMMENT."
20884
+ },
20885
+ ...BODY_OPTIONS,
20886
+ { name: "commit-id", kind: "string", valueName: "SHA", description: "The commit reviewed." },
20887
+ {
20888
+ name: "comments-file",
20889
+ kind: "string",
20890
+ valueName: "PATH",
20891
+ description: "JSON array of inline comments (path, line, side, body and optional startLine)."
20892
+ },
20893
+ YES_OPTION
20894
+ ], async (context, parsed) => {
20895
+ requireYes(parsed, "This submits a review as your GitHub user");
20896
+ const body = await bodyText(context, parsed, false);
20897
+ const commitId = stringOption(parsed.options, "commit-id");
20898
+ const comments = await reviewComments(context, parsed);
20899
+ return {
20900
+ ...comments !== undefined ? { comments } : {},
20901
+ event: oneOf(parsed.options.event, PUBLIC_PR_REVIEW_EVENTS, "--event"),
20902
+ ...body !== undefined ? { body } : {},
20903
+ ...commitId !== undefined ? { commitId } : {}
20904
+ };
20905
+ }, { inputFields: { "body-file": "body", "comments-file": "comments", yes: null } }),
20906
+ change("review-comment", "pr.review.comment", "Add one inline review comment to the environment's pull request.", [
20907
+ ...INLINE_OPTIONS,
20908
+ ...BODY_OPTIONS,
20909
+ {
20910
+ name: "commit-id",
20911
+ kind: "string",
20912
+ valueName: "SHA",
20913
+ description: "Omitted: the current head."
20914
+ }
20915
+ ], async (context, parsed) => {
20916
+ const startLine = integerOption(parsed, "start-line");
20917
+ const commitId = stringOption(parsed.options, "commit-id");
20918
+ return {
20919
+ path: requiredOption(parsed, "path"),
20920
+ line: requiredInteger(parsed, "line"),
20921
+ side: oneOf(parsed.options.side, PUBLIC_PR_SIDES, "--side"),
20922
+ ...startLine !== undefined ? { startLine } : {},
20923
+ body: await bodyText(context, parsed, true),
20924
+ ...commitId !== undefined ? { commitId } : {}
20925
+ };
20926
+ }, { inputFields: { "body-file": "body" } }),
20927
+ change("thread-reply", "pr.thread.reply", "Reply to a review thread on the environment's pull request.", [
20928
+ { name: "thread-id", kind: "string", valueName: "ID", description: "From pr files." },
20929
+ ...BODY_OPTIONS
20930
+ ], async (context, parsed) => ({
20931
+ threadId: requiredOption(parsed, "thread-id"),
20932
+ body: await bodyText(context, parsed, true)
20933
+ }), { inputFields: { "body-file": "body" } }),
20934
+ change("thread-resolve", "pr.thread.resolve", "Resolve a review thread on the environment's pull request.", [
20935
+ { name: "thread-id", kind: "string", valueName: "ID", description: "From pr files." },
20936
+ { name: "unresolve", kind: "boolean", description: "Unresolve it instead." }
20937
+ ], (_context, parsed) => ({
20938
+ threadId: requiredOption(parsed, "thread-id"),
20939
+ ...parsed.options.unresolve === true ? { resolved: false } : {}
20940
+ }), { inputFields: { unresolve: "resolved" } }),
20941
+ change("file-viewed", "pr.file.viewed", "Mark a changed file as viewed on the environment's pull request.", [
20942
+ { name: "path", kind: "string", valueName: "PATH", description: "Changed file." },
20943
+ { name: "unviewed", kind: "boolean", description: "Clear it instead." }
20944
+ ], (_context, parsed) => ({
20945
+ path: requiredOption(parsed, "path"),
20946
+ ...parsed.options.unviewed === true ? { viewed: false } : {}
20947
+ }), { inputFields: { unviewed: "viewed" } }),
20948
+ change("reviewers", "pr.reviewers", "Request or remove reviewers on the environment's pull request.", [
20949
+ {
20950
+ name: "add",
20951
+ kind: "string",
20952
+ valueName: "LIST",
20953
+ description: "Comma-separated logins or org/team-slugs."
20954
+ },
20955
+ {
20956
+ name: "remove",
20957
+ kind: "string",
20958
+ valueName: "LIST",
20959
+ description: "Comma-separated logins or org/team-slugs."
20960
+ }
20961
+ ], (_context, parsed) => ({
20962
+ add: repeatedList(parsed.options.add),
20963
+ remove: repeatedList(parsed.options.remove)
20964
+ })),
20965
+ change("label", "pr.label", "Add or remove one label on the environment's pull request.", [
20966
+ { name: "name", kind: "string", valueName: "LABEL", description: "Label name." },
20967
+ { name: "remove", kind: "boolean", description: "Remove it instead." }
20968
+ ], (_context, parsed) => ({
20969
+ name: requiredOption(parsed, "name"),
20970
+ ...parsed.options.remove === true ? { present: false } : {}
20971
+ }), { inputFields: { remove: "present" } })
20972
+ ];
20973
+
19270
20974
  // src/client/help.ts
19271
20975
  var GROUP_SUMMARIES = {
19272
20976
  connection: "Select and verify the backend this client talks to.",
@@ -19628,8 +21332,10 @@ var CLIENT_COMMANDS = [
19628
21332
  ...projectCommands,
19629
21333
  ...environmentCommands,
19630
21334
  ...sessionCommands,
21335
+ ...sessionGoalCommands,
19631
21336
  ...changesCommands,
19632
21337
  ...workflowCommands,
21338
+ ...pullRequestCommands,
19633
21339
  ...buildCommands,
19634
21340
  ...graphCommands,
19635
21341
  ...attentionCommands,