orkestrator 2.23.0 → 2.25.0-alpha

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
@@ -36,6 +36,37 @@ var __exportCjs = (target, getters, setters) => {
36
36
  };
37
37
  var __require = import.meta.require;
38
38
 
39
+ // ../toolchain/src/layout.ts
40
+ import { existsSync, readdirSync, realpathSync } from "fs";
41
+ import path2 from "path";
42
+ function toolchainRootDir(dataDir) {
43
+ return path2.join(dataDir, TOOLCHAIN_DIRECTORY);
44
+ }
45
+ function currentToolchainBinDir(dataDir) {
46
+ return path2.join(toolchainRootDir(dataDir), "bin", CURRENT_TOOLCHAIN_LINK);
47
+ }
48
+ function defaultToolchainBinDir(dataDir) {
49
+ const current = currentToolchainBinDir(dataDir);
50
+ if (existsSync(current))
51
+ return realpathSync(current);
52
+ const binRoot = path2.join(toolchainRootDir(dataDir), "bin");
53
+ return soleActivatedToolchainSet(binRoot) ?? binRoot;
54
+ }
55
+ function soleActivatedToolchainSet(binRoot) {
56
+ try {
57
+ const entries = readdirSync(binRoot, { withFileTypes: true }).filter((entry) => !entry.name.startsWith("."));
58
+ const [only] = entries;
59
+ if (entries.length !== 1 || !only?.isDirectory())
60
+ return;
61
+ const set = path2.join(binRoot, only.name);
62
+ return readdirSync(set).length > 0 ? realpathSync(set) : undefined;
63
+ } catch {
64
+ return;
65
+ }
66
+ }
67
+ var TOOLCHAIN_DIRECTORY = "toolchains", CURRENT_TOOLCHAIN_LINK = "current";
68
+ var init_layout = () => {};
69
+
39
70
  // src/client/io.ts
40
71
  async function readStreamBounded(stream, maxBytes, onOverflow, signal) {
41
72
  const reader = stream.getReader();
@@ -100,27 +131,27 @@ __export(exports_standalone_service, {
100
131
  writeServiceConfig: () => writeServiceConfig
101
132
  });
102
133
  import { execFile } from "child_process";
103
- import { existsSync, readFileSync, readlinkSync } from "fs";
134
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readlinkSync } from "fs";
104
135
  import { copyFile, mkdir, readFile, rename, rm, symlink, writeFile } from "fs/promises";
105
- import path9 from "path";
136
+ import path12 from "path";
106
137
  import { promisify } from "util";
107
138
  function standaloneLayout(dataDir) {
108
- const root = path9.join(dataDir, STANDALONE_DIRECTORY);
139
+ const root = path12.join(dataDir, STANDALONE_DIRECTORY);
109
140
  return {
110
141
  root,
111
- releases: path9.join(root, "releases"),
112
- current: path9.join(root, "current"),
113
- previous: path9.join(root, "previous"),
114
- serviceFile: path9.join(root, "service.json"),
115
- stateFile: path9.join(root, "launcher-state.json"),
116
- restartMarker: path9.join(root, "restart-requested"),
117
- launcher: path9.join(root, "launcher.js"),
118
- logs: path9.join(root, "logs")
142
+ releases: path12.join(root, "releases"),
143
+ current: path12.join(root, "current"),
144
+ previous: path12.join(root, "previous"),
145
+ serviceFile: path12.join(root, "service.json"),
146
+ stateFile: path12.join(root, "launcher-state.json"),
147
+ restartMarker: path12.join(root, "restart-requested"),
148
+ launcher: path12.join(root, "launcher.js"),
149
+ logs: path12.join(root, "logs")
119
150
  };
120
151
  }
121
152
  function readJson(file) {
122
153
  try {
123
- return JSON.parse(readFileSync(file, "utf8"));
154
+ return JSON.parse(readFileSync3(file, "utf8"));
124
155
  } catch {
125
156
  return null;
126
157
  }
@@ -161,28 +192,28 @@ async function writeAtomically(file, contents) {
161
192
  }
162
193
  function linkedVersion(link) {
163
194
  try {
164
- return path9.basename(readlinkSync(link));
195
+ return path12.basename(readlinkSync(link));
165
196
  } catch {
166
197
  return null;
167
198
  }
168
199
  }
169
200
  function releaseDirectory(layout, version) {
170
- return path9.join(layout.releases, version);
201
+ return path12.join(layout.releases, version);
171
202
  }
172
203
  function releasePackageRoot(releaseDir) {
173
- return path9.join(releaseDir, "node_modules", STANDALONE_PACKAGE);
204
+ return path12.join(releaseDir, "node_modules", STANDALONE_PACKAGE);
174
205
  }
175
206
  function releaseEntry(releaseDir) {
176
- return path9.join(releasePackageRoot(releaseDir), "bin", "orkestrator.js");
207
+ return path12.join(releasePackageRoot(releaseDir), "bin", "orkestrator.js");
177
208
  }
178
209
  function releaseLauncher(releaseDir) {
179
- return path9.join(releasePackageRoot(releaseDir), "bin", "orkestrator-launcher.js");
210
+ return path12.join(releasePackageRoot(releaseDir), "bin", "orkestrator-launcher.js");
180
211
  }
181
212
  function readReleaseManifest(releaseDir) {
182
- const manifest = readJson(path9.join(releasePackageRoot(releaseDir), "package.json"));
213
+ const manifest = readJson(path12.join(releasePackageRoot(releaseDir), "package.json"));
183
214
  if (!manifest || typeof manifest.version !== "string")
184
215
  return null;
185
- if (!existsSync(releaseEntry(releaseDir)))
216
+ if (!existsSync3(releaseEntry(releaseDir)))
186
217
  return null;
187
218
  return {
188
219
  version: manifest.version,
@@ -195,11 +226,11 @@ async function installRelease(layout, version, options2) {
195
226
  return target;
196
227
  const run = options2.run ?? runFile;
197
228
  await mkdir(layout.releases, { recursive: true });
198
- const staging = path9.join(layout.releases, `.staging-${version}-${process.pid}`);
229
+ const staging = path12.join(layout.releases, `.staging-${version}-${process.pid}`);
199
230
  await rm(staging, { recursive: true, force: true });
200
231
  await mkdir(staging, { recursive: true });
201
232
  try {
202
- await writeFile(path9.join(staging, "package.json"), `${JSON.stringify({
233
+ await writeFile(path12.join(staging, "package.json"), `${JSON.stringify({
203
234
  name: "orkestrator-release",
204
235
  private: true,
205
236
  dependencies: { [STANDALONE_PACKAGE]: version }
@@ -4825,10 +4856,6 @@ var init_manifest = __esm(() => {
4825
4856
  ];
4826
4857
  });
4827
4858
 
4828
- // ../toolchain/src/layout.ts
4829
- var TOOLCHAIN_DIRECTORY = "toolchains", CURRENT_TOOLCHAIN_LINK = "current";
4830
- var init_layout = () => {};
4831
-
4832
4859
  // ../toolchain/src/manager.ts
4833
4860
  import { spawn } from "child_process";
4834
4861
  import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
@@ -4838,37 +4865,37 @@ import {
4838
4865
  copyFile as copyFile2,
4839
4866
  lstat,
4840
4867
  mkdir as mkdir3,
4841
- mkdtemp,
4868
+ mkdtemp as mkdtemp2,
4842
4869
  open as open2,
4843
4870
  readFile as readFile2,
4844
- readdir,
4871
+ readdir as readdir2,
4845
4872
  readlink,
4846
4873
  rename as rename2,
4847
4874
  rm as rm3,
4848
- stat,
4875
+ stat as stat2,
4849
4876
  symlink as symlink2,
4850
4877
  utimes
4851
4878
  } from "fs/promises";
4852
- import path11 from "path";
4879
+ import path14 from "path";
4853
4880
  import { pipeline } from "stream/promises";
4854
4881
  import { createGunzip } from "zlib";
4855
4882
  function delay(milliseconds) {
4856
4883
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
4857
4884
  }
4858
4885
  function artifactDirectory(rootDir, artifact) {
4859
- return path11.join(rootDir, artifact.name, artifact.version, `${artifact.platform}-${artifact.architecture}`);
4886
+ return path14.join(rootDir, artifact.name, artifact.version, `${artifact.platform}-${artifact.architecture}`);
4860
4887
  }
4861
4888
  function artifactExecutablePath(rootDir, artifact) {
4862
- return path11.join(artifactDirectory(rootDir, artifact), artifact.executable.fileName);
4889
+ return path14.join(artifactDirectory(rootDir, artifact), artifact.executable.fileName);
4863
4890
  }
4864
4891
  function upstreamExecutablePath(rootDir, artifact) {
4865
- return path11.join(artifactDirectory(rootDir, artifact), `.upstream-${artifact.executable.fileName}`);
4892
+ return path14.join(artifactDirectory(rootDir, artifact), `.upstream-${artifact.executable.fileName}`);
4866
4893
  }
4867
4894
  function artifactCompanions(artifact) {
4868
4895
  return artifact.companions ?? [];
4869
4896
  }
4870
4897
  function companionPath(rootDir, artifact, companion) {
4871
- return path11.join(artifactDirectory(rootDir, artifact), companion.fileName);
4898
+ return path14.join(artifactDirectory(rootDir, artifact), companion.fileName);
4872
4899
  }
4873
4900
  function artifactArchiveBytes(artifact) {
4874
4901
  return artifactCompanions(artifact).reduce((total, companion) => total + companion.archive.size, artifact.archive.size);
@@ -4892,7 +4919,7 @@ function assertValidArtifactMetadata(artifact) {
4892
4919
  }
4893
4920
  const paths = new Set;
4894
4921
  for (const file of artifact.archive.bundleFiles) {
4895
- const normalized = path11.posix.normalize(file.path);
4922
+ const normalized = path14.posix.normalize(file.path);
4896
4923
  if (normalized !== file.path || normalized.startsWith("../") || normalized.startsWith("/") || normalized.includes("\\") || paths.has(normalized) || !Number.isSafeInteger(file.size) || file.size < 1 || !/^[a-f0-9]{64}$/.test(file.sha256)) {
4897
4924
  throw new Error(`${artifact.name} manifest has an unsafe bundle file`);
4898
4925
  }
@@ -4918,7 +4945,7 @@ function assertValidArtifactMetadata(artifact) {
4918
4945
  }
4919
4946
  const activationNames = new Set([artifact.name, artifact.executable.fileName]);
4920
4947
  for (const alias of artifact.activationAliases ?? []) {
4921
- if (!alias || alias.startsWith(".") || alias.includes("/") || alias.includes("\\") || alias !== path11.basename(alias) || activationNames.has(alias)) {
4948
+ if (!alias || alias.startsWith(".") || alias.includes("/") || alias.includes("\\") || alias !== path14.basename(alias) || activationNames.has(alias)) {
4922
4949
  throw new Error(`${artifact.name} manifest has an unsafe or duplicate activation alias`);
4923
4950
  }
4924
4951
  activationNames.add(alias);
@@ -4928,7 +4955,7 @@ function assertValidArtifactMetadata(artifact) {
4928
4955
  if (companion.archive.format !== "zip" && companion.archive.format !== "tar.gz") {
4929
4956
  throw new Error(`${artifact.name} companion has an unsupported archive format`);
4930
4957
  }
4931
- if (!companion.fileName || companion.fileName.startsWith(".") || companion.fileName.includes("/") || companion.fileName.includes("\\") || companion.fileName !== path11.basename(companion.fileName)) {
4958
+ if (!companion.fileName || companion.fileName.startsWith(".") || companion.fileName.includes("/") || companion.fileName.includes("\\") || companion.fileName !== path14.basename(companion.fileName)) {
4932
4959
  throw new Error(`${artifact.name} companion has an unsafe file name`);
4933
4960
  }
4934
4961
  if (companionNames.has(companion.fileName)) {
@@ -4994,7 +5021,7 @@ async function isValidExecutable(rootDir, artifact) {
4994
5021
  if (!await isValidUpstreamExecutable(rootDir, artifact))
4995
5022
  return false;
4996
5023
  for (const bundled of artifact.archive.bundleFiles ?? []) {
4997
- if (!await isValidFile(path11.join(artifactDirectory(rootDir, artifact), ...bundled.path.split("/")), bundled)) {
5024
+ if (!await isValidFile(path14.join(artifactDirectory(rootDir, artifact), ...bundled.path.split("/")), bundled)) {
4998
5025
  return false;
4999
5026
  }
5000
5027
  }
@@ -5011,7 +5038,7 @@ async function isValidExecutable(rootDir, artifact) {
5011
5038
  if ((file.mode & 511) !== 320)
5012
5039
  await chmod(executablePath, 320);
5013
5040
  for (const bundled of artifact.archive.bundleFiles ?? []) {
5014
- if (!await isValidFile(path11.join(artifactDirectory(rootDir, artifact), ...bundled.path.split("/")), bundled)) {
5041
+ if (!await isValidFile(path14.join(artifactDirectory(rootDir, artifact), ...bundled.path.split("/")), bundled)) {
5015
5042
  return false;
5016
5043
  }
5017
5044
  }
@@ -5030,9 +5057,9 @@ async function bundleTreeEntries(directory, artifact) {
5030
5057
  excludedExecutablePaths.add(`.upstream-${artifact.executable.fileName}`);
5031
5058
  }
5032
5059
  const visit = async (currentDirectory, prefix = "") => {
5033
- for (const entry of await readdir(currentDirectory, { withFileTypes: true })) {
5060
+ for (const entry of await readdir2(currentDirectory, { withFileTypes: true })) {
5034
5061
  const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
5035
- const filePath = path11.join(currentDirectory, entry.name);
5062
+ const filePath = path14.join(currentDirectory, entry.name);
5036
5063
  if (entry.isDirectory()) {
5037
5064
  await visit(filePath, relativePath);
5038
5065
  } else if (entry.isFile() && !entry.isSymbolicLink()) {
@@ -5147,7 +5174,7 @@ async function initializeInstallLock(lockPath, owner, openLockFile) {
5147
5174
  }
5148
5175
  }
5149
5176
  async function acquireInstallLock(rootDir, onProgress, timings, openLockFile, ownerProcessExists) {
5150
- const lockPath = path11.join(rootDir, INSTALL_LOCK);
5177
+ const lockPath = path14.join(rootDir, INSTALL_LOCK);
5151
5178
  const startedAt = Date.now();
5152
5179
  let announcedWait = false;
5153
5180
  while (Date.now() - startedAt < timings.lockWaitTimeoutMs) {
@@ -5198,7 +5225,7 @@ async function acquireInstallLock(rootDir, onProgress, timings, openLockFile, ow
5198
5225
  continue;
5199
5226
  }
5200
5227
  }
5201
- const lockStat = await stat(lockPath).catch(() => null);
5228
+ const lockStat = await stat2(lockPath).catch(() => null);
5202
5229
  if (!observedOwner && lockStat && Date.now() - lockStat.mtimeMs > timings.lockStaleAfterMs) {
5203
5230
  const currentOwner = await readInstallLockOwner(lockPath);
5204
5231
  if (!currentOwner) {
@@ -5328,8 +5355,8 @@ async function extractTarGzipBundle(archivePath, destinationDirectory, label, ar
5328
5355
  stream.resume();
5329
5356
  return;
5330
5357
  }
5331
- const normalized = path11.posix.normalize(relative);
5332
- if (normalized.startsWith("../") || normalized.startsWith("/") || normalized.includes("\\") || path11.isAbsolute(normalized)) {
5358
+ const normalized = path14.posix.normalize(relative);
5359
+ if (normalized.startsWith("../") || normalized.startsWith("/") || normalized.includes("\\") || path14.isAbsolute(normalized)) {
5333
5360
  extract2.destroy(new Error(`Unsafe path in ${label} bundle`));
5334
5361
  stream.resume();
5335
5362
  return;
@@ -5341,7 +5368,7 @@ async function extractTarGzipBundle(archivePath, destinationDirectory, label, ar
5341
5368
  stream.resume();
5342
5369
  return;
5343
5370
  }
5344
- const destination = path11.join(destinationDirectory, ...normalized.split("/"));
5371
+ const destination = path14.join(destinationDirectory, ...normalized.split("/"));
5345
5372
  if (header.type === "directory") {
5346
5373
  stream.once("end", next);
5347
5374
  stream.resume();
@@ -5359,7 +5386,7 @@ async function extractTarGzipBundle(archivePath, destinationDirectory, label, ar
5359
5386
  }
5360
5387
  if (header.name === archive.entryPath)
5361
5388
  foundExecutable = true;
5362
- mkdir3(path11.dirname(destination), { recursive: true, mode: 448 }).then(() => pipeline(stream, createWriteStream(destination, {
5389
+ mkdir3(path14.dirname(destination), { recursive: true, mode: 448 }).then(() => pipeline(stream, createWriteStream(destination, {
5363
5390
  flags: "wx",
5364
5391
  mode: (header.mode ?? 0) & 73 ? 320 : 256
5365
5392
  }))).then(() => next(), (error) => extract2.destroy(error instanceof Error ? error : new Error(String(error))));
@@ -5521,7 +5548,7 @@ async function repairInvalidMacSignature(executablePath, artifact, timeoutMs, sp
5521
5548
  await runCodesign(["--sign", "-", "--force", executablePath], `${artifact.name} could not be ad-hoc signed after source verification`, timeoutMs, spawnProcess);
5522
5549
  }
5523
5550
  async function prepareRepairableExecutable(sourcePath, executablePath, artifact, skipExecutableProbeForTests, processTimeoutMs, spawnProcess) {
5524
- const temporaryPath = path11.join(path11.dirname(executablePath), `.repair-${artifact.executable.fileName}-${randomUUID4()}.tmp`);
5551
+ const temporaryPath = path14.join(path14.dirname(executablePath), `.repair-${artifact.executable.fileName}-${randomUUID4()}.tmp`);
5525
5552
  try {
5526
5553
  await copyFile2(sourcePath, temporaryPath);
5527
5554
  await chmod(temporaryPath, 448);
@@ -5553,8 +5580,8 @@ async function refreshRepairableArtifact(rootDir, artifact, skipExecutableProbeF
5553
5580
  async function stageCompanions(stagingDirectory, artifact, companions, fetchImpl, onBytes, startBytes, allowInsecureDownloadsForTests, skipExecutableProbeForTests, timings, spawnProcess) {
5554
5581
  let downloadedBytes = startBytes;
5555
5582
  for (const companion of companions) {
5556
- const companionArchivePath = path11.join(stagingDirectory, `.archive-${companion.fileName}.${archiveExtension(companion.archive)}`);
5557
- const companionFilePath = path11.join(stagingDirectory, companion.fileName);
5583
+ const companionArchivePath = path14.join(stagingDirectory, `.archive-${companion.fileName}.${archiveExtension(companion.archive)}`);
5584
+ const companionFilePath = path14.join(stagingDirectory, companion.fileName);
5558
5585
  const precedingBytes = downloadedBytes;
5559
5586
  await downloadArchive(companion.fileName, companion.archive, companionArchivePath, fetchImpl, (received) => onBytes(precedingBytes + received), allowInsecureDownloadsForTests, timings.downloadTimeoutMs);
5560
5587
  downloadedBytes += companion.archive.size;
@@ -5579,28 +5606,28 @@ async function stageCompanions(stagingDirectory, artifact, companions, fetchImpl
5579
5606
  }
5580
5607
  }
5581
5608
  async function repairArtifactCompanions(rootDir, artifact, companions, fetchImpl, onBytes, onVerify, allowInsecureDownloadsForTests, skipExecutableProbeForTests, timings, spawnProcess) {
5582
- const stagingDirectory = await mkdtemp(path11.join(rootDir, `.staging-${artifact.name}-`));
5609
+ const stagingDirectory = await mkdtemp2(path14.join(rootDir, `.staging-${artifact.name}-`));
5583
5610
  try {
5584
5611
  await stageCompanions(stagingDirectory, artifact, companions, fetchImpl, onBytes, 0, allowInsecureDownloadsForTests, skipExecutableProbeForTests, timings, spawnProcess);
5585
5612
  onVerify();
5586
5613
  const destinationDirectory = artifactDirectory(rootDir, artifact);
5587
5614
  for (const companion of companions) {
5588
- await rename2(path11.join(stagingDirectory, companion.fileName), path11.join(destinationDirectory, companion.fileName));
5615
+ await rename2(path14.join(stagingDirectory, companion.fileName), path14.join(destinationDirectory, companion.fileName));
5589
5616
  }
5590
5617
  } finally {
5591
5618
  await rm3(stagingDirectory, { recursive: true, force: true });
5592
5619
  }
5593
5620
  }
5594
5621
  async function installArtifact(rootDir, artifact, fetchImpl, onBytes, onVerify, allowInsecureDownloadsForTests, skipExecutableProbeForTests, timings, spawnProcess) {
5595
- const stagingDirectory = await mkdtemp(path11.join(rootDir, `.staging-${artifact.name}-`));
5596
- const archivePath = path11.join(stagingDirectory, `archive.${archiveExtension(artifact.archive)}`);
5597
- const executablePath = path11.join(stagingDirectory, artifact.executable.fileName);
5598
- const extractedPath = artifact.executable.repairInvalidMacSignature ? path11.join(stagingDirectory, `.upstream-${artifact.executable.fileName}`) : executablePath;
5622
+ const stagingDirectory = await mkdtemp2(path14.join(rootDir, `.staging-${artifact.name}-`));
5623
+ const archivePath = path14.join(stagingDirectory, `archive.${archiveExtension(artifact.archive)}`);
5624
+ const executablePath = path14.join(stagingDirectory, artifact.executable.fileName);
5625
+ const extractedPath = artifact.executable.repairInvalidMacSignature ? path14.join(stagingDirectory, `.upstream-${artifact.executable.fileName}`) : executablePath;
5599
5626
  try {
5600
5627
  await downloadArchive(artifact.name, artifact.archive, archivePath, fetchImpl, onBytes, allowInsecureDownloadsForTests, timings.downloadTimeoutMs);
5601
5628
  if (artifact.archive.bundleRoot) {
5602
5629
  await extractTarGzipBundle(archivePath, stagingDirectory, artifact.name, artifact.archive);
5603
- const bundledExecutable = path11.join(stagingDirectory, ...artifact.archive.entryPath.slice(artifact.archive.bundleRoot.length).split("/"));
5630
+ const bundledExecutable = path14.join(stagingDirectory, ...artifact.archive.entryPath.slice(artifact.archive.bundleRoot.length).split("/"));
5604
5631
  if (bundledExecutable !== extractedPath) {
5605
5632
  await rename2(bundledExecutable, extractedPath);
5606
5633
  }
@@ -5651,7 +5678,7 @@ async function installArtifact(rootDir, artifact, fetchImpl, onBytes, onVerify,
5651
5678
  await stageCompanions(stagingDirectory, artifact, artifactCompanions(artifact), fetchImpl, onBytes, artifact.archive.size, allowInsecureDownloadsForTests, skipExecutableProbeForTests, timings, spawnProcess);
5652
5679
  onVerify();
5653
5680
  const destinationDirectory = artifactDirectory(rootDir, artifact);
5654
- await mkdir3(path11.dirname(destinationDirectory), { recursive: true, mode: 448 });
5681
+ await mkdir3(path14.dirname(destinationDirectory), { recursive: true, mode: 448 });
5655
5682
  await rm3(destinationDirectory, { recursive: true, force: true });
5656
5683
  await rename2(stagingDirectory, destinationDirectory);
5657
5684
  await chmod(destinationDirectory, 448);
@@ -5662,17 +5689,17 @@ async function installArtifact(rootDir, artifact, fetchImpl, onBytes, onVerify,
5662
5689
  }
5663
5690
  }
5664
5691
  async function cleanStagingDirectories(rootDir) {
5665
- const entries = await readdir(rootDir, { withFileTypes: true });
5666
- await Promise.all(entries.filter((entry) => entry.isDirectory() && entry.name.startsWith(".staging-")).map((entry) => rm3(path11.join(rootDir, entry.name), { recursive: true, force: true })));
5692
+ const entries = await readdir2(rootDir, { withFileTypes: true });
5693
+ await Promise.all(entries.filter((entry) => entry.isDirectory() && entry.name.startsWith(".staging-")).map((entry) => rm3(path14.join(rootDir, entry.name), { recursive: true, force: true })));
5667
5694
  }
5668
5695
  async function touchArtifactDirectory(rootDir, artifact) {
5669
5696
  const now = new Date;
5670
- await utimes(path11.dirname(artifactDirectory(rootDir, artifact)), now, now).catch(() => {
5697
+ await utimes(path14.dirname(artifactDirectory(rootDir, artifact)), now, now).catch(() => {
5671
5698
  return;
5672
5699
  });
5673
5700
  }
5674
5701
  function versionLeaseDirectory(rootDir, name, version) {
5675
- return path11.join(rootDir, LEASE_DIRECTORY, name, version);
5702
+ return path14.join(rootDir, LEASE_DIRECTORY, name, version);
5676
5703
  }
5677
5704
  async function acquireVersionLease(rootDir, artifact, openLeaseFile) {
5678
5705
  const leaseKey = `${rootDir}\x00${artifact.name}\x00${artifact.version}`;
@@ -5687,7 +5714,7 @@ async function acquireVersionLease(rootDir, artifact, openLeaseFile) {
5687
5714
  };
5688
5715
  const directory = versionLeaseDirectory(rootDir, artifact.name, artifact.version);
5689
5716
  await mkdir3(directory, { recursive: true, mode: 448 });
5690
- const leasePath = path11.join(directory, `${owner.pid}-${owner.token}.json`);
5717
+ const leasePath = path14.join(directory, `${owner.pid}-${owner.token}.json`);
5691
5718
  const handle = await openLeaseFile(leasePath, "wx", 384);
5692
5719
  try {
5693
5720
  await handle.writeFile(JSON.stringify(owner), "utf8");
@@ -5705,10 +5732,10 @@ async function acquireVersionLease(rootDir, artifact, openLeaseFile) {
5705
5732
  }
5706
5733
  async function versionHasLiveLease(rootDir, name, version, ownerProcessExists) {
5707
5734
  const directory = versionLeaseDirectory(rootDir, name, version);
5708
- const entries = await readdir(directory, { withFileTypes: true }).catch(() => []);
5735
+ const entries = await readdir2(directory, { withFileTypes: true }).catch(() => []);
5709
5736
  let live = false;
5710
5737
  await Promise.all(entries.filter((entry) => entry.isFile()).map(async (entry) => {
5711
- const leasePath = path11.join(directory, entry.name);
5738
+ const leasePath = path14.join(directory, entry.name);
5712
5739
  try {
5713
5740
  const parsed = JSON.parse(await readFile2(leasePath, "utf8"));
5714
5741
  if (typeof parsed.token === "string" && typeof parsed.pid === "number" && typeof parsed.createdAt === "string" && ownerProcessExists(parsed.pid)) {
@@ -5731,13 +5758,13 @@ async function pruneSupersededVersions(rootDir, artifacts, retainSupersededMs, o
5731
5758
  }
5732
5759
  const staleBefore = Date.now() - retainSupersededMs;
5733
5760
  await Promise.all(Array.from(keepByName, async ([name, keep]) => {
5734
- const toolDir = path11.join(rootDir, name);
5761
+ const toolDir = path14.join(rootDir, name);
5735
5762
  try {
5736
- const entries = await readdir(toolDir, { withFileTypes: true });
5763
+ const entries = await readdir2(toolDir, { withFileTypes: true });
5737
5764
  await Promise.all(entries.filter((entry) => entry.isDirectory() && !keep.has(entry.name)).map(async (entry) => {
5738
- const versionDir = path11.join(toolDir, entry.name);
5765
+ const versionDir = path14.join(toolDir, entry.name);
5739
5766
  try {
5740
- if ((await stat(versionDir)).mtimeMs > staleBefore)
5767
+ if ((await stat2(versionDir)).mtimeMs > staleBefore)
5741
5768
  return;
5742
5769
  if (await versionHasLiveLease(rootDir, name, entry.name, ownerProcessExists))
5743
5770
  return;
@@ -5776,8 +5803,8 @@ function activationSetId(artifacts) {
5776
5803
  return createHash4("sha256").update(JSON.stringify(identity)).digest("hex");
5777
5804
  }
5778
5805
  async function activateExecutables(rootDir, artifacts) {
5779
- const binRoot = path11.join(rootDir, "bin");
5780
- const binDir = path11.join(binRoot, activationSetId(artifacts));
5806
+ const binRoot = path14.join(rootDir, "bin");
5807
+ const binDir = path14.join(binRoot, activationSetId(artifacts));
5781
5808
  await mkdir3(binRoot, { recursive: true, mode: 448 });
5782
5809
  const binRootEntry = await lstat(binRoot);
5783
5810
  if (!binRootEntry.isDirectory() || binRootEntry.isSymbolicLink()) {
@@ -5792,8 +5819,8 @@ async function activateExecutables(rootDir, artifacts) {
5792
5819
  await chmod(binDir, 448);
5793
5820
  const executables = {};
5794
5821
  const activate = async (linkName, target) => {
5795
- const activePath = path11.join(binDir, linkName);
5796
- const temporaryLink = path11.join(binDir, `.${linkName}-${randomUUID4()}.tmp`);
5822
+ const activePath = path14.join(binDir, linkName);
5823
+ const temporaryLink = path14.join(binDir, `.${linkName}-${randomUUID4()}.tmp`);
5797
5824
  const existingTarget = await readlink(activePath).catch(() => null);
5798
5825
  if (existingTarget !== target) {
5799
5826
  await symlink2(target, temporaryLink, "file");
@@ -5841,7 +5868,7 @@ async function ensurePinnedToolchains(options2) {
5841
5868
  const timings = { ...DEFAULT_TIMINGS, ...options2.timingsForTests };
5842
5869
  const spawnProcess = options2.spawnForTests ?? spawn;
5843
5870
  const ownerProcessExists = options2.processExistsForTests ?? ((pid) => processExists(pid, options2.processKillForTests));
5844
- const rootDir = path11.join(options2.dataDir, TOOLCHAIN_DIRECTORY);
5871
+ const rootDir = path14.join(options2.dataDir, TOOLCHAIN_DIRECTORY);
5845
5872
  const totalTools = artifacts.length;
5846
5873
  const toolFractions = new Map(artifacts.map((artifact) => [artifact.name, 0]));
5847
5874
  const progress = (value) => {
@@ -6006,7 +6033,7 @@ var init_agent_platforms = __esm(() => {
6006
6033
 
6007
6034
  // ../toolchain/src/platform-selection.ts
6008
6035
  import { promises as fs7 } from "fs";
6009
- import path12 from "path";
6036
+ import path15 from "path";
6010
6037
  async function readJsonFile(filePath) {
6011
6038
  try {
6012
6039
  return JSON.parse(await fs7.readFile(filePath, "utf8"));
@@ -6015,7 +6042,7 @@ async function readJsonFile(filePath) {
6015
6042
  }
6016
6043
  }
6017
6044
  async function loadAgentPlatformSelection(dataDir) {
6018
- const config = await readJsonFile(path12.join(dataDir, AGENT_CONFIG_FILE));
6045
+ const config = await readJsonFile(path15.join(dataDir, AGENT_CONFIG_FILE));
6019
6046
  if (config && typeof config === "object" && !Array.isArray(config)) {
6020
6047
  const global2 = config.global;
6021
6048
  if (global2 && typeof global2 === "object" && !Array.isArray(global2)) {
@@ -6031,7 +6058,7 @@ async function loadAgentPlatformSelection(dataDir) {
6031
6058
  needsFirstRunChoice: false
6032
6059
  };
6033
6060
  }
6034
- const sidecar = await readJsonFile(path12.join(dataDir, AGENT_PLATFORM_SELECTION_FILE));
6061
+ const sidecar = await readJsonFile(path15.join(dataDir, AGENT_PLATFORM_SELECTION_FILE));
6035
6062
  if (sidecar && typeof sidecar === "object" && !Array.isArray(sidecar)) {
6036
6063
  const enabled = normalizeAgentPlatforms(sidecar.enabled, []);
6037
6064
  if (enabled.length > 0)
@@ -6046,8 +6073,8 @@ var init_platform_selection = __esm(() => {
6046
6073
 
6047
6074
  // ../toolchain/src/install.ts
6048
6075
  import { randomUUID as randomUUID5 } from "crypto";
6049
- import { lstat as lstat2, mkdir as mkdir4, readdir as readdir2, readlink as readlink2, rename as rename3, rm as rm4, symlink as symlink3 } from "fs/promises";
6050
- import path13 from "path";
6076
+ import { lstat as lstat2, mkdir as mkdir4, readdir as readdir3, readlink as readlink2, rename as rename3, rm as rm4, symlink as symlink3 } from "fs/promises";
6077
+ import path16 from "path";
6051
6078
  async function resolveToolchainSelection(options2) {
6052
6079
  const known = Object.keys(PINNED_TOOL_VERSIONS);
6053
6080
  const withSupportTools = (chosen) => known.filter((name) => chosen.has(name) || isSupportToolName(name));
@@ -6106,16 +6133,16 @@ async function installPinnedToolchains(options2) {
6106
6133
  };
6107
6134
  }
6108
6135
  async function toolsInCurrent(rootDir) {
6109
- const entries = await readdir2(path13.join(rootDir, "bin", CURRENT_TOOLCHAIN_LINK)).catch(() => []);
6136
+ const entries = await readdir3(path16.join(rootDir, "bin", CURRENT_TOOLCHAIN_LINK)).catch(() => []);
6110
6137
  return Object.keys(PINNED_TOOL_VERSIONS).filter((tool) => entries.includes(tool));
6111
6138
  }
6112
6139
  async function pointCurrentAt(rootDir, binDir) {
6113
- const binRoot = path13.join(rootDir, "bin");
6114
- if (path13.dirname(binDir) !== binRoot) {
6140
+ const binRoot = path16.join(rootDir, "bin");
6141
+ if (path16.dirname(binDir) !== binRoot) {
6115
6142
  throw new Error(`Activation directory ${binDir} is not directly inside ${binRoot}`);
6116
6143
  }
6117
- const link = path13.join(binRoot, CURRENT_TOOLCHAIN_LINK);
6118
- const target = path13.basename(binDir);
6144
+ const link = path16.join(binRoot, CURRENT_TOOLCHAIN_LINK);
6145
+ const target = path16.basename(binDir);
6119
6146
  await mkdir4(binRoot, { recursive: true, mode: 448 });
6120
6147
  const existing = await lstat2(link).catch(() => null);
6121
6148
  if (existing && !existing.isSymbolicLink()) {
@@ -6123,7 +6150,7 @@ async function pointCurrentAt(rootDir, binDir) {
6123
6150
  }
6124
6151
  if (existing && await readlink2(link) === target)
6125
6152
  return link;
6126
- const temporary = path13.join(binRoot, `.${CURRENT_TOOLCHAIN_LINK}-${randomUUID5()}.tmp`);
6153
+ const temporary = path16.join(binRoot, `.${CURRENT_TOOLCHAIN_LINK}-${randomUUID5()}.tmp`);
6127
6154
  await symlink3(target, temporary, "dir");
6128
6155
  try {
6129
6156
  await rename3(temporary, link);
@@ -6164,6 +6191,7 @@ var SERVER_VALUE_FLAGS = Object.freeze([
6164
6191
  "--docker-image",
6165
6192
  "--fallback-host",
6166
6193
  "--host",
6194
+ "--loopback-client-port",
6167
6195
  "--port",
6168
6196
  "--renderer-dev-server-url",
6169
6197
  "--renderer-root",
@@ -6178,6 +6206,7 @@ var SERVER_VALUE_FLAGS = Object.freeze([
6178
6206
  ]);
6179
6207
  var SERVER_BOOLEAN_FLAGS = Object.freeze([
6180
6208
  "--allow-non-tailscale-bind",
6209
+ "--command-node",
6181
6210
  "--desktop-updater",
6182
6211
  "--desktop-web-client",
6183
6212
  "--strict-docker-owner",
@@ -6512,6 +6541,22 @@ var PUBLIC_ACTIONS = {
6512
6541
  milestone: "C",
6513
6542
  summary: "Read one bounded, ordered transcript page with a continuation cursor."
6514
6543
  },
6544
+ "changes.view": {
6545
+ version: 1,
6546
+ mutation: false,
6547
+ effects: [],
6548
+ observation: "immediate",
6549
+ milestone: "D",
6550
+ summary: "Read the backend's change projection for an environment (scope, state, line counts and their quality) with one page of changed files."
6551
+ },
6552
+ "changes.history": {
6553
+ version: 1,
6554
+ mutation: false,
6555
+ effects: [],
6556
+ observation: "immediate",
6557
+ milestone: "D",
6558
+ summary: "Read one page of a session's turns, each with the backend's change projection."
6559
+ },
6515
6560
  "run.get": {
6516
6561
  version: 1,
6517
6562
  mutation: false,
@@ -7138,89 +7183,115 @@ function splitFlag(token) {
7138
7183
  return [body.slice(0, equals), body.slice(equals + 1)];
7139
7184
  }
7140
7185
 
7141
- // src/client/commands/connection.ts
7186
+ // src/client/fix.ts
7187
+ import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync as realpathSync2 } from "fs";
7188
+ import { mkdtemp, readdir, rmdir, stat } from "fs/promises";
7189
+ import os3 from "os";
7142
7190
  import path5 from "path";
7143
- import { randomUUID as randomUUID3 } from "crypto";
7144
- import { promises as fs4 } from "fs";
7145
7191
 
7146
- // ../protocol/src/backend-instance.ts
7147
- var BACKEND_INSTANCE_DESCRIPTOR_FILE = "backend-instance.json";
7148
- var BACKEND_INSTANCE_DESCRIPTOR_TYPE = "orkestrator-backend-instance";
7149
- var BACKEND_INSTANCE_DESCRIPTOR_MAX_BYTES = 16 * 1024;
7192
+ // ../protocol/src/host-maintenance.ts
7193
+ var HOST_UPDATE_IDLE_WAIT = Object.freeze({
7194
+ defaultSeconds: 3600,
7195
+ minSeconds: 60,
7196
+ maxSeconds: 86400
7197
+ });
7198
+ var HOST_UPDATE_CHANNELS = ["stable", "beta", "unstable"];
7199
+ var HOST_UPDATE_CHANNEL_TAGS = Object.freeze({ stable: "latest", beta: "beta", unstable: "alpha" });
7200
+ function isHostUpdateChannel(value) {
7201
+ return HOST_UPDATE_CHANNELS.includes(value);
7202
+ }
7203
+ var ACTIVE_HOST_UPDATE_JOB_STATES = new Set([
7204
+ "requested",
7205
+ "downloading",
7206
+ "waiting-for-idle",
7207
+ "awaiting-restart",
7208
+ "restarting"
7209
+ ]);
7210
+ var CANCELLABLE_HOST_UPDATE_JOB_STATES = new Set([
7211
+ "requested",
7212
+ "downloading",
7213
+ "waiting-for-idle",
7214
+ "awaiting-restart"
7215
+ ]);
7216
+ var HOST_MAINTENANCE_LIMITS = Object.freeze({
7217
+ idMaxChars: 100,
7218
+ versionMaxChars: 64,
7219
+ errorMaxChars: 500
7220
+ });
7221
+ var VERSION_PATTERN = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
7222
+ function isHostVersion(value) {
7223
+ return typeof value === "string" && value.length <= HOST_MAINTENANCE_LIMITS.versionMaxChars && VERSION_PATTERN.test(value);
7224
+ }
7225
+
7226
+ // ../protocol/src/node/application-logging.ts
7227
+ import { readFileSync } from "fs";
7228
+ import path from "path";
7229
+
7230
+ // ../protocol/src/debug-logging.ts
7231
+ var DEFAULT_DEBUG_LOG_RETENTION_DAYS = 7;
7232
+ var MIN_DEBUG_LOG_RETENTION_DAYS = 1;
7233
+ var MAX_DEBUG_LOG_RETENTION_DAYS = 3650;
7234
+ function isValidDebugLogRetentionDays(value) {
7235
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= MIN_DEBUG_LOG_RETENTION_DAYS && value <= MAX_DEBUG_LOG_RETENTION_DAYS;
7236
+ }
7237
+ function normalizeDebugLogRetentionDays(value) {
7238
+ return isValidDebugLogRetentionDays(value) ? value : DEFAULT_DEBUG_LOG_RETENTION_DAYS;
7239
+ }
7240
+
7241
+ // ../protocol/src/node/application-logging.ts
7242
+ var LOG_CLEANUP_INTERVAL_MS = 6 * 60 * 60 * 1000;
7243
+ var MAX_PENDING_LOG_BYTES = 4 * 1024 * 1024;
7244
+ var MAX_LOG_ENTRY_BYTES = 64 * 1024;
7245
+ var MAX_LOG_FILE_BYTES = 32 * 1024 * 1024;
7246
+ var MAX_LOG_DIRECTORY_BYTES = 512 * 1024 * 1024;
7150
7247
  function isRecord2(value) {
7151
- return value !== null && typeof value === "object" && !Array.isArray(value);
7248
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7152
7249
  }
7153
- function isHttpUrl(value) {
7154
- if (typeof value !== "string" || value.length > 2048)
7155
- return false;
7250
+ function readApplicationLogSettings(dataDir) {
7156
7251
  try {
7157
- const url = new URL(value);
7158
- return (url.protocol === "http:" || url.protocol === "https:") && url.username === "" && url.password === "" && url.search === "" && url.hash === "";
7252
+ const parsed = JSON.parse(readFileSync(path.join(dataDir, "config.json"), "utf8"));
7253
+ const global2 = isRecord2(parsed) && isRecord2(parsed.global) ? parsed.global : {};
7254
+ return {
7255
+ enabled: global2.debugLogging === true,
7256
+ retentionDays: normalizeDebugLogRetentionDays(global2.debugLogRetentionDays)
7257
+ };
7159
7258
  } catch {
7160
- return false;
7161
- }
7162
- }
7163
- function parseBackendInstanceDescriptor(value) {
7164
- if (!isRecord2(value))
7165
- throw new Error("Backend instance descriptor must be an object");
7166
- if (value.version !== 1 || value.type !== BACKEND_INSTANCE_DESCRIPTOR_TYPE) {
7167
- throw new Error("Backend instance descriptor has an unsupported version");
7168
- }
7169
- const text = (key, max = 512) => {
7170
- const field = value[key];
7171
- if (typeof field !== "string" || field.length === 0 || field.length > max) {
7172
- throw new Error(`Backend instance descriptor field is invalid: ${key}`);
7173
- }
7174
- return field;
7175
- };
7176
- if (!Number.isSafeInteger(value.pid) || value.pid <= 0) {
7177
- throw new Error("Backend instance descriptor field is invalid: pid");
7178
- }
7179
- if (!isHttpUrl(value.url))
7180
- throw new Error("Backend instance descriptor field is invalid: url");
7181
- if (value.browserUrl !== undefined && !isHttpUrl(value.browserUrl)) {
7182
- throw new Error("Backend instance descriptor field is invalid: browserUrl");
7183
- }
7184
- if (!Number.isSafeInteger(value.publicApiSchemaVersion) || value.publicApiSchemaVersion < 1) {
7185
- throw new Error("Backend instance descriptor field is invalid: publicApiSchemaVersion");
7186
- }
7187
- const authFile = text("authFile", 4096);
7188
- const dataDir = text("dataDir", 4096);
7189
- if (!authFile.startsWith("/") || !dataDir.startsWith("/")) {
7190
- throw new Error("Backend instance descriptor paths must be absolute");
7259
+ return {
7260
+ enabled: false,
7261
+ retentionDays: normalizeDebugLogRetentionDays(undefined)
7262
+ };
7191
7263
  }
7192
- return {
7193
- version: 1,
7194
- type: BACKEND_INSTANCE_DESCRIPTOR_TYPE,
7195
- installationId: text("installationId", 200),
7196
- generation: text("generation", 200),
7197
- pid: value.pid,
7198
- startedAt: text("startedAt", 64),
7199
- url: value.url,
7200
- ...value.browserUrl !== undefined ? { browserUrl: value.browserUrl } : {},
7201
- authFile,
7202
- dataDir,
7203
- appVersion: text("appVersion", 64),
7204
- publicApiSchemaVersion: value.publicApiSchemaVersion
7205
- };
7206
7264
  }
7265
+ var BENIGN_RMDIR_CODES = new Set(["ENOENT", "ENOTEMPTY", "EEXIST", "ERR_FS_EISDIR"]);
7266
+
7267
+ // ../protocol/src/terminal-agent-launch.ts
7268
+ var OPENCODE_PINNED_RUNTIME_ENV = Object.freeze({
7269
+ OPENCODE_DISABLE_AUTOUPDATE: "1"
7270
+ });
7271
+ var CLAUDE_PINNED_RUNTIME_ENV = Object.freeze({
7272
+ DISABLE_AUTOUPDATER: "1",
7273
+ FORCE_AUTOUPDATE_PLUGINS: "1"
7274
+ });
7275
+
7276
+ // src/client/fix.ts
7277
+ init_layout();
7207
7278
 
7208
7279
  // src/client/config.ts
7209
7280
  import { randomUUID } from "crypto";
7210
7281
  import { promises as fs } from "fs";
7211
7282
  import os from "os";
7212
- import path from "path";
7283
+ import path3 from "path";
7213
7284
  var CLI_CONFIG_VERSION = 1;
7214
7285
  var MAX_CONFIG_BYTES = 256 * 1024;
7215
7286
  var MAX_CONNECTIONS = 64;
7216
7287
  var CONNECTION_NAME = /^[a-z0-9][a-z0-9._-]{0,47}$/;
7217
7288
  function defaultCliConfigDir(env, platform = process.platform, home = os.homedir()) {
7218
7289
  if (env.ORKESTRATOR_CLI_CONFIG_DIR)
7219
- return path.resolve(env.ORKESTRATOR_CLI_CONFIG_DIR);
7290
+ return path3.resolve(env.ORKESTRATOR_CLI_CONFIG_DIR);
7220
7291
  if (platform === "darwin") {
7221
- return path.join(home, "Library", "Application Support", "orkestrator-cli");
7292
+ return path3.join(home, "Library", "Application Support", "orkestrator-cli");
7222
7293
  }
7223
- return path.join(env.XDG_CONFIG_HOME ?? path.join(home, ".config"), "orkestrator-cli");
7294
+ return path3.join(env.XDG_CONFIG_HOME ?? path3.join(home, ".config"), "orkestrator-cli");
7224
7295
  }
7225
7296
  function isConnectionName(value) {
7226
7297
  return CONNECTION_NAME.test(value);
@@ -7249,7 +7320,7 @@ function parseSaved(value) {
7249
7320
  const record = value;
7250
7321
  const addedAt = typeof record.addedAt === "string" ? record.addedAt : new Date(0).toISOString();
7251
7322
  const installationId = typeof record.installationId === "string" && record.installationId.length <= 200 ? record.installationId : undefined;
7252
- if (record.kind === "descriptor" && typeof record.descriptorPath === "string" && path.isAbsolute(record.descriptorPath)) {
7323
+ if (record.kind === "descriptor" && typeof record.descriptorPath === "string" && path3.isAbsolute(record.descriptorPath)) {
7253
7324
  return {
7254
7325
  kind: "descriptor",
7255
7326
  descriptorPath: record.descriptorPath,
@@ -7257,7 +7328,7 @@ function parseSaved(value) {
7257
7328
  addedAt
7258
7329
  };
7259
7330
  }
7260
- if (record.kind === "endpoint" && typeof record.url === "string" && typeof record.credentialFile === "string" && path.isAbsolute(record.credentialFile)) {
7331
+ if (record.kind === "endpoint" && typeof record.url === "string" && typeof record.credentialFile === "string" && path3.isAbsolute(record.credentialFile)) {
7261
7332
  return {
7262
7333
  kind: "endpoint",
7263
7334
  url: record.url,
@@ -7276,13 +7347,13 @@ class CliConfigStore {
7276
7347
  this.directory = directory;
7277
7348
  }
7278
7349
  get file() {
7279
- return path.join(this.directory, "connections.json");
7350
+ return path3.join(this.directory, "connections.json");
7280
7351
  }
7281
7352
  get credentialsDirectory() {
7282
- return path.join(this.directory, "credentials");
7353
+ return path3.join(this.directory, "credentials");
7283
7354
  }
7284
7355
  get receiptsDirectory() {
7285
- return path.join(this.directory, "receipts");
7356
+ return path3.join(this.directory, "receipts");
7286
7357
  }
7287
7358
  async read() {
7288
7359
  let text;
@@ -7338,24 +7409,24 @@ class CliConfigStore {
7338
7409
  `);
7339
7410
  }
7340
7411
  async storeCredential(name, token) {
7341
- const file = path.join(this.credentialsDirectory, `${name}.json`);
7412
+ const file = path3.join(this.credentialsDirectory, `${name}.json`);
7342
7413
  await writePrivateFile(file, `${JSON.stringify({ token })}
7343
7414
  `);
7344
7415
  return file;
7345
7416
  }
7346
7417
  async removeOwnedCredential(file) {
7347
- if (path.dirname(file) !== this.credentialsDirectory)
7418
+ if (path3.dirname(file) !== this.credentialsDirectory)
7348
7419
  return;
7349
7420
  await fs.rm(file, { force: true });
7350
7421
  }
7351
7422
  }
7352
7423
  async function writePrivateFile(file, contents) {
7353
- const directory = path.dirname(file);
7424
+ const directory = path3.dirname(file);
7354
7425
  await fs.mkdir(directory, { recursive: true, mode: 448 });
7355
7426
  await fs.chmod(directory, 448).catch(() => {
7356
7427
  return;
7357
7428
  });
7358
- const temporary = path.join(directory, `.${path.basename(file)}.${randomUUID()}.tmp`);
7429
+ const temporary = path3.join(directory, `.${path3.basename(file)}.${randomUUID()}.tmp`);
7359
7430
  try {
7360
7431
  await fs.writeFile(temporary, contents, { mode: 384, flag: "wx" });
7361
7432
  await fs.rename(temporary, file);
@@ -7367,126 +7438,1271 @@ async function writePrivateFile(file, contents) {
7367
7438
  }
7368
7439
  }
7369
7440
 
7370
- // src/client/format.ts
7371
- function table(headers, rows) {
7372
- if (rows.length === 0)
7373
- return ["(none)"];
7374
- const widths = headers.map((header, index) => Math.min(60, Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length))));
7375
- const line = (cells) => cells.map((cell, index) => {
7376
- const text = cell.length > widths[index] ? `${cell.slice(0, widths[index] - 1)}\u2026` : cell;
7377
- return text.padEnd(widths[index]);
7378
- }).join(" ").trimEnd();
7379
- return [line(headers), ...rows.map(line)];
7380
- }
7381
- function keyValues(entries) {
7382
- const width = Math.max(...entries.map(([key]) => key.length));
7383
- return entries.filter(([, value]) => value !== undefined).map(([key, value]) => {
7384
- const text = value === null ? "-" : typeof value === "object" ? JSON.stringify(value) : String(value);
7385
- return `${key.padEnd(width)} ${text}`;
7386
- });
7441
+ // ../../apps/backend/src/data-dir.ts
7442
+ import os2 from "os";
7443
+ import path4 from "path";
7444
+ // ../../apps/backend/package.json
7445
+ var package_default = {
7446
+ name: "@orkestrator/backend",
7447
+ version: "2.25.0-alpha",
7448
+ private: true,
7449
+ type: "module",
7450
+ exports: {
7451
+ "./gateway": "./src/gateway.ts"
7452
+ },
7453
+ scripts: {
7454
+ build: "bun scripts/build.ts",
7455
+ dev: "bun --watch src/main.ts",
7456
+ "dev:web": "bun --watch src/main.ts --port 34121 --fallback-host 127.0.0.1 --renderer-dev-server-url http://127.0.0.1:1420",
7457
+ start: "bun dist/main.js",
7458
+ "start:web": "bun dist/main.js --fallback-host 127.0.0.1",
7459
+ "start:web-public": "bun dist/main.js --tailscale-serve --allowed-origins https://orkestrator.dev,https://www.orkestrator.dev",
7460
+ test: "bun test --preload ../../tests/setup-node.ts src tests --parallel",
7461
+ "test:workspace": "bun test --preload ../../tests/setup-node.ts src tests --only-failures ${ORKESTRATOR_TEST_TIMINGS_DIR:+--timings=$ORKESTRATOR_TEST_TIMINGS_DIR/backend.json --update-timings} --parallel=${ORKESTRATOR_TEST_WORKERS:-2}",
7462
+ typecheck: "bunx tsc --noEmit -p tsconfig.json && bunx tsc --noEmit -p tsconfig.test.json"
7463
+ },
7464
+ dependencies: {
7465
+ "@modelcontextprotocol/node": "2.1.1",
7466
+ "@modelcontextprotocol/server": "2.3.1",
7467
+ "@opencode-ai/sdk": "1.19.0",
7468
+ "@orkestrator/protocol": "workspace:*",
7469
+ "@orkestrator/toolchain": "workspace:*",
7470
+ "@xterm/addon-serialize": "0.14.0",
7471
+ "@xterm/headless": "6.0.0",
7472
+ "chart.js": "4.5.1",
7473
+ d3: "7.9.0",
7474
+ dompurify: "3.4.17",
7475
+ mermaid: "12.2.0",
7476
+ "pdfjs-dist": "6.4.299",
7477
+ "playwright-core": "1.64.0",
7478
+ sharp: "^0.35.5",
7479
+ ws: "8.22.0",
7480
+ zod: "4.6.7"
7481
+ },
7482
+ devDependencies: {
7483
+ "@modelcontextprotocol/client": "2.3.1",
7484
+ "@types/ws": "8.18.2"
7485
+ }
7486
+ };
7487
+
7488
+ // ../../apps/backend/src/core/constants.ts
7489
+ var APP_SLUG = "orkestrator-v2";
7490
+ var AGENT_NETWORK_DOMAINS_BY_PLATFORM = Object.freeze({
7491
+ codex: Object.freeze(["chatgpt.com", "auth.openai.com"]),
7492
+ cursor: Object.freeze([
7493
+ "api2.cursor.sh",
7494
+ "api3.cursor.sh",
7495
+ "api4.cursor.sh",
7496
+ "api5.cursor.sh",
7497
+ "repo42.cursor.sh",
7498
+ "authenticator.cursor.sh",
7499
+ "marketplace.cursorapi.com",
7500
+ "cursor-cdn.com",
7501
+ "cursor.com"
7502
+ ]),
7503
+ grok: Object.freeze([
7504
+ "x.ai",
7505
+ "auth.x.ai",
7506
+ "api.x.ai",
7507
+ "cli-chat-proxy.grok.com"
7508
+ ]),
7509
+ pi: Object.freeze([
7510
+ "pi.dev",
7511
+ "radius.pi.dev",
7512
+ "api.anthropic.com",
7513
+ "api.openai.com",
7514
+ "generativelanguage.googleapis.com",
7515
+ "api.x.ai",
7516
+ "api.mistral.ai",
7517
+ "api.groq.com",
7518
+ "api.deepseek.com",
7519
+ "api.cerebras.ai",
7520
+ "api.together.ai",
7521
+ "openrouter.ai"
7522
+ ])
7523
+ });
7524
+ var MAX_CODEX_CONCURRENT_THREADS = Number.MAX_SAFE_INTEGER - 1;
7525
+ function sanitizeAppVersion(raw) {
7526
+ const trimmed = raw?.trim() ?? "";
7527
+ return /^[A-Za-z0-9._+-]{1,64}$/.test(trimmed) ? trimmed : "0.0.0";
7387
7528
  }
7388
- function projectLines(project) {
7389
- return keyValues([
7390
- ["id", project.id],
7391
- ["name", project.name],
7392
- ["remote", project.gitUrl],
7393
- ["path", project.localPath],
7394
- ["folder", project.folder],
7395
- ["environments", project.environmentCount],
7396
- ["revision", project.revision]
7397
- ]);
7529
+ var BACKEND_PACKAGE_VERSION = sanitizeAppVersion(package_default.version);
7530
+ var APP_VERSION = sanitizeAppVersion(process.env.ORKESTRATOR_VERSION?.trim() || BACKEND_PACKAGE_VERSION);
7531
+
7532
+ // ../../apps/backend/src/data-dir.ts
7533
+ function assertSupportedPlatform(platform = process.platform) {
7534
+ if (platform === "win32") {
7535
+ throw new Error("Orkestrator does not support Windows. Use macOS or Linux.");
7536
+ }
7398
7537
  }
7399
- function projectRows(projects) {
7400
- return table(["ID", "NAME", "ENVS", "PATH"], projects.map((project) => [
7401
- project.id,
7402
- project.name,
7403
- String(project.environmentCount),
7404
- project.localPath ?? "-"
7405
- ]));
7538
+ function defaultDataDir(platform = process.platform, env = process.env, home = os2.homedir()) {
7539
+ assertSupportedPlatform(platform);
7540
+ if (platform === "darwin")
7541
+ return path4.join(home, "Library", "Application Support", APP_SLUG);
7542
+ return path4.join(env.XDG_CONFIG_HOME ?? path4.join(home, ".config"), APP_SLUG);
7406
7543
  }
7407
- function environmentLines(environment) {
7408
- return keyValues([
7409
- ["id", environment.id],
7410
- ["project", environment.projectId],
7411
- ["name", environment.name],
7412
- ["branch", environment.branch],
7413
- ["type", environment.environmentType],
7414
- ["status", environment.status],
7415
- ["ready", environment.ready],
7416
- ["setup", `${environment.setup.phase}${environment.setup.overridden ? " (overridden)" : ""}`],
7417
- ["activity", environment.activity.state],
7418
- ["lifecycle error", environment.lifecycle.error],
7419
- ["workspace", environment.workspacePath],
7420
- ["base", environment.base.commit ?? environment.base.branch],
7421
- ["startup session", environment.startupSession?.sessionId]
7422
- ]);
7544
+
7545
+ // src/client/fix.ts
7546
+ var FIX_ISSUE_REPOSITORY = "orkestrator-ai/orkestrator-v2";
7547
+ var FIX_AGENTS = [
7548
+ {
7549
+ name: "claude",
7550
+ displayName: "Claude Code",
7551
+ args: (prompt, { logsDir }) => [prompt, ...logsDir ? ["--add-dir", logsDir] : []],
7552
+ managedEnv: CLAUDE_PINNED_RUNTIME_ENV
7553
+ },
7554
+ { name: "codex", displayName: "Codex", args: (prompt) => [prompt] },
7555
+ {
7556
+ name: "opencode",
7557
+ displayName: "OpenCode",
7558
+ args: (prompt) => ["--prompt", prompt],
7559
+ env: OPENCODE_PINNED_RUNTIME_ENV
7560
+ }
7561
+ ];
7562
+ var AGENT_LIST = FIX_AGENTS.map((agent) => agent.name).join(", ");
7563
+ var FIX_HELP = [
7564
+ "Usage: orkestrator --fix AGENT [--data-dir DIR]",
7565
+ "",
7566
+ "Report a problem with Orkestrator. Instead of starting Orkestrator, opens AGENT",
7567
+ `(${AGENT_LIST}) with instructions to ask you what went wrong, collect`,
7568
+ "logs and version details from this machine, and draft a GitHub issue for",
7569
+ `${FIX_ISSUE_REPOSITORY}. Nothing is filed until you approve the draft, and the`,
7570
+ "agent is told to leave Orkestrator's files and settings unchanged.",
7571
+ "",
7572
+ "The agent on PATH is used; otherwise the copy Orkestrator installed. The agent",
7573
+ "works in a new temporary directory, where it saves the draft as issue.md.",
7574
+ "",
7575
+ "Options:",
7576
+ " --data-dir DIR Orkestrator data directory to inspect (default: ORKESTRATOR_DATA_DIR,",
7577
+ " then the platform default)",
7578
+ "",
7579
+ "Examples:",
7580
+ " orkestrator --fix claude",
7581
+ " bunx orkestrator --fix codex"
7582
+ ].join(`
7583
+ `);
7584
+ var SAFE_WORD2 = /^[a-z][a-z0-9-]{0,39}$/;
7585
+ function describeToken2(token) {
7586
+ return SAFE_WORD2.test(token) ? `'${token}'` : "argument";
7423
7587
  }
7424
- function environmentRows(environments) {
7425
- return table(["ID", "NAME", "TYPE", "STATUS", "READY"], environments.map((environment) => [
7426
- environment.id,
7427
- environment.name,
7428
- environment.environmentType,
7429
- environment.status,
7430
- environment.ready ? "yes" : "no"
7431
- ]));
7588
+ function isFixInvocation(argv) {
7589
+ const end = argv.indexOf("--");
7590
+ return (end < 0 ? argv : argv.slice(0, end)).some((token) => token === "--fix" || token.startsWith("--fix="));
7432
7591
  }
7433
- function sessionRows(sessions) {
7434
- return table(["SESSION", "AGENT", "TAB", "ACTIVITY", "TITLE"], sessions.map((session) => [
7435
- session.id,
7436
- session.agent,
7437
- session.tabId,
7438
- session.activity,
7439
- session.title ?? ""
7440
- ]));
7592
+ function parseFixArguments(argv) {
7593
+ const values = {};
7594
+ let help = false;
7595
+ for (let index = 0;index < argv.length; index += 1) {
7596
+ const token = argv[index];
7597
+ if (token === "--help" || token === "-h") {
7598
+ help = true;
7599
+ continue;
7600
+ }
7601
+ const equals = token.indexOf("=");
7602
+ const name = token.startsWith("--") ? token.slice(2, equals > 0 ? equals : undefined) : null;
7603
+ if (name !== "fix" && name !== "data-dir") {
7604
+ return {
7605
+ kind: "error",
7606
+ message: name === null ? `Unexpected ${describeToken2(token)} for --fix; usage: orkestrator --fix AGENT` : `Unknown option for --fix: ${describeToken2(name)}`
7607
+ };
7608
+ }
7609
+ if (values[name] !== undefined) {
7610
+ return { kind: "error", message: `--${name} may be given only once` };
7611
+ }
7612
+ let value = "";
7613
+ if (equals > 0) {
7614
+ value = token.slice(equals + 1);
7615
+ } else if (argv[index + 1] !== undefined && !argv[index + 1].startsWith("-")) {
7616
+ value = argv[index + 1];
7617
+ index += 1;
7618
+ }
7619
+ values[name] = value;
7620
+ }
7621
+ if (help)
7622
+ return { kind: "help" };
7623
+ if (values["data-dir"] === "")
7624
+ return { kind: "error", message: "Missing value for --data-dir" };
7625
+ if (!values.fix)
7626
+ return { kind: "error", message: `--fix needs an agent: ${AGENT_LIST}` };
7627
+ const normalized = values.fix.toLowerCase();
7628
+ const known = FIX_AGENTS.find((candidate) => candidate.name === normalized);
7629
+ if (!known) {
7630
+ return {
7631
+ kind: "error",
7632
+ message: `Unknown agent ${describeToken2(normalized)} for --fix; choose ${AGENT_LIST}`
7633
+ };
7634
+ }
7635
+ const dataDir = values["data-dir"];
7636
+ return { kind: "run", agent: known.name, ...dataDir !== undefined ? { dataDir } : {} };
7441
7637
  }
7442
- function sessionLines(session) {
7443
- return keyValues([
7444
- ["id", session.id],
7445
- ["environment", session.environmentId],
7446
- ["tab", session.tabId],
7447
- ["agent", session.agent],
7448
- ["title", session.title],
7449
- ["activity", session.activity],
7450
- ["latest request", session.latestRequestId],
7451
- ["pending interactions", session.pendingInteractionCount],
7452
- ["recoverable dispatch", session.recoverableDispatch]
7453
- ]);
7638
+ function resolveFixAgent(name, options2) {
7639
+ const agent = FIX_AGENTS.find((candidate) => candidate.name === name);
7640
+ const onPath = Bun.which(agent.name, { PATH: options2.env.PATH ?? "" });
7641
+ const managed = path5.join(defaultToolchainBinDir(options2.dataDir), agent.name);
7642
+ if (onPath) {
7643
+ let isManaged = false;
7644
+ try {
7645
+ const root = realpathSync2(toolchainRootDir(options2.dataDir));
7646
+ const target = realpathSync2(onPath);
7647
+ const relative = path5.relative(root, target);
7648
+ isManaged = !relative.startsWith(`..${path5.sep}`) && relative !== ".." && !path5.isAbsolute(relative) || existsSync2(managed) && target === realpathSync2(managed);
7649
+ } catch {}
7650
+ return { agent, executable: onPath, source: isManaged ? "managed" : "path" };
7651
+ }
7652
+ if (existsSync2(managed))
7653
+ return { agent, executable: managed, source: "managed" };
7654
+ return null;
7454
7655
  }
7455
- function receiptLines(receipt) {
7456
- return keyValues([
7457
- ["operation", receipt.operationId],
7458
- ["action", receipt.action],
7459
- ["state", receipt.state],
7460
- ["stage", receipt.stage],
7461
- ["request", `${receipt.requestId} (${receipt.namespace})`],
7462
- ["replayed", receipt.replayed],
7463
- ["resources", receipt.resources],
7464
- ["dispatch", receipt.dispatch?.state],
7465
- ["execution", receipt.execution?.state],
7466
- ["evidence", receipt.execution?.evidence],
7467
- ["exit code", receipt.execution?.exitCode],
7468
- ["error", receipt.error ? `${receipt.error.code}: ${receipt.error.message}` : undefined],
7469
- ["updated", receipt.updatedAt],
7470
- ["retained until", receipt.retainedUntil]
7471
- ]);
7656
+ var MAX_LISTED_LOGS = 8;
7657
+ async function recentLogFiles(logsDir) {
7658
+ const names = await readdir(logsDir).catch(() => []);
7659
+ const files = await Promise.all(names.filter((name) => name.endsWith(".log")).map(async (name) => {
7660
+ const info = await stat(path5.join(logsDir, name)).catch(() => null);
7661
+ return info?.isFile() ? { name, bytes: info.size, modifiedMs: info.mtimeMs } : null;
7662
+ }));
7663
+ return files.filter((file) => file !== null).sort((left, right) => right.modifiedMs - left.modifiedMs).slice(0, MAX_LISTED_LOGS).map((file) => ({
7664
+ name: file.name,
7665
+ bytes: file.bytes,
7666
+ modified: new Date(file.modifiedMs).toISOString()
7667
+ }));
7472
7668
  }
7473
- function settingsLines(snapshot) {
7669
+ function processRunning(pid) {
7670
+ try {
7671
+ process.kill(pid, 0);
7672
+ return true;
7673
+ } catch (error) {
7674
+ return error.code === "EPERM";
7675
+ }
7676
+ }
7677
+ function backendInstance(dataDir) {
7678
+ let parsed;
7679
+ try {
7680
+ parsed = JSON.parse(readFileSync2(path5.join(dataDir, "backend-instance.json"), "utf8"));
7681
+ } catch {
7682
+ return null;
7683
+ }
7684
+ if (!parsed || typeof parsed !== "object")
7685
+ return null;
7686
+ const record = parsed;
7687
+ const pid = typeof record.pid === "number" ? record.pid : undefined;
7688
+ return {
7689
+ ...typeof record.appVersion === "string" ? { appVersion: record.appVersion } : {},
7690
+ ...typeof record.startedAt === "string" ? { startedAt: record.startedAt } : {},
7691
+ ...pid !== undefined ? { pid } : {},
7692
+ running: pid !== undefined && processRunning(pid)
7693
+ };
7694
+ }
7695
+ async function managedGhBinary(dataDir) {
7696
+ const activated = path5.join(defaultToolchainBinDir(dataDir), "gh");
7697
+ if (existsSync2(activated))
7698
+ return activated;
7699
+ const latestRoot = path5.join(toolchainRootDir(dataDir), "gh-latest");
7700
+ const latestVersions = await readdir(latestRoot).catch(() => []);
7701
+ for (const version of latestVersions.filter(isHostVersion).sort((left, right) => right.localeCompare(left, undefined, { numeric: true }))) {
7702
+ const candidate = path5.join(latestRoot, version, "gh");
7703
+ if (existsSync2(candidate))
7704
+ return candidate;
7705
+ }
7706
+ const root = path5.join(toolchainRootDir(dataDir), "gh");
7707
+ const versions = await readdir(root).catch(() => []);
7708
+ for (const version of versions.sort((left, right) => right.localeCompare(left, undefined, { numeric: true }))) {
7709
+ const platforms = await readdir(path5.join(root, version)).catch(() => []);
7710
+ for (const platform of platforms) {
7711
+ const candidate = path5.join(root, version, platform, "gh");
7712
+ if (existsSync2(candidate))
7713
+ return candidate;
7714
+ }
7715
+ }
7716
+ return null;
7717
+ }
7718
+ async function diagnosticConnection(dataDir, env) {
7719
+ try {
7720
+ const descriptor = realpathSync2(path5.join(dataDir, "backend-instance.json"));
7721
+ const config = await new CliConfigStore(defaultCliConfigDir(env)).read();
7722
+ for (const [name, connection] of Object.entries(config.connections)) {
7723
+ if (connection.kind !== "descriptor")
7724
+ continue;
7725
+ try {
7726
+ if (realpathSync2(connection.descriptorPath) === descriptor)
7727
+ return name;
7728
+ } catch {}
7729
+ }
7730
+ } catch {}
7731
+ return null;
7732
+ }
7733
+ function desktopAppPath(home) {
7734
+ if (process.platform !== "darwin")
7735
+ return null;
7736
+ for (const candidate of [
7737
+ "/Applications/OrkestratorV2.app",
7738
+ path5.join(home, "Applications", "OrkestratorV2.app")
7739
+ ]) {
7740
+ if (existsSync2(candidate))
7741
+ return candidate;
7742
+ }
7743
+ return null;
7744
+ }
7745
+ async function collectFixFacts(options2) {
7746
+ const { resolved, dataDir, env } = options2;
7747
+ const logsDir = path5.join(dataDir, "logs");
7748
+ const binDir = defaultToolchainBinDir(dataDir);
7749
+ return {
7750
+ agent: {
7751
+ name: resolved.agent.name,
7752
+ displayName: resolved.agent.displayName,
7753
+ executable: resolved.executable,
7754
+ source: resolved.source === "path" ? "PATH" : "Orkestrator's managed toolchain"
7755
+ },
7756
+ cliVersion: options2.version,
7757
+ cliCommand: Bun.which("orkestrator", { PATH: env.PATH ?? "" }) ? "orkestrator" : `bunx orkestrator@${options2.version}`,
7758
+ platform: process.platform,
7759
+ osRelease: `${os3.type()} ${os3.release()}`,
7760
+ arch: process.arch,
7761
+ bunVersion: Bun.version,
7762
+ now: (options2.now ?? new Date).toISOString(),
7763
+ workDir: options2.workDir,
7764
+ dataDir,
7765
+ dataDirExists: existsSync2(dataDir),
7766
+ logsDir,
7767
+ debugLogging: readApplicationLogSettings(dataDir).enabled,
7768
+ recentLogs: await recentLogFiles(logsDir),
7769
+ backend: backendInstance(dataDir),
7770
+ toolchainBinDir: existsSync2(binDir) ? binDir : null,
7771
+ managedGh: await managedGhBinary(dataDir),
7772
+ diagnosticConnection: await diagnosticConnection(dataDir, env),
7773
+ desktopApp: desktopAppPath(os3.homedir())
7774
+ };
7775
+ }
7776
+ function describeBackend(facts) {
7777
+ const backend = facts.backend;
7778
+ if (!backend)
7779
+ return "no `backend-instance.json` (no backend has run with this data directory, or it was removed)";
7780
+ const parts = [
7781
+ backend.appVersion ? `version ${backend.appVersion}` : "version not recorded",
7782
+ backend.startedAt ? `started ${backend.startedAt}` : null,
7783
+ backend.pid !== undefined ? `pid ${backend.pid} ${backend.running ? "(running)" : "(not running)"}` : null
7784
+ ];
7785
+ return parts.filter(Boolean).join(", ");
7786
+ }
7787
+ function describeLogs(facts) {
7788
+ const state = facts.debugLogging ? "on" : "off (Settings \u2192 Debug \u2192 Save Logs for Debugging); new events are not being written";
7789
+ const lines = [`- Logs directory: ${facts.logsDir} \u2014 debug logging is ${state}`];
7790
+ if (facts.recentLogs.length === 0) {
7791
+ lines.push("- Log files: none");
7792
+ } else {
7793
+ lines.push("- Most recent log files:");
7794
+ for (const log of facts.recentLogs) {
7795
+ lines.push(` - ${log.name} (${log.bytes} bytes, modified ${log.modified})`);
7796
+ }
7797
+ }
7798
+ return lines;
7799
+ }
7800
+ function buildFixPrompt(facts) {
7801
+ const issue = path5.join(facts.workDir, "issue.md");
7802
+ const gh = facts.managedGh ? `\`gh\` on PATH, or Orkestrator's copy at ${facts.managedGh}` : "`gh`";
7474
7803
  return [
7475
- `revision ${snapshot.revision}`,
7476
- ...table(["KEY", "VALUE", "EFFECTIVE", "SOURCE", "APPLIES"], snapshot.settings.map((setting) => [
7477
- setting.key,
7478
- setting.value === null || setting.value === undefined ? "-" : JSON.stringify(setting.value),
7479
- setting.effective === null || setting.effective === undefined ? "-" : JSON.stringify(setting.effective),
7480
- setting.source,
7481
- setting.application
7482
- ]))
7804
+ `I started you with \`orkestrator --fix ${facts.agent.name}\` because something in Orkestrator is not working for me. Help me report it to the Orkestrator maintainers. Your job is to (1) understand the problem by talking with me, (2) collect the evidence a maintainer needs from this machine, and (3) file a GitHub issue on https://github.com/${FIX_ISSUE_REPOSITORY} detailed enough that the maintainer can understand, reproduce and fix the problem without coming back to me with questions.`,
7805
+ "",
7806
+ "## About Orkestrator",
7807
+ "Orkestrator runs coding agents (Claude Code, Codex, OpenCode, Cursor, Grok Build, Pi) in isolated environments: local git worktrees or Docker containers. It is an Electron desktop app with a Bun backend. The backend can also run on its own through the `orkestrator` CLI (`orkestrator serve`, or as a launchd/systemd service) and be used from a browser or the iOS app, including from another computer over Tailscale. In that case the logs that matter are on the backend's computer.",
7808
+ "",
7809
+ "## This machine",
7810
+ `- Collected: ${facts.now}`,
7811
+ `- OS: ${facts.platform} (${facts.osRelease}), ${facts.arch}; Bun ${facts.bunVersion}`,
7812
+ `- Orkestrator CLI: ${facts.cliVersion} (run client commands as \`${facts.cliCommand} \u2026\`)`,
7813
+ `- Desktop app: ${facts.desktopApp ?? "not found in /Applications (on Linux, or not installed)"}`,
7814
+ `- Data directory: ${facts.dataDir}${facts.dataDirExists ? "" : " (does not exist)"}`,
7815
+ `- Backend: ${describeBackend(facts)}`,
7816
+ ...describeLogs(facts),
7817
+ `- Managed agent binaries: ${facts.toolchainBinDir ?? "none installed"}`,
7818
+ `- You: ${facts.agent.displayName} from ${facts.agent.source} (${facts.agent.executable})`,
7819
+ `- Your working directory (scratch space for notes and the draft): ${facts.workDir}`,
7820
+ "",
7821
+ "## 1. Understand the problem",
7822
+ "Start with a one- or two-sentence introduction, then ask me to describe the problem in my own words. Follow up a few questions at a time, not as one long questionnaire, until you know:",
7823
+ "- what I was trying to do, what I expected, and what happened instead, with the exact error text (I can give you a screenshot path);",
7824
+ "- the steps that reproduce it, and whether it happens every time;",
7825
+ "- when it started, and whether it began after an Orkestrator update;",
7826
+ "- where it happens: desktop app, web client, iOS app or CLI; a local-worktree, Docker or remote-host environment; and which agent and model.",
7827
+ "",
7828
+ "## 2. Collect diagnostics",
7829
+ "Gather evidence yourself instead of asking me to copy it. Before each command, say in one sentence what it checks. Stay read-only: do not edit, delete, move, restart, reinstall or update anything, and do not change Orkestrator's data directory or settings. If something needs changing, explain it and let me do it.",
7830
+ "Useful sources:",
7831
+ "- Log files: `orkestrator-ai-YYYY-MM-DD.log` is the desktop app (it includes its backend's output), `orkestrator-backend-YYYY-MM-DD.log` a standalone backend; a busy day continues in `.1.log`, `.2.log`, and so on. Search around the time of the problem (errors, warnings, stack traces, the environment or session name) rather than reading whole files.",
7832
+ "- If debug logging is off or the logs predate the problem, ask me to turn on Settings \u2192 Debug \u2192 Save Logs for Debugging, reproduce the problem, and tell you when I have.",
7833
+ facts.diagnosticConnection ? `- Run \`${facts.cliCommand} host status --connection ${facts.diagnosticConnection}\` and \`${facts.cliCommand} connection check --connection ${facts.diagnosticConnection}\` for this installation; add \`--json\` for detail. They never start or change anything.` : "- No saved CLI connection was verified for this data directory. Use the local evidence above; do not run backend status commands against the saved default or change CLI configuration.",
7834
+ "- Docker environments: `docker version`, `docker ps -a`, and `docker logs --tail 200 <container>` for the affected container.",
7835
+ "- Versions: `<agent> --version` for the agents involved, `git --version`, `gh --version`; on macOS, `defaults read /Applications/OrkestratorV2.app/Contents/Info.plist CFBundleShortVersionString` for the desktop app.",
7836
+ "- `config.json` in the data directory holds settings. Read only the keys that bear on the problem.",
7837
+ "Never open credentials: `gateway-auth.json`, anything under `agent-accounts/`, the file an `authFile` field points to, `.env` files, keychains, or any other token or auth file.",
7838
+ "",
7839
+ "Then work out what is going on: tell me what you think the cause is, how confident you are, and what you ruled out. If it is clearly a problem with my setup (for example Docker is not running or an agent is not signed in), tell me how to fix it and ask whether I still want to file an issue. Name the part of Orkestrator that seems to be at fault if the evidence shows it, but do not guess source file names you have not seen.",
7840
+ "",
7841
+ "## 3. Draft the issue",
7842
+ "GitHub issues on this repository are public. In everything you put in the issue:",
7843
+ "- remove API keys, tokens, passwords, cookies, auth headers, URLs that carry credentials, and anything read from a credential file;",
7844
+ "- replace my home directory with `~`, and my username, e-mail address, hostnames, Tailscale names, IP addresses, and private repository or project names with placeholders, unless I say they can stay;",
7845
+ "- leave out my prompts, agent conversations and project source code unless I explicitly agree to include them;",
7846
+ "- keep log excerpts to the lines that matter, with their timestamps: a few dozen lines in total.",
7847
+ "",
7848
+ `Write the issue body to ${issue}, using this structure:`,
7849
+ "",
7850
+ "```markdown",
7851
+ "## Summary",
7852
+ "## Steps to reproduce",
7853
+ "## Expected behavior",
7854
+ "## Actual behavior",
7855
+ "## Environment",
7856
+ "(Orkestrator desktop, backend and CLI versions; OS and architecture; desktop, web, iOS or CLI; local, Docker or remote host; agents, models and their versions; how often it happens; first seen; whether an update caused it)",
7857
+ "## Diagnostics",
7858
+ "(log excerpts and command output in fenced code blocks, each inside <details><summary>what it is</summary> \u2026 </details>)",
7859
+ "## Analysis",
7860
+ "(likely cause, confidence, what was ruled out)",
7861
+ "## Additional context",
7862
+ "",
7863
+ `_Reported with \`orkestrator --fix ${facts.agent.name}\`._`,
7864
+ "```",
7865
+ "",
7866
+ "Choose a specific title under 80 characters that says what fails and where. Show me the title and the complete body, and ask me to approve it or ask for changes. Do not file anything until I explicitly approve.",
7867
+ "",
7868
+ "## 4. File it",
7869
+ `- If ${gh} is signed in (\`gh auth status\`), run \`gh issue create --repo ${FIX_ISSUE_REPOSITORY} --title "<title>" --body-file "${issue}"\` and give me the URL it prints. Do not add labels, assignees or milestones.`,
7870
+ `- If \`gh\` is missing, not signed in, or the command fails, give me https://github.com/${FIX_ISSUE_REPOSITORY}/issues/new and tell me to paste in the title and the contents of ${issue}.`,
7871
+ "- Screenshots and recordings cannot be attached from the command line. If I have any, tell me to drag them into the issue on GitHub once it exists.",
7872
+ "",
7873
+ "Finish by giving me the issue URL (or where the draft is saved) and telling me the maintainer may follow up on the issue."
7874
+ ].join(`
7875
+ `);
7876
+ }
7877
+ var defaultSpawn = (command, options2) => {
7878
+ const child = Bun.spawn(command, {
7879
+ cwd: options2.cwd,
7880
+ env: options2.env,
7881
+ stdio: ["inherit", "inherit", "inherit"]
7882
+ });
7883
+ return {
7884
+ exited: child.exited,
7885
+ kill: (signal) => child.kill(signal)
7886
+ };
7887
+ };
7888
+ function failure(io, code, message, exitCode) {
7889
+ io.stderr(`error [${code}]: ${message}
7890
+ `);
7891
+ return exitCode;
7892
+ }
7893
+ async function runFix(argv, options2) {
7894
+ const { io } = options2;
7895
+ const parsed = parseFixArguments(argv);
7896
+ if (parsed.kind === "help") {
7897
+ io.stdout(`${FIX_HELP}
7898
+ `);
7899
+ return 0;
7900
+ }
7901
+ if (parsed.kind === "error") {
7902
+ io.stderr(`${FIX_HELP}
7903
+
7904
+ `);
7905
+ return failure(io, "invalid-input", parsed.message, 2);
7906
+ }
7907
+ const dataDir = path5.resolve(parsed.dataDir ?? io.env.ORKESTRATOR_DATA_DIR ?? defaultDataDir(process.platform, io.env));
7908
+ const resolved = resolveFixAgent(parsed.agent, { env: io.env, dataDir });
7909
+ if (!resolved) {
7910
+ const displayName = FIX_AGENTS.find((agent) => agent.name === parsed.agent).displayName;
7911
+ return failure(io, "not-found", `${displayName} (\`${parsed.agent}\`) is not on PATH and Orkestrator has not installed it. Install it, or pick another agent: ${AGENT_LIST}`, 3);
7912
+ }
7913
+ let workDir;
7914
+ try {
7915
+ workDir = await mkdtemp(path5.join(options2.tmpDir ?? os3.tmpdir(), "orkestrator-fix-"));
7916
+ const facts = await collectFixFacts({
7917
+ resolved,
7918
+ version: options2.version,
7919
+ dataDir,
7920
+ workDir,
7921
+ env: io.env
7922
+ });
7923
+ const agent = resolved.agent;
7924
+ const env = {
7925
+ ...io.env,
7926
+ ...agent.env,
7927
+ ...resolved.source === "managed" ? agent.managedEnv : {}
7928
+ };
7929
+ io.stderr(`Starting ${agent.displayName} to help you report a problem with Orkestrator.
7930
+ ` + `It works in ${workDir} and will ask before filing anything.
7931
+
7932
+ `);
7933
+ const child = (options2.spawn ?? defaultSpawn)([
7934
+ resolved.executable,
7935
+ ...agent.args(buildFixPrompt(facts), {
7936
+ logsDir: existsSync2(facts.logsDir) ? facts.logsDir : null
7937
+ })
7938
+ ], { cwd: workDir, env });
7939
+ const ignore = () => {};
7940
+ const forward = (signal) => child.kill(signal);
7941
+ process.on("SIGINT", ignore);
7942
+ process.on("SIGTERM", forward);
7943
+ process.on("SIGHUP", forward);
7944
+ let exitCode;
7945
+ try {
7946
+ exitCode = await child.exited;
7947
+ } finally {
7948
+ process.off("SIGINT", ignore);
7949
+ process.off("SIGTERM", forward);
7950
+ process.off("SIGHUP", forward);
7951
+ }
7952
+ return exitCode;
7953
+ } catch {
7954
+ return failure(io, "agent-failed", "Unable to start or complete the reporting agent", 1);
7955
+ } finally {
7956
+ if (workDir) {
7957
+ const remaining = await readdir(workDir).catch(() => []);
7958
+ if (remaining.length === 0) {
7959
+ await rmdir(workDir).catch(() => {});
7960
+ } else {
7961
+ io.stderr(`
7962
+ Notes and the issue draft are in ${workDir}
7963
+ `);
7964
+ }
7965
+ }
7966
+ }
7967
+ }
7968
+ function runFixProcess(argv, version) {
7969
+ return runFix(argv, {
7970
+ version,
7971
+ io: {
7972
+ env: process.env,
7973
+ stdout: (text) => process.stdout.write(text),
7974
+ stderr: (text) => process.stderr.write(text)
7975
+ }
7976
+ });
7977
+ }
7978
+
7979
+ // ../protocol/src/public-api-resources.ts
7980
+ var PUBLIC_CHANGE_SCOPES = Object.freeze(["environment"]);
7981
+ var AGENT_PLATFORM_KEYS = ["claude", "codex", "cursor", "grok", "opencode", "pi"];
7982
+ function agentSettingDescriptors(scope) {
7983
+ const application = "next-session";
7984
+ return [
7985
+ {
7986
+ key: "agent.defaultAgent",
7987
+ scope,
7988
+ type: "agent",
7989
+ inherits: true,
7990
+ application,
7991
+ description: "Agent platform used when a launch does not name one."
7992
+ },
7993
+ ...AGENT_PLATFORM_KEYS.flatMap((platform) => [
7994
+ {
7995
+ key: `agent.${platform}.mode`,
7996
+ scope,
7997
+ type: "agent-mode",
7998
+ inherits: true,
7999
+ application,
8000
+ description: `Launch mode (native or terminal) for ${platform}.`
8001
+ },
8002
+ {
8003
+ key: `agent.${platform}.model`,
8004
+ scope,
8005
+ type: "string",
8006
+ inherits: true,
8007
+ application,
8008
+ description: `Default model for new ${platform} sessions.`
8009
+ },
8010
+ {
8011
+ key: `agent.${platform}.reasoningEffort`,
8012
+ scope,
8013
+ type: "string",
8014
+ inherits: true,
8015
+ application,
8016
+ description: `Default reasoning level for new ${platform} sessions.`
8017
+ },
8018
+ {
8019
+ key: `agent.${platform}.fastMode`,
8020
+ scope,
8021
+ type: "boolean",
8022
+ inherits: true,
8023
+ application,
8024
+ description: `Default speed for new ${platform} sessions (true is Fast).`
8025
+ }
8026
+ ])
7483
8027
  ];
7484
8028
  }
8029
+ var PUBLIC_PROJECT_SETTINGS = Object.freeze([
8030
+ {
8031
+ key: "defaultBranch",
8032
+ scope: "project",
8033
+ type: "string",
8034
+ inherits: false,
8035
+ application: "next-environment",
8036
+ description: "Branch new environments are created from."
8037
+ },
8038
+ {
8039
+ key: "prBaseBranch",
8040
+ scope: "project",
8041
+ type: "string",
8042
+ inherits: false,
8043
+ application: "applied",
8044
+ description: "Base branch for pull requests and diff baselines."
8045
+ },
8046
+ {
8047
+ key: "defaultPortMappings",
8048
+ scope: "project",
8049
+ type: "port-mappings",
8050
+ inherits: false,
8051
+ environmentTypes: ["container"],
8052
+ application: "next-environment",
8053
+ description: "Port mappings copied into new container environments."
8054
+ },
8055
+ {
8056
+ key: "filesToCopy",
8057
+ scope: "project",
8058
+ type: "string-list",
8059
+ inherits: false,
8060
+ application: "next-environment",
8061
+ description: "Untracked files copied into new environments."
8062
+ },
8063
+ {
8064
+ key: "entryPort",
8065
+ scope: "project",
8066
+ type: "port",
8067
+ inherits: false,
8068
+ application: "next-environment",
8069
+ description: "Default preview entry port for new environments."
8070
+ },
8071
+ ...agentSettingDescriptors("project")
8072
+ ]);
8073
+ var PUBLIC_ENVIRONMENT_SETTINGS = Object.freeze([
8074
+ {
8075
+ key: "portMappings",
8076
+ scope: "environment",
8077
+ type: "port-mappings",
8078
+ inherits: false,
8079
+ environmentTypes: ["container"],
8080
+ application: "next-start",
8081
+ description: "Container port mappings; a running container adopts them on recreate."
8082
+ },
8083
+ {
8084
+ key: "allowedDomains",
8085
+ scope: "environment",
8086
+ type: "domain-list",
8087
+ inherits: true,
8088
+ environmentTypes: ["container"],
8089
+ application: "next-start",
8090
+ description: "Restricted-network allowlist; unset inherits the global list. A running container whose image supports it applies the change immediately (reported as applied)."
8091
+ },
8092
+ ...agentSettingDescriptors("environment")
8093
+ ]);
8094
+
8095
+ // src/client/format.ts
8096
+ function table(headers, rows) {
8097
+ if (rows.length === 0)
8098
+ return ["(none)"];
8099
+ const widths = headers.map((header, index) => Math.min(60, Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length))));
8100
+ const line = (cells) => cells.map((cell, index) => {
8101
+ const text = cell.length > widths[index] ? `${cell.slice(0, widths[index] - 1)}\u2026` : cell;
8102
+ return text.padEnd(widths[index]);
8103
+ }).join(" ").trimEnd();
8104
+ return [line(headers), ...rows.map(line)];
8105
+ }
8106
+ function keyValues(entries) {
8107
+ const width = Math.max(...entries.map(([key]) => key.length));
8108
+ return entries.filter(([, value]) => value !== undefined).map(([key, value]) => {
8109
+ const text = value === null ? "-" : typeof value === "object" ? JSON.stringify(value) : String(value);
8110
+ return `${key.padEnd(width)} ${text}`;
8111
+ });
8112
+ }
8113
+ function projectLines(project) {
8114
+ return keyValues([
8115
+ ["id", project.id],
8116
+ ["name", project.name],
8117
+ ["remote", project.gitUrl],
8118
+ ["path", project.localPath],
8119
+ ["folder", project.folder],
8120
+ ["environments", project.environmentCount],
8121
+ ["revision", project.revision]
8122
+ ]);
8123
+ }
8124
+ function projectRows(projects) {
8125
+ return table(["ID", "NAME", "ENVS", "PATH"], projects.map((project) => [
8126
+ project.id,
8127
+ project.name,
8128
+ String(project.environmentCount),
8129
+ project.localPath ?? "-"
8130
+ ]));
8131
+ }
8132
+ function environmentLines(environment) {
8133
+ return keyValues([
8134
+ ["id", environment.id],
8135
+ ["project", environment.projectId],
8136
+ ["name", environment.name],
8137
+ ["branch", environment.branch],
8138
+ ["type", environment.environmentType],
8139
+ ["status", environment.status],
8140
+ ["ready", environment.ready],
8141
+ ["setup", `${environment.setup.phase}${environment.setup.overridden ? " (overridden)" : ""}`],
8142
+ ["activity", environment.activity.state],
8143
+ ["lifecycle error", environment.lifecycle.error],
8144
+ ["workspace", environment.workspacePath],
8145
+ ["base", environment.base.commit ?? environment.base.branch],
8146
+ ["startup session", environment.startupSession?.sessionId]
8147
+ ]);
8148
+ }
8149
+ function environmentRows(environments) {
8150
+ return table(["ID", "NAME", "TYPE", "STATUS", "READY"], environments.map((environment) => [
8151
+ environment.id,
8152
+ environment.name,
8153
+ environment.environmentType,
8154
+ environment.status,
8155
+ environment.ready ? "yes" : "no"
8156
+ ]));
8157
+ }
8158
+ function sessionRows(sessions) {
8159
+ return table(["SESSION", "AGENT", "TAB", "ACTIVITY", "TITLE"], sessions.map((session) => [
8160
+ session.id,
8161
+ session.agent,
8162
+ session.tabId,
8163
+ session.activity,
8164
+ session.title ?? ""
8165
+ ]));
8166
+ }
8167
+ function sessionLines(session) {
8168
+ return keyValues([
8169
+ ["id", session.id],
8170
+ ["environment", session.environmentId],
8171
+ ["tab", session.tabId],
8172
+ ["agent", session.agent],
8173
+ ["title", session.title],
8174
+ ["activity", session.activity],
8175
+ ["latest request", session.latestRequestId],
8176
+ ["pending interactions", session.pendingInteractionCount],
8177
+ ["recoverable dispatch", session.recoverableDispatch]
8178
+ ]);
8179
+ }
8180
+ function receiptLines(receipt) {
8181
+ return keyValues([
8182
+ ["operation", receipt.operationId],
8183
+ ["action", receipt.action],
8184
+ ["state", receipt.state],
8185
+ ["stage", receipt.stage],
8186
+ ["request", `${receipt.requestId} (${receipt.namespace})`],
8187
+ ["replayed", receipt.replayed],
8188
+ ["resources", receipt.resources],
8189
+ ["dispatch", receipt.dispatch?.state],
8190
+ ["execution", receipt.execution?.state],
8191
+ ["evidence", receipt.execution?.evidence],
8192
+ ["exit code", receipt.execution?.exitCode],
8193
+ ["error", receipt.error ? `${receipt.error.code}: ${receipt.error.message}` : undefined],
8194
+ ["updated", receipt.updatedAt],
8195
+ ["retained until", receipt.retainedUntil]
8196
+ ]);
8197
+ }
8198
+ function settingsLines(snapshot) {
8199
+ return [
8200
+ `revision ${snapshot.revision}`,
8201
+ ...table(["KEY", "VALUE", "EFFECTIVE", "SOURCE", "APPLIES"], snapshot.settings.map((setting) => [
8202
+ setting.key,
8203
+ setting.value === null || setting.value === undefined ? "-" : JSON.stringify(setting.value),
8204
+ setting.effective === null || setting.effective === undefined ? "-" : JSON.stringify(setting.effective),
8205
+ setting.source,
8206
+ setting.application
8207
+ ]))
8208
+ ];
8209
+ }
8210
+
8211
+ // src/client/wait.ts
8212
+ class ObservationStopped extends Error {
8213
+ reason;
8214
+ signalName;
8215
+ constructor(reason, signalName) {
8216
+ super(reason === "deadline" ? "Observation deadline passed" : "Observation interrupted");
8217
+ this.reason = reason;
8218
+ this.signalName = signalName;
8219
+ }
8220
+ }
8221
+ function defaultSleep(ms, signal) {
8222
+ return new Promise((resolve) => {
8223
+ if (signal?.aborted) {
8224
+ resolve();
8225
+ return;
8226
+ }
8227
+ const timer = setTimeout(done, ms);
8228
+ function done() {
8229
+ clearTimeout(timer);
8230
+ signal?.removeEventListener("abort", done);
8231
+ resolve();
8232
+ }
8233
+ signal?.addEventListener("abort", done, { once: true });
8234
+ });
8235
+ }
8236
+ function signalName(signal) {
8237
+ return signal?.reason === "SIGTERM" ? "SIGTERM" : "SIGINT";
8238
+ }
8239
+ async function observe(check, options2) {
8240
+ if (options2.timeoutMs > PUBLIC_API_LIMITS.waitMaxMs) {
8241
+ throw new CliError("invalid-input", "--timeout may be at most 24h");
8242
+ }
8243
+ const now = options2.now ?? Date.now;
8244
+ const sleep = options2.sleep ?? defaultSleep;
8245
+ const deadline = now() + options2.timeoutMs;
8246
+ let delay = 250;
8247
+ for (;; ) {
8248
+ if (options2.signal?.aborted)
8249
+ throw new ObservationStopped("signal", signalName(options2.signal));
8250
+ const step = await check();
8251
+ if (step.done)
8252
+ return step.value;
8253
+ const remaining = deadline - now();
8254
+ if (remaining <= 0)
8255
+ throw new ObservationStopped("deadline");
8256
+ await sleep(Math.min(delay, remaining), options2.signal);
8257
+ delay = Math.min(delay * 2, 2000);
8258
+ }
8259
+ }
8260
+
8261
+ // src/client/commands/common.ts
8262
+ var REQUEST_ID_OPTION = {
8263
+ name: "request-id",
8264
+ kind: "string",
8265
+ valueName: "KEY",
8266
+ description: "Idempotency key. Reusing it returns the original operation; reusing it with a different intent conflicts. Generated and saved locally when omitted."
8267
+ };
8268
+ var TIMEOUT_OPTION = {
8269
+ name: "timeout",
8270
+ kind: "duration",
8271
+ valueName: "DURATION",
8272
+ description: "How long to observe before exiting 5 (the operation keeps running). Default 10m."
8273
+ };
8274
+ var PROMPT_OPTIONS = [
8275
+ {
8276
+ name: "prompt-file",
8277
+ kind: "string",
8278
+ valueName: "PATH",
8279
+ description: "Read the prompt from a local file."
8280
+ },
8281
+ { name: "prompt-stdin", kind: "boolean", description: "Read the prompt from stdin." },
8282
+ {
8283
+ name: "prompt",
8284
+ kind: "string",
8285
+ valueName: "TEXT",
8286
+ description: "Inline prompt (prefer a file for long text)."
8287
+ }
8288
+ ];
8289
+ var DEFAULT_WAIT_MS = 10 * 60 * 1000;
8290
+ function stringOption(options2, name) {
8291
+ const value = options2[name];
8292
+ return typeof value === "string" ? value : undefined;
8293
+ }
8294
+ function oneOf(value, allowed, label) {
8295
+ if (typeof value !== "string" || !allowed.includes(value)) {
8296
+ throw new CliError("invalid-input", `${label} must be one of: ${allowed.join(", ")}`);
8297
+ }
8298
+ return value;
8299
+ }
8300
+ function optionalBoolean(options2, name) {
8301
+ const value = options2[name];
8302
+ return typeof value === "boolean" ? value : undefined;
8303
+ }
8304
+ async function submit(context, action, input, options2) {
8305
+ const session = await context.session();
8306
+ const outcome = await session.mutate(action, input, stringOption(options2, "request-id"));
8307
+ return { session, ...outcome };
8308
+ }
8309
+ async function readOperation(session, operationId) {
8310
+ const { receipt } = await session.readWithReceipt("run.get", { operationId });
8311
+ if (!receipt)
8312
+ throw new CliError("response-invalid", "run.get returned no receipt");
8313
+ return receipt;
8314
+ }
8315
+ function isTerminalReceipt(receipt) {
8316
+ return !["admitted", "running", "unknown"].includes(receipt.state);
8317
+ }
8318
+ function stoppedError(error, action, receipt, what) {
8319
+ if (error.reason === "signal") {
8320
+ return new CliError("observation-interrupted", `Stopped observing ${what}; it continues in the backend`, {
8321
+ action,
8322
+ ...receipt ? { receipt } : {},
8323
+ details: { signal: error.signalName ?? "SIGINT" }
8324
+ });
8325
+ }
8326
+ return new CliError("deadline-exceeded", `Timed out observing ${what}; it continues in the backend`, {
8327
+ action,
8328
+ ...receipt ? { receipt } : {}
8329
+ });
8330
+ }
8331
+ function receiptFailure(receipt) {
8332
+ if (receipt.state === "succeeded")
8333
+ return;
8334
+ const message = receipt.error?.message ?? `Operation ended ${receipt.state}`;
8335
+ const code = receipt.state === "partial" ? "partial-failure" : receipt.state === "cancelled" ? "run-cancelled" : receipt.state === "interrupted" ? "run-interrupted" : receipt.state === "unknown" ? "run-unknown" : receipt.error?.code ?? "operation-failed";
8336
+ return new CliError(code, message, { receipt, action: receipt.action });
8337
+ }
8338
+ async function waitForOperation(context, session, initial, timeoutMs, options2 = {}) {
8339
+ let latest = initial;
8340
+ try {
8341
+ return await observe(async () => {
8342
+ latest = await readOperation(session, initial.operationId);
8343
+ if (isTerminalReceipt(latest))
8344
+ return { done: true, value: latest };
8345
+ const execution = latest.execution;
8346
+ if (execution?.state === "waiting-for-input" && !options2.continueOnInteraction) {
8347
+ throw new CliError("interaction-required", "The run is waiting for an answer to a pending interaction", {
8348
+ receipt: latest,
8349
+ action: latest.action,
8350
+ details: { interactions: execution.interactions ?? [] }
8351
+ });
8352
+ }
8353
+ if (latest.state === "unknown" && latest.dispatch?.state === "unknown") {
8354
+ throw new CliError("dispatch-unknown", "The provider may or may not have received the prompt; use `run retry` (same key) or `run discard`", { receipt: latest, action: latest.action, retryable: true });
8355
+ }
8356
+ if (latest.state === "unknown") {
8357
+ throw new CliError("run-unknown", latest.error?.message ?? "The outcome of this run is not known", {
8358
+ receipt: latest,
8359
+ action: latest.action
8360
+ });
8361
+ }
8362
+ if (execution?.state === "unsupported") {
8363
+ throw new CliError("capability-unavailable", execution.reason ?? "Completion of this run cannot be observed for its provider", { receipt: latest, action: latest.action });
8364
+ }
8365
+ return { done: false };
8366
+ }, { timeoutMs, signal: context.signal });
8367
+ } catch (error) {
8368
+ if (error instanceof ObservationStopped) {
8369
+ throw stoppedError(error, latest.action, latest, "the operation");
8370
+ }
8371
+ throw error;
8372
+ }
8373
+ }
8374
+ async function waitForEnvironment(context, session, environmentId, condition, receipt, timeoutMs) {
8375
+ let latestReceipt = receipt;
8376
+ const conditionMet = (environment) => {
8377
+ if (condition === "deleted")
8378
+ return environment === null;
8379
+ if (!environment)
8380
+ return false;
8381
+ if (condition === "running")
8382
+ return environment.status === "running";
8383
+ if (condition === "stopped")
8384
+ return environment.status === "stopped";
8385
+ return environment.ready;
8386
+ };
8387
+ try {
8388
+ return await observe(async () => {
8389
+ if (latestReceipt && !isTerminalReceipt(latestReceipt)) {
8390
+ latestReceipt = await readOperation(session, latestReceipt.operationId);
8391
+ }
8392
+ if (latestReceipt && isTerminalReceipt(latestReceipt)) {
8393
+ const failure = receiptFailure(latestReceipt);
8394
+ if (failure)
8395
+ throw failure;
8396
+ }
8397
+ let environment;
8398
+ try {
8399
+ environment = await session.read("environment.get", {
8400
+ environmentId
8401
+ });
8402
+ } catch (error) {
8403
+ if (error instanceof CliError && error.code === "not-found")
8404
+ environment = null;
8405
+ else
8406
+ throw error;
8407
+ }
8408
+ if (condition !== "deleted" && !environment) {
8409
+ throw new CliError("not-found", "The environment no longer exists", latestReceipt ? { receipt: latestReceipt } : {});
8410
+ }
8411
+ if (environment && (condition === "ready" || condition === "running")) {
8412
+ if (environment.setup.phase === "failed" || environment.status === "error") {
8413
+ throw new CliError("setup-failed", environment.lifecycle.error ?? "Environment setup failed", { ...latestReceipt ? { receipt: latestReceipt } : {}, details: { environmentId } });
8414
+ }
8415
+ }
8416
+ const operationSettled = !latestReceipt || isTerminalReceipt(latestReceipt) || condition === "running" && latestReceipt.stage === "setup";
8417
+ if (operationSettled && conditionMet(environment)) {
8418
+ return {
8419
+ done: true,
8420
+ value: { environment, ...latestReceipt ? { receipt: latestReceipt } : {} }
8421
+ };
8422
+ }
8423
+ return { done: false };
8424
+ }, { timeoutMs, signal: context.signal });
8425
+ } catch (error) {
8426
+ if (error instanceof ObservationStopped) {
8427
+ throw stoppedError(error, latestReceipt?.action ?? "environment.get", latestReceipt, `the environment (${condition})`);
8428
+ }
8429
+ throw error;
8430
+ }
8431
+ }
8432
+
8433
+ // src/client/commands/changes.ts
8434
+ var SCOPE_LABELS = {
8435
+ "changes.scope.environment-net": "Environment changes",
8436
+ "changes.scope.turn-net": "Turn changes",
8437
+ "changes.scope.provider-turn-diff": "Provider-reported turn changes",
8438
+ "changes.scope.thread-net": "Thread changes"
8439
+ };
8440
+ var PROVENANCE_LABELS = {
8441
+ "changes.provenance.observed": "observed in the workspace",
8442
+ "changes.provenance.observed-fresh": "measured in the workspace",
8443
+ "changes.provenance.provider-reported": "reported by the provider",
8444
+ "changes.provenance.unknown": "unknown"
8445
+ };
8446
+ function label(key, labels) {
8447
+ return labels[key] ?? key.replace(/^changes\.[a-z-]+\./, "").replaceAll("-", " ");
8448
+ }
8449
+ function formatLineCount(sign, count) {
8450
+ if (count.quality === "not-applicable")
8451
+ return "";
8452
+ if (count.value === null)
8453
+ return `${sign}?`;
8454
+ const value = `${sign}${count.value}`;
8455
+ if (count.bound === "lower")
8456
+ return `\u2265${value}`;
8457
+ if (count.bound === "upper")
8458
+ return `\u2264${value}`;
8459
+ return count.quality === "complete" ? value : `~${value}`;
8460
+ }
8461
+ function fileCount(count) {
8462
+ if (count === null)
8463
+ return "? files";
8464
+ return `${count} file${count === 1 ? "" : "s"}`;
8465
+ }
8466
+ function countsSummary(projection) {
8467
+ if (projection.measurementId === null) {
8468
+ return projection.state === "unavailable" ? "unavailable" : "not measured yet";
8469
+ }
8470
+ const { additions, deletions, changedFiles } = projection.counters;
8471
+ const lines = [formatLineCount("+", additions), formatLineCount("-", deletions)].filter(Boolean).join(" ");
8472
+ return [fileCount(changedFiles), lines].filter(Boolean).join(" ");
8473
+ }
8474
+ function rowPath(row) {
8475
+ if (row.oldPathDisplay && row.newPathDisplay && row.oldPathDisplay !== row.newPathDisplay) {
8476
+ return `${row.oldPathDisplay} \u2192 ${row.newPathDisplay}`;
8477
+ }
8478
+ return row.newPathDisplay ?? row.oldPathDisplay ?? row.pathId;
8479
+ }
8480
+ function rowCounts(row) {
8481
+ if (row.additions.quality === "not-applicable" && row.deletions.quality === "not-applicable") {
8482
+ return row.fileKind;
8483
+ }
8484
+ return [formatLineCount("+", row.additions), formatLineCount("-", row.deletions)].filter(Boolean).join(" ");
8485
+ }
8486
+ function changesViewLines(view) {
8487
+ const { projection, files } = view;
8488
+ const states = [
8489
+ projection.state,
8490
+ `observation ${projection.observationState}`,
8491
+ `coverage ${projection.coverageState}`
8492
+ ].join(", ");
8493
+ const lines = [
8494
+ `${label(projection.labelKey, SCOPE_LABELS)} (environment ${view.environmentId})`,
8495
+ ...keyValues([
8496
+ ["state", states],
8497
+ ["changes", countsSummary(projection)],
8498
+ ["provenance", label(projection.provenanceLabelKey, PROVENANCE_LABELS)],
8499
+ ["checked", projection.freshness.lastSuccessfulCheckAt],
8500
+ [
8501
+ "reasons",
8502
+ projection.reasonKeys.length > 0 ? projection.reasonKeys.map((key) => label(key, {})).join("; ") : undefined
8503
+ ]
8504
+ ])
8505
+ ];
8506
+ if (files.rows.length > 0) {
8507
+ lines.push("", ...table(["FILE", "STATUS", "LINES", "NOTE"], files.rows.map((row) => [
8508
+ rowPath(row),
8509
+ row.status,
8510
+ rowCounts(row),
8511
+ row.reasonKey ? label(row.reasonKey, {}) : ""
8512
+ ])));
8513
+ }
8514
+ if (files.nextCursor) {
8515
+ lines.push("", `${files.rows.length} of ${files.totalRows ?? "?"} files shown; next page: --cursor ${files.nextCursor}`);
8516
+ }
8517
+ return lines;
8518
+ }
8519
+ function changesHistoryLines(history) {
8520
+ const lines = table(["TURN", "LIFECYCLE", "FINISHED", "CHANGES"], history.entries.map((entry) => {
8521
+ const projection = entry.projections[entry.turnResourceKey];
8522
+ return [
8523
+ entry.turnId,
8524
+ entry.lifecycle,
8525
+ entry.finishedAt ?? "-",
8526
+ projection ? countsSummary(projection) : "unavailable"
8527
+ ];
8528
+ }));
8529
+ if (history.nextCursor)
8530
+ lines.push("", `newer turns: --cursor ${history.nextCursor}`);
8531
+ return lines;
8532
+ }
8533
+ var changesCommands = [
8534
+ {
8535
+ path: ["changes", "view"],
8536
+ summary: "Show an environment's file and line changes as the backend measures them.",
8537
+ description: "Read-only: prints the backend's change projection for the environment \u2014 the numbers the desktop shows \u2014 and one page of changed files. Counts carry their quality: ~ partial or estimated, ? unknown, \u2265/\u2264 a bound; binary files and submodules have no line counts. Nothing is ever shown as zero unless the backend measured zero. Pass the printed cursor as --cursor for the next page; a cursor stops working once the changes move on. --json prints the action result verbatim ({projection, files}).",
8538
+ positionals: [{ name: "environment", required: true, description: "Environment ID." }],
8539
+ options: [
8540
+ {
8541
+ name: "scope",
8542
+ kind: "string",
8543
+ valueName: PUBLIC_CHANGE_SCOPES.join("|"),
8544
+ description: "What to compare (default environment: its baseline to its workspace now)."
8545
+ },
8546
+ {
8547
+ name: "page-size",
8548
+ kind: "integer",
8549
+ valueName: "N",
8550
+ description: "Files per page (1-200, default 200)."
8551
+ },
8552
+ {
8553
+ name: "cursor",
8554
+ kind: "string",
8555
+ valueName: "CURSOR",
8556
+ description: "Next-page cursor from a previous read."
8557
+ }
8558
+ ],
8559
+ examples: [
8560
+ 'orkestrator changes view "$ENV"',
8561
+ `orkestrator changes view "$ENV" --page-size 20 --json | jq '.result.projection.counters'`
8562
+ ],
8563
+ async run(context, parsed) {
8564
+ const input = { environmentId: parsed.positionals.environment };
8565
+ const scope = stringOption(parsed.options, "scope");
8566
+ if (scope !== undefined)
8567
+ input.scope = oneOf(scope, PUBLIC_CHANGE_SCOPES, "--scope");
8568
+ if (parsed.options["page-size"] !== undefined)
8569
+ input.pageSize = parsed.options["page-size"];
8570
+ const cursor = stringOption(parsed.options, "cursor");
8571
+ if (cursor !== undefined)
8572
+ input.cursor = cursor;
8573
+ const session = await context.session();
8574
+ const view = await session.read("changes.view", input);
8575
+ return {
8576
+ action: "changes.view",
8577
+ result: view,
8578
+ connection: session.identity,
8579
+ human: changesViewLines(view)
8580
+ };
8581
+ }
8582
+ },
8583
+ {
8584
+ path: ["changes", "history"],
8585
+ summary: "List a session's turns, oldest first, with the changes each one made.",
8586
+ description: "Read-only: each turn's backend measurement from its start to its end, with the same count qualities as `changes view`. No workspace scan, transcript or provider read. Pass the printed cursor as --cursor for newer turns.",
8587
+ positionals: [{ name: "session", required: true, description: "Session ID (ses_\u2026)." }],
8588
+ options: [
8589
+ {
8590
+ name: "limit",
8591
+ kind: "integer",
8592
+ valueName: "N",
8593
+ description: "Turns per page (1-200, default 50)."
8594
+ },
8595
+ {
8596
+ name: "cursor",
8597
+ kind: "string",
8598
+ valueName: "CURSOR",
8599
+ description: "Cursor from a previous page."
8600
+ }
8601
+ ],
8602
+ async run(context, parsed) {
8603
+ const input = { sessionId: parsed.positionals.session };
8604
+ if (parsed.options.limit !== undefined)
8605
+ input.limit = parsed.options.limit;
8606
+ const cursor = stringOption(parsed.options, "cursor");
8607
+ if (cursor !== undefined)
8608
+ input.cursor = cursor;
8609
+ const session = await context.session();
8610
+ const history = await session.read("changes.history", input);
8611
+ return {
8612
+ action: "changes.history",
8613
+ result: history,
8614
+ connection: session.identity,
8615
+ human: changesHistoryLines(history)
8616
+ };
8617
+ }
8618
+ }
8619
+ ];
8620
+
8621
+ // src/client/commands/connection.ts
8622
+ import path9 from "path";
8623
+ import { randomUUID as randomUUID3 } from "crypto";
8624
+ import { promises as fs4 } from "fs";
8625
+
8626
+ // ../protocol/src/backend-instance.ts
8627
+ var BACKEND_INSTANCE_DESCRIPTOR_FILE = "backend-instance.json";
8628
+ var BACKEND_INSTANCE_DESCRIPTOR_TYPE = "orkestrator-backend-instance";
8629
+ var BACKEND_INSTANCE_DESCRIPTOR_MAX_BYTES = 16 * 1024;
8630
+ var BACKEND_INSTANCE_MAX_ENDPOINTS = 8;
8631
+ function isRecord3(value) {
8632
+ return value !== null && typeof value === "object" && !Array.isArray(value);
8633
+ }
8634
+ function isHttpUrl(value) {
8635
+ if (typeof value !== "string" || value.length > 2048)
8636
+ return false;
8637
+ try {
8638
+ const url = new URL(value);
8639
+ return (url.protocol === "http:" || url.protocol === "https:") && url.username === "" && url.password === "" && url.search === "" && url.hash === "";
8640
+ } catch {
8641
+ return false;
8642
+ }
8643
+ }
8644
+ function parseEndpoints(value) {
8645
+ if (!Array.isArray(value) || value.length > BACKEND_INSTANCE_MAX_ENDPOINTS) {
8646
+ throw new Error("Backend instance descriptor field is invalid: endpoints");
8647
+ }
8648
+ return value.map((entry) => {
8649
+ if (!isRecord3(entry) || entry.role !== "control" && entry.role !== "client" || !isHttpUrl(entry.url)) {
8650
+ throw new Error("Backend instance descriptor field is invalid: endpoints");
8651
+ }
8652
+ return { role: entry.role, url: entry.url };
8653
+ });
8654
+ }
8655
+ function parseBackendInstanceDescriptor(value) {
8656
+ if (!isRecord3(value))
8657
+ throw new Error("Backend instance descriptor must be an object");
8658
+ if (value.version !== 1 || value.type !== BACKEND_INSTANCE_DESCRIPTOR_TYPE) {
8659
+ throw new Error("Backend instance descriptor has an unsupported version");
8660
+ }
8661
+ const text = (key, max = 512) => {
8662
+ const field = value[key];
8663
+ if (typeof field !== "string" || field.length === 0 || field.length > max) {
8664
+ throw new Error(`Backend instance descriptor field is invalid: ${key}`);
8665
+ }
8666
+ return field;
8667
+ };
8668
+ if (!Number.isSafeInteger(value.pid) || value.pid <= 0) {
8669
+ throw new Error("Backend instance descriptor field is invalid: pid");
8670
+ }
8671
+ if (!isHttpUrl(value.url))
8672
+ throw new Error("Backend instance descriptor field is invalid: url");
8673
+ if (value.browserUrl !== undefined && !isHttpUrl(value.browserUrl)) {
8674
+ throw new Error("Backend instance descriptor field is invalid: browserUrl");
8675
+ }
8676
+ const endpoints = value.endpoints === undefined ? undefined : parseEndpoints(value.endpoints);
8677
+ if (!Number.isSafeInteger(value.publicApiSchemaVersion) || value.publicApiSchemaVersion < 1) {
8678
+ throw new Error("Backend instance descriptor field is invalid: publicApiSchemaVersion");
8679
+ }
8680
+ const authFile = text("authFile", 4096);
8681
+ const dataDir = text("dataDir", 4096);
8682
+ if (!authFile.startsWith("/") || !dataDir.startsWith("/")) {
8683
+ throw new Error("Backend instance descriptor paths must be absolute");
8684
+ }
8685
+ return {
8686
+ version: 1,
8687
+ type: BACKEND_INSTANCE_DESCRIPTOR_TYPE,
8688
+ installationId: text("installationId", 200),
8689
+ generation: text("generation", 200),
8690
+ pid: value.pid,
8691
+ startedAt: text("startedAt", 64),
8692
+ url: value.url,
8693
+ ...value.browserUrl !== undefined ? { browserUrl: value.browserUrl } : {},
8694
+ ...endpoints !== undefined ? { endpoints } : {},
8695
+ authFile,
8696
+ dataDir,
8697
+ appVersion: text("appVersion", 64),
8698
+ publicApiSchemaVersion: value.publicApiSchemaVersion
8699
+ };
8700
+ }
7485
8701
 
7486
8702
  // src/client/receipts.ts
7487
8703
  import { createHash } from "crypto";
7488
8704
  import { promises as fs2 } from "fs";
7489
- import path2 from "path";
8705
+ import path6 from "path";
7490
8706
  var MAX_LOCAL_RECEIPTS_PER_INSTALLATION = 1000;
7491
8707
  var MAX_RECEIPT_BYTES = 8 * 1024;
7492
8708
  function receiptKey(action, requestId) {
@@ -7502,10 +8718,10 @@ class LocalReceiptStore {
7502
8718
  this.root = root;
7503
8719
  }
7504
8720
  directory(installationId) {
7505
- return path2.join(this.root, safeSegment(installationId));
8721
+ return path6.join(this.root, safeSegment(installationId));
7506
8722
  }
7507
8723
  file(installationId, action, requestId) {
7508
- return path2.join(this.directory(installationId), `${receiptKey(action, requestId)}.json`);
8724
+ return path6.join(this.directory(installationId), `${receiptKey(action, requestId)}.json`);
7509
8725
  }
7510
8726
  async find(installationId, action, requestId) {
7511
8727
  return this.readFile(this.file(installationId, action, requestId));
@@ -7529,14 +8745,14 @@ class LocalReceiptStore {
7529
8745
  });
7530
8746
  }
7531
8747
  async list(installationId, limit = 50) {
7532
- const directories = installationId ? [this.directory(installationId)] : (await fs2.readdir(this.root).catch(() => [])).map((name) => path2.join(this.root, name));
8748
+ const directories = installationId ? [this.directory(installationId)] : (await fs2.readdir(this.root).catch(() => [])).map((name) => path6.join(this.root, name));
7533
8749
  const receipts = [];
7534
8750
  for (const directory of directories) {
7535
8751
  const names = await fs2.readdir(directory).catch(() => []);
7536
8752
  for (const name of names) {
7537
8753
  if (!name.endsWith(".json"))
7538
8754
  continue;
7539
- const receipt = await this.readFile(path2.join(directory, name));
8755
+ const receipt = await this.readFile(path6.join(directory, name));
7540
8756
  if (receipt)
7541
8757
  receipts.push(receipt);
7542
8758
  }
@@ -7564,7 +8780,7 @@ class LocalReceiptStore {
7564
8780
  if (names.length <= MAX_LOCAL_RECEIPTS_PER_INSTALLATION)
7565
8781
  return;
7566
8782
  const entries = await Promise.all(names.map(async (name) => {
7567
- const file = path2.join(directory, name);
8783
+ const file = path6.join(directory, name);
7568
8784
  const stat = await fs2.stat(file).catch(() => null);
7569
8785
  return { file, mtime: stat?.mtimeMs ?? 0 };
7570
8786
  }));
@@ -7859,12 +9075,12 @@ function envelopeError(response) {
7859
9075
 
7860
9076
  // src/client/targets.ts
7861
9077
  import { promises as fs3 } from "fs";
7862
- import path4 from "path";
9078
+ import path8 from "path";
7863
9079
  import { isIPv4 } from "net";
7864
9080
 
7865
9081
  // ../protocol/src/node/runtime-profile-status.ts
7866
- import os2 from "os";
7867
- import path3 from "path";
9082
+ import os4 from "os";
9083
+ import path7 from "path";
7868
9084
  var RUNTIME_APP_SLUG = "orkestrator-v2";
7869
9085
  var RUNTIME_STATUS_KEYS = new Set([
7870
9086
  "version",
@@ -7906,29 +9122,29 @@ function normalizeRuntimeProfileId(value) {
7906
9122
  }
7907
9123
  return normalized;
7908
9124
  }
7909
- function defaultRuntimeProfileRoots(platform = process.platform, env = process.env, homeDir = os2.homedir()) {
9125
+ function defaultRuntimeProfileRoots(platform = process.platform, env = process.env, homeDir = os4.homedir()) {
7910
9126
  if (platform === "darwin") {
7911
- const appSupport = path3.join(homeDir, "Library", "Application Support");
9127
+ const appSupport = path7.join(homeDir, "Library", "Application Support");
7912
9128
  return {
7913
- developmentRoot: path3.join(appSupport, `${RUNTIME_APP_SLUG}-dev`),
7914
- productionDataDir: path3.join(appSupport, RUNTIME_APP_SLUG),
9129
+ developmentRoot: path7.join(appSupport, `${RUNTIME_APP_SLUG}-dev`),
9130
+ productionDataDir: path7.join(appSupport, RUNTIME_APP_SLUG),
7915
9131
  homeDir
7916
9132
  };
7917
9133
  }
7918
9134
  if (platform === "win32") {
7919
9135
  throw new Error("Orkestrator development profiles support macOS and Linux only");
7920
9136
  }
7921
- const configRoot = env.XDG_CONFIG_HOME ?? path3.join(homeDir, ".config");
9137
+ const configRoot = env.XDG_CONFIG_HOME ?? path7.join(homeDir, ".config");
7922
9138
  return {
7923
- developmentRoot: path3.join(configRoot, `${RUNTIME_APP_SLUG}-dev`),
7924
- productionDataDir: path3.join(configRoot, RUNTIME_APP_SLUG),
9139
+ developmentRoot: path7.join(configRoot, `${RUNTIME_APP_SLUG}-dev`),
9140
+ productionDataDir: path7.join(configRoot, RUNTIME_APP_SLUG),
7925
9141
  homeDir
7926
9142
  };
7927
9143
  }
7928
9144
  function runtimeProfileStatusPath(developmentRoot, profileName) {
7929
9145
  const id = normalizeRuntimeProfileId(profileName);
7930
- const profileRoot = path3.resolve(developmentRoot, "profiles", id);
7931
- return { id, profileRoot, statusPath: path3.join(profileRoot, "runtime", "status.json") };
9146
+ const profileRoot = path7.resolve(developmentRoot, "profiles", id);
9147
+ return { id, profileRoot, statusPath: path7.join(profileRoot, "runtime", "status.json") };
7932
9148
  }
7933
9149
 
7934
9150
  // src/client/targets.ts
@@ -8005,7 +9221,7 @@ async function readDescriptor(file) {
8005
9221
  throw new Error("too large");
8006
9222
  text = await fs3.readFile(file, "utf8");
8007
9223
  } catch {
8008
- throw new CliError("connection-failed", `No running backend published ${path4.basename(file)} at the selected location`);
9224
+ throw new CliError("connection-failed", `No running backend published ${path8.basename(file)} at the selected location`);
8009
9225
  }
8010
9226
  try {
8011
9227
  return parseBackendInstanceDescriptor(JSON.parse(text));
@@ -8102,7 +9318,7 @@ async function resolveProfile(profileName, env) {
8102
9318
  }
8103
9319
  const refresh = () => resolveProfile(profileName, env);
8104
9320
  if (manifest.dataDir) {
8105
- const descriptorFile = path4.join(manifest.dataDir, BACKEND_INSTANCE_DESCRIPTOR_FILE);
9321
+ const descriptorFile = path8.join(manifest.dataDir, BACKEND_INSTANCE_DESCRIPTOR_FILE);
8106
9322
  const descriptor = await readDescriptor(descriptorFile).catch(() => null);
8107
9323
  if (descriptor) {
8108
9324
  if (descriptor.pid !== backendPid) {
@@ -8115,235 +9331,13 @@ async function resolveProfile(profileName, env) {
8115
9331
  throw new CliError("profile-unavailable", `Profile '${located.id}' does not publish a gateway endpoint`);
8116
9332
  }
8117
9333
  const baseUrl = normalizeBaseUrl(manifest.browserUrl);
8118
- const authFile = manifest.authFile;
8119
- return {
8120
- identity: { name: located.id, kind: "profile", endpoint: redactEndpoint(baseUrl) },
8121
- baseUrl,
8122
- readToken: () => readTokenFile(authFile, true),
8123
- refresh
8124
- };
8125
- }
8126
-
8127
- // src/client/wait.ts
8128
- class ObservationStopped extends Error {
8129
- reason;
8130
- signalName;
8131
- constructor(reason, signalName) {
8132
- super(reason === "deadline" ? "Observation deadline passed" : "Observation interrupted");
8133
- this.reason = reason;
8134
- this.signalName = signalName;
8135
- }
8136
- }
8137
- function defaultSleep(ms, signal) {
8138
- return new Promise((resolve) => {
8139
- if (signal?.aborted) {
8140
- resolve();
8141
- return;
8142
- }
8143
- const timer = setTimeout(done, ms);
8144
- function done() {
8145
- clearTimeout(timer);
8146
- signal?.removeEventListener("abort", done);
8147
- resolve();
8148
- }
8149
- signal?.addEventListener("abort", done, { once: true });
8150
- });
8151
- }
8152
- function signalName(signal) {
8153
- return signal?.reason === "SIGTERM" ? "SIGTERM" : "SIGINT";
8154
- }
8155
- async function observe(check, options2) {
8156
- if (options2.timeoutMs > PUBLIC_API_LIMITS.waitMaxMs) {
8157
- throw new CliError("invalid-input", "--timeout may be at most 24h");
8158
- }
8159
- const now = options2.now ?? Date.now;
8160
- const sleep = options2.sleep ?? defaultSleep;
8161
- const deadline = now() + options2.timeoutMs;
8162
- let delay = 250;
8163
- for (;; ) {
8164
- if (options2.signal?.aborted)
8165
- throw new ObservationStopped("signal", signalName(options2.signal));
8166
- const step = await check();
8167
- if (step.done)
8168
- return step.value;
8169
- const remaining = deadline - now();
8170
- if (remaining <= 0)
8171
- throw new ObservationStopped("deadline");
8172
- await sleep(Math.min(delay, remaining), options2.signal);
8173
- delay = Math.min(delay * 2, 2000);
8174
- }
8175
- }
8176
-
8177
- // src/client/commands/common.ts
8178
- var REQUEST_ID_OPTION = {
8179
- name: "request-id",
8180
- kind: "string",
8181
- valueName: "KEY",
8182
- description: "Idempotency key. Reusing it returns the original operation; reusing it with a different intent conflicts. Generated and saved locally when omitted."
8183
- };
8184
- var TIMEOUT_OPTION = {
8185
- name: "timeout",
8186
- kind: "duration",
8187
- valueName: "DURATION",
8188
- description: "How long to observe before exiting 5 (the operation keeps running). Default 10m."
8189
- };
8190
- var PROMPT_OPTIONS = [
8191
- {
8192
- name: "prompt-file",
8193
- kind: "string",
8194
- valueName: "PATH",
8195
- description: "Read the prompt from a local file."
8196
- },
8197
- { name: "prompt-stdin", kind: "boolean", description: "Read the prompt from stdin." },
8198
- {
8199
- name: "prompt",
8200
- kind: "string",
8201
- valueName: "TEXT",
8202
- description: "Inline prompt (prefer a file for long text)."
8203
- }
8204
- ];
8205
- var DEFAULT_WAIT_MS = 10 * 60 * 1000;
8206
- function stringOption(options2, name) {
8207
- const value = options2[name];
8208
- return typeof value === "string" ? value : undefined;
8209
- }
8210
- function oneOf(value, allowed, label) {
8211
- if (typeof value !== "string" || !allowed.includes(value)) {
8212
- throw new CliError("invalid-input", `${label} must be one of: ${allowed.join(", ")}`);
8213
- }
8214
- return value;
8215
- }
8216
- function optionalBoolean(options2, name) {
8217
- const value = options2[name];
8218
- return typeof value === "boolean" ? value : undefined;
8219
- }
8220
- async function submit(context, action, input, options2) {
8221
- const session = await context.session();
8222
- const outcome = await session.mutate(action, input, stringOption(options2, "request-id"));
8223
- return { session, ...outcome };
8224
- }
8225
- async function readOperation(session, operationId) {
8226
- const { receipt } = await session.readWithReceipt("run.get", { operationId });
8227
- if (!receipt)
8228
- throw new CliError("response-invalid", "run.get returned no receipt");
8229
- return receipt;
8230
- }
8231
- function isTerminalReceipt(receipt) {
8232
- return !["admitted", "running", "unknown"].includes(receipt.state);
8233
- }
8234
- function stoppedError(error, action, receipt, what) {
8235
- if (error.reason === "signal") {
8236
- return new CliError("observation-interrupted", `Stopped observing ${what}; it continues in the backend`, {
8237
- action,
8238
- ...receipt ? { receipt } : {},
8239
- details: { signal: error.signalName ?? "SIGINT" }
8240
- });
8241
- }
8242
- return new CliError("deadline-exceeded", `Timed out observing ${what}; it continues in the backend`, {
8243
- action,
8244
- ...receipt ? { receipt } : {}
8245
- });
8246
- }
8247
- function receiptFailure(receipt) {
8248
- if (receipt.state === "succeeded")
8249
- return;
8250
- const message = receipt.error?.message ?? `Operation ended ${receipt.state}`;
8251
- const code = receipt.state === "partial" ? "partial-failure" : receipt.state === "cancelled" ? "run-cancelled" : receipt.state === "interrupted" ? "run-interrupted" : receipt.state === "unknown" ? "run-unknown" : receipt.error?.code ?? "operation-failed";
8252
- return new CliError(code, message, { receipt, action: receipt.action });
8253
- }
8254
- async function waitForOperation(context, session, initial, timeoutMs, options2 = {}) {
8255
- let latest = initial;
8256
- try {
8257
- return await observe(async () => {
8258
- latest = await readOperation(session, initial.operationId);
8259
- if (isTerminalReceipt(latest))
8260
- return { done: true, value: latest };
8261
- const execution = latest.execution;
8262
- if (execution?.state === "waiting-for-input" && !options2.continueOnInteraction) {
8263
- throw new CliError("interaction-required", "The run is waiting for an answer to a pending interaction", {
8264
- receipt: latest,
8265
- action: latest.action,
8266
- details: { interactions: execution.interactions ?? [] }
8267
- });
8268
- }
8269
- if (latest.state === "unknown" && latest.dispatch?.state === "unknown") {
8270
- throw new CliError("dispatch-unknown", "The provider may or may not have received the prompt; use `run retry` (same key) or `run discard`", { receipt: latest, action: latest.action, retryable: true });
8271
- }
8272
- if (latest.state === "unknown") {
8273
- throw new CliError("run-unknown", latest.error?.message ?? "The outcome of this run is not known", {
8274
- receipt: latest,
8275
- action: latest.action
8276
- });
8277
- }
8278
- if (execution?.state === "unsupported") {
8279
- throw new CliError("capability-unavailable", execution.reason ?? "Completion of this run cannot be observed for its provider", { receipt: latest, action: latest.action });
8280
- }
8281
- return { done: false };
8282
- }, { timeoutMs, signal: context.signal });
8283
- } catch (error) {
8284
- if (error instanceof ObservationStopped) {
8285
- throw stoppedError(error, latest.action, latest, "the operation");
8286
- }
8287
- throw error;
8288
- }
8289
- }
8290
- async function waitForEnvironment(context, session, environmentId, condition, receipt, timeoutMs) {
8291
- let latestReceipt = receipt;
8292
- const conditionMet = (environment) => {
8293
- if (condition === "deleted")
8294
- return environment === null;
8295
- if (!environment)
8296
- return false;
8297
- if (condition === "running")
8298
- return environment.status === "running";
8299
- if (condition === "stopped")
8300
- return environment.status === "stopped";
8301
- return environment.ready;
8302
- };
8303
- try {
8304
- return await observe(async () => {
8305
- if (latestReceipt && !isTerminalReceipt(latestReceipt)) {
8306
- latestReceipt = await readOperation(session, latestReceipt.operationId);
8307
- }
8308
- if (latestReceipt && isTerminalReceipt(latestReceipt)) {
8309
- const failure = receiptFailure(latestReceipt);
8310
- if (failure)
8311
- throw failure;
8312
- }
8313
- let environment;
8314
- try {
8315
- environment = await session.read("environment.get", {
8316
- environmentId
8317
- });
8318
- } catch (error) {
8319
- if (error instanceof CliError && error.code === "not-found")
8320
- environment = null;
8321
- else
8322
- throw error;
8323
- }
8324
- if (condition !== "deleted" && !environment) {
8325
- throw new CliError("not-found", "The environment no longer exists", latestReceipt ? { receipt: latestReceipt } : {});
8326
- }
8327
- if (environment && (condition === "ready" || condition === "running")) {
8328
- if (environment.setup.phase === "failed" || environment.status === "error") {
8329
- throw new CliError("setup-failed", environment.lifecycle.error ?? "Environment setup failed", { ...latestReceipt ? { receipt: latestReceipt } : {}, details: { environmentId } });
8330
- }
8331
- }
8332
- const operationSettled = !latestReceipt || isTerminalReceipt(latestReceipt) || condition === "running" && latestReceipt.stage === "setup";
8333
- if (operationSettled && conditionMet(environment)) {
8334
- return {
8335
- done: true,
8336
- value: { environment, ...latestReceipt ? { receipt: latestReceipt } : {} }
8337
- };
8338
- }
8339
- return { done: false };
8340
- }, { timeoutMs, signal: context.signal });
8341
- } catch (error) {
8342
- if (error instanceof ObservationStopped) {
8343
- throw stoppedError(error, latestReceipt?.action ?? "environment.get", latestReceipt, `the environment (${condition})`);
8344
- }
8345
- throw error;
8346
- }
9334
+ const authFile = manifest.authFile;
9335
+ return {
9336
+ identity: { name: located.id, kind: "profile", endpoint: redactEndpoint(baseUrl) },
9337
+ baseUrl,
9338
+ readToken: () => readTokenFile(authFile, true),
9339
+ refresh
9340
+ };
8347
9341
  }
8348
9342
 
8349
9343
  // src/client/commands/connection.ts
@@ -8353,7 +9347,7 @@ function store(context) {
8353
9347
  async function removeManagedCredential(configStore, name, saved) {
8354
9348
  if (saved?.kind !== "endpoint")
8355
9349
  return;
8356
- const legacyFile = path5.join(configStore.credentialsDirectory, `${name}.json`);
9350
+ const legacyFile = path9.join(configStore.credentialsDirectory, `${name}.json`);
8357
9351
  if (saved.managedCredential !== true && saved.credentialFile !== legacyFile)
8358
9352
  return;
8359
9353
  await configStore.removeOwnedCredential(saved.credentialFile).catch(() => {
@@ -8513,7 +9507,7 @@ var connectionCommands = [
8513
9507
  const normalizedUrl = normalizeBaseUrl(url);
8514
9508
  let file;
8515
9509
  if (credentialFile) {
8516
- file = path5.resolve(context.io.cwd, credentialFile);
9510
+ file = path9.resolve(context.io.cwd, credentialFile);
8517
9511
  await readTokenFile(file, true);
8518
9512
  } else {
8519
9513
  const bytes = await context.io.readStdin(16 * 1024);
@@ -8532,7 +9526,7 @@ var connectionCommands = [
8532
9526
  addedAt: now
8533
9527
  };
8534
9528
  } else {
8535
- const descriptorPath = path5.resolve(context.io.cwd, descriptorOption ?? path5.join(dataDir, BACKEND_INSTANCE_DESCRIPTOR_FILE));
9529
+ const descriptorPath = path9.resolve(context.io.cwd, descriptorOption ?? path9.join(dataDir, BACKEND_INSTANCE_DESCRIPTOR_FILE));
8536
9530
  saved = { kind: "descriptor", descriptorPath, addedAt: now };
8537
9531
  }
8538
9532
  let checked = null;
@@ -8656,7 +9650,7 @@ var connectionCommands = [
8656
9650
 
8657
9651
  // src/client/inputs.ts
8658
9652
  import { promises as fs5 } from "fs";
8659
- import path6 from "path";
9653
+ import path10 from "path";
8660
9654
  function decodeUtf8(bytes, label) {
8661
9655
  try {
8662
9656
  return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
@@ -8665,7 +9659,7 @@ function decodeUtf8(bytes, label) {
8665
9659
  }
8666
9660
  }
8667
9661
  async function readFileBounded(io, file, label, maxBytes) {
8668
- const resolved = path6.resolve(io.cwd, file);
9662
+ const resolved = path10.resolve(io.cwd, file);
8669
9663
  let handle;
8670
9664
  try {
8671
9665
  handle = await fs5.open(resolved, "r");
@@ -8774,121 +9768,6 @@ async function readPatch(io, options2) {
8774
9768
  return parsed;
8775
9769
  }
8776
9770
 
8777
- // ../protocol/src/public-api-resources.ts
8778
- var AGENT_PLATFORM_KEYS = ["claude", "codex", "cursor", "grok", "opencode", "pi"];
8779
- function agentSettingDescriptors(scope) {
8780
- const application = "next-session";
8781
- return [
8782
- {
8783
- key: "agent.defaultAgent",
8784
- scope,
8785
- type: "agent",
8786
- inherits: true,
8787
- application,
8788
- description: "Agent platform used when a launch does not name one."
8789
- },
8790
- ...AGENT_PLATFORM_KEYS.flatMap((platform) => [
8791
- {
8792
- key: `agent.${platform}.mode`,
8793
- scope,
8794
- type: "agent-mode",
8795
- inherits: true,
8796
- application,
8797
- description: `Launch mode (native or terminal) for ${platform}.`
8798
- },
8799
- {
8800
- key: `agent.${platform}.model`,
8801
- scope,
8802
- type: "string",
8803
- inherits: true,
8804
- application,
8805
- description: `Default model for new ${platform} sessions.`
8806
- },
8807
- {
8808
- key: `agent.${platform}.reasoningEffort`,
8809
- scope,
8810
- type: "string",
8811
- inherits: true,
8812
- application,
8813
- description: `Default reasoning level for new ${platform} sessions.`
8814
- },
8815
- {
8816
- key: `agent.${platform}.fastMode`,
8817
- scope,
8818
- type: "boolean",
8819
- inherits: true,
8820
- application,
8821
- description: `Default speed for new ${platform} sessions (true is Fast).`
8822
- }
8823
- ])
8824
- ];
8825
- }
8826
- var PUBLIC_PROJECT_SETTINGS = Object.freeze([
8827
- {
8828
- key: "defaultBranch",
8829
- scope: "project",
8830
- type: "string",
8831
- inherits: false,
8832
- application: "next-environment",
8833
- description: "Branch new environments are created from."
8834
- },
8835
- {
8836
- key: "prBaseBranch",
8837
- scope: "project",
8838
- type: "string",
8839
- inherits: false,
8840
- application: "applied",
8841
- description: "Base branch for pull requests and diff baselines."
8842
- },
8843
- {
8844
- key: "defaultPortMappings",
8845
- scope: "project",
8846
- type: "port-mappings",
8847
- inherits: false,
8848
- environmentTypes: ["container"],
8849
- application: "next-environment",
8850
- description: "Port mappings copied into new container environments."
8851
- },
8852
- {
8853
- key: "filesToCopy",
8854
- scope: "project",
8855
- type: "string-list",
8856
- inherits: false,
8857
- application: "next-environment",
8858
- description: "Untracked files copied into new environments."
8859
- },
8860
- {
8861
- key: "entryPort",
8862
- scope: "project",
8863
- type: "port",
8864
- inherits: false,
8865
- application: "next-environment",
8866
- description: "Default preview entry port for new environments."
8867
- },
8868
- ...agentSettingDescriptors("project")
8869
- ]);
8870
- var PUBLIC_ENVIRONMENT_SETTINGS = Object.freeze([
8871
- {
8872
- key: "portMappings",
8873
- scope: "environment",
8874
- type: "port-mappings",
8875
- inherits: false,
8876
- environmentTypes: ["container"],
8877
- application: "next-start",
8878
- description: "Container port mappings; a running container adopts them on recreate."
8879
- },
8880
- {
8881
- key: "allowedDomains",
8882
- scope: "environment",
8883
- type: "domain-list",
8884
- inherits: true,
8885
- environmentTypes: ["container"],
8886
- application: "next-start",
8887
- description: "Restricted-network allowlist; unset inherits the global list. A running container whose image supports it applies the change immediately (reported as applied)."
8888
- },
8889
- ...agentSettingDescriptors("environment")
8890
- ]);
8891
-
8892
9771
  // src/client/commands/settings.ts
8893
9772
  function parseAssignment(raw) {
8894
9773
  const equals = raw.indexOf("=");
@@ -9567,36 +10446,6 @@ var environmentCommands = [
9567
10446
  ...settingsCommands("environment")
9568
10447
  ];
9569
10448
 
9570
- // ../protocol/src/host-maintenance.ts
9571
- var HOST_UPDATE_IDLE_WAIT = Object.freeze({
9572
- defaultSeconds: 3600,
9573
- minSeconds: 60,
9574
- maxSeconds: 86400
9575
- });
9576
- var HOST_UPDATE_CHANNELS = ["stable", "beta", "unstable"];
9577
- var HOST_UPDATE_CHANNEL_TAGS = Object.freeze({ stable: "latest", beta: "beta", unstable: "alpha" });
9578
- function isHostUpdateChannel(value) {
9579
- return HOST_UPDATE_CHANNELS.includes(value);
9580
- }
9581
- var ACTIVE_HOST_UPDATE_JOB_STATES = new Set([
9582
- "requested",
9583
- "downloading",
9584
- "waiting-for-idle",
9585
- "awaiting-restart",
9586
- "restarting"
9587
- ]);
9588
- var CANCELLABLE_HOST_UPDATE_JOB_STATES = new Set([
9589
- "requested",
9590
- "downloading",
9591
- "waiting-for-idle",
9592
- "awaiting-restart"
9593
- ]);
9594
- var HOST_MAINTENANCE_LIMITS = Object.freeze({
9595
- idMaxChars: 100,
9596
- versionMaxChars: 64,
9597
- errorMaxChars: 500
9598
- });
9599
-
9600
10449
  // src/client/commands/host.ts
9601
10450
  var WAIT_OPTION = {
9602
10451
  name: "wait",
@@ -9908,117 +10757,18 @@ var hostCommands = [
9908
10757
 
9909
10758
  // src/client/commands/service.ts
9910
10759
  import { spawnSync } from "child_process";
9911
- import { existsSync as existsSync2 } from "fs";
10760
+ import { existsSync as existsSync4 } from "fs";
9912
10761
  import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
9913
- import path10 from "path";
9914
-
9915
- // ../../apps/backend/src/data-dir.ts
9916
- import os3 from "os";
9917
- import path7 from "path";
9918
- // ../../apps/backend/package.json
9919
- var package_default = {
9920
- name: "@orkestrator/backend",
9921
- version: "2.23.0",
9922
- private: true,
9923
- type: "module",
9924
- exports: {
9925
- "./gateway": "./src/gateway.ts"
9926
- },
9927
- scripts: {
9928
- build: "bun scripts/build.ts",
9929
- dev: "bun --watch src/main.ts",
9930
- "dev:web": "bun --watch src/main.ts --port 34121 --fallback-host 127.0.0.1 --renderer-dev-server-url http://127.0.0.1:1420",
9931
- start: "bun dist/main.js",
9932
- "start:web": "bun dist/main.js --fallback-host 127.0.0.1",
9933
- "start:web-public": "bun dist/main.js --tailscale-serve --allowed-origins https://orkestrator.dev,https://www.orkestrator.dev",
9934
- test: "bun test --preload ../../tests/setup-node.ts src tests --parallel",
9935
- "test:workspace": "bun test --preload ../../tests/setup-node.ts src tests --only-failures ${ORKESTRATOR_TEST_TIMINGS_DIR:+--timings=$ORKESTRATOR_TEST_TIMINGS_DIR/backend.json --update-timings} --parallel=${ORKESTRATOR_TEST_WORKERS:-2}",
9936
- typecheck: "bunx tsc --noEmit -p tsconfig.json && bunx tsc --noEmit -p tsconfig.test.json"
9937
- },
9938
- dependencies: {
9939
- "@modelcontextprotocol/node": "2.1.1",
9940
- "@modelcontextprotocol/server": "2.3.1",
9941
- "@opencode-ai/sdk": "1.19.0",
9942
- "@orkestrator/protocol": "workspace:*",
9943
- "@orkestrator/toolchain": "workspace:*",
9944
- "@xterm/addon-serialize": "0.14.0",
9945
- "@xterm/headless": "6.0.0",
9946
- "playwright-core": "1.64.0",
9947
- sharp: "^0.35.5",
9948
- ws: "8.22.0",
9949
- zod: "4.6.7"
9950
- },
9951
- devDependencies: {
9952
- "@modelcontextprotocol/client": "2.3.1",
9953
- "@types/ws": "8.18.2"
9954
- }
9955
- };
9956
-
9957
- // ../../apps/backend/src/core/constants.ts
9958
- var APP_SLUG = "orkestrator-v2";
9959
- var AGENT_NETWORK_DOMAINS_BY_PLATFORM = Object.freeze({
9960
- codex: Object.freeze(["chatgpt.com", "auth.openai.com"]),
9961
- cursor: Object.freeze([
9962
- "api2.cursor.sh",
9963
- "api3.cursor.sh",
9964
- "api4.cursor.sh",
9965
- "api5.cursor.sh",
9966
- "repo42.cursor.sh",
9967
- "authenticator.cursor.sh",
9968
- "marketplace.cursorapi.com",
9969
- "cursor-cdn.com",
9970
- "cursor.com"
9971
- ]),
9972
- grok: Object.freeze([
9973
- "x.ai",
9974
- "auth.x.ai",
9975
- "api.x.ai",
9976
- "cli-chat-proxy.grok.com"
9977
- ]),
9978
- pi: Object.freeze([
9979
- "pi.dev",
9980
- "radius.pi.dev",
9981
- "api.anthropic.com",
9982
- "api.openai.com",
9983
- "generativelanguage.googleapis.com",
9984
- "api.x.ai",
9985
- "api.mistral.ai",
9986
- "api.groq.com",
9987
- "api.deepseek.com",
9988
- "api.cerebras.ai",
9989
- "api.together.ai",
9990
- "openrouter.ai"
9991
- ])
9992
- });
9993
- var MAX_CODEX_CONCURRENT_THREADS = Number.MAX_SAFE_INTEGER - 1;
9994
- function sanitizeAppVersion(raw) {
9995
- const trimmed = raw?.trim() ?? "";
9996
- return /^[A-Za-z0-9._+-]{1,64}$/.test(trimmed) ? trimmed : "0.0.0";
9997
- }
9998
- var BACKEND_PACKAGE_VERSION = sanitizeAppVersion(package_default.version);
9999
- var APP_VERSION = sanitizeAppVersion(process.env.ORKESTRATOR_VERSION?.trim() || BACKEND_PACKAGE_VERSION);
10000
-
10001
- // ../../apps/backend/src/data-dir.ts
10002
- function assertSupportedPlatform(platform = process.platform) {
10003
- if (platform === "win32") {
10004
- throw new Error("Orkestrator does not support Windows. Use macOS or Linux.");
10005
- }
10006
- }
10007
- function defaultDataDir(platform = process.platform, env = process.env, home = os3.homedir()) {
10008
- assertSupportedPlatform(platform);
10009
- if (platform === "darwin")
10010
- return path7.join(home, "Library", "Application Support", APP_SLUG);
10011
- return path7.join(env.XDG_CONFIG_HOME ?? path7.join(home, ".config"), APP_SLUG);
10012
- }
10762
+ import path13 from "path";
10013
10763
 
10014
10764
  // src/client/service-units.ts
10015
10765
  import { createHash as createHash3 } from "crypto";
10016
- import os4 from "os";
10017
- import path8 from "path";
10766
+ import os5 from "os";
10767
+ import path11 from "path";
10018
10768
  function suffix(dataDir, defaultDataDir) {
10019
- if (path8.resolve(dataDir) === path8.resolve(defaultDataDir))
10769
+ if (path11.resolve(dataDir) === path11.resolve(defaultDataDir))
10020
10770
  return "";
10021
- return `-${createHash3("sha256").update(path8.resolve(dataDir)).digest("hex").slice(0, 8)}`;
10771
+ return `-${createHash3("sha256").update(path11.resolve(dataDir)).digest("hex").slice(0, 8)}`;
10022
10772
  }
10023
10773
  function xml(text) {
10024
10774
  return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
@@ -10027,7 +10777,7 @@ function systemdWord(text) {
10027
10777
  return `"${text.replaceAll("\\", "\\\\").replaceAll('"', "\\\"").replaceAll("%", "%%")}"`;
10028
10778
  }
10029
10779
  function serviceUnit(options2) {
10030
- const home = options2.home ?? os4.homedir();
10780
+ const home = options2.home ?? os5.homedir();
10031
10781
  const tag = suffix(options2.dataDir, options2.defaultDataDir);
10032
10782
  const pathValue = options2.env.PATH ?? "/usr/local/bin:/usr/bin:/bin";
10033
10783
  if (options2.platform === "darwin") {
@@ -10037,7 +10787,7 @@ function serviceUnit(options2) {
10037
10787
  return {
10038
10788
  manager: "launchd",
10039
10789
  name,
10040
- file: path8.join(home, "Library", "LaunchAgents", `${name}.plist`),
10790
+ file: path11.join(home, "Library", "LaunchAgents", `${name}.plist`),
10041
10791
  contents: `<?xml version="1.0" encoding="UTF-8"?>
10042
10792
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
10043
10793
  <plist version="1.0">
@@ -10071,11 +10821,11 @@ ${args}
10071
10821
  };
10072
10822
  }
10073
10823
  const name = `orkestrator-backend${tag}.service`;
10074
- const configHome = options2.env.XDG_CONFIG_HOME ?? path8.join(home, ".config");
10824
+ const configHome = options2.env.XDG_CONFIG_HOME ?? path11.join(home, ".config");
10075
10825
  return {
10076
10826
  manager: "systemd",
10077
10827
  name,
10078
- file: path8.join(configHome, "systemd", "user", name),
10828
+ file: path11.join(configHome, "systemd", "user", name),
10079
10829
  contents: `[Unit]
10080
10830
  Description=Orkestrator backend (${options2.dataDir})
10081
10831
  After=network-online.target
@@ -10130,7 +10880,7 @@ var DATA_DIR_OPTION = {
10130
10880
  };
10131
10881
  var RESERVED_SERVE_FLAGS = new Set(["--data-dir", "--standalone-updater", "--desktop-updater"]);
10132
10882
  function dataDirectory(context, parsed) {
10133
- return path10.resolve(stringOption(parsed.options, "data-dir") ?? context.io.env.ORKESTRATOR_DATA_DIR ?? defaultDataDir(process.platform, context.io.env));
10883
+ return path13.resolve(stringOption(parsed.options, "data-dir") ?? context.io.env.ORKESTRATOR_DATA_DIR ?? defaultDataDir(process.platform, context.io.env));
10134
10884
  }
10135
10885
  function unitFor(context, dataDir, bun) {
10136
10886
  if (process.platform !== "darwin" && process.platform !== "linux") {
@@ -10147,7 +10897,7 @@ function unitFor(context, dataDir, bun) {
10147
10897
  defaultDataDir: defaultDataDir(process.platform, context.io.env),
10148
10898
  bun,
10149
10899
  launcher: layout.launcher,
10150
- logFile: path10.join(layout.logs, "service.log"),
10900
+ logFile: path13.join(layout.logs, "service.log"),
10151
10901
  env: context.io.env
10152
10902
  })
10153
10903
  };
@@ -10262,7 +11012,7 @@ var serviceCommands = [
10262
11012
  installedAt: new Date().toISOString()
10263
11013
  });
10264
11014
  await mkdir2(layout.logs, { recursive: true });
10265
- await mkdir2(path10.dirname(unit.file), { recursive: true });
11015
+ await mkdir2(path13.dirname(unit.file), { recursive: true });
10266
11016
  await writeFile2(unit.file, unit.contents);
10267
11017
  const started = parsed.options["skip-start"] !== true;
10268
11018
  if (started) {
@@ -10276,7 +11026,7 @@ var serviceCommands = [
10276
11026
  ...keyValues([
10277
11027
  ["release", `${version} (${releaseDir})`],
10278
11028
  ["unit", `${unit.manager} ${unit.name}`],
10279
- ["logs", path10.join(layout.logs, "service.log")]
11029
+ ["logs", path13.join(layout.logs, "service.log")]
10280
11030
  ]),
10281
11031
  "",
10282
11032
  started ? "The service is running. Remote clients can update it from Settings \u2192 Updates or `orkestrator host update`." : `The unit is written but not loaded; load it with: ${serviceManagerCommands(unit).load.at(-1).join(" ")}`,
@@ -10302,7 +11052,7 @@ var serviceCommands = [
10302
11052
  const dataDir = dataDirectory(context, parsed);
10303
11053
  const { service, layout, unit } = await unitFor(context, dataDir, process.execPath);
10304
11054
  const config = service.readServiceConfig(layout);
10305
- if (existsSync2(unit.file)) {
11055
+ if (existsSync4(unit.file)) {
10306
11056
  for (const argv of serviceManagerCommands(unit).unload)
10307
11057
  runManager(argv, true);
10308
11058
  await rm2(unit.file, { force: true });
@@ -10330,7 +11080,7 @@ var serviceCommands = [
10330
11080
  const { service, layout, unit } = await unitFor(context, dataDir, process.execPath);
10331
11081
  const config = service.readServiceConfig(layout);
10332
11082
  const state = service.readLauncherState(layout);
10333
- const installed = existsSync2(unit.file);
11083
+ const installed = existsSync4(unit.file);
10334
11084
  const running = installed ? runManager(serviceManagerCommands(unit).status, true).ok : false;
10335
11085
  const result = {
10336
11086
  dataDir,
@@ -11506,9 +12256,9 @@ var sessionCommands = [
11506
12256
  ];
11507
12257
 
11508
12258
  // src/client/commands/toolchain.ts
11509
- import path14 from "path";
12259
+ import path17 from "path";
11510
12260
  function dataDirectory2(context, parsed) {
11511
- return path14.resolve(stringOption(parsed.options, "data-dir") ?? context.io.env.ORKESTRATOR_DATA_DIR ?? defaultDataDir(process.platform, context.io.env));
12261
+ return path17.resolve(stringOption(parsed.options, "data-dir") ?? context.io.env.ORKESTRATOR_DATA_DIR ?? defaultDataDir(process.platform, context.io.env));
11512
12262
  }
11513
12263
  function requestedTools(parsed) {
11514
12264
  const raw = parsed.options.tool;
@@ -11611,7 +12361,7 @@ var toolchainCommands = [
11611
12361
  const rows = installed.tools.map((tool) => ({
11612
12362
  tool,
11613
12363
  version: installed.versions[tool] ?? "",
11614
- executable: path14.join(installed.currentBinDir, path14.basename(installed.executables[tool]))
12364
+ executable: path17.join(installed.currentBinDir, path17.basename(installed.executables[tool]))
11615
12365
  }));
11616
12366
  return {
11617
12367
  action: "toolchain.install",
@@ -11648,6 +12398,7 @@ var GROUP_SUMMARIES = {
11648
12398
  environment: "Create, start, stop, configure, launch and delete environments.",
11649
12399
  agent: "Discover agents, models and controls.",
11650
12400
  session: "Start, prompt, control and inspect native-agent conversations.",
12401
+ changes: "Read the file and line changes the backend measures (read-only).",
11651
12402
  run: "Inspect, wait for and recover individual operations."
11652
12403
  };
11653
12404
  function optionLine(option) {
@@ -11697,6 +12448,7 @@ function rootHelp(tree) {
11697
12448
  "Usage:",
11698
12449
  " orkestrator [serve] [service options] Run the backend service in the foreground",
11699
12450
  " orkestrator <group> <command> [options] Talk to an already-running backend",
12451
+ " orkestrator --fix AGENT Get an agent's help reporting a problem",
11700
12452
  "",
11701
12453
  "Client commands never start a backend. Select the backend explicitly with",
11702
12454
  "--profile NAME (an isolated dev profile) or --connection NAME, or save a",
@@ -11707,6 +12459,8 @@ function rootHelp(tree) {
11707
12459
  "",
11708
12460
  "Other:",
11709
12461
  " serve Run the backend service (same as passing only service options)",
12462
+ " --fix AGENT Open claude, codex or opencode to diagnose an Orkestrator",
12463
+ " problem and file a GitHub issue (`orkestrator --fix --help`)",
11710
12464
  " help [COMMAND] Show help for a group or command",
11711
12465
  " version Print the client version",
11712
12466
  "",
@@ -11872,6 +12626,7 @@ var CLIENT_COMMANDS = [
11872
12626
  ...projectCommands,
11873
12627
  ...environmentCommands,
11874
12628
  ...sessionCommands,
12629
+ ...changesCommands,
11875
12630
  ...runCommands,
11876
12631
  ...toolchainCommands,
11877
12632
  ...hostCommands,
@@ -12016,7 +12771,7 @@ var CLIENT_FLAGS = new Set(GLOBAL_OPTIONS.flatMap((option) => [
12016
12771
  ...option.short ? [`-${option.short}`] : []
12017
12772
  ]));
12018
12773
  var CLIENT_VALUE_FLAGS = new Set(GLOBAL_OPTIONS.filter((option) => option.kind !== "boolean").map((option) => `--${option.name}`));
12019
- var SAFE_WORD2 = /^[a-z][a-z0-9-]{0,39}$/;
12774
+ var SAFE_WORD3 = /^[a-z][a-z0-9-]{0,39}$/;
12020
12775
  function flagName(token) {
12021
12776
  const equals = token.indexOf("=");
12022
12777
  return equals < 0 ? token : token.slice(0, equals);
@@ -12024,6 +12779,8 @@ function flagName(token) {
12024
12779
  function classifyInvocation(argv) {
12025
12780
  if (argv.length === 0)
12026
12781
  return { mode: "serve", args: [] };
12782
+ if (isFixInvocation(argv))
12783
+ return { mode: "fix", argv: [...argv] };
12027
12784
  const first = argv[0];
12028
12785
  if (first === "serve") {
12029
12786
  const rest = argv.slice(1);
@@ -12069,7 +12826,7 @@ function classifyInvocation(argv) {
12069
12826
  return { mode: "client", argv: [...argv] };
12070
12827
  return {
12071
12828
  mode: "error",
12072
- message: `Unknown command ${SAFE_WORD2.test(first) ? `'${first}'` : "argument"}; run \`orkestrator help\``
12829
+ message: `Unknown command ${SAFE_WORD3.test(first) ? `'${first}'` : "argument"}; run \`orkestrator help\``
12073
12830
  };
12074
12831
  }
12075
12832
  function renderInvocationError(argv, message) {
@@ -12094,5 +12851,6 @@ export {
12094
12851
  classifyInvocation,
12095
12852
  renderInvocationError,
12096
12853
  runClient,
12097
- runClientProcess
12854
+ runClientProcess,
12855
+ runFixProcess
12098
12856
  };