orkestrator 2.23.0 → 2.24.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/README.md +24 -1
- package/bin/orkestrator.js +5 -2
- package/dist/client.js +1521 -779
- package/dist/main.js +50221 -23881
- package/package.json +3 -3
- package/resources/claude-bridge/dist/index.js +1776 -157
- package/resources/codex-bridge/dist/index.js +7512 -5812
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
|
|
136
|
+
import path12 from "path";
|
|
106
137
|
import { promisify } from "util";
|
|
107
138
|
function standaloneLayout(dataDir) {
|
|
108
|
-
const root =
|
|
139
|
+
const root = path12.join(dataDir, STANDALONE_DIRECTORY);
|
|
109
140
|
return {
|
|
110
141
|
root,
|
|
111
|
-
releases:
|
|
112
|
-
current:
|
|
113
|
-
previous:
|
|
114
|
-
serviceFile:
|
|
115
|
-
stateFile:
|
|
116
|
-
restartMarker:
|
|
117
|
-
launcher:
|
|
118
|
-
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(
|
|
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
|
|
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
|
|
201
|
+
return path12.join(layout.releases, version);
|
|
171
202
|
}
|
|
172
203
|
function releasePackageRoot(releaseDir) {
|
|
173
|
-
return
|
|
204
|
+
return path12.join(releaseDir, "node_modules", STANDALONE_PACKAGE);
|
|
174
205
|
}
|
|
175
206
|
function releaseEntry(releaseDir) {
|
|
176
|
-
return
|
|
207
|
+
return path12.join(releasePackageRoot(releaseDir), "bin", "orkestrator.js");
|
|
177
208
|
}
|
|
178
209
|
function releaseLauncher(releaseDir) {
|
|
179
|
-
return
|
|
210
|
+
return path12.join(releasePackageRoot(releaseDir), "bin", "orkestrator-launcher.js");
|
|
180
211
|
}
|
|
181
212
|
function readReleaseManifest(releaseDir) {
|
|
182
|
-
const manifest = readJson(
|
|
213
|
+
const manifest = readJson(path12.join(releasePackageRoot(releaseDir), "package.json"));
|
|
183
214
|
if (!manifest || typeof manifest.version !== "string")
|
|
184
215
|
return null;
|
|
185
|
-
if (!
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
4886
|
+
return path14.join(rootDir, artifact.name, artifact.version, `${artifact.platform}-${artifact.architecture}`);
|
|
4860
4887
|
}
|
|
4861
4888
|
function artifactExecutablePath(rootDir, artifact) {
|
|
4862
|
-
return
|
|
4889
|
+
return path14.join(artifactDirectory(rootDir, artifact), artifact.executable.fileName);
|
|
4863
4890
|
}
|
|
4864
4891
|
function upstreamExecutablePath(rootDir, artifact) {
|
|
4865
|
-
return
|
|
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
|
|
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 =
|
|
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 !==
|
|
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 !==
|
|
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(
|
|
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(
|
|
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
|
|
5060
|
+
for (const entry of await readdir2(currentDirectory, { withFileTypes: true })) {
|
|
5034
5061
|
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
5035
|
-
const filePath =
|
|
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 =
|
|
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
|
|
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 =
|
|
5332
|
-
if (normalized.startsWith("../") || normalized.startsWith("/") || normalized.includes("\\") ||
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
5557
|
-
const companionFilePath =
|
|
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
|
|
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(
|
|
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
|
|
5596
|
-
const archivePath =
|
|
5597
|
-
const executablePath =
|
|
5598
|
-
const extractedPath = artifact.executable.repairInvalidMacSignature ?
|
|
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 =
|
|
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(
|
|
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
|
|
5666
|
-
await Promise.all(entries.filter((entry) => entry.isDirectory() && entry.name.startsWith(".staging-")).map((entry) => rm3(
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
5761
|
+
const toolDir = path14.join(rootDir, name);
|
|
5735
5762
|
try {
|
|
5736
|
-
const entries = await
|
|
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 =
|
|
5765
|
+
const versionDir = path14.join(toolDir, entry.name);
|
|
5739
5766
|
try {
|
|
5740
|
-
if ((await
|
|
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 =
|
|
5780
|
-
const binDir =
|
|
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 =
|
|
5796
|
-
const temporaryLink =
|
|
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 =
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
6050
|
-
import
|
|
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
|
|
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 =
|
|
6114
|
-
if (
|
|
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 =
|
|
6118
|
-
const target =
|
|
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 =
|
|
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);
|
|
@@ -6512,6 +6539,22 @@ var PUBLIC_ACTIONS = {
|
|
|
6512
6539
|
milestone: "C",
|
|
6513
6540
|
summary: "Read one bounded, ordered transcript page with a continuation cursor."
|
|
6514
6541
|
},
|
|
6542
|
+
"changes.view": {
|
|
6543
|
+
version: 1,
|
|
6544
|
+
mutation: false,
|
|
6545
|
+
effects: [],
|
|
6546
|
+
observation: "immediate",
|
|
6547
|
+
milestone: "D",
|
|
6548
|
+
summary: "Read the backend's change projection for an environment (scope, state, line counts and their quality) with one page of changed files."
|
|
6549
|
+
},
|
|
6550
|
+
"changes.history": {
|
|
6551
|
+
version: 1,
|
|
6552
|
+
mutation: false,
|
|
6553
|
+
effects: [],
|
|
6554
|
+
observation: "immediate",
|
|
6555
|
+
milestone: "D",
|
|
6556
|
+
summary: "Read one page of a session's turns, each with the backend's change projection."
|
|
6557
|
+
},
|
|
6515
6558
|
"run.get": {
|
|
6516
6559
|
version: 1,
|
|
6517
6560
|
mutation: false,
|
|
@@ -7138,89 +7181,115 @@ function splitFlag(token) {
|
|
|
7138
7181
|
return [body.slice(0, equals), body.slice(equals + 1)];
|
|
7139
7182
|
}
|
|
7140
7183
|
|
|
7141
|
-
// src/client/
|
|
7184
|
+
// src/client/fix.ts
|
|
7185
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, realpathSync as realpathSync2 } from "fs";
|
|
7186
|
+
import { mkdtemp, readdir, rmdir, stat } from "fs/promises";
|
|
7187
|
+
import os3 from "os";
|
|
7142
7188
|
import path5 from "path";
|
|
7143
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
7144
|
-
import { promises as fs4 } from "fs";
|
|
7145
7189
|
|
|
7146
|
-
// ../protocol/src/
|
|
7147
|
-
var
|
|
7148
|
-
|
|
7149
|
-
|
|
7190
|
+
// ../protocol/src/host-maintenance.ts
|
|
7191
|
+
var HOST_UPDATE_IDLE_WAIT = Object.freeze({
|
|
7192
|
+
defaultSeconds: 3600,
|
|
7193
|
+
minSeconds: 60,
|
|
7194
|
+
maxSeconds: 86400
|
|
7195
|
+
});
|
|
7196
|
+
var HOST_UPDATE_CHANNELS = ["stable", "beta", "unstable"];
|
|
7197
|
+
var HOST_UPDATE_CHANNEL_TAGS = Object.freeze({ stable: "latest", beta: "beta", unstable: "alpha" });
|
|
7198
|
+
function isHostUpdateChannel(value) {
|
|
7199
|
+
return HOST_UPDATE_CHANNELS.includes(value);
|
|
7200
|
+
}
|
|
7201
|
+
var ACTIVE_HOST_UPDATE_JOB_STATES = new Set([
|
|
7202
|
+
"requested",
|
|
7203
|
+
"downloading",
|
|
7204
|
+
"waiting-for-idle",
|
|
7205
|
+
"awaiting-restart",
|
|
7206
|
+
"restarting"
|
|
7207
|
+
]);
|
|
7208
|
+
var CANCELLABLE_HOST_UPDATE_JOB_STATES = new Set([
|
|
7209
|
+
"requested",
|
|
7210
|
+
"downloading",
|
|
7211
|
+
"waiting-for-idle",
|
|
7212
|
+
"awaiting-restart"
|
|
7213
|
+
]);
|
|
7214
|
+
var HOST_MAINTENANCE_LIMITS = Object.freeze({
|
|
7215
|
+
idMaxChars: 100,
|
|
7216
|
+
versionMaxChars: 64,
|
|
7217
|
+
errorMaxChars: 500
|
|
7218
|
+
});
|
|
7219
|
+
var VERSION_PATTERN = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
|
|
7220
|
+
function isHostVersion(value) {
|
|
7221
|
+
return typeof value === "string" && value.length <= HOST_MAINTENANCE_LIMITS.versionMaxChars && VERSION_PATTERN.test(value);
|
|
7222
|
+
}
|
|
7223
|
+
|
|
7224
|
+
// ../protocol/src/node/application-logging.ts
|
|
7225
|
+
import { readFileSync } from "fs";
|
|
7226
|
+
import path from "path";
|
|
7227
|
+
|
|
7228
|
+
// ../protocol/src/debug-logging.ts
|
|
7229
|
+
var DEFAULT_DEBUG_LOG_RETENTION_DAYS = 7;
|
|
7230
|
+
var MIN_DEBUG_LOG_RETENTION_DAYS = 1;
|
|
7231
|
+
var MAX_DEBUG_LOG_RETENTION_DAYS = 3650;
|
|
7232
|
+
function isValidDebugLogRetentionDays(value) {
|
|
7233
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= MIN_DEBUG_LOG_RETENTION_DAYS && value <= MAX_DEBUG_LOG_RETENTION_DAYS;
|
|
7234
|
+
}
|
|
7235
|
+
function normalizeDebugLogRetentionDays(value) {
|
|
7236
|
+
return isValidDebugLogRetentionDays(value) ? value : DEFAULT_DEBUG_LOG_RETENTION_DAYS;
|
|
7237
|
+
}
|
|
7238
|
+
|
|
7239
|
+
// ../protocol/src/node/application-logging.ts
|
|
7240
|
+
var LOG_CLEANUP_INTERVAL_MS = 6 * 60 * 60 * 1000;
|
|
7241
|
+
var MAX_PENDING_LOG_BYTES = 4 * 1024 * 1024;
|
|
7242
|
+
var MAX_LOG_ENTRY_BYTES = 64 * 1024;
|
|
7243
|
+
var MAX_LOG_FILE_BYTES = 32 * 1024 * 1024;
|
|
7244
|
+
var MAX_LOG_DIRECTORY_BYTES = 512 * 1024 * 1024;
|
|
7150
7245
|
function isRecord2(value) {
|
|
7151
|
-
return value
|
|
7246
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7152
7247
|
}
|
|
7153
|
-
function
|
|
7154
|
-
if (typeof value !== "string" || value.length > 2048)
|
|
7155
|
-
return false;
|
|
7248
|
+
function readApplicationLogSettings(dataDir) {
|
|
7156
7249
|
try {
|
|
7157
|
-
const
|
|
7158
|
-
|
|
7250
|
+
const parsed = JSON.parse(readFileSync(path.join(dataDir, "config.json"), "utf8"));
|
|
7251
|
+
const global2 = isRecord2(parsed) && isRecord2(parsed.global) ? parsed.global : {};
|
|
7252
|
+
return {
|
|
7253
|
+
enabled: global2.debugLogging === true,
|
|
7254
|
+
retentionDays: normalizeDebugLogRetentionDays(global2.debugLogRetentionDays)
|
|
7255
|
+
};
|
|
7159
7256
|
} catch {
|
|
7160
|
-
return
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
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");
|
|
7257
|
+
return {
|
|
7258
|
+
enabled: false,
|
|
7259
|
+
retentionDays: normalizeDebugLogRetentionDays(undefined)
|
|
7260
|
+
};
|
|
7191
7261
|
}
|
|
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
7262
|
}
|
|
7263
|
+
var BENIGN_RMDIR_CODES = new Set(["ENOENT", "ENOTEMPTY", "EEXIST", "ERR_FS_EISDIR"]);
|
|
7264
|
+
|
|
7265
|
+
// ../protocol/src/terminal-agent-launch.ts
|
|
7266
|
+
var OPENCODE_PINNED_RUNTIME_ENV = Object.freeze({
|
|
7267
|
+
OPENCODE_DISABLE_AUTOUPDATE: "1"
|
|
7268
|
+
});
|
|
7269
|
+
var CLAUDE_PINNED_RUNTIME_ENV = Object.freeze({
|
|
7270
|
+
DISABLE_AUTOUPDATER: "1",
|
|
7271
|
+
FORCE_AUTOUPDATE_PLUGINS: "1"
|
|
7272
|
+
});
|
|
7273
|
+
|
|
7274
|
+
// src/client/fix.ts
|
|
7275
|
+
init_layout();
|
|
7207
7276
|
|
|
7208
7277
|
// src/client/config.ts
|
|
7209
7278
|
import { randomUUID } from "crypto";
|
|
7210
7279
|
import { promises as fs } from "fs";
|
|
7211
7280
|
import os from "os";
|
|
7212
|
-
import
|
|
7281
|
+
import path3 from "path";
|
|
7213
7282
|
var CLI_CONFIG_VERSION = 1;
|
|
7214
7283
|
var MAX_CONFIG_BYTES = 256 * 1024;
|
|
7215
7284
|
var MAX_CONNECTIONS = 64;
|
|
7216
7285
|
var CONNECTION_NAME = /^[a-z0-9][a-z0-9._-]{0,47}$/;
|
|
7217
7286
|
function defaultCliConfigDir(env, platform = process.platform, home = os.homedir()) {
|
|
7218
7287
|
if (env.ORKESTRATOR_CLI_CONFIG_DIR)
|
|
7219
|
-
return
|
|
7288
|
+
return path3.resolve(env.ORKESTRATOR_CLI_CONFIG_DIR);
|
|
7220
7289
|
if (platform === "darwin") {
|
|
7221
|
-
return
|
|
7290
|
+
return path3.join(home, "Library", "Application Support", "orkestrator-cli");
|
|
7222
7291
|
}
|
|
7223
|
-
return
|
|
7292
|
+
return path3.join(env.XDG_CONFIG_HOME ?? path3.join(home, ".config"), "orkestrator-cli");
|
|
7224
7293
|
}
|
|
7225
7294
|
function isConnectionName(value) {
|
|
7226
7295
|
return CONNECTION_NAME.test(value);
|
|
@@ -7249,7 +7318,7 @@ function parseSaved(value) {
|
|
|
7249
7318
|
const record = value;
|
|
7250
7319
|
const addedAt = typeof record.addedAt === "string" ? record.addedAt : new Date(0).toISOString();
|
|
7251
7320
|
const installationId = typeof record.installationId === "string" && record.installationId.length <= 200 ? record.installationId : undefined;
|
|
7252
|
-
if (record.kind === "descriptor" && typeof record.descriptorPath === "string" &&
|
|
7321
|
+
if (record.kind === "descriptor" && typeof record.descriptorPath === "string" && path3.isAbsolute(record.descriptorPath)) {
|
|
7253
7322
|
return {
|
|
7254
7323
|
kind: "descriptor",
|
|
7255
7324
|
descriptorPath: record.descriptorPath,
|
|
@@ -7257,7 +7326,7 @@ function parseSaved(value) {
|
|
|
7257
7326
|
addedAt
|
|
7258
7327
|
};
|
|
7259
7328
|
}
|
|
7260
|
-
if (record.kind === "endpoint" && typeof record.url === "string" && typeof record.credentialFile === "string" &&
|
|
7329
|
+
if (record.kind === "endpoint" && typeof record.url === "string" && typeof record.credentialFile === "string" && path3.isAbsolute(record.credentialFile)) {
|
|
7261
7330
|
return {
|
|
7262
7331
|
kind: "endpoint",
|
|
7263
7332
|
url: record.url,
|
|
@@ -7276,13 +7345,13 @@ class CliConfigStore {
|
|
|
7276
7345
|
this.directory = directory;
|
|
7277
7346
|
}
|
|
7278
7347
|
get file() {
|
|
7279
|
-
return
|
|
7348
|
+
return path3.join(this.directory, "connections.json");
|
|
7280
7349
|
}
|
|
7281
7350
|
get credentialsDirectory() {
|
|
7282
|
-
return
|
|
7351
|
+
return path3.join(this.directory, "credentials");
|
|
7283
7352
|
}
|
|
7284
7353
|
get receiptsDirectory() {
|
|
7285
|
-
return
|
|
7354
|
+
return path3.join(this.directory, "receipts");
|
|
7286
7355
|
}
|
|
7287
7356
|
async read() {
|
|
7288
7357
|
let text;
|
|
@@ -7338,24 +7407,24 @@ class CliConfigStore {
|
|
|
7338
7407
|
`);
|
|
7339
7408
|
}
|
|
7340
7409
|
async storeCredential(name, token) {
|
|
7341
|
-
const file =
|
|
7410
|
+
const file = path3.join(this.credentialsDirectory, `${name}.json`);
|
|
7342
7411
|
await writePrivateFile(file, `${JSON.stringify({ token })}
|
|
7343
7412
|
`);
|
|
7344
7413
|
return file;
|
|
7345
7414
|
}
|
|
7346
7415
|
async removeOwnedCredential(file) {
|
|
7347
|
-
if (
|
|
7416
|
+
if (path3.dirname(file) !== this.credentialsDirectory)
|
|
7348
7417
|
return;
|
|
7349
7418
|
await fs.rm(file, { force: true });
|
|
7350
7419
|
}
|
|
7351
7420
|
}
|
|
7352
7421
|
async function writePrivateFile(file, contents) {
|
|
7353
|
-
const directory =
|
|
7422
|
+
const directory = path3.dirname(file);
|
|
7354
7423
|
await fs.mkdir(directory, { recursive: true, mode: 448 });
|
|
7355
7424
|
await fs.chmod(directory, 448).catch(() => {
|
|
7356
7425
|
return;
|
|
7357
7426
|
});
|
|
7358
|
-
const temporary =
|
|
7427
|
+
const temporary = path3.join(directory, `.${path3.basename(file)}.${randomUUID()}.tmp`);
|
|
7359
7428
|
try {
|
|
7360
7429
|
await fs.writeFile(temporary, contents, { mode: 384, flag: "wx" });
|
|
7361
7430
|
await fs.rename(temporary, file);
|
|
@@ -7367,126 +7436,1257 @@ async function writePrivateFile(file, contents) {
|
|
|
7367
7436
|
}
|
|
7368
7437
|
}
|
|
7369
7438
|
|
|
7370
|
-
// src/
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7439
|
+
// ../../apps/backend/src/data-dir.ts
|
|
7440
|
+
import os2 from "os";
|
|
7441
|
+
import path4 from "path";
|
|
7442
|
+
// ../../apps/backend/package.json
|
|
7443
|
+
var package_default = {
|
|
7444
|
+
name: "@orkestrator/backend",
|
|
7445
|
+
version: "2.24.0-alpha",
|
|
7446
|
+
private: true,
|
|
7447
|
+
type: "module",
|
|
7448
|
+
exports: {
|
|
7449
|
+
"./gateway": "./src/gateway.ts"
|
|
7450
|
+
},
|
|
7451
|
+
scripts: {
|
|
7452
|
+
build: "bun scripts/build.ts",
|
|
7453
|
+
dev: "bun --watch src/main.ts",
|
|
7454
|
+
"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",
|
|
7455
|
+
start: "bun dist/main.js",
|
|
7456
|
+
"start:web": "bun dist/main.js --fallback-host 127.0.0.1",
|
|
7457
|
+
"start:web-public": "bun dist/main.js --tailscale-serve --allowed-origins https://orkestrator.dev,https://www.orkestrator.dev",
|
|
7458
|
+
test: "bun test --preload ../../tests/setup-node.ts src tests --parallel",
|
|
7459
|
+
"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}",
|
|
7460
|
+
typecheck: "bunx tsc --noEmit -p tsconfig.json && bunx tsc --noEmit -p tsconfig.test.json"
|
|
7461
|
+
},
|
|
7462
|
+
dependencies: {
|
|
7463
|
+
"@modelcontextprotocol/node": "2.1.1",
|
|
7464
|
+
"@modelcontextprotocol/server": "2.3.1",
|
|
7465
|
+
"@opencode-ai/sdk": "1.19.0",
|
|
7466
|
+
"@orkestrator/protocol": "workspace:*",
|
|
7467
|
+
"@orkestrator/toolchain": "workspace:*",
|
|
7468
|
+
"@xterm/addon-serialize": "0.14.0",
|
|
7469
|
+
"@xterm/headless": "6.0.0",
|
|
7470
|
+
"chart.js": "4.5.1",
|
|
7471
|
+
d3: "7.9.0",
|
|
7472
|
+
dompurify: "3.4.17",
|
|
7473
|
+
mermaid: "12.2.0",
|
|
7474
|
+
"pdfjs-dist": "6.4.299",
|
|
7475
|
+
"playwright-core": "1.64.0",
|
|
7476
|
+
sharp: "^0.35.5",
|
|
7477
|
+
ws: "8.22.0",
|
|
7478
|
+
zod: "4.6.7"
|
|
7479
|
+
},
|
|
7480
|
+
devDependencies: {
|
|
7481
|
+
"@modelcontextprotocol/client": "2.3.1",
|
|
7482
|
+
"@types/ws": "8.18.2"
|
|
7483
|
+
}
|
|
7484
|
+
};
|
|
7485
|
+
|
|
7486
|
+
// ../../apps/backend/src/core/constants.ts
|
|
7487
|
+
var APP_SLUG = "orkestrator-v2";
|
|
7488
|
+
var AGENT_NETWORK_DOMAINS_BY_PLATFORM = Object.freeze({
|
|
7489
|
+
codex: Object.freeze(["chatgpt.com", "auth.openai.com"]),
|
|
7490
|
+
cursor: Object.freeze([
|
|
7491
|
+
"api2.cursor.sh",
|
|
7492
|
+
"api3.cursor.sh",
|
|
7493
|
+
"api4.cursor.sh",
|
|
7494
|
+
"api5.cursor.sh",
|
|
7495
|
+
"repo42.cursor.sh",
|
|
7496
|
+
"authenticator.cursor.sh",
|
|
7497
|
+
"marketplace.cursorapi.com",
|
|
7498
|
+
"cursor-cdn.com",
|
|
7499
|
+
"cursor.com"
|
|
7500
|
+
]),
|
|
7501
|
+
grok: Object.freeze([
|
|
7502
|
+
"x.ai",
|
|
7503
|
+
"auth.x.ai",
|
|
7504
|
+
"api.x.ai",
|
|
7505
|
+
"cli-chat-proxy.grok.com"
|
|
7506
|
+
]),
|
|
7507
|
+
pi: Object.freeze([
|
|
7508
|
+
"pi.dev",
|
|
7509
|
+
"radius.pi.dev",
|
|
7510
|
+
"api.anthropic.com",
|
|
7511
|
+
"api.openai.com",
|
|
7512
|
+
"generativelanguage.googleapis.com",
|
|
7513
|
+
"api.x.ai",
|
|
7514
|
+
"api.mistral.ai",
|
|
7515
|
+
"api.groq.com",
|
|
7516
|
+
"api.deepseek.com",
|
|
7517
|
+
"api.cerebras.ai",
|
|
7518
|
+
"api.together.ai",
|
|
7519
|
+
"openrouter.ai"
|
|
7520
|
+
])
|
|
7521
|
+
});
|
|
7522
|
+
var MAX_CODEX_CONCURRENT_THREADS = Number.MAX_SAFE_INTEGER - 1;
|
|
7523
|
+
function sanitizeAppVersion(raw) {
|
|
7524
|
+
const trimmed = raw?.trim() ?? "";
|
|
7525
|
+
return /^[A-Za-z0-9._+-]{1,64}$/.test(trimmed) ? trimmed : "0.0.0";
|
|
7387
7526
|
}
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
["revision", project.revision]
|
|
7397
|
-
]);
|
|
7527
|
+
var BACKEND_PACKAGE_VERSION = sanitizeAppVersion(package_default.version);
|
|
7528
|
+
var APP_VERSION = sanitizeAppVersion(process.env.ORKESTRATOR_VERSION?.trim() || BACKEND_PACKAGE_VERSION);
|
|
7529
|
+
|
|
7530
|
+
// ../../apps/backend/src/data-dir.ts
|
|
7531
|
+
function assertSupportedPlatform(platform = process.platform) {
|
|
7532
|
+
if (platform === "win32") {
|
|
7533
|
+
throw new Error("Orkestrator does not support Windows. Use macOS or Linux.");
|
|
7534
|
+
}
|
|
7398
7535
|
}
|
|
7399
|
-
function
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
project.localPath ?? "-"
|
|
7405
|
-
]));
|
|
7536
|
+
function defaultDataDir(platform = process.platform, env = process.env, home = os2.homedir()) {
|
|
7537
|
+
assertSupportedPlatform(platform);
|
|
7538
|
+
if (platform === "darwin")
|
|
7539
|
+
return path4.join(home, "Library", "Application Support", APP_SLUG);
|
|
7540
|
+
return path4.join(env.XDG_CONFIG_HOME ?? path4.join(home, ".config"), APP_SLUG);
|
|
7406
7541
|
}
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
["
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
["
|
|
7422
|
-
|
|
7542
|
+
|
|
7543
|
+
// src/client/fix.ts
|
|
7544
|
+
var FIX_ISSUE_REPOSITORY = "orkestrator-ai/orkestrator-v2";
|
|
7545
|
+
var FIX_AGENTS = [
|
|
7546
|
+
{
|
|
7547
|
+
name: "claude",
|
|
7548
|
+
displayName: "Claude Code",
|
|
7549
|
+
args: (prompt, { logsDir }) => [prompt, ...logsDir ? ["--add-dir", logsDir] : []],
|
|
7550
|
+
managedEnv: CLAUDE_PINNED_RUNTIME_ENV
|
|
7551
|
+
},
|
|
7552
|
+
{ name: "codex", displayName: "Codex", args: (prompt) => [prompt] },
|
|
7553
|
+
{
|
|
7554
|
+
name: "opencode",
|
|
7555
|
+
displayName: "OpenCode",
|
|
7556
|
+
args: (prompt) => ["--prompt", prompt],
|
|
7557
|
+
env: OPENCODE_PINNED_RUNTIME_ENV
|
|
7558
|
+
}
|
|
7559
|
+
];
|
|
7560
|
+
var AGENT_LIST = FIX_AGENTS.map((agent) => agent.name).join(", ");
|
|
7561
|
+
var FIX_HELP = [
|
|
7562
|
+
"Usage: orkestrator --fix AGENT [--data-dir DIR]",
|
|
7563
|
+
"",
|
|
7564
|
+
"Report a problem with Orkestrator. Instead of starting Orkestrator, opens AGENT",
|
|
7565
|
+
`(${AGENT_LIST}) with instructions to ask you what went wrong, collect`,
|
|
7566
|
+
"logs and version details from this machine, and draft a GitHub issue for",
|
|
7567
|
+
`${FIX_ISSUE_REPOSITORY}. Nothing is filed until you approve the draft, and the`,
|
|
7568
|
+
"agent is told to leave Orkestrator's files and settings unchanged.",
|
|
7569
|
+
"",
|
|
7570
|
+
"The agent on PATH is used; otherwise the copy Orkestrator installed. The agent",
|
|
7571
|
+
"works in a new temporary directory, where it saves the draft as issue.md.",
|
|
7572
|
+
"",
|
|
7573
|
+
"Options:",
|
|
7574
|
+
" --data-dir DIR Orkestrator data directory to inspect (default: ORKESTRATOR_DATA_DIR,",
|
|
7575
|
+
" then the platform default)",
|
|
7576
|
+
"",
|
|
7577
|
+
"Examples:",
|
|
7578
|
+
" orkestrator --fix claude",
|
|
7579
|
+
" bunx orkestrator --fix codex"
|
|
7580
|
+
].join(`
|
|
7581
|
+
`);
|
|
7582
|
+
var SAFE_WORD2 = /^[a-z][a-z0-9-]{0,39}$/;
|
|
7583
|
+
function describeToken2(token) {
|
|
7584
|
+
return SAFE_WORD2.test(token) ? `'${token}'` : "argument";
|
|
7423
7585
|
}
|
|
7424
|
-
function
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
environment.name,
|
|
7428
|
-
environment.environmentType,
|
|
7429
|
-
environment.status,
|
|
7430
|
-
environment.ready ? "yes" : "no"
|
|
7431
|
-
]));
|
|
7586
|
+
function isFixInvocation(argv) {
|
|
7587
|
+
const end = argv.indexOf("--");
|
|
7588
|
+
return (end < 0 ? argv : argv.slice(0, end)).some((token) => token === "--fix" || token.startsWith("--fix="));
|
|
7432
7589
|
}
|
|
7433
|
-
function
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7590
|
+
function parseFixArguments(argv) {
|
|
7591
|
+
const values = {};
|
|
7592
|
+
let help = false;
|
|
7593
|
+
for (let index = 0;index < argv.length; index += 1) {
|
|
7594
|
+
const token = argv[index];
|
|
7595
|
+
if (token === "--help" || token === "-h") {
|
|
7596
|
+
help = true;
|
|
7597
|
+
continue;
|
|
7598
|
+
}
|
|
7599
|
+
const equals = token.indexOf("=");
|
|
7600
|
+
const name = token.startsWith("--") ? token.slice(2, equals > 0 ? equals : undefined) : null;
|
|
7601
|
+
if (name !== "fix" && name !== "data-dir") {
|
|
7602
|
+
return {
|
|
7603
|
+
kind: "error",
|
|
7604
|
+
message: name === null ? `Unexpected ${describeToken2(token)} for --fix; usage: orkestrator --fix AGENT` : `Unknown option for --fix: ${describeToken2(name)}`
|
|
7605
|
+
};
|
|
7606
|
+
}
|
|
7607
|
+
if (values[name] !== undefined) {
|
|
7608
|
+
return { kind: "error", message: `--${name} may be given only once` };
|
|
7609
|
+
}
|
|
7610
|
+
let value = "";
|
|
7611
|
+
if (equals > 0) {
|
|
7612
|
+
value = token.slice(equals + 1);
|
|
7613
|
+
} else if (argv[index + 1] !== undefined && !argv[index + 1].startsWith("-")) {
|
|
7614
|
+
value = argv[index + 1];
|
|
7615
|
+
index += 1;
|
|
7616
|
+
}
|
|
7617
|
+
values[name] = value;
|
|
7618
|
+
}
|
|
7619
|
+
if (help)
|
|
7620
|
+
return { kind: "help" };
|
|
7621
|
+
if (values["data-dir"] === "")
|
|
7622
|
+
return { kind: "error", message: "Missing value for --data-dir" };
|
|
7623
|
+
if (!values.fix)
|
|
7624
|
+
return { kind: "error", message: `--fix needs an agent: ${AGENT_LIST}` };
|
|
7625
|
+
const normalized = values.fix.toLowerCase();
|
|
7626
|
+
const known = FIX_AGENTS.find((candidate) => candidate.name === normalized);
|
|
7627
|
+
if (!known) {
|
|
7628
|
+
return {
|
|
7629
|
+
kind: "error",
|
|
7630
|
+
message: `Unknown agent ${describeToken2(normalized)} for --fix; choose ${AGENT_LIST}`
|
|
7631
|
+
};
|
|
7632
|
+
}
|
|
7633
|
+
const dataDir = values["data-dir"];
|
|
7634
|
+
return { kind: "run", agent: known.name, ...dataDir !== undefined ? { dataDir } : {} };
|
|
7441
7635
|
}
|
|
7442
|
-
function
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7636
|
+
function resolveFixAgent(name, options2) {
|
|
7637
|
+
const agent = FIX_AGENTS.find((candidate) => candidate.name === name);
|
|
7638
|
+
const onPath = Bun.which(agent.name, { PATH: options2.env.PATH ?? "" });
|
|
7639
|
+
const managed = path5.join(defaultToolchainBinDir(options2.dataDir), agent.name);
|
|
7640
|
+
if (onPath) {
|
|
7641
|
+
let isManaged = false;
|
|
7642
|
+
try {
|
|
7643
|
+
const root = realpathSync2(toolchainRootDir(options2.dataDir));
|
|
7644
|
+
const target = realpathSync2(onPath);
|
|
7645
|
+
const relative = path5.relative(root, target);
|
|
7646
|
+
isManaged = !relative.startsWith(`..${path5.sep}`) && relative !== ".." && !path5.isAbsolute(relative) || existsSync2(managed) && target === realpathSync2(managed);
|
|
7647
|
+
} catch {}
|
|
7648
|
+
return { agent, executable: onPath, source: isManaged ? "managed" : "path" };
|
|
7649
|
+
}
|
|
7650
|
+
if (existsSync2(managed))
|
|
7651
|
+
return { agent, executable: managed, source: "managed" };
|
|
7652
|
+
return null;
|
|
7454
7653
|
}
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
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
|
-
]);
|
|
7654
|
+
var MAX_LISTED_LOGS = 8;
|
|
7655
|
+
async function recentLogFiles(logsDir) {
|
|
7656
|
+
const names = await readdir(logsDir).catch(() => []);
|
|
7657
|
+
const files = await Promise.all(names.filter((name) => name.endsWith(".log")).map(async (name) => {
|
|
7658
|
+
const info = await stat(path5.join(logsDir, name)).catch(() => null);
|
|
7659
|
+
return info?.isFile() ? { name, bytes: info.size, modifiedMs: info.mtimeMs } : null;
|
|
7660
|
+
}));
|
|
7661
|
+
return files.filter((file) => file !== null).sort((left, right) => right.modifiedMs - left.modifiedMs).slice(0, MAX_LISTED_LOGS).map((file) => ({
|
|
7662
|
+
name: file.name,
|
|
7663
|
+
bytes: file.bytes,
|
|
7664
|
+
modified: new Date(file.modifiedMs).toISOString()
|
|
7665
|
+
}));
|
|
7472
7666
|
}
|
|
7473
|
-
function
|
|
7667
|
+
function processRunning(pid) {
|
|
7668
|
+
try {
|
|
7669
|
+
process.kill(pid, 0);
|
|
7670
|
+
return true;
|
|
7671
|
+
} catch (error) {
|
|
7672
|
+
return error.code === "EPERM";
|
|
7673
|
+
}
|
|
7674
|
+
}
|
|
7675
|
+
function backendInstance(dataDir) {
|
|
7676
|
+
let parsed;
|
|
7677
|
+
try {
|
|
7678
|
+
parsed = JSON.parse(readFileSync2(path5.join(dataDir, "backend-instance.json"), "utf8"));
|
|
7679
|
+
} catch {
|
|
7680
|
+
return null;
|
|
7681
|
+
}
|
|
7682
|
+
if (!parsed || typeof parsed !== "object")
|
|
7683
|
+
return null;
|
|
7684
|
+
const record = parsed;
|
|
7685
|
+
const pid = typeof record.pid === "number" ? record.pid : undefined;
|
|
7686
|
+
return {
|
|
7687
|
+
...typeof record.appVersion === "string" ? { appVersion: record.appVersion } : {},
|
|
7688
|
+
...typeof record.startedAt === "string" ? { startedAt: record.startedAt } : {},
|
|
7689
|
+
...pid !== undefined ? { pid } : {},
|
|
7690
|
+
running: pid !== undefined && processRunning(pid)
|
|
7691
|
+
};
|
|
7692
|
+
}
|
|
7693
|
+
async function managedGhBinary(dataDir) {
|
|
7694
|
+
const activated = path5.join(defaultToolchainBinDir(dataDir), "gh");
|
|
7695
|
+
if (existsSync2(activated))
|
|
7696
|
+
return activated;
|
|
7697
|
+
const latestRoot = path5.join(toolchainRootDir(dataDir), "gh-latest");
|
|
7698
|
+
const latestVersions = await readdir(latestRoot).catch(() => []);
|
|
7699
|
+
for (const version of latestVersions.filter(isHostVersion).sort((left, right) => right.localeCompare(left, undefined, { numeric: true }))) {
|
|
7700
|
+
const candidate = path5.join(latestRoot, version, "gh");
|
|
7701
|
+
if (existsSync2(candidate))
|
|
7702
|
+
return candidate;
|
|
7703
|
+
}
|
|
7704
|
+
const root = path5.join(toolchainRootDir(dataDir), "gh");
|
|
7705
|
+
const versions = await readdir(root).catch(() => []);
|
|
7706
|
+
for (const version of versions.sort((left, right) => right.localeCompare(left, undefined, { numeric: true }))) {
|
|
7707
|
+
const platforms = await readdir(path5.join(root, version)).catch(() => []);
|
|
7708
|
+
for (const platform of platforms) {
|
|
7709
|
+
const candidate = path5.join(root, version, platform, "gh");
|
|
7710
|
+
if (existsSync2(candidate))
|
|
7711
|
+
return candidate;
|
|
7712
|
+
}
|
|
7713
|
+
}
|
|
7714
|
+
return null;
|
|
7715
|
+
}
|
|
7716
|
+
async function diagnosticConnection(dataDir, env) {
|
|
7717
|
+
try {
|
|
7718
|
+
const descriptor = realpathSync2(path5.join(dataDir, "backend-instance.json"));
|
|
7719
|
+
const config = await new CliConfigStore(defaultCliConfigDir(env)).read();
|
|
7720
|
+
for (const [name, connection] of Object.entries(config.connections)) {
|
|
7721
|
+
if (connection.kind !== "descriptor")
|
|
7722
|
+
continue;
|
|
7723
|
+
try {
|
|
7724
|
+
if (realpathSync2(connection.descriptorPath) === descriptor)
|
|
7725
|
+
return name;
|
|
7726
|
+
} catch {}
|
|
7727
|
+
}
|
|
7728
|
+
} catch {}
|
|
7729
|
+
return null;
|
|
7730
|
+
}
|
|
7731
|
+
function desktopAppPath(home) {
|
|
7732
|
+
if (process.platform !== "darwin")
|
|
7733
|
+
return null;
|
|
7734
|
+
for (const candidate of [
|
|
7735
|
+
"/Applications/OrkestratorV2.app",
|
|
7736
|
+
path5.join(home, "Applications", "OrkestratorV2.app")
|
|
7737
|
+
]) {
|
|
7738
|
+
if (existsSync2(candidate))
|
|
7739
|
+
return candidate;
|
|
7740
|
+
}
|
|
7741
|
+
return null;
|
|
7742
|
+
}
|
|
7743
|
+
async function collectFixFacts(options2) {
|
|
7744
|
+
const { resolved, dataDir, env } = options2;
|
|
7745
|
+
const logsDir = path5.join(dataDir, "logs");
|
|
7746
|
+
const binDir = defaultToolchainBinDir(dataDir);
|
|
7747
|
+
return {
|
|
7748
|
+
agent: {
|
|
7749
|
+
name: resolved.agent.name,
|
|
7750
|
+
displayName: resolved.agent.displayName,
|
|
7751
|
+
executable: resolved.executable,
|
|
7752
|
+
source: resolved.source === "path" ? "PATH" : "Orkestrator's managed toolchain"
|
|
7753
|
+
},
|
|
7754
|
+
cliVersion: options2.version,
|
|
7755
|
+
cliCommand: Bun.which("orkestrator", { PATH: env.PATH ?? "" }) ? "orkestrator" : `bunx orkestrator@${options2.version}`,
|
|
7756
|
+
platform: process.platform,
|
|
7757
|
+
osRelease: `${os3.type()} ${os3.release()}`,
|
|
7758
|
+
arch: process.arch,
|
|
7759
|
+
bunVersion: Bun.version,
|
|
7760
|
+
now: (options2.now ?? new Date).toISOString(),
|
|
7761
|
+
workDir: options2.workDir,
|
|
7762
|
+
dataDir,
|
|
7763
|
+
dataDirExists: existsSync2(dataDir),
|
|
7764
|
+
logsDir,
|
|
7765
|
+
debugLogging: readApplicationLogSettings(dataDir).enabled,
|
|
7766
|
+
recentLogs: await recentLogFiles(logsDir),
|
|
7767
|
+
backend: backendInstance(dataDir),
|
|
7768
|
+
toolchainBinDir: existsSync2(binDir) ? binDir : null,
|
|
7769
|
+
managedGh: await managedGhBinary(dataDir),
|
|
7770
|
+
diagnosticConnection: await diagnosticConnection(dataDir, env),
|
|
7771
|
+
desktopApp: desktopAppPath(os3.homedir())
|
|
7772
|
+
};
|
|
7773
|
+
}
|
|
7774
|
+
function describeBackend(facts) {
|
|
7775
|
+
const backend = facts.backend;
|
|
7776
|
+
if (!backend)
|
|
7777
|
+
return "no `backend-instance.json` (no backend has run with this data directory, or it was removed)";
|
|
7778
|
+
const parts = [
|
|
7779
|
+
backend.appVersion ? `version ${backend.appVersion}` : "version not recorded",
|
|
7780
|
+
backend.startedAt ? `started ${backend.startedAt}` : null,
|
|
7781
|
+
backend.pid !== undefined ? `pid ${backend.pid} ${backend.running ? "(running)" : "(not running)"}` : null
|
|
7782
|
+
];
|
|
7783
|
+
return parts.filter(Boolean).join(", ");
|
|
7784
|
+
}
|
|
7785
|
+
function describeLogs(facts) {
|
|
7786
|
+
const state = facts.debugLogging ? "on" : "off (Settings \u2192 Debug \u2192 Save Logs for Debugging); new events are not being written";
|
|
7787
|
+
const lines = [`- Logs directory: ${facts.logsDir} \u2014 debug logging is ${state}`];
|
|
7788
|
+
if (facts.recentLogs.length === 0) {
|
|
7789
|
+
lines.push("- Log files: none");
|
|
7790
|
+
} else {
|
|
7791
|
+
lines.push("- Most recent log files:");
|
|
7792
|
+
for (const log of facts.recentLogs) {
|
|
7793
|
+
lines.push(` - ${log.name} (${log.bytes} bytes, modified ${log.modified})`);
|
|
7794
|
+
}
|
|
7795
|
+
}
|
|
7796
|
+
return lines;
|
|
7797
|
+
}
|
|
7798
|
+
function buildFixPrompt(facts) {
|
|
7799
|
+
const issue = path5.join(facts.workDir, "issue.md");
|
|
7800
|
+
const gh = facts.managedGh ? `\`gh\` on PATH, or Orkestrator's copy at ${facts.managedGh}` : "`gh`";
|
|
7474
7801
|
return [
|
|
7475
|
-
`
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7802
|
+
`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.`,
|
|
7803
|
+
"",
|
|
7804
|
+
"## About Orkestrator",
|
|
7805
|
+
"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.",
|
|
7806
|
+
"",
|
|
7807
|
+
"## This machine",
|
|
7808
|
+
`- Collected: ${facts.now}`,
|
|
7809
|
+
`- OS: ${facts.platform} (${facts.osRelease}), ${facts.arch}; Bun ${facts.bunVersion}`,
|
|
7810
|
+
`- Orkestrator CLI: ${facts.cliVersion} (run client commands as \`${facts.cliCommand} \u2026\`)`,
|
|
7811
|
+
`- Desktop app: ${facts.desktopApp ?? "not found in /Applications (on Linux, or not installed)"}`,
|
|
7812
|
+
`- Data directory: ${facts.dataDir}${facts.dataDirExists ? "" : " (does not exist)"}`,
|
|
7813
|
+
`- Backend: ${describeBackend(facts)}`,
|
|
7814
|
+
...describeLogs(facts),
|
|
7815
|
+
`- Managed agent binaries: ${facts.toolchainBinDir ?? "none installed"}`,
|
|
7816
|
+
`- You: ${facts.agent.displayName} from ${facts.agent.source} (${facts.agent.executable})`,
|
|
7817
|
+
`- Your working directory (scratch space for notes and the draft): ${facts.workDir}`,
|
|
7818
|
+
"",
|
|
7819
|
+
"## 1. Understand the problem",
|
|
7820
|
+
"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:",
|
|
7821
|
+
"- 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);",
|
|
7822
|
+
"- the steps that reproduce it, and whether it happens every time;",
|
|
7823
|
+
"- when it started, and whether it began after an Orkestrator update;",
|
|
7824
|
+
"- where it happens: desktop app, web client, iOS app or CLI; a local-worktree, Docker or remote-host environment; and which agent and model.",
|
|
7825
|
+
"",
|
|
7826
|
+
"## 2. Collect diagnostics",
|
|
7827
|
+
"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.",
|
|
7828
|
+
"Useful sources:",
|
|
7829
|
+
"- 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.",
|
|
7830
|
+
"- 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.",
|
|
7831
|
+
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.",
|
|
7832
|
+
"- Docker environments: `docker version`, `docker ps -a`, and `docker logs --tail 200 <container>` for the affected container.",
|
|
7833
|
+
"- 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.",
|
|
7834
|
+
"- `config.json` in the data directory holds settings. Read only the keys that bear on the problem.",
|
|
7835
|
+
"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.",
|
|
7836
|
+
"",
|
|
7837
|
+
"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.",
|
|
7838
|
+
"",
|
|
7839
|
+
"## 3. Draft the issue",
|
|
7840
|
+
"GitHub issues on this repository are public. In everything you put in the issue:",
|
|
7841
|
+
"- remove API keys, tokens, passwords, cookies, auth headers, URLs that carry credentials, and anything read from a credential file;",
|
|
7842
|
+
"- 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;",
|
|
7843
|
+
"- leave out my prompts, agent conversations and project source code unless I explicitly agree to include them;",
|
|
7844
|
+
"- keep log excerpts to the lines that matter, with their timestamps: a few dozen lines in total.",
|
|
7845
|
+
"",
|
|
7846
|
+
`Write the issue body to ${issue}, using this structure:`,
|
|
7847
|
+
"",
|
|
7848
|
+
"```markdown",
|
|
7849
|
+
"## Summary",
|
|
7850
|
+
"## Steps to reproduce",
|
|
7851
|
+
"## Expected behavior",
|
|
7852
|
+
"## Actual behavior",
|
|
7853
|
+
"## Environment",
|
|
7854
|
+
"(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)",
|
|
7855
|
+
"## Diagnostics",
|
|
7856
|
+
"(log excerpts and command output in fenced code blocks, each inside <details><summary>what it is</summary> \u2026 </details>)",
|
|
7857
|
+
"## Analysis",
|
|
7858
|
+
"(likely cause, confidence, what was ruled out)",
|
|
7859
|
+
"## Additional context",
|
|
7860
|
+
"",
|
|
7861
|
+
`_Reported with \`orkestrator --fix ${facts.agent.name}\`._`,
|
|
7862
|
+
"```",
|
|
7863
|
+
"",
|
|
7864
|
+
"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.",
|
|
7865
|
+
"",
|
|
7866
|
+
"## 4. File it",
|
|
7867
|
+
`- 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.`,
|
|
7868
|
+
`- 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}.`,
|
|
7869
|
+
"- 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.",
|
|
7870
|
+
"",
|
|
7871
|
+
"Finish by giving me the issue URL (or where the draft is saved) and telling me the maintainer may follow up on the issue."
|
|
7872
|
+
].join(`
|
|
7873
|
+
`);
|
|
7874
|
+
}
|
|
7875
|
+
var defaultSpawn = (command, options2) => {
|
|
7876
|
+
const child = Bun.spawn(command, {
|
|
7877
|
+
cwd: options2.cwd,
|
|
7878
|
+
env: options2.env,
|
|
7879
|
+
stdio: ["inherit", "inherit", "inherit"]
|
|
7880
|
+
});
|
|
7881
|
+
return {
|
|
7882
|
+
exited: child.exited,
|
|
7883
|
+
kill: (signal) => child.kill(signal)
|
|
7884
|
+
};
|
|
7885
|
+
};
|
|
7886
|
+
function failure(io, code, message, exitCode) {
|
|
7887
|
+
io.stderr(`error [${code}]: ${message}
|
|
7888
|
+
`);
|
|
7889
|
+
return exitCode;
|
|
7890
|
+
}
|
|
7891
|
+
async function runFix(argv, options2) {
|
|
7892
|
+
const { io } = options2;
|
|
7893
|
+
const parsed = parseFixArguments(argv);
|
|
7894
|
+
if (parsed.kind === "help") {
|
|
7895
|
+
io.stdout(`${FIX_HELP}
|
|
7896
|
+
`);
|
|
7897
|
+
return 0;
|
|
7898
|
+
}
|
|
7899
|
+
if (parsed.kind === "error") {
|
|
7900
|
+
io.stderr(`${FIX_HELP}
|
|
7901
|
+
|
|
7902
|
+
`);
|
|
7903
|
+
return failure(io, "invalid-input", parsed.message, 2);
|
|
7904
|
+
}
|
|
7905
|
+
const dataDir = path5.resolve(parsed.dataDir ?? io.env.ORKESTRATOR_DATA_DIR ?? defaultDataDir(process.platform, io.env));
|
|
7906
|
+
const resolved = resolveFixAgent(parsed.agent, { env: io.env, dataDir });
|
|
7907
|
+
if (!resolved) {
|
|
7908
|
+
const displayName = FIX_AGENTS.find((agent) => agent.name === parsed.agent).displayName;
|
|
7909
|
+
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);
|
|
7910
|
+
}
|
|
7911
|
+
let workDir;
|
|
7912
|
+
try {
|
|
7913
|
+
workDir = await mkdtemp(path5.join(options2.tmpDir ?? os3.tmpdir(), "orkestrator-fix-"));
|
|
7914
|
+
const facts = await collectFixFacts({
|
|
7915
|
+
resolved,
|
|
7916
|
+
version: options2.version,
|
|
7917
|
+
dataDir,
|
|
7918
|
+
workDir,
|
|
7919
|
+
env: io.env
|
|
7920
|
+
});
|
|
7921
|
+
const agent = resolved.agent;
|
|
7922
|
+
const env = {
|
|
7923
|
+
...io.env,
|
|
7924
|
+
...agent.env,
|
|
7925
|
+
...resolved.source === "managed" ? agent.managedEnv : {}
|
|
7926
|
+
};
|
|
7927
|
+
io.stderr(`Starting ${agent.displayName} to help you report a problem with Orkestrator.
|
|
7928
|
+
` + `It works in ${workDir} and will ask before filing anything.
|
|
7929
|
+
|
|
7930
|
+
`);
|
|
7931
|
+
const child = (options2.spawn ?? defaultSpawn)([
|
|
7932
|
+
resolved.executable,
|
|
7933
|
+
...agent.args(buildFixPrompt(facts), {
|
|
7934
|
+
logsDir: existsSync2(facts.logsDir) ? facts.logsDir : null
|
|
7935
|
+
})
|
|
7936
|
+
], { cwd: workDir, env });
|
|
7937
|
+
const ignore = () => {};
|
|
7938
|
+
const forward = (signal) => child.kill(signal);
|
|
7939
|
+
process.on("SIGINT", ignore);
|
|
7940
|
+
process.on("SIGTERM", forward);
|
|
7941
|
+
process.on("SIGHUP", forward);
|
|
7942
|
+
let exitCode;
|
|
7943
|
+
try {
|
|
7944
|
+
exitCode = await child.exited;
|
|
7945
|
+
} finally {
|
|
7946
|
+
process.off("SIGINT", ignore);
|
|
7947
|
+
process.off("SIGTERM", forward);
|
|
7948
|
+
process.off("SIGHUP", forward);
|
|
7949
|
+
}
|
|
7950
|
+
return exitCode;
|
|
7951
|
+
} catch {
|
|
7952
|
+
return failure(io, "agent-failed", "Unable to start or complete the reporting agent", 1);
|
|
7953
|
+
} finally {
|
|
7954
|
+
if (workDir) {
|
|
7955
|
+
const remaining = await readdir(workDir).catch(() => []);
|
|
7956
|
+
if (remaining.length === 0) {
|
|
7957
|
+
await rmdir(workDir).catch(() => {});
|
|
7958
|
+
} else {
|
|
7959
|
+
io.stderr(`
|
|
7960
|
+
Notes and the issue draft are in ${workDir}
|
|
7961
|
+
`);
|
|
7962
|
+
}
|
|
7963
|
+
}
|
|
7964
|
+
}
|
|
7965
|
+
}
|
|
7966
|
+
function runFixProcess(argv, version) {
|
|
7967
|
+
return runFix(argv, {
|
|
7968
|
+
version,
|
|
7969
|
+
io: {
|
|
7970
|
+
env: process.env,
|
|
7971
|
+
stdout: (text) => process.stdout.write(text),
|
|
7972
|
+
stderr: (text) => process.stderr.write(text)
|
|
7973
|
+
}
|
|
7974
|
+
});
|
|
7975
|
+
}
|
|
7976
|
+
|
|
7977
|
+
// ../protocol/src/public-api-resources.ts
|
|
7978
|
+
var PUBLIC_CHANGE_SCOPES = Object.freeze(["environment"]);
|
|
7979
|
+
var AGENT_PLATFORM_KEYS = ["claude", "codex", "cursor", "grok", "opencode", "pi"];
|
|
7980
|
+
function agentSettingDescriptors(scope) {
|
|
7981
|
+
const application = "next-session";
|
|
7982
|
+
return [
|
|
7983
|
+
{
|
|
7984
|
+
key: "agent.defaultAgent",
|
|
7985
|
+
scope,
|
|
7986
|
+
type: "agent",
|
|
7987
|
+
inherits: true,
|
|
7988
|
+
application,
|
|
7989
|
+
description: "Agent platform used when a launch does not name one."
|
|
7990
|
+
},
|
|
7991
|
+
...AGENT_PLATFORM_KEYS.flatMap((platform) => [
|
|
7992
|
+
{
|
|
7993
|
+
key: `agent.${platform}.mode`,
|
|
7994
|
+
scope,
|
|
7995
|
+
type: "agent-mode",
|
|
7996
|
+
inherits: true,
|
|
7997
|
+
application,
|
|
7998
|
+
description: `Launch mode (native or terminal) for ${platform}.`
|
|
7999
|
+
},
|
|
8000
|
+
{
|
|
8001
|
+
key: `agent.${platform}.model`,
|
|
8002
|
+
scope,
|
|
8003
|
+
type: "string",
|
|
8004
|
+
inherits: true,
|
|
8005
|
+
application,
|
|
8006
|
+
description: `Default model for new ${platform} sessions.`
|
|
8007
|
+
},
|
|
8008
|
+
{
|
|
8009
|
+
key: `agent.${platform}.reasoningEffort`,
|
|
8010
|
+
scope,
|
|
8011
|
+
type: "string",
|
|
8012
|
+
inherits: true,
|
|
8013
|
+
application,
|
|
8014
|
+
description: `Default reasoning level for new ${platform} sessions.`
|
|
8015
|
+
},
|
|
8016
|
+
{
|
|
8017
|
+
key: `agent.${platform}.fastMode`,
|
|
8018
|
+
scope,
|
|
8019
|
+
type: "boolean",
|
|
8020
|
+
inherits: true,
|
|
8021
|
+
application,
|
|
8022
|
+
description: `Default speed for new ${platform} sessions (true is Fast).`
|
|
8023
|
+
}
|
|
8024
|
+
])
|
|
7483
8025
|
];
|
|
7484
8026
|
}
|
|
8027
|
+
var PUBLIC_PROJECT_SETTINGS = Object.freeze([
|
|
8028
|
+
{
|
|
8029
|
+
key: "defaultBranch",
|
|
8030
|
+
scope: "project",
|
|
8031
|
+
type: "string",
|
|
8032
|
+
inherits: false,
|
|
8033
|
+
application: "next-environment",
|
|
8034
|
+
description: "Branch new environments are created from."
|
|
8035
|
+
},
|
|
8036
|
+
{
|
|
8037
|
+
key: "prBaseBranch",
|
|
8038
|
+
scope: "project",
|
|
8039
|
+
type: "string",
|
|
8040
|
+
inherits: false,
|
|
8041
|
+
application: "applied",
|
|
8042
|
+
description: "Base branch for pull requests and diff baselines."
|
|
8043
|
+
},
|
|
8044
|
+
{
|
|
8045
|
+
key: "defaultPortMappings",
|
|
8046
|
+
scope: "project",
|
|
8047
|
+
type: "port-mappings",
|
|
8048
|
+
inherits: false,
|
|
8049
|
+
environmentTypes: ["container"],
|
|
8050
|
+
application: "next-environment",
|
|
8051
|
+
description: "Port mappings copied into new container environments."
|
|
8052
|
+
},
|
|
8053
|
+
{
|
|
8054
|
+
key: "filesToCopy",
|
|
8055
|
+
scope: "project",
|
|
8056
|
+
type: "string-list",
|
|
8057
|
+
inherits: false,
|
|
8058
|
+
application: "next-environment",
|
|
8059
|
+
description: "Untracked files copied into new environments."
|
|
8060
|
+
},
|
|
8061
|
+
{
|
|
8062
|
+
key: "entryPort",
|
|
8063
|
+
scope: "project",
|
|
8064
|
+
type: "port",
|
|
8065
|
+
inherits: false,
|
|
8066
|
+
application: "next-environment",
|
|
8067
|
+
description: "Default preview entry port for new environments."
|
|
8068
|
+
},
|
|
8069
|
+
...agentSettingDescriptors("project")
|
|
8070
|
+
]);
|
|
8071
|
+
var PUBLIC_ENVIRONMENT_SETTINGS = Object.freeze([
|
|
8072
|
+
{
|
|
8073
|
+
key: "portMappings",
|
|
8074
|
+
scope: "environment",
|
|
8075
|
+
type: "port-mappings",
|
|
8076
|
+
inherits: false,
|
|
8077
|
+
environmentTypes: ["container"],
|
|
8078
|
+
application: "next-start",
|
|
8079
|
+
description: "Container port mappings; a running container adopts them on recreate."
|
|
8080
|
+
},
|
|
8081
|
+
{
|
|
8082
|
+
key: "allowedDomains",
|
|
8083
|
+
scope: "environment",
|
|
8084
|
+
type: "domain-list",
|
|
8085
|
+
inherits: true,
|
|
8086
|
+
environmentTypes: ["container"],
|
|
8087
|
+
application: "next-start",
|
|
8088
|
+
description: "Restricted-network allowlist; unset inherits the global list. A running container whose image supports it applies the change immediately (reported as applied)."
|
|
8089
|
+
},
|
|
8090
|
+
...agentSettingDescriptors("environment")
|
|
8091
|
+
]);
|
|
8092
|
+
|
|
8093
|
+
// src/client/format.ts
|
|
8094
|
+
function table(headers, rows) {
|
|
8095
|
+
if (rows.length === 0)
|
|
8096
|
+
return ["(none)"];
|
|
8097
|
+
const widths = headers.map((header, index) => Math.min(60, Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length))));
|
|
8098
|
+
const line = (cells) => cells.map((cell, index) => {
|
|
8099
|
+
const text = cell.length > widths[index] ? `${cell.slice(0, widths[index] - 1)}\u2026` : cell;
|
|
8100
|
+
return text.padEnd(widths[index]);
|
|
8101
|
+
}).join(" ").trimEnd();
|
|
8102
|
+
return [line(headers), ...rows.map(line)];
|
|
8103
|
+
}
|
|
8104
|
+
function keyValues(entries) {
|
|
8105
|
+
const width = Math.max(...entries.map(([key]) => key.length));
|
|
8106
|
+
return entries.filter(([, value]) => value !== undefined).map(([key, value]) => {
|
|
8107
|
+
const text = value === null ? "-" : typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
8108
|
+
return `${key.padEnd(width)} ${text}`;
|
|
8109
|
+
});
|
|
8110
|
+
}
|
|
8111
|
+
function projectLines(project) {
|
|
8112
|
+
return keyValues([
|
|
8113
|
+
["id", project.id],
|
|
8114
|
+
["name", project.name],
|
|
8115
|
+
["remote", project.gitUrl],
|
|
8116
|
+
["path", project.localPath],
|
|
8117
|
+
["folder", project.folder],
|
|
8118
|
+
["environments", project.environmentCount],
|
|
8119
|
+
["revision", project.revision]
|
|
8120
|
+
]);
|
|
8121
|
+
}
|
|
8122
|
+
function projectRows(projects) {
|
|
8123
|
+
return table(["ID", "NAME", "ENVS", "PATH"], projects.map((project) => [
|
|
8124
|
+
project.id,
|
|
8125
|
+
project.name,
|
|
8126
|
+
String(project.environmentCount),
|
|
8127
|
+
project.localPath ?? "-"
|
|
8128
|
+
]));
|
|
8129
|
+
}
|
|
8130
|
+
function environmentLines(environment) {
|
|
8131
|
+
return keyValues([
|
|
8132
|
+
["id", environment.id],
|
|
8133
|
+
["project", environment.projectId],
|
|
8134
|
+
["name", environment.name],
|
|
8135
|
+
["branch", environment.branch],
|
|
8136
|
+
["type", environment.environmentType],
|
|
8137
|
+
["status", environment.status],
|
|
8138
|
+
["ready", environment.ready],
|
|
8139
|
+
["setup", `${environment.setup.phase}${environment.setup.overridden ? " (overridden)" : ""}`],
|
|
8140
|
+
["activity", environment.activity.state],
|
|
8141
|
+
["lifecycle error", environment.lifecycle.error],
|
|
8142
|
+
["workspace", environment.workspacePath],
|
|
8143
|
+
["base", environment.base.commit ?? environment.base.branch],
|
|
8144
|
+
["startup session", environment.startupSession?.sessionId]
|
|
8145
|
+
]);
|
|
8146
|
+
}
|
|
8147
|
+
function environmentRows(environments) {
|
|
8148
|
+
return table(["ID", "NAME", "TYPE", "STATUS", "READY"], environments.map((environment) => [
|
|
8149
|
+
environment.id,
|
|
8150
|
+
environment.name,
|
|
8151
|
+
environment.environmentType,
|
|
8152
|
+
environment.status,
|
|
8153
|
+
environment.ready ? "yes" : "no"
|
|
8154
|
+
]));
|
|
8155
|
+
}
|
|
8156
|
+
function sessionRows(sessions) {
|
|
8157
|
+
return table(["SESSION", "AGENT", "TAB", "ACTIVITY", "TITLE"], sessions.map((session) => [
|
|
8158
|
+
session.id,
|
|
8159
|
+
session.agent,
|
|
8160
|
+
session.tabId,
|
|
8161
|
+
session.activity,
|
|
8162
|
+
session.title ?? ""
|
|
8163
|
+
]));
|
|
8164
|
+
}
|
|
8165
|
+
function sessionLines(session) {
|
|
8166
|
+
return keyValues([
|
|
8167
|
+
["id", session.id],
|
|
8168
|
+
["environment", session.environmentId],
|
|
8169
|
+
["tab", session.tabId],
|
|
8170
|
+
["agent", session.agent],
|
|
8171
|
+
["title", session.title],
|
|
8172
|
+
["activity", session.activity],
|
|
8173
|
+
["latest request", session.latestRequestId],
|
|
8174
|
+
["pending interactions", session.pendingInteractionCount],
|
|
8175
|
+
["recoverable dispatch", session.recoverableDispatch]
|
|
8176
|
+
]);
|
|
8177
|
+
}
|
|
8178
|
+
function receiptLines(receipt) {
|
|
8179
|
+
return keyValues([
|
|
8180
|
+
["operation", receipt.operationId],
|
|
8181
|
+
["action", receipt.action],
|
|
8182
|
+
["state", receipt.state],
|
|
8183
|
+
["stage", receipt.stage],
|
|
8184
|
+
["request", `${receipt.requestId} (${receipt.namespace})`],
|
|
8185
|
+
["replayed", receipt.replayed],
|
|
8186
|
+
["resources", receipt.resources],
|
|
8187
|
+
["dispatch", receipt.dispatch?.state],
|
|
8188
|
+
["execution", receipt.execution?.state],
|
|
8189
|
+
["evidence", receipt.execution?.evidence],
|
|
8190
|
+
["exit code", receipt.execution?.exitCode],
|
|
8191
|
+
["error", receipt.error ? `${receipt.error.code}: ${receipt.error.message}` : undefined],
|
|
8192
|
+
["updated", receipt.updatedAt],
|
|
8193
|
+
["retained until", receipt.retainedUntil]
|
|
8194
|
+
]);
|
|
8195
|
+
}
|
|
8196
|
+
function settingsLines(snapshot) {
|
|
8197
|
+
return [
|
|
8198
|
+
`revision ${snapshot.revision}`,
|
|
8199
|
+
...table(["KEY", "VALUE", "EFFECTIVE", "SOURCE", "APPLIES"], snapshot.settings.map((setting) => [
|
|
8200
|
+
setting.key,
|
|
8201
|
+
setting.value === null || setting.value === undefined ? "-" : JSON.stringify(setting.value),
|
|
8202
|
+
setting.effective === null || setting.effective === undefined ? "-" : JSON.stringify(setting.effective),
|
|
8203
|
+
setting.source,
|
|
8204
|
+
setting.application
|
|
8205
|
+
]))
|
|
8206
|
+
];
|
|
8207
|
+
}
|
|
8208
|
+
|
|
8209
|
+
// src/client/wait.ts
|
|
8210
|
+
class ObservationStopped extends Error {
|
|
8211
|
+
reason;
|
|
8212
|
+
signalName;
|
|
8213
|
+
constructor(reason, signalName) {
|
|
8214
|
+
super(reason === "deadline" ? "Observation deadline passed" : "Observation interrupted");
|
|
8215
|
+
this.reason = reason;
|
|
8216
|
+
this.signalName = signalName;
|
|
8217
|
+
}
|
|
8218
|
+
}
|
|
8219
|
+
function defaultSleep(ms, signal) {
|
|
8220
|
+
return new Promise((resolve) => {
|
|
8221
|
+
if (signal?.aborted) {
|
|
8222
|
+
resolve();
|
|
8223
|
+
return;
|
|
8224
|
+
}
|
|
8225
|
+
const timer = setTimeout(done, ms);
|
|
8226
|
+
function done() {
|
|
8227
|
+
clearTimeout(timer);
|
|
8228
|
+
signal?.removeEventListener("abort", done);
|
|
8229
|
+
resolve();
|
|
8230
|
+
}
|
|
8231
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
8232
|
+
});
|
|
8233
|
+
}
|
|
8234
|
+
function signalName(signal) {
|
|
8235
|
+
return signal?.reason === "SIGTERM" ? "SIGTERM" : "SIGINT";
|
|
8236
|
+
}
|
|
8237
|
+
async function observe(check, options2) {
|
|
8238
|
+
if (options2.timeoutMs > PUBLIC_API_LIMITS.waitMaxMs) {
|
|
8239
|
+
throw new CliError("invalid-input", "--timeout may be at most 24h");
|
|
8240
|
+
}
|
|
8241
|
+
const now = options2.now ?? Date.now;
|
|
8242
|
+
const sleep = options2.sleep ?? defaultSleep;
|
|
8243
|
+
const deadline = now() + options2.timeoutMs;
|
|
8244
|
+
let delay = 250;
|
|
8245
|
+
for (;; ) {
|
|
8246
|
+
if (options2.signal?.aborted)
|
|
8247
|
+
throw new ObservationStopped("signal", signalName(options2.signal));
|
|
8248
|
+
const step = await check();
|
|
8249
|
+
if (step.done)
|
|
8250
|
+
return step.value;
|
|
8251
|
+
const remaining = deadline - now();
|
|
8252
|
+
if (remaining <= 0)
|
|
8253
|
+
throw new ObservationStopped("deadline");
|
|
8254
|
+
await sleep(Math.min(delay, remaining), options2.signal);
|
|
8255
|
+
delay = Math.min(delay * 2, 2000);
|
|
8256
|
+
}
|
|
8257
|
+
}
|
|
8258
|
+
|
|
8259
|
+
// src/client/commands/common.ts
|
|
8260
|
+
var REQUEST_ID_OPTION = {
|
|
8261
|
+
name: "request-id",
|
|
8262
|
+
kind: "string",
|
|
8263
|
+
valueName: "KEY",
|
|
8264
|
+
description: "Idempotency key. Reusing it returns the original operation; reusing it with a different intent conflicts. Generated and saved locally when omitted."
|
|
8265
|
+
};
|
|
8266
|
+
var TIMEOUT_OPTION = {
|
|
8267
|
+
name: "timeout",
|
|
8268
|
+
kind: "duration",
|
|
8269
|
+
valueName: "DURATION",
|
|
8270
|
+
description: "How long to observe before exiting 5 (the operation keeps running). Default 10m."
|
|
8271
|
+
};
|
|
8272
|
+
var PROMPT_OPTIONS = [
|
|
8273
|
+
{
|
|
8274
|
+
name: "prompt-file",
|
|
8275
|
+
kind: "string",
|
|
8276
|
+
valueName: "PATH",
|
|
8277
|
+
description: "Read the prompt from a local file."
|
|
8278
|
+
},
|
|
8279
|
+
{ name: "prompt-stdin", kind: "boolean", description: "Read the prompt from stdin." },
|
|
8280
|
+
{
|
|
8281
|
+
name: "prompt",
|
|
8282
|
+
kind: "string",
|
|
8283
|
+
valueName: "TEXT",
|
|
8284
|
+
description: "Inline prompt (prefer a file for long text)."
|
|
8285
|
+
}
|
|
8286
|
+
];
|
|
8287
|
+
var DEFAULT_WAIT_MS = 10 * 60 * 1000;
|
|
8288
|
+
function stringOption(options2, name) {
|
|
8289
|
+
const value = options2[name];
|
|
8290
|
+
return typeof value === "string" ? value : undefined;
|
|
8291
|
+
}
|
|
8292
|
+
function oneOf(value, allowed, label) {
|
|
8293
|
+
if (typeof value !== "string" || !allowed.includes(value)) {
|
|
8294
|
+
throw new CliError("invalid-input", `${label} must be one of: ${allowed.join(", ")}`);
|
|
8295
|
+
}
|
|
8296
|
+
return value;
|
|
8297
|
+
}
|
|
8298
|
+
function optionalBoolean(options2, name) {
|
|
8299
|
+
const value = options2[name];
|
|
8300
|
+
return typeof value === "boolean" ? value : undefined;
|
|
8301
|
+
}
|
|
8302
|
+
async function submit(context, action, input, options2) {
|
|
8303
|
+
const session = await context.session();
|
|
8304
|
+
const outcome = await session.mutate(action, input, stringOption(options2, "request-id"));
|
|
8305
|
+
return { session, ...outcome };
|
|
8306
|
+
}
|
|
8307
|
+
async function readOperation(session, operationId) {
|
|
8308
|
+
const { receipt } = await session.readWithReceipt("run.get", { operationId });
|
|
8309
|
+
if (!receipt)
|
|
8310
|
+
throw new CliError("response-invalid", "run.get returned no receipt");
|
|
8311
|
+
return receipt;
|
|
8312
|
+
}
|
|
8313
|
+
function isTerminalReceipt(receipt) {
|
|
8314
|
+
return !["admitted", "running", "unknown"].includes(receipt.state);
|
|
8315
|
+
}
|
|
8316
|
+
function stoppedError(error, action, receipt, what) {
|
|
8317
|
+
if (error.reason === "signal") {
|
|
8318
|
+
return new CliError("observation-interrupted", `Stopped observing ${what}; it continues in the backend`, {
|
|
8319
|
+
action,
|
|
8320
|
+
...receipt ? { receipt } : {},
|
|
8321
|
+
details: { signal: error.signalName ?? "SIGINT" }
|
|
8322
|
+
});
|
|
8323
|
+
}
|
|
8324
|
+
return new CliError("deadline-exceeded", `Timed out observing ${what}; it continues in the backend`, {
|
|
8325
|
+
action,
|
|
8326
|
+
...receipt ? { receipt } : {}
|
|
8327
|
+
});
|
|
8328
|
+
}
|
|
8329
|
+
function receiptFailure(receipt) {
|
|
8330
|
+
if (receipt.state === "succeeded")
|
|
8331
|
+
return;
|
|
8332
|
+
const message = receipt.error?.message ?? `Operation ended ${receipt.state}`;
|
|
8333
|
+
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";
|
|
8334
|
+
return new CliError(code, message, { receipt, action: receipt.action });
|
|
8335
|
+
}
|
|
8336
|
+
async function waitForOperation(context, session, initial, timeoutMs, options2 = {}) {
|
|
8337
|
+
let latest = initial;
|
|
8338
|
+
try {
|
|
8339
|
+
return await observe(async () => {
|
|
8340
|
+
latest = await readOperation(session, initial.operationId);
|
|
8341
|
+
if (isTerminalReceipt(latest))
|
|
8342
|
+
return { done: true, value: latest };
|
|
8343
|
+
const execution = latest.execution;
|
|
8344
|
+
if (execution?.state === "waiting-for-input" && !options2.continueOnInteraction) {
|
|
8345
|
+
throw new CliError("interaction-required", "The run is waiting for an answer to a pending interaction", {
|
|
8346
|
+
receipt: latest,
|
|
8347
|
+
action: latest.action,
|
|
8348
|
+
details: { interactions: execution.interactions ?? [] }
|
|
8349
|
+
});
|
|
8350
|
+
}
|
|
8351
|
+
if (latest.state === "unknown" && latest.dispatch?.state === "unknown") {
|
|
8352
|
+
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 });
|
|
8353
|
+
}
|
|
8354
|
+
if (latest.state === "unknown") {
|
|
8355
|
+
throw new CliError("run-unknown", latest.error?.message ?? "The outcome of this run is not known", {
|
|
8356
|
+
receipt: latest,
|
|
8357
|
+
action: latest.action
|
|
8358
|
+
});
|
|
8359
|
+
}
|
|
8360
|
+
if (execution?.state === "unsupported") {
|
|
8361
|
+
throw new CliError("capability-unavailable", execution.reason ?? "Completion of this run cannot be observed for its provider", { receipt: latest, action: latest.action });
|
|
8362
|
+
}
|
|
8363
|
+
return { done: false };
|
|
8364
|
+
}, { timeoutMs, signal: context.signal });
|
|
8365
|
+
} catch (error) {
|
|
8366
|
+
if (error instanceof ObservationStopped) {
|
|
8367
|
+
throw stoppedError(error, latest.action, latest, "the operation");
|
|
8368
|
+
}
|
|
8369
|
+
throw error;
|
|
8370
|
+
}
|
|
8371
|
+
}
|
|
8372
|
+
async function waitForEnvironment(context, session, environmentId, condition, receipt, timeoutMs) {
|
|
8373
|
+
let latestReceipt = receipt;
|
|
8374
|
+
const conditionMet = (environment) => {
|
|
8375
|
+
if (condition === "deleted")
|
|
8376
|
+
return environment === null;
|
|
8377
|
+
if (!environment)
|
|
8378
|
+
return false;
|
|
8379
|
+
if (condition === "running")
|
|
8380
|
+
return environment.status === "running";
|
|
8381
|
+
if (condition === "stopped")
|
|
8382
|
+
return environment.status === "stopped";
|
|
8383
|
+
return environment.ready;
|
|
8384
|
+
};
|
|
8385
|
+
try {
|
|
8386
|
+
return await observe(async () => {
|
|
8387
|
+
if (latestReceipt && !isTerminalReceipt(latestReceipt)) {
|
|
8388
|
+
latestReceipt = await readOperation(session, latestReceipt.operationId);
|
|
8389
|
+
}
|
|
8390
|
+
if (latestReceipt && isTerminalReceipt(latestReceipt)) {
|
|
8391
|
+
const failure = receiptFailure(latestReceipt);
|
|
8392
|
+
if (failure)
|
|
8393
|
+
throw failure;
|
|
8394
|
+
}
|
|
8395
|
+
let environment;
|
|
8396
|
+
try {
|
|
8397
|
+
environment = await session.read("environment.get", {
|
|
8398
|
+
environmentId
|
|
8399
|
+
});
|
|
8400
|
+
} catch (error) {
|
|
8401
|
+
if (error instanceof CliError && error.code === "not-found")
|
|
8402
|
+
environment = null;
|
|
8403
|
+
else
|
|
8404
|
+
throw error;
|
|
8405
|
+
}
|
|
8406
|
+
if (condition !== "deleted" && !environment) {
|
|
8407
|
+
throw new CliError("not-found", "The environment no longer exists", latestReceipt ? { receipt: latestReceipt } : {});
|
|
8408
|
+
}
|
|
8409
|
+
if (environment && (condition === "ready" || condition === "running")) {
|
|
8410
|
+
if (environment.setup.phase === "failed" || environment.status === "error") {
|
|
8411
|
+
throw new CliError("setup-failed", environment.lifecycle.error ?? "Environment setup failed", { ...latestReceipt ? { receipt: latestReceipt } : {}, details: { environmentId } });
|
|
8412
|
+
}
|
|
8413
|
+
}
|
|
8414
|
+
const operationSettled = !latestReceipt || isTerminalReceipt(latestReceipt) || condition === "running" && latestReceipt.stage === "setup";
|
|
8415
|
+
if (operationSettled && conditionMet(environment)) {
|
|
8416
|
+
return {
|
|
8417
|
+
done: true,
|
|
8418
|
+
value: { environment, ...latestReceipt ? { receipt: latestReceipt } : {} }
|
|
8419
|
+
};
|
|
8420
|
+
}
|
|
8421
|
+
return { done: false };
|
|
8422
|
+
}, { timeoutMs, signal: context.signal });
|
|
8423
|
+
} catch (error) {
|
|
8424
|
+
if (error instanceof ObservationStopped) {
|
|
8425
|
+
throw stoppedError(error, latestReceipt?.action ?? "environment.get", latestReceipt, `the environment (${condition})`);
|
|
8426
|
+
}
|
|
8427
|
+
throw error;
|
|
8428
|
+
}
|
|
8429
|
+
}
|
|
8430
|
+
|
|
8431
|
+
// src/client/commands/changes.ts
|
|
8432
|
+
var SCOPE_LABELS = {
|
|
8433
|
+
"changes.scope.environment-net": "Environment changes",
|
|
8434
|
+
"changes.scope.turn-net": "Turn changes",
|
|
8435
|
+
"changes.scope.provider-turn-diff": "Provider-reported turn changes",
|
|
8436
|
+
"changes.scope.thread-net": "Thread changes"
|
|
8437
|
+
};
|
|
8438
|
+
var PROVENANCE_LABELS = {
|
|
8439
|
+
"changes.provenance.observed": "observed in the workspace",
|
|
8440
|
+
"changes.provenance.observed-fresh": "measured in the workspace",
|
|
8441
|
+
"changes.provenance.provider-reported": "reported by the provider",
|
|
8442
|
+
"changes.provenance.unknown": "unknown"
|
|
8443
|
+
};
|
|
8444
|
+
function label(key, labels) {
|
|
8445
|
+
return labels[key] ?? key.replace(/^changes\.[a-z-]+\./, "").replaceAll("-", " ");
|
|
8446
|
+
}
|
|
8447
|
+
function formatLineCount(sign, count) {
|
|
8448
|
+
if (count.quality === "not-applicable")
|
|
8449
|
+
return "";
|
|
8450
|
+
if (count.value === null)
|
|
8451
|
+
return `${sign}?`;
|
|
8452
|
+
const value = `${sign}${count.value}`;
|
|
8453
|
+
if (count.bound === "lower")
|
|
8454
|
+
return `\u2265${value}`;
|
|
8455
|
+
if (count.bound === "upper")
|
|
8456
|
+
return `\u2264${value}`;
|
|
8457
|
+
return count.quality === "complete" ? value : `~${value}`;
|
|
8458
|
+
}
|
|
8459
|
+
function fileCount(count) {
|
|
8460
|
+
if (count === null)
|
|
8461
|
+
return "? files";
|
|
8462
|
+
return `${count} file${count === 1 ? "" : "s"}`;
|
|
8463
|
+
}
|
|
8464
|
+
function countsSummary(projection) {
|
|
8465
|
+
if (projection.measurementId === null) {
|
|
8466
|
+
return projection.state === "unavailable" ? "unavailable" : "not measured yet";
|
|
8467
|
+
}
|
|
8468
|
+
const { additions, deletions, changedFiles } = projection.counters;
|
|
8469
|
+
const lines = [formatLineCount("+", additions), formatLineCount("-", deletions)].filter(Boolean).join(" ");
|
|
8470
|
+
return [fileCount(changedFiles), lines].filter(Boolean).join(" ");
|
|
8471
|
+
}
|
|
8472
|
+
function rowPath(row) {
|
|
8473
|
+
if (row.oldPathDisplay && row.newPathDisplay && row.oldPathDisplay !== row.newPathDisplay) {
|
|
8474
|
+
return `${row.oldPathDisplay} \u2192 ${row.newPathDisplay}`;
|
|
8475
|
+
}
|
|
8476
|
+
return row.newPathDisplay ?? row.oldPathDisplay ?? row.pathId;
|
|
8477
|
+
}
|
|
8478
|
+
function rowCounts(row) {
|
|
8479
|
+
if (row.additions.quality === "not-applicable" && row.deletions.quality === "not-applicable") {
|
|
8480
|
+
return row.fileKind;
|
|
8481
|
+
}
|
|
8482
|
+
return [formatLineCount("+", row.additions), formatLineCount("-", row.deletions)].filter(Boolean).join(" ");
|
|
8483
|
+
}
|
|
8484
|
+
function changesViewLines(view) {
|
|
8485
|
+
const { projection, files } = view;
|
|
8486
|
+
const states = [
|
|
8487
|
+
projection.state,
|
|
8488
|
+
`observation ${projection.observationState}`,
|
|
8489
|
+
`coverage ${projection.coverageState}`
|
|
8490
|
+
].join(", ");
|
|
8491
|
+
const lines = [
|
|
8492
|
+
`${label(projection.labelKey, SCOPE_LABELS)} (environment ${view.environmentId})`,
|
|
8493
|
+
...keyValues([
|
|
8494
|
+
["state", states],
|
|
8495
|
+
["changes", countsSummary(projection)],
|
|
8496
|
+
["provenance", label(projection.provenanceLabelKey, PROVENANCE_LABELS)],
|
|
8497
|
+
["checked", projection.freshness.lastSuccessfulCheckAt],
|
|
8498
|
+
[
|
|
8499
|
+
"reasons",
|
|
8500
|
+
projection.reasonKeys.length > 0 ? projection.reasonKeys.map((key) => label(key, {})).join("; ") : undefined
|
|
8501
|
+
]
|
|
8502
|
+
])
|
|
8503
|
+
];
|
|
8504
|
+
if (files.rows.length > 0) {
|
|
8505
|
+
lines.push("", ...table(["FILE", "STATUS", "LINES", "NOTE"], files.rows.map((row) => [
|
|
8506
|
+
rowPath(row),
|
|
8507
|
+
row.status,
|
|
8508
|
+
rowCounts(row),
|
|
8509
|
+
row.reasonKey ? label(row.reasonKey, {}) : ""
|
|
8510
|
+
])));
|
|
8511
|
+
}
|
|
8512
|
+
if (files.nextCursor) {
|
|
8513
|
+
lines.push("", `${files.rows.length} of ${files.totalRows ?? "?"} files shown; next page: --cursor ${files.nextCursor}`);
|
|
8514
|
+
}
|
|
8515
|
+
return lines;
|
|
8516
|
+
}
|
|
8517
|
+
function changesHistoryLines(history) {
|
|
8518
|
+
const lines = table(["TURN", "LIFECYCLE", "FINISHED", "CHANGES"], history.entries.map((entry) => {
|
|
8519
|
+
const projection = entry.projections[entry.turnResourceKey];
|
|
8520
|
+
return [
|
|
8521
|
+
entry.turnId,
|
|
8522
|
+
entry.lifecycle,
|
|
8523
|
+
entry.finishedAt ?? "-",
|
|
8524
|
+
projection ? countsSummary(projection) : "unavailable"
|
|
8525
|
+
];
|
|
8526
|
+
}));
|
|
8527
|
+
if (history.nextCursor)
|
|
8528
|
+
lines.push("", `newer turns: --cursor ${history.nextCursor}`);
|
|
8529
|
+
return lines;
|
|
8530
|
+
}
|
|
8531
|
+
var changesCommands = [
|
|
8532
|
+
{
|
|
8533
|
+
path: ["changes", "view"],
|
|
8534
|
+
summary: "Show an environment's file and line changes as the backend measures them.",
|
|
8535
|
+
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}).",
|
|
8536
|
+
positionals: [{ name: "environment", required: true, description: "Environment ID." }],
|
|
8537
|
+
options: [
|
|
8538
|
+
{
|
|
8539
|
+
name: "scope",
|
|
8540
|
+
kind: "string",
|
|
8541
|
+
valueName: PUBLIC_CHANGE_SCOPES.join("|"),
|
|
8542
|
+
description: "What to compare (default environment: its baseline to its workspace now)."
|
|
8543
|
+
},
|
|
8544
|
+
{
|
|
8545
|
+
name: "page-size",
|
|
8546
|
+
kind: "integer",
|
|
8547
|
+
valueName: "N",
|
|
8548
|
+
description: "Files per page (1-200, default 200)."
|
|
8549
|
+
},
|
|
8550
|
+
{
|
|
8551
|
+
name: "cursor",
|
|
8552
|
+
kind: "string",
|
|
8553
|
+
valueName: "CURSOR",
|
|
8554
|
+
description: "Next-page cursor from a previous read."
|
|
8555
|
+
}
|
|
8556
|
+
],
|
|
8557
|
+
examples: [
|
|
8558
|
+
'orkestrator changes view "$ENV"',
|
|
8559
|
+
`orkestrator changes view "$ENV" --page-size 20 --json | jq '.result.projection.counters'`
|
|
8560
|
+
],
|
|
8561
|
+
async run(context, parsed) {
|
|
8562
|
+
const input = { environmentId: parsed.positionals.environment };
|
|
8563
|
+
const scope = stringOption(parsed.options, "scope");
|
|
8564
|
+
if (scope !== undefined)
|
|
8565
|
+
input.scope = oneOf(scope, PUBLIC_CHANGE_SCOPES, "--scope");
|
|
8566
|
+
if (parsed.options["page-size"] !== undefined)
|
|
8567
|
+
input.pageSize = parsed.options["page-size"];
|
|
8568
|
+
const cursor = stringOption(parsed.options, "cursor");
|
|
8569
|
+
if (cursor !== undefined)
|
|
8570
|
+
input.cursor = cursor;
|
|
8571
|
+
const session = await context.session();
|
|
8572
|
+
const view = await session.read("changes.view", input);
|
|
8573
|
+
return {
|
|
8574
|
+
action: "changes.view",
|
|
8575
|
+
result: view,
|
|
8576
|
+
connection: session.identity,
|
|
8577
|
+
human: changesViewLines(view)
|
|
8578
|
+
};
|
|
8579
|
+
}
|
|
8580
|
+
},
|
|
8581
|
+
{
|
|
8582
|
+
path: ["changes", "history"],
|
|
8583
|
+
summary: "List a session's turns, oldest first, with the changes each one made.",
|
|
8584
|
+
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.",
|
|
8585
|
+
positionals: [{ name: "session", required: true, description: "Session ID (ses_\u2026)." }],
|
|
8586
|
+
options: [
|
|
8587
|
+
{
|
|
8588
|
+
name: "limit",
|
|
8589
|
+
kind: "integer",
|
|
8590
|
+
valueName: "N",
|
|
8591
|
+
description: "Turns per page (1-200, default 50)."
|
|
8592
|
+
},
|
|
8593
|
+
{
|
|
8594
|
+
name: "cursor",
|
|
8595
|
+
kind: "string",
|
|
8596
|
+
valueName: "CURSOR",
|
|
8597
|
+
description: "Cursor from a previous page."
|
|
8598
|
+
}
|
|
8599
|
+
],
|
|
8600
|
+
async run(context, parsed) {
|
|
8601
|
+
const input = { sessionId: parsed.positionals.session };
|
|
8602
|
+
if (parsed.options.limit !== undefined)
|
|
8603
|
+
input.limit = parsed.options.limit;
|
|
8604
|
+
const cursor = stringOption(parsed.options, "cursor");
|
|
8605
|
+
if (cursor !== undefined)
|
|
8606
|
+
input.cursor = cursor;
|
|
8607
|
+
const session = await context.session();
|
|
8608
|
+
const history = await session.read("changes.history", input);
|
|
8609
|
+
return {
|
|
8610
|
+
action: "changes.history",
|
|
8611
|
+
result: history,
|
|
8612
|
+
connection: session.identity,
|
|
8613
|
+
human: changesHistoryLines(history)
|
|
8614
|
+
};
|
|
8615
|
+
}
|
|
8616
|
+
}
|
|
8617
|
+
];
|
|
8618
|
+
|
|
8619
|
+
// src/client/commands/connection.ts
|
|
8620
|
+
import path9 from "path";
|
|
8621
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
8622
|
+
import { promises as fs4 } from "fs";
|
|
8623
|
+
|
|
8624
|
+
// ../protocol/src/backend-instance.ts
|
|
8625
|
+
var BACKEND_INSTANCE_DESCRIPTOR_FILE = "backend-instance.json";
|
|
8626
|
+
var BACKEND_INSTANCE_DESCRIPTOR_TYPE = "orkestrator-backend-instance";
|
|
8627
|
+
var BACKEND_INSTANCE_DESCRIPTOR_MAX_BYTES = 16 * 1024;
|
|
8628
|
+
function isRecord3(value) {
|
|
8629
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8630
|
+
}
|
|
8631
|
+
function isHttpUrl(value) {
|
|
8632
|
+
if (typeof value !== "string" || value.length > 2048)
|
|
8633
|
+
return false;
|
|
8634
|
+
try {
|
|
8635
|
+
const url = new URL(value);
|
|
8636
|
+
return (url.protocol === "http:" || url.protocol === "https:") && url.username === "" && url.password === "" && url.search === "" && url.hash === "";
|
|
8637
|
+
} catch {
|
|
8638
|
+
return false;
|
|
8639
|
+
}
|
|
8640
|
+
}
|
|
8641
|
+
function parseBackendInstanceDescriptor(value) {
|
|
8642
|
+
if (!isRecord3(value))
|
|
8643
|
+
throw new Error("Backend instance descriptor must be an object");
|
|
8644
|
+
if (value.version !== 1 || value.type !== BACKEND_INSTANCE_DESCRIPTOR_TYPE) {
|
|
8645
|
+
throw new Error("Backend instance descriptor has an unsupported version");
|
|
8646
|
+
}
|
|
8647
|
+
const text = (key, max = 512) => {
|
|
8648
|
+
const field = value[key];
|
|
8649
|
+
if (typeof field !== "string" || field.length === 0 || field.length > max) {
|
|
8650
|
+
throw new Error(`Backend instance descriptor field is invalid: ${key}`);
|
|
8651
|
+
}
|
|
8652
|
+
return field;
|
|
8653
|
+
};
|
|
8654
|
+
if (!Number.isSafeInteger(value.pid) || value.pid <= 0) {
|
|
8655
|
+
throw new Error("Backend instance descriptor field is invalid: pid");
|
|
8656
|
+
}
|
|
8657
|
+
if (!isHttpUrl(value.url))
|
|
8658
|
+
throw new Error("Backend instance descriptor field is invalid: url");
|
|
8659
|
+
if (value.browserUrl !== undefined && !isHttpUrl(value.browserUrl)) {
|
|
8660
|
+
throw new Error("Backend instance descriptor field is invalid: browserUrl");
|
|
8661
|
+
}
|
|
8662
|
+
if (!Number.isSafeInteger(value.publicApiSchemaVersion) || value.publicApiSchemaVersion < 1) {
|
|
8663
|
+
throw new Error("Backend instance descriptor field is invalid: publicApiSchemaVersion");
|
|
8664
|
+
}
|
|
8665
|
+
const authFile = text("authFile", 4096);
|
|
8666
|
+
const dataDir = text("dataDir", 4096);
|
|
8667
|
+
if (!authFile.startsWith("/") || !dataDir.startsWith("/")) {
|
|
8668
|
+
throw new Error("Backend instance descriptor paths must be absolute");
|
|
8669
|
+
}
|
|
8670
|
+
return {
|
|
8671
|
+
version: 1,
|
|
8672
|
+
type: BACKEND_INSTANCE_DESCRIPTOR_TYPE,
|
|
8673
|
+
installationId: text("installationId", 200),
|
|
8674
|
+
generation: text("generation", 200),
|
|
8675
|
+
pid: value.pid,
|
|
8676
|
+
startedAt: text("startedAt", 64),
|
|
8677
|
+
url: value.url,
|
|
8678
|
+
...value.browserUrl !== undefined ? { browserUrl: value.browserUrl } : {},
|
|
8679
|
+
authFile,
|
|
8680
|
+
dataDir,
|
|
8681
|
+
appVersion: text("appVersion", 64),
|
|
8682
|
+
publicApiSchemaVersion: value.publicApiSchemaVersion
|
|
8683
|
+
};
|
|
8684
|
+
}
|
|
7485
8685
|
|
|
7486
8686
|
// src/client/receipts.ts
|
|
7487
8687
|
import { createHash } from "crypto";
|
|
7488
8688
|
import { promises as fs2 } from "fs";
|
|
7489
|
-
import
|
|
8689
|
+
import path6 from "path";
|
|
7490
8690
|
var MAX_LOCAL_RECEIPTS_PER_INSTALLATION = 1000;
|
|
7491
8691
|
var MAX_RECEIPT_BYTES = 8 * 1024;
|
|
7492
8692
|
function receiptKey(action, requestId) {
|
|
@@ -7502,10 +8702,10 @@ class LocalReceiptStore {
|
|
|
7502
8702
|
this.root = root;
|
|
7503
8703
|
}
|
|
7504
8704
|
directory(installationId) {
|
|
7505
|
-
return
|
|
8705
|
+
return path6.join(this.root, safeSegment(installationId));
|
|
7506
8706
|
}
|
|
7507
8707
|
file(installationId, action, requestId) {
|
|
7508
|
-
return
|
|
8708
|
+
return path6.join(this.directory(installationId), `${receiptKey(action, requestId)}.json`);
|
|
7509
8709
|
}
|
|
7510
8710
|
async find(installationId, action, requestId) {
|
|
7511
8711
|
return this.readFile(this.file(installationId, action, requestId));
|
|
@@ -7529,14 +8729,14 @@ class LocalReceiptStore {
|
|
|
7529
8729
|
});
|
|
7530
8730
|
}
|
|
7531
8731
|
async list(installationId, limit = 50) {
|
|
7532
|
-
const directories = installationId ? [this.directory(installationId)] : (await fs2.readdir(this.root).catch(() => [])).map((name) =>
|
|
8732
|
+
const directories = installationId ? [this.directory(installationId)] : (await fs2.readdir(this.root).catch(() => [])).map((name) => path6.join(this.root, name));
|
|
7533
8733
|
const receipts = [];
|
|
7534
8734
|
for (const directory of directories) {
|
|
7535
8735
|
const names = await fs2.readdir(directory).catch(() => []);
|
|
7536
8736
|
for (const name of names) {
|
|
7537
8737
|
if (!name.endsWith(".json"))
|
|
7538
8738
|
continue;
|
|
7539
|
-
const receipt = await this.readFile(
|
|
8739
|
+
const receipt = await this.readFile(path6.join(directory, name));
|
|
7540
8740
|
if (receipt)
|
|
7541
8741
|
receipts.push(receipt);
|
|
7542
8742
|
}
|
|
@@ -7564,7 +8764,7 @@ class LocalReceiptStore {
|
|
|
7564
8764
|
if (names.length <= MAX_LOCAL_RECEIPTS_PER_INSTALLATION)
|
|
7565
8765
|
return;
|
|
7566
8766
|
const entries = await Promise.all(names.map(async (name) => {
|
|
7567
|
-
const file =
|
|
8767
|
+
const file = path6.join(directory, name);
|
|
7568
8768
|
const stat = await fs2.stat(file).catch(() => null);
|
|
7569
8769
|
return { file, mtime: stat?.mtimeMs ?? 0 };
|
|
7570
8770
|
}));
|
|
@@ -7859,12 +9059,12 @@ function envelopeError(response) {
|
|
|
7859
9059
|
|
|
7860
9060
|
// src/client/targets.ts
|
|
7861
9061
|
import { promises as fs3 } from "fs";
|
|
7862
|
-
import
|
|
9062
|
+
import path8 from "path";
|
|
7863
9063
|
import { isIPv4 } from "net";
|
|
7864
9064
|
|
|
7865
9065
|
// ../protocol/src/node/runtime-profile-status.ts
|
|
7866
|
-
import
|
|
7867
|
-
import
|
|
9066
|
+
import os4 from "os";
|
|
9067
|
+
import path7 from "path";
|
|
7868
9068
|
var RUNTIME_APP_SLUG = "orkestrator-v2";
|
|
7869
9069
|
var RUNTIME_STATUS_KEYS = new Set([
|
|
7870
9070
|
"version",
|
|
@@ -7906,29 +9106,29 @@ function normalizeRuntimeProfileId(value) {
|
|
|
7906
9106
|
}
|
|
7907
9107
|
return normalized;
|
|
7908
9108
|
}
|
|
7909
|
-
function defaultRuntimeProfileRoots(platform = process.platform, env = process.env, homeDir =
|
|
9109
|
+
function defaultRuntimeProfileRoots(platform = process.platform, env = process.env, homeDir = os4.homedir()) {
|
|
7910
9110
|
if (platform === "darwin") {
|
|
7911
|
-
const appSupport =
|
|
9111
|
+
const appSupport = path7.join(homeDir, "Library", "Application Support");
|
|
7912
9112
|
return {
|
|
7913
|
-
developmentRoot:
|
|
7914
|
-
productionDataDir:
|
|
9113
|
+
developmentRoot: path7.join(appSupport, `${RUNTIME_APP_SLUG}-dev`),
|
|
9114
|
+
productionDataDir: path7.join(appSupport, RUNTIME_APP_SLUG),
|
|
7915
9115
|
homeDir
|
|
7916
9116
|
};
|
|
7917
9117
|
}
|
|
7918
9118
|
if (platform === "win32") {
|
|
7919
9119
|
throw new Error("Orkestrator development profiles support macOS and Linux only");
|
|
7920
9120
|
}
|
|
7921
|
-
const configRoot = env.XDG_CONFIG_HOME ??
|
|
9121
|
+
const configRoot = env.XDG_CONFIG_HOME ?? path7.join(homeDir, ".config");
|
|
7922
9122
|
return {
|
|
7923
|
-
developmentRoot:
|
|
7924
|
-
productionDataDir:
|
|
9123
|
+
developmentRoot: path7.join(configRoot, `${RUNTIME_APP_SLUG}-dev`),
|
|
9124
|
+
productionDataDir: path7.join(configRoot, RUNTIME_APP_SLUG),
|
|
7925
9125
|
homeDir
|
|
7926
9126
|
};
|
|
7927
9127
|
}
|
|
7928
9128
|
function runtimeProfileStatusPath(developmentRoot, profileName) {
|
|
7929
9129
|
const id = normalizeRuntimeProfileId(profileName);
|
|
7930
|
-
const profileRoot =
|
|
7931
|
-
return { id, profileRoot, statusPath:
|
|
9130
|
+
const profileRoot = path7.resolve(developmentRoot, "profiles", id);
|
|
9131
|
+
return { id, profileRoot, statusPath: path7.join(profileRoot, "runtime", "status.json") };
|
|
7932
9132
|
}
|
|
7933
9133
|
|
|
7934
9134
|
// src/client/targets.ts
|
|
@@ -8005,7 +9205,7 @@ async function readDescriptor(file) {
|
|
|
8005
9205
|
throw new Error("too large");
|
|
8006
9206
|
text = await fs3.readFile(file, "utf8");
|
|
8007
9207
|
} catch {
|
|
8008
|
-
throw new CliError("connection-failed", `No running backend published ${
|
|
9208
|
+
throw new CliError("connection-failed", `No running backend published ${path8.basename(file)} at the selected location`);
|
|
8009
9209
|
}
|
|
8010
9210
|
try {
|
|
8011
9211
|
return parseBackendInstanceDescriptor(JSON.parse(text));
|
|
@@ -8102,7 +9302,7 @@ async function resolveProfile(profileName, env) {
|
|
|
8102
9302
|
}
|
|
8103
9303
|
const refresh = () => resolveProfile(profileName, env);
|
|
8104
9304
|
if (manifest.dataDir) {
|
|
8105
|
-
const descriptorFile =
|
|
9305
|
+
const descriptorFile = path8.join(manifest.dataDir, BACKEND_INSTANCE_DESCRIPTOR_FILE);
|
|
8106
9306
|
const descriptor = await readDescriptor(descriptorFile).catch(() => null);
|
|
8107
9307
|
if (descriptor) {
|
|
8108
9308
|
if (descriptor.pid !== backendPid) {
|
|
@@ -8115,235 +9315,13 @@ async function resolveProfile(profileName, env) {
|
|
|
8115
9315
|
throw new CliError("profile-unavailable", `Profile '${located.id}' does not publish a gateway endpoint`);
|
|
8116
9316
|
}
|
|
8117
9317
|
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
|
-
}
|
|
9318
|
+
const authFile = manifest.authFile;
|
|
9319
|
+
return {
|
|
9320
|
+
identity: { name: located.id, kind: "profile", endpoint: redactEndpoint(baseUrl) },
|
|
9321
|
+
baseUrl,
|
|
9322
|
+
readToken: () => readTokenFile(authFile, true),
|
|
9323
|
+
refresh
|
|
9324
|
+
};
|
|
8347
9325
|
}
|
|
8348
9326
|
|
|
8349
9327
|
// src/client/commands/connection.ts
|
|
@@ -8353,7 +9331,7 @@ function store(context) {
|
|
|
8353
9331
|
async function removeManagedCredential(configStore, name, saved) {
|
|
8354
9332
|
if (saved?.kind !== "endpoint")
|
|
8355
9333
|
return;
|
|
8356
|
-
const legacyFile =
|
|
9334
|
+
const legacyFile = path9.join(configStore.credentialsDirectory, `${name}.json`);
|
|
8357
9335
|
if (saved.managedCredential !== true && saved.credentialFile !== legacyFile)
|
|
8358
9336
|
return;
|
|
8359
9337
|
await configStore.removeOwnedCredential(saved.credentialFile).catch(() => {
|
|
@@ -8513,7 +9491,7 @@ var connectionCommands = [
|
|
|
8513
9491
|
const normalizedUrl = normalizeBaseUrl(url);
|
|
8514
9492
|
let file;
|
|
8515
9493
|
if (credentialFile) {
|
|
8516
|
-
file =
|
|
9494
|
+
file = path9.resolve(context.io.cwd, credentialFile);
|
|
8517
9495
|
await readTokenFile(file, true);
|
|
8518
9496
|
} else {
|
|
8519
9497
|
const bytes = await context.io.readStdin(16 * 1024);
|
|
@@ -8532,7 +9510,7 @@ var connectionCommands = [
|
|
|
8532
9510
|
addedAt: now
|
|
8533
9511
|
};
|
|
8534
9512
|
} else {
|
|
8535
|
-
const descriptorPath =
|
|
9513
|
+
const descriptorPath = path9.resolve(context.io.cwd, descriptorOption ?? path9.join(dataDir, BACKEND_INSTANCE_DESCRIPTOR_FILE));
|
|
8536
9514
|
saved = { kind: "descriptor", descriptorPath, addedAt: now };
|
|
8537
9515
|
}
|
|
8538
9516
|
let checked = null;
|
|
@@ -8656,7 +9634,7 @@ var connectionCommands = [
|
|
|
8656
9634
|
|
|
8657
9635
|
// src/client/inputs.ts
|
|
8658
9636
|
import { promises as fs5 } from "fs";
|
|
8659
|
-
import
|
|
9637
|
+
import path10 from "path";
|
|
8660
9638
|
function decodeUtf8(bytes, label) {
|
|
8661
9639
|
try {
|
|
8662
9640
|
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
@@ -8665,7 +9643,7 @@ function decodeUtf8(bytes, label) {
|
|
|
8665
9643
|
}
|
|
8666
9644
|
}
|
|
8667
9645
|
async function readFileBounded(io, file, label, maxBytes) {
|
|
8668
|
-
const resolved =
|
|
9646
|
+
const resolved = path10.resolve(io.cwd, file);
|
|
8669
9647
|
let handle;
|
|
8670
9648
|
try {
|
|
8671
9649
|
handle = await fs5.open(resolved, "r");
|
|
@@ -8774,121 +9752,6 @@ async function readPatch(io, options2) {
|
|
|
8774
9752
|
return parsed;
|
|
8775
9753
|
}
|
|
8776
9754
|
|
|
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
9755
|
// src/client/commands/settings.ts
|
|
8893
9756
|
function parseAssignment(raw) {
|
|
8894
9757
|
const equals = raw.indexOf("=");
|
|
@@ -9567,36 +10430,6 @@ var environmentCommands = [
|
|
|
9567
10430
|
...settingsCommands("environment")
|
|
9568
10431
|
];
|
|
9569
10432
|
|
|
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
10433
|
// src/client/commands/host.ts
|
|
9601
10434
|
var WAIT_OPTION = {
|
|
9602
10435
|
name: "wait",
|
|
@@ -9908,117 +10741,18 @@ var hostCommands = [
|
|
|
9908
10741
|
|
|
9909
10742
|
// src/client/commands/service.ts
|
|
9910
10743
|
import { spawnSync } from "child_process";
|
|
9911
|
-
import { existsSync as
|
|
10744
|
+
import { existsSync as existsSync4 } from "fs";
|
|
9912
10745
|
import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
9913
|
-
import
|
|
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
|
-
}
|
|
10746
|
+
import path13 from "path";
|
|
10013
10747
|
|
|
10014
10748
|
// src/client/service-units.ts
|
|
10015
10749
|
import { createHash as createHash3 } from "crypto";
|
|
10016
|
-
import
|
|
10017
|
-
import
|
|
10750
|
+
import os5 from "os";
|
|
10751
|
+
import path11 from "path";
|
|
10018
10752
|
function suffix(dataDir, defaultDataDir) {
|
|
10019
|
-
if (
|
|
10753
|
+
if (path11.resolve(dataDir) === path11.resolve(defaultDataDir))
|
|
10020
10754
|
return "";
|
|
10021
|
-
return `-${createHash3("sha256").update(
|
|
10755
|
+
return `-${createHash3("sha256").update(path11.resolve(dataDir)).digest("hex").slice(0, 8)}`;
|
|
10022
10756
|
}
|
|
10023
10757
|
function xml(text) {
|
|
10024
10758
|
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
@@ -10027,7 +10761,7 @@ function systemdWord(text) {
|
|
|
10027
10761
|
return `"${text.replaceAll("\\", "\\\\").replaceAll('"', "\\\"").replaceAll("%", "%%")}"`;
|
|
10028
10762
|
}
|
|
10029
10763
|
function serviceUnit(options2) {
|
|
10030
|
-
const home = options2.home ??
|
|
10764
|
+
const home = options2.home ?? os5.homedir();
|
|
10031
10765
|
const tag = suffix(options2.dataDir, options2.defaultDataDir);
|
|
10032
10766
|
const pathValue = options2.env.PATH ?? "/usr/local/bin:/usr/bin:/bin";
|
|
10033
10767
|
if (options2.platform === "darwin") {
|
|
@@ -10037,7 +10771,7 @@ function serviceUnit(options2) {
|
|
|
10037
10771
|
return {
|
|
10038
10772
|
manager: "launchd",
|
|
10039
10773
|
name,
|
|
10040
|
-
file:
|
|
10774
|
+
file: path11.join(home, "Library", "LaunchAgents", `${name}.plist`),
|
|
10041
10775
|
contents: `<?xml version="1.0" encoding="UTF-8"?>
|
|
10042
10776
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
10043
10777
|
<plist version="1.0">
|
|
@@ -10071,11 +10805,11 @@ ${args}
|
|
|
10071
10805
|
};
|
|
10072
10806
|
}
|
|
10073
10807
|
const name = `orkestrator-backend${tag}.service`;
|
|
10074
|
-
const configHome = options2.env.XDG_CONFIG_HOME ??
|
|
10808
|
+
const configHome = options2.env.XDG_CONFIG_HOME ?? path11.join(home, ".config");
|
|
10075
10809
|
return {
|
|
10076
10810
|
manager: "systemd",
|
|
10077
10811
|
name,
|
|
10078
|
-
file:
|
|
10812
|
+
file: path11.join(configHome, "systemd", "user", name),
|
|
10079
10813
|
contents: `[Unit]
|
|
10080
10814
|
Description=Orkestrator backend (${options2.dataDir})
|
|
10081
10815
|
After=network-online.target
|
|
@@ -10130,7 +10864,7 @@ var DATA_DIR_OPTION = {
|
|
|
10130
10864
|
};
|
|
10131
10865
|
var RESERVED_SERVE_FLAGS = new Set(["--data-dir", "--standalone-updater", "--desktop-updater"]);
|
|
10132
10866
|
function dataDirectory(context, parsed) {
|
|
10133
|
-
return
|
|
10867
|
+
return path13.resolve(stringOption(parsed.options, "data-dir") ?? context.io.env.ORKESTRATOR_DATA_DIR ?? defaultDataDir(process.platform, context.io.env));
|
|
10134
10868
|
}
|
|
10135
10869
|
function unitFor(context, dataDir, bun) {
|
|
10136
10870
|
if (process.platform !== "darwin" && process.platform !== "linux") {
|
|
@@ -10147,7 +10881,7 @@ function unitFor(context, dataDir, bun) {
|
|
|
10147
10881
|
defaultDataDir: defaultDataDir(process.platform, context.io.env),
|
|
10148
10882
|
bun,
|
|
10149
10883
|
launcher: layout.launcher,
|
|
10150
|
-
logFile:
|
|
10884
|
+
logFile: path13.join(layout.logs, "service.log"),
|
|
10151
10885
|
env: context.io.env
|
|
10152
10886
|
})
|
|
10153
10887
|
};
|
|
@@ -10262,7 +10996,7 @@ var serviceCommands = [
|
|
|
10262
10996
|
installedAt: new Date().toISOString()
|
|
10263
10997
|
});
|
|
10264
10998
|
await mkdir2(layout.logs, { recursive: true });
|
|
10265
|
-
await mkdir2(
|
|
10999
|
+
await mkdir2(path13.dirname(unit.file), { recursive: true });
|
|
10266
11000
|
await writeFile2(unit.file, unit.contents);
|
|
10267
11001
|
const started = parsed.options["skip-start"] !== true;
|
|
10268
11002
|
if (started) {
|
|
@@ -10276,7 +11010,7 @@ var serviceCommands = [
|
|
|
10276
11010
|
...keyValues([
|
|
10277
11011
|
["release", `${version} (${releaseDir})`],
|
|
10278
11012
|
["unit", `${unit.manager} ${unit.name}`],
|
|
10279
|
-
["logs",
|
|
11013
|
+
["logs", path13.join(layout.logs, "service.log")]
|
|
10280
11014
|
]),
|
|
10281
11015
|
"",
|
|
10282
11016
|
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 +11036,7 @@ var serviceCommands = [
|
|
|
10302
11036
|
const dataDir = dataDirectory(context, parsed);
|
|
10303
11037
|
const { service, layout, unit } = await unitFor(context, dataDir, process.execPath);
|
|
10304
11038
|
const config = service.readServiceConfig(layout);
|
|
10305
|
-
if (
|
|
11039
|
+
if (existsSync4(unit.file)) {
|
|
10306
11040
|
for (const argv of serviceManagerCommands(unit).unload)
|
|
10307
11041
|
runManager(argv, true);
|
|
10308
11042
|
await rm2(unit.file, { force: true });
|
|
@@ -10330,7 +11064,7 @@ var serviceCommands = [
|
|
|
10330
11064
|
const { service, layout, unit } = await unitFor(context, dataDir, process.execPath);
|
|
10331
11065
|
const config = service.readServiceConfig(layout);
|
|
10332
11066
|
const state = service.readLauncherState(layout);
|
|
10333
|
-
const installed =
|
|
11067
|
+
const installed = existsSync4(unit.file);
|
|
10334
11068
|
const running = installed ? runManager(serviceManagerCommands(unit).status, true).ok : false;
|
|
10335
11069
|
const result = {
|
|
10336
11070
|
dataDir,
|
|
@@ -11506,9 +12240,9 @@ var sessionCommands = [
|
|
|
11506
12240
|
];
|
|
11507
12241
|
|
|
11508
12242
|
// src/client/commands/toolchain.ts
|
|
11509
|
-
import
|
|
12243
|
+
import path17 from "path";
|
|
11510
12244
|
function dataDirectory2(context, parsed) {
|
|
11511
|
-
return
|
|
12245
|
+
return path17.resolve(stringOption(parsed.options, "data-dir") ?? context.io.env.ORKESTRATOR_DATA_DIR ?? defaultDataDir(process.platform, context.io.env));
|
|
11512
12246
|
}
|
|
11513
12247
|
function requestedTools(parsed) {
|
|
11514
12248
|
const raw = parsed.options.tool;
|
|
@@ -11611,7 +12345,7 @@ var toolchainCommands = [
|
|
|
11611
12345
|
const rows = installed.tools.map((tool) => ({
|
|
11612
12346
|
tool,
|
|
11613
12347
|
version: installed.versions[tool] ?? "",
|
|
11614
|
-
executable:
|
|
12348
|
+
executable: path17.join(installed.currentBinDir, path17.basename(installed.executables[tool]))
|
|
11615
12349
|
}));
|
|
11616
12350
|
return {
|
|
11617
12351
|
action: "toolchain.install",
|
|
@@ -11648,6 +12382,7 @@ var GROUP_SUMMARIES = {
|
|
|
11648
12382
|
environment: "Create, start, stop, configure, launch and delete environments.",
|
|
11649
12383
|
agent: "Discover agents, models and controls.",
|
|
11650
12384
|
session: "Start, prompt, control and inspect native-agent conversations.",
|
|
12385
|
+
changes: "Read the file and line changes the backend measures (read-only).",
|
|
11651
12386
|
run: "Inspect, wait for and recover individual operations."
|
|
11652
12387
|
};
|
|
11653
12388
|
function optionLine(option) {
|
|
@@ -11697,6 +12432,7 @@ function rootHelp(tree) {
|
|
|
11697
12432
|
"Usage:",
|
|
11698
12433
|
" orkestrator [serve] [service options] Run the backend service in the foreground",
|
|
11699
12434
|
" orkestrator <group> <command> [options] Talk to an already-running backend",
|
|
12435
|
+
" orkestrator --fix AGENT Get an agent's help reporting a problem",
|
|
11700
12436
|
"",
|
|
11701
12437
|
"Client commands never start a backend. Select the backend explicitly with",
|
|
11702
12438
|
"--profile NAME (an isolated dev profile) or --connection NAME, or save a",
|
|
@@ -11707,6 +12443,8 @@ function rootHelp(tree) {
|
|
|
11707
12443
|
"",
|
|
11708
12444
|
"Other:",
|
|
11709
12445
|
" serve Run the backend service (same as passing only service options)",
|
|
12446
|
+
" --fix AGENT Open claude, codex or opencode to diagnose an Orkestrator",
|
|
12447
|
+
" problem and file a GitHub issue (`orkestrator --fix --help`)",
|
|
11710
12448
|
" help [COMMAND] Show help for a group or command",
|
|
11711
12449
|
" version Print the client version",
|
|
11712
12450
|
"",
|
|
@@ -11872,6 +12610,7 @@ var CLIENT_COMMANDS = [
|
|
|
11872
12610
|
...projectCommands,
|
|
11873
12611
|
...environmentCommands,
|
|
11874
12612
|
...sessionCommands,
|
|
12613
|
+
...changesCommands,
|
|
11875
12614
|
...runCommands,
|
|
11876
12615
|
...toolchainCommands,
|
|
11877
12616
|
...hostCommands,
|
|
@@ -12016,7 +12755,7 @@ var CLIENT_FLAGS = new Set(GLOBAL_OPTIONS.flatMap((option) => [
|
|
|
12016
12755
|
...option.short ? [`-${option.short}`] : []
|
|
12017
12756
|
]));
|
|
12018
12757
|
var CLIENT_VALUE_FLAGS = new Set(GLOBAL_OPTIONS.filter((option) => option.kind !== "boolean").map((option) => `--${option.name}`));
|
|
12019
|
-
var
|
|
12758
|
+
var SAFE_WORD3 = /^[a-z][a-z0-9-]{0,39}$/;
|
|
12020
12759
|
function flagName(token) {
|
|
12021
12760
|
const equals = token.indexOf("=");
|
|
12022
12761
|
return equals < 0 ? token : token.slice(0, equals);
|
|
@@ -12024,6 +12763,8 @@ function flagName(token) {
|
|
|
12024
12763
|
function classifyInvocation(argv) {
|
|
12025
12764
|
if (argv.length === 0)
|
|
12026
12765
|
return { mode: "serve", args: [] };
|
|
12766
|
+
if (isFixInvocation(argv))
|
|
12767
|
+
return { mode: "fix", argv: [...argv] };
|
|
12027
12768
|
const first = argv[0];
|
|
12028
12769
|
if (first === "serve") {
|
|
12029
12770
|
const rest = argv.slice(1);
|
|
@@ -12069,7 +12810,7 @@ function classifyInvocation(argv) {
|
|
|
12069
12810
|
return { mode: "client", argv: [...argv] };
|
|
12070
12811
|
return {
|
|
12071
12812
|
mode: "error",
|
|
12072
|
-
message: `Unknown command ${
|
|
12813
|
+
message: `Unknown command ${SAFE_WORD3.test(first) ? `'${first}'` : "argument"}; run \`orkestrator help\``
|
|
12073
12814
|
};
|
|
12074
12815
|
}
|
|
12075
12816
|
function renderInvocationError(argv, message) {
|
|
@@ -12094,5 +12835,6 @@ export {
|
|
|
12094
12835
|
classifyInvocation,
|
|
12095
12836
|
renderInvocationError,
|
|
12096
12837
|
runClient,
|
|
12097
|
-
runClientProcess
|
|
12838
|
+
runClientProcess,
|
|
12839
|
+
runFixProcess
|
|
12098
12840
|
};
|