orkestrator 2.22.3 → 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 +29 -1
- package/bin/orkestrator.js +5 -2
- package/dist/client.js +1563 -763
- package/dist/main.js +53035 -25212
- package/package.json +3 -3
- package/resources/claude-bridge/dist/index.js +1925 -182
- 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,
|
|
@@ -6560,6 +6603,14 @@ var PUBLIC_ACTIONS = {
|
|
|
6560
6603
|
milestone: "D",
|
|
6561
6604
|
summary: "Read the host's Orkestrator version, its updater, the last check and the update job."
|
|
6562
6605
|
},
|
|
6606
|
+
"host.update.channel": {
|
|
6607
|
+
version: 1,
|
|
6608
|
+
mutation: true,
|
|
6609
|
+
effects: ["metadata"],
|
|
6610
|
+
observation: "receipt",
|
|
6611
|
+
milestone: "D",
|
|
6612
|
+
summary: "Choose the host update channel and request a fresh check."
|
|
6613
|
+
},
|
|
6563
6614
|
"host.update.check": {
|
|
6564
6615
|
version: 1,
|
|
6565
6616
|
mutation: true,
|
|
@@ -7130,89 +7181,115 @@ function splitFlag(token) {
|
|
|
7130
7181
|
return [body.slice(0, equals), body.slice(equals + 1)];
|
|
7131
7182
|
}
|
|
7132
7183
|
|
|
7133
|
-
// 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";
|
|
7134
7188
|
import path5 from "path";
|
|
7135
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
7136
|
-
import { promises as fs4 } from "fs";
|
|
7137
7189
|
|
|
7138
|
-
// ../protocol/src/
|
|
7139
|
-
var
|
|
7140
|
-
|
|
7141
|
-
|
|
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;
|
|
7142
7245
|
function isRecord2(value) {
|
|
7143
|
-
return value
|
|
7246
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7144
7247
|
}
|
|
7145
|
-
function
|
|
7146
|
-
if (typeof value !== "string" || value.length > 2048)
|
|
7147
|
-
return false;
|
|
7248
|
+
function readApplicationLogSettings(dataDir) {
|
|
7148
7249
|
try {
|
|
7149
|
-
const
|
|
7150
|
-
|
|
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
|
+
};
|
|
7151
7256
|
} catch {
|
|
7152
|
-
return
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
if (!isRecord2(value))
|
|
7157
|
-
throw new Error("Backend instance descriptor must be an object");
|
|
7158
|
-
if (value.version !== 1 || value.type !== BACKEND_INSTANCE_DESCRIPTOR_TYPE) {
|
|
7159
|
-
throw new Error("Backend instance descriptor has an unsupported version");
|
|
7160
|
-
}
|
|
7161
|
-
const text = (key, max = 512) => {
|
|
7162
|
-
const field = value[key];
|
|
7163
|
-
if (typeof field !== "string" || field.length === 0 || field.length > max) {
|
|
7164
|
-
throw new Error(`Backend instance descriptor field is invalid: ${key}`);
|
|
7165
|
-
}
|
|
7166
|
-
return field;
|
|
7167
|
-
};
|
|
7168
|
-
if (!Number.isSafeInteger(value.pid) || value.pid <= 0) {
|
|
7169
|
-
throw new Error("Backend instance descriptor field is invalid: pid");
|
|
7170
|
-
}
|
|
7171
|
-
if (!isHttpUrl(value.url))
|
|
7172
|
-
throw new Error("Backend instance descriptor field is invalid: url");
|
|
7173
|
-
if (value.browserUrl !== undefined && !isHttpUrl(value.browserUrl)) {
|
|
7174
|
-
throw new Error("Backend instance descriptor field is invalid: browserUrl");
|
|
7175
|
-
}
|
|
7176
|
-
if (!Number.isSafeInteger(value.publicApiSchemaVersion) || value.publicApiSchemaVersion < 1) {
|
|
7177
|
-
throw new Error("Backend instance descriptor field is invalid: publicApiSchemaVersion");
|
|
7178
|
-
}
|
|
7179
|
-
const authFile = text("authFile", 4096);
|
|
7180
|
-
const dataDir = text("dataDir", 4096);
|
|
7181
|
-
if (!authFile.startsWith("/") || !dataDir.startsWith("/")) {
|
|
7182
|
-
throw new Error("Backend instance descriptor paths must be absolute");
|
|
7257
|
+
return {
|
|
7258
|
+
enabled: false,
|
|
7259
|
+
retentionDays: normalizeDebugLogRetentionDays(undefined)
|
|
7260
|
+
};
|
|
7183
7261
|
}
|
|
7184
|
-
return {
|
|
7185
|
-
version: 1,
|
|
7186
|
-
type: BACKEND_INSTANCE_DESCRIPTOR_TYPE,
|
|
7187
|
-
installationId: text("installationId", 200),
|
|
7188
|
-
generation: text("generation", 200),
|
|
7189
|
-
pid: value.pid,
|
|
7190
|
-
startedAt: text("startedAt", 64),
|
|
7191
|
-
url: value.url,
|
|
7192
|
-
...value.browserUrl !== undefined ? { browserUrl: value.browserUrl } : {},
|
|
7193
|
-
authFile,
|
|
7194
|
-
dataDir,
|
|
7195
|
-
appVersion: text("appVersion", 64),
|
|
7196
|
-
publicApiSchemaVersion: value.publicApiSchemaVersion
|
|
7197
|
-
};
|
|
7198
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();
|
|
7199
7276
|
|
|
7200
7277
|
// src/client/config.ts
|
|
7201
7278
|
import { randomUUID } from "crypto";
|
|
7202
7279
|
import { promises as fs } from "fs";
|
|
7203
7280
|
import os from "os";
|
|
7204
|
-
import
|
|
7281
|
+
import path3 from "path";
|
|
7205
7282
|
var CLI_CONFIG_VERSION = 1;
|
|
7206
7283
|
var MAX_CONFIG_BYTES = 256 * 1024;
|
|
7207
7284
|
var MAX_CONNECTIONS = 64;
|
|
7208
7285
|
var CONNECTION_NAME = /^[a-z0-9][a-z0-9._-]{0,47}$/;
|
|
7209
7286
|
function defaultCliConfigDir(env, platform = process.platform, home = os.homedir()) {
|
|
7210
7287
|
if (env.ORKESTRATOR_CLI_CONFIG_DIR)
|
|
7211
|
-
return
|
|
7288
|
+
return path3.resolve(env.ORKESTRATOR_CLI_CONFIG_DIR);
|
|
7212
7289
|
if (platform === "darwin") {
|
|
7213
|
-
return
|
|
7290
|
+
return path3.join(home, "Library", "Application Support", "orkestrator-cli");
|
|
7214
7291
|
}
|
|
7215
|
-
return
|
|
7292
|
+
return path3.join(env.XDG_CONFIG_HOME ?? path3.join(home, ".config"), "orkestrator-cli");
|
|
7216
7293
|
}
|
|
7217
7294
|
function isConnectionName(value) {
|
|
7218
7295
|
return CONNECTION_NAME.test(value);
|
|
@@ -7241,7 +7318,7 @@ function parseSaved(value) {
|
|
|
7241
7318
|
const record = value;
|
|
7242
7319
|
const addedAt = typeof record.addedAt === "string" ? record.addedAt : new Date(0).toISOString();
|
|
7243
7320
|
const installationId = typeof record.installationId === "string" && record.installationId.length <= 200 ? record.installationId : undefined;
|
|
7244
|
-
if (record.kind === "descriptor" && typeof record.descriptorPath === "string" &&
|
|
7321
|
+
if (record.kind === "descriptor" && typeof record.descriptorPath === "string" && path3.isAbsolute(record.descriptorPath)) {
|
|
7245
7322
|
return {
|
|
7246
7323
|
kind: "descriptor",
|
|
7247
7324
|
descriptorPath: record.descriptorPath,
|
|
@@ -7249,7 +7326,7 @@ function parseSaved(value) {
|
|
|
7249
7326
|
addedAt
|
|
7250
7327
|
};
|
|
7251
7328
|
}
|
|
7252
|
-
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)) {
|
|
7253
7330
|
return {
|
|
7254
7331
|
kind: "endpoint",
|
|
7255
7332
|
url: record.url,
|
|
@@ -7268,13 +7345,13 @@ class CliConfigStore {
|
|
|
7268
7345
|
this.directory = directory;
|
|
7269
7346
|
}
|
|
7270
7347
|
get file() {
|
|
7271
|
-
return
|
|
7348
|
+
return path3.join(this.directory, "connections.json");
|
|
7272
7349
|
}
|
|
7273
7350
|
get credentialsDirectory() {
|
|
7274
|
-
return
|
|
7351
|
+
return path3.join(this.directory, "credentials");
|
|
7275
7352
|
}
|
|
7276
7353
|
get receiptsDirectory() {
|
|
7277
|
-
return
|
|
7354
|
+
return path3.join(this.directory, "receipts");
|
|
7278
7355
|
}
|
|
7279
7356
|
async read() {
|
|
7280
7357
|
let text;
|
|
@@ -7330,24 +7407,24 @@ class CliConfigStore {
|
|
|
7330
7407
|
`);
|
|
7331
7408
|
}
|
|
7332
7409
|
async storeCredential(name, token) {
|
|
7333
|
-
const file =
|
|
7410
|
+
const file = path3.join(this.credentialsDirectory, `${name}.json`);
|
|
7334
7411
|
await writePrivateFile(file, `${JSON.stringify({ token })}
|
|
7335
7412
|
`);
|
|
7336
7413
|
return file;
|
|
7337
7414
|
}
|
|
7338
7415
|
async removeOwnedCredential(file) {
|
|
7339
|
-
if (
|
|
7416
|
+
if (path3.dirname(file) !== this.credentialsDirectory)
|
|
7340
7417
|
return;
|
|
7341
7418
|
await fs.rm(file, { force: true });
|
|
7342
7419
|
}
|
|
7343
7420
|
}
|
|
7344
7421
|
async function writePrivateFile(file, contents) {
|
|
7345
|
-
const directory =
|
|
7422
|
+
const directory = path3.dirname(file);
|
|
7346
7423
|
await fs.mkdir(directory, { recursive: true, mode: 448 });
|
|
7347
7424
|
await fs.chmod(directory, 448).catch(() => {
|
|
7348
7425
|
return;
|
|
7349
7426
|
});
|
|
7350
|
-
const temporary =
|
|
7427
|
+
const temporary = path3.join(directory, `.${path3.basename(file)}.${randomUUID()}.tmp`);
|
|
7351
7428
|
try {
|
|
7352
7429
|
await fs.writeFile(temporary, contents, { mode: 384, flag: "wx" });
|
|
7353
7430
|
await fs.rename(temporary, file);
|
|
@@ -7359,126 +7436,1257 @@ async function writePrivateFile(file, contents) {
|
|
|
7359
7436
|
}
|
|
7360
7437
|
}
|
|
7361
7438
|
|
|
7362
|
-
// src/
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
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";
|
|
7379
7526
|
}
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
["revision", project.revision]
|
|
7389
|
-
]);
|
|
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
|
+
}
|
|
7390
7535
|
}
|
|
7391
|
-
function
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
project.localPath ?? "-"
|
|
7397
|
-
]));
|
|
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);
|
|
7398
7541
|
}
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
["
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
["
|
|
7414
|
-
|
|
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";
|
|
7415
7585
|
}
|
|
7416
|
-
function
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
environment.name,
|
|
7420
|
-
environment.environmentType,
|
|
7421
|
-
environment.status,
|
|
7422
|
-
environment.ready ? "yes" : "no"
|
|
7423
|
-
]));
|
|
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="));
|
|
7424
7589
|
}
|
|
7425
|
-
function
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
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 } : {} };
|
|
7433
7635
|
}
|
|
7434
|
-
function
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
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;
|
|
7446
7653
|
}
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
["exit code", receipt.execution?.exitCode],
|
|
7460
|
-
["error", receipt.error ? `${receipt.error.code}: ${receipt.error.message}` : undefined],
|
|
7461
|
-
["updated", receipt.updatedAt],
|
|
7462
|
-
["retained until", receipt.retainedUntil]
|
|
7463
|
-
]);
|
|
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
|
+
}));
|
|
7464
7666
|
}
|
|
7465
|
-
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`";
|
|
7466
7801
|
return [
|
|
7467
|
-
`
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
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
|
+
])
|
|
7475
8025
|
];
|
|
7476
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
|
+
}
|
|
7477
8685
|
|
|
7478
8686
|
// src/client/receipts.ts
|
|
7479
8687
|
import { createHash } from "crypto";
|
|
7480
8688
|
import { promises as fs2 } from "fs";
|
|
7481
|
-
import
|
|
8689
|
+
import path6 from "path";
|
|
7482
8690
|
var MAX_LOCAL_RECEIPTS_PER_INSTALLATION = 1000;
|
|
7483
8691
|
var MAX_RECEIPT_BYTES = 8 * 1024;
|
|
7484
8692
|
function receiptKey(action, requestId) {
|
|
@@ -7494,10 +8702,10 @@ class LocalReceiptStore {
|
|
|
7494
8702
|
this.root = root;
|
|
7495
8703
|
}
|
|
7496
8704
|
directory(installationId) {
|
|
7497
|
-
return
|
|
8705
|
+
return path6.join(this.root, safeSegment(installationId));
|
|
7498
8706
|
}
|
|
7499
8707
|
file(installationId, action, requestId) {
|
|
7500
|
-
return
|
|
8708
|
+
return path6.join(this.directory(installationId), `${receiptKey(action, requestId)}.json`);
|
|
7501
8709
|
}
|
|
7502
8710
|
async find(installationId, action, requestId) {
|
|
7503
8711
|
return this.readFile(this.file(installationId, action, requestId));
|
|
@@ -7521,14 +8729,14 @@ class LocalReceiptStore {
|
|
|
7521
8729
|
});
|
|
7522
8730
|
}
|
|
7523
8731
|
async list(installationId, limit = 50) {
|
|
7524
|
-
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));
|
|
7525
8733
|
const receipts = [];
|
|
7526
8734
|
for (const directory of directories) {
|
|
7527
8735
|
const names = await fs2.readdir(directory).catch(() => []);
|
|
7528
8736
|
for (const name of names) {
|
|
7529
8737
|
if (!name.endsWith(".json"))
|
|
7530
8738
|
continue;
|
|
7531
|
-
const receipt = await this.readFile(
|
|
8739
|
+
const receipt = await this.readFile(path6.join(directory, name));
|
|
7532
8740
|
if (receipt)
|
|
7533
8741
|
receipts.push(receipt);
|
|
7534
8742
|
}
|
|
@@ -7556,7 +8764,7 @@ class LocalReceiptStore {
|
|
|
7556
8764
|
if (names.length <= MAX_LOCAL_RECEIPTS_PER_INSTALLATION)
|
|
7557
8765
|
return;
|
|
7558
8766
|
const entries = await Promise.all(names.map(async (name) => {
|
|
7559
|
-
const file =
|
|
8767
|
+
const file = path6.join(directory, name);
|
|
7560
8768
|
const stat = await fs2.stat(file).catch(() => null);
|
|
7561
8769
|
return { file, mtime: stat?.mtimeMs ?? 0 };
|
|
7562
8770
|
}));
|
|
@@ -7851,12 +9059,12 @@ function envelopeError(response) {
|
|
|
7851
9059
|
|
|
7852
9060
|
// src/client/targets.ts
|
|
7853
9061
|
import { promises as fs3 } from "fs";
|
|
7854
|
-
import
|
|
9062
|
+
import path8 from "path";
|
|
7855
9063
|
import { isIPv4 } from "net";
|
|
7856
9064
|
|
|
7857
9065
|
// ../protocol/src/node/runtime-profile-status.ts
|
|
7858
|
-
import
|
|
7859
|
-
import
|
|
9066
|
+
import os4 from "os";
|
|
9067
|
+
import path7 from "path";
|
|
7860
9068
|
var RUNTIME_APP_SLUG = "orkestrator-v2";
|
|
7861
9069
|
var RUNTIME_STATUS_KEYS = new Set([
|
|
7862
9070
|
"version",
|
|
@@ -7898,29 +9106,29 @@ function normalizeRuntimeProfileId(value) {
|
|
|
7898
9106
|
}
|
|
7899
9107
|
return normalized;
|
|
7900
9108
|
}
|
|
7901
|
-
function defaultRuntimeProfileRoots(platform = process.platform, env = process.env, homeDir =
|
|
9109
|
+
function defaultRuntimeProfileRoots(platform = process.platform, env = process.env, homeDir = os4.homedir()) {
|
|
7902
9110
|
if (platform === "darwin") {
|
|
7903
|
-
const appSupport =
|
|
9111
|
+
const appSupport = path7.join(homeDir, "Library", "Application Support");
|
|
7904
9112
|
return {
|
|
7905
|
-
developmentRoot:
|
|
7906
|
-
productionDataDir:
|
|
9113
|
+
developmentRoot: path7.join(appSupport, `${RUNTIME_APP_SLUG}-dev`),
|
|
9114
|
+
productionDataDir: path7.join(appSupport, RUNTIME_APP_SLUG),
|
|
7907
9115
|
homeDir
|
|
7908
9116
|
};
|
|
7909
9117
|
}
|
|
7910
9118
|
if (platform === "win32") {
|
|
7911
9119
|
throw new Error("Orkestrator development profiles support macOS and Linux only");
|
|
7912
9120
|
}
|
|
7913
|
-
const configRoot = env.XDG_CONFIG_HOME ??
|
|
9121
|
+
const configRoot = env.XDG_CONFIG_HOME ?? path7.join(homeDir, ".config");
|
|
7914
9122
|
return {
|
|
7915
|
-
developmentRoot:
|
|
7916
|
-
productionDataDir:
|
|
9123
|
+
developmentRoot: path7.join(configRoot, `${RUNTIME_APP_SLUG}-dev`),
|
|
9124
|
+
productionDataDir: path7.join(configRoot, RUNTIME_APP_SLUG),
|
|
7917
9125
|
homeDir
|
|
7918
9126
|
};
|
|
7919
9127
|
}
|
|
7920
9128
|
function runtimeProfileStatusPath(developmentRoot, profileName) {
|
|
7921
9129
|
const id = normalizeRuntimeProfileId(profileName);
|
|
7922
|
-
const profileRoot =
|
|
7923
|
-
return { id, profileRoot, statusPath:
|
|
9130
|
+
const profileRoot = path7.resolve(developmentRoot, "profiles", id);
|
|
9131
|
+
return { id, profileRoot, statusPath: path7.join(profileRoot, "runtime", "status.json") };
|
|
7924
9132
|
}
|
|
7925
9133
|
|
|
7926
9134
|
// src/client/targets.ts
|
|
@@ -7997,7 +9205,7 @@ async function readDescriptor(file) {
|
|
|
7997
9205
|
throw new Error("too large");
|
|
7998
9206
|
text = await fs3.readFile(file, "utf8");
|
|
7999
9207
|
} catch {
|
|
8000
|
-
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`);
|
|
8001
9209
|
}
|
|
8002
9210
|
try {
|
|
8003
9211
|
return parseBackendInstanceDescriptor(JSON.parse(text));
|
|
@@ -8090,252 +9298,30 @@ async function resolveProfile(profileName, env) {
|
|
|
8090
9298
|
}
|
|
8091
9299
|
const backendPid = manifest.pids.backend;
|
|
8092
9300
|
if (!backendPid || !processIsAlive(backendPid)) {
|
|
8093
|
-
throw new CliError("profile-unavailable", `Profile '${located.id}' reports ready but its backend is not running (stale status)`);
|
|
8094
|
-
}
|
|
8095
|
-
const refresh = () => resolveProfile(profileName, env);
|
|
8096
|
-
if (manifest.dataDir) {
|
|
8097
|
-
const descriptorFile =
|
|
8098
|
-
const descriptor = await readDescriptor(descriptorFile).catch(() => null);
|
|
8099
|
-
if (descriptor) {
|
|
8100
|
-
if (descriptor.pid !== backendPid) {
|
|
8101
|
-
throw new CliError("profile-unavailable", `Profile '${located.id}' has a descriptor from a different backend process`);
|
|
8102
|
-
}
|
|
8103
|
-
return descriptorTarget(located.id, "profile", descriptor, undefined, refresh);
|
|
8104
|
-
}
|
|
8105
|
-
}
|
|
8106
|
-
if (!manifest.browserUrl || !manifest.authFile) {
|
|
8107
|
-
throw new CliError("profile-unavailable", `Profile '${located.id}' does not publish a gateway endpoint`);
|
|
8108
|
-
}
|
|
8109
|
-
const baseUrl = normalizeBaseUrl(manifest.browserUrl);
|
|
8110
|
-
const authFile = manifest.authFile;
|
|
8111
|
-
return {
|
|
8112
|
-
identity: { name: located.id, kind: "profile", endpoint: redactEndpoint(baseUrl) },
|
|
8113
|
-
baseUrl,
|
|
8114
|
-
readToken: () => readTokenFile(authFile, true),
|
|
8115
|
-
refresh
|
|
8116
|
-
};
|
|
8117
|
-
}
|
|
8118
|
-
|
|
8119
|
-
// src/client/wait.ts
|
|
8120
|
-
class ObservationStopped extends Error {
|
|
8121
|
-
reason;
|
|
8122
|
-
signalName;
|
|
8123
|
-
constructor(reason, signalName) {
|
|
8124
|
-
super(reason === "deadline" ? "Observation deadline passed" : "Observation interrupted");
|
|
8125
|
-
this.reason = reason;
|
|
8126
|
-
this.signalName = signalName;
|
|
8127
|
-
}
|
|
8128
|
-
}
|
|
8129
|
-
function defaultSleep(ms, signal) {
|
|
8130
|
-
return new Promise((resolve) => {
|
|
8131
|
-
if (signal?.aborted) {
|
|
8132
|
-
resolve();
|
|
8133
|
-
return;
|
|
8134
|
-
}
|
|
8135
|
-
const timer = setTimeout(done, ms);
|
|
8136
|
-
function done() {
|
|
8137
|
-
clearTimeout(timer);
|
|
8138
|
-
signal?.removeEventListener("abort", done);
|
|
8139
|
-
resolve();
|
|
8140
|
-
}
|
|
8141
|
-
signal?.addEventListener("abort", done, { once: true });
|
|
8142
|
-
});
|
|
8143
|
-
}
|
|
8144
|
-
function signalName(signal) {
|
|
8145
|
-
return signal?.reason === "SIGTERM" ? "SIGTERM" : "SIGINT";
|
|
8146
|
-
}
|
|
8147
|
-
async function observe(check, options2) {
|
|
8148
|
-
if (options2.timeoutMs > PUBLIC_API_LIMITS.waitMaxMs) {
|
|
8149
|
-
throw new CliError("invalid-input", "--timeout may be at most 24h");
|
|
8150
|
-
}
|
|
8151
|
-
const now = options2.now ?? Date.now;
|
|
8152
|
-
const sleep = options2.sleep ?? defaultSleep;
|
|
8153
|
-
const deadline = now() + options2.timeoutMs;
|
|
8154
|
-
let delay = 250;
|
|
8155
|
-
for (;; ) {
|
|
8156
|
-
if (options2.signal?.aborted)
|
|
8157
|
-
throw new ObservationStopped("signal", signalName(options2.signal));
|
|
8158
|
-
const step = await check();
|
|
8159
|
-
if (step.done)
|
|
8160
|
-
return step.value;
|
|
8161
|
-
const remaining = deadline - now();
|
|
8162
|
-
if (remaining <= 0)
|
|
8163
|
-
throw new ObservationStopped("deadline");
|
|
8164
|
-
await sleep(Math.min(delay, remaining), options2.signal);
|
|
8165
|
-
delay = Math.min(delay * 2, 2000);
|
|
8166
|
-
}
|
|
8167
|
-
}
|
|
8168
|
-
|
|
8169
|
-
// src/client/commands/common.ts
|
|
8170
|
-
var REQUEST_ID_OPTION = {
|
|
8171
|
-
name: "request-id",
|
|
8172
|
-
kind: "string",
|
|
8173
|
-
valueName: "KEY",
|
|
8174
|
-
description: "Idempotency key. Reusing it returns the original operation; reusing it with a different intent conflicts. Generated and saved locally when omitted."
|
|
8175
|
-
};
|
|
8176
|
-
var TIMEOUT_OPTION = {
|
|
8177
|
-
name: "timeout",
|
|
8178
|
-
kind: "duration",
|
|
8179
|
-
valueName: "DURATION",
|
|
8180
|
-
description: "How long to observe before exiting 5 (the operation keeps running). Default 10m."
|
|
8181
|
-
};
|
|
8182
|
-
var PROMPT_OPTIONS = [
|
|
8183
|
-
{
|
|
8184
|
-
name: "prompt-file",
|
|
8185
|
-
kind: "string",
|
|
8186
|
-
valueName: "PATH",
|
|
8187
|
-
description: "Read the prompt from a local file."
|
|
8188
|
-
},
|
|
8189
|
-
{ name: "prompt-stdin", kind: "boolean", description: "Read the prompt from stdin." },
|
|
8190
|
-
{
|
|
8191
|
-
name: "prompt",
|
|
8192
|
-
kind: "string",
|
|
8193
|
-
valueName: "TEXT",
|
|
8194
|
-
description: "Inline prompt (prefer a file for long text)."
|
|
8195
|
-
}
|
|
8196
|
-
];
|
|
8197
|
-
var DEFAULT_WAIT_MS = 10 * 60 * 1000;
|
|
8198
|
-
function stringOption(options2, name) {
|
|
8199
|
-
const value = options2[name];
|
|
8200
|
-
return typeof value === "string" ? value : undefined;
|
|
8201
|
-
}
|
|
8202
|
-
function oneOf(value, allowed, label) {
|
|
8203
|
-
if (typeof value !== "string" || !allowed.includes(value)) {
|
|
8204
|
-
throw new CliError("invalid-input", `${label} must be one of: ${allowed.join(", ")}`);
|
|
8205
|
-
}
|
|
8206
|
-
return value;
|
|
8207
|
-
}
|
|
8208
|
-
function optionalBoolean(options2, name) {
|
|
8209
|
-
const value = options2[name];
|
|
8210
|
-
return typeof value === "boolean" ? value : undefined;
|
|
8211
|
-
}
|
|
8212
|
-
async function submit(context, action, input, options2) {
|
|
8213
|
-
const session = await context.session();
|
|
8214
|
-
const outcome = await session.mutate(action, input, stringOption(options2, "request-id"));
|
|
8215
|
-
return { session, ...outcome };
|
|
8216
|
-
}
|
|
8217
|
-
async function readOperation(session, operationId) {
|
|
8218
|
-
const { receipt } = await session.readWithReceipt("run.get", { operationId });
|
|
8219
|
-
if (!receipt)
|
|
8220
|
-
throw new CliError("response-invalid", "run.get returned no receipt");
|
|
8221
|
-
return receipt;
|
|
8222
|
-
}
|
|
8223
|
-
function isTerminalReceipt(receipt) {
|
|
8224
|
-
return !["admitted", "running", "unknown"].includes(receipt.state);
|
|
8225
|
-
}
|
|
8226
|
-
function stoppedError(error, action, receipt, what) {
|
|
8227
|
-
if (error.reason === "signal") {
|
|
8228
|
-
return new CliError("observation-interrupted", `Stopped observing ${what}; it continues in the backend`, {
|
|
8229
|
-
action,
|
|
8230
|
-
...receipt ? { receipt } : {},
|
|
8231
|
-
details: { signal: error.signalName ?? "SIGINT" }
|
|
8232
|
-
});
|
|
8233
|
-
}
|
|
8234
|
-
return new CliError("deadline-exceeded", `Timed out observing ${what}; it continues in the backend`, {
|
|
8235
|
-
action,
|
|
8236
|
-
...receipt ? { receipt } : {}
|
|
8237
|
-
});
|
|
8238
|
-
}
|
|
8239
|
-
function receiptFailure(receipt) {
|
|
8240
|
-
if (receipt.state === "succeeded")
|
|
8241
|
-
return;
|
|
8242
|
-
const message = receipt.error?.message ?? `Operation ended ${receipt.state}`;
|
|
8243
|
-
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";
|
|
8244
|
-
return new CliError(code, message, { receipt, action: receipt.action });
|
|
8245
|
-
}
|
|
8246
|
-
async function waitForOperation(context, session, initial, timeoutMs, options2 = {}) {
|
|
8247
|
-
let latest = initial;
|
|
8248
|
-
try {
|
|
8249
|
-
return await observe(async () => {
|
|
8250
|
-
latest = await readOperation(session, initial.operationId);
|
|
8251
|
-
if (isTerminalReceipt(latest))
|
|
8252
|
-
return { done: true, value: latest };
|
|
8253
|
-
const execution = latest.execution;
|
|
8254
|
-
if (execution?.state === "waiting-for-input" && !options2.continueOnInteraction) {
|
|
8255
|
-
throw new CliError("interaction-required", "The run is waiting for an answer to a pending interaction", {
|
|
8256
|
-
receipt: latest,
|
|
8257
|
-
action: latest.action,
|
|
8258
|
-
details: { interactions: execution.interactions ?? [] }
|
|
8259
|
-
});
|
|
8260
|
-
}
|
|
8261
|
-
if (latest.state === "unknown" && latest.dispatch?.state === "unknown") {
|
|
8262
|
-
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 });
|
|
8263
|
-
}
|
|
8264
|
-
if (latest.state === "unknown") {
|
|
8265
|
-
throw new CliError("run-unknown", latest.error?.message ?? "The outcome of this run is not known", {
|
|
8266
|
-
receipt: latest,
|
|
8267
|
-
action: latest.action
|
|
8268
|
-
});
|
|
8269
|
-
}
|
|
8270
|
-
if (execution?.state === "unsupported") {
|
|
8271
|
-
throw new CliError("capability-unavailable", execution.reason ?? "Completion of this run cannot be observed for its provider", { receipt: latest, action: latest.action });
|
|
8272
|
-
}
|
|
8273
|
-
return { done: false };
|
|
8274
|
-
}, { timeoutMs, signal: context.signal });
|
|
8275
|
-
} catch (error) {
|
|
8276
|
-
if (error instanceof ObservationStopped) {
|
|
8277
|
-
throw stoppedError(error, latest.action, latest, "the operation");
|
|
8278
|
-
}
|
|
8279
|
-
throw error;
|
|
8280
|
-
}
|
|
8281
|
-
}
|
|
8282
|
-
async function waitForEnvironment(context, session, environmentId, condition, receipt, timeoutMs) {
|
|
8283
|
-
let latestReceipt = receipt;
|
|
8284
|
-
const conditionMet = (environment) => {
|
|
8285
|
-
if (condition === "deleted")
|
|
8286
|
-
return environment === null;
|
|
8287
|
-
if (!environment)
|
|
8288
|
-
return false;
|
|
8289
|
-
if (condition === "running")
|
|
8290
|
-
return environment.status === "running";
|
|
8291
|
-
if (condition === "stopped")
|
|
8292
|
-
return environment.status === "stopped";
|
|
8293
|
-
return environment.ready;
|
|
8294
|
-
};
|
|
8295
|
-
try {
|
|
8296
|
-
return await observe(async () => {
|
|
8297
|
-
if (latestReceipt && !isTerminalReceipt(latestReceipt)) {
|
|
8298
|
-
latestReceipt = await readOperation(session, latestReceipt.operationId);
|
|
8299
|
-
}
|
|
8300
|
-
if (latestReceipt && isTerminalReceipt(latestReceipt)) {
|
|
8301
|
-
const failure = receiptFailure(latestReceipt);
|
|
8302
|
-
if (failure)
|
|
8303
|
-
throw failure;
|
|
8304
|
-
}
|
|
8305
|
-
let environment;
|
|
8306
|
-
try {
|
|
8307
|
-
environment = await session.read("environment.get", {
|
|
8308
|
-
environmentId
|
|
8309
|
-
});
|
|
8310
|
-
} catch (error) {
|
|
8311
|
-
if (error instanceof CliError && error.code === "not-found")
|
|
8312
|
-
environment = null;
|
|
8313
|
-
else
|
|
8314
|
-
throw error;
|
|
8315
|
-
}
|
|
8316
|
-
if (condition !== "deleted" && !environment) {
|
|
8317
|
-
throw new CliError("not-found", "The environment no longer exists", latestReceipt ? { receipt: latestReceipt } : {});
|
|
8318
|
-
}
|
|
8319
|
-
if (environment && (condition === "ready" || condition === "running")) {
|
|
8320
|
-
if (environment.setup.phase === "failed" || environment.status === "error") {
|
|
8321
|
-
throw new CliError("setup-failed", environment.lifecycle.error ?? "Environment setup failed", { ...latestReceipt ? { receipt: latestReceipt } : {}, details: { environmentId } });
|
|
8322
|
-
}
|
|
8323
|
-
}
|
|
8324
|
-
const operationSettled = !latestReceipt || isTerminalReceipt(latestReceipt) || condition === "running" && latestReceipt.stage === "setup";
|
|
8325
|
-
if (operationSettled && conditionMet(environment)) {
|
|
8326
|
-
return {
|
|
8327
|
-
done: true,
|
|
8328
|
-
value: { environment, ...latestReceipt ? { receipt: latestReceipt } : {} }
|
|
8329
|
-
};
|
|
9301
|
+
throw new CliError("profile-unavailable", `Profile '${located.id}' reports ready but its backend is not running (stale status)`);
|
|
9302
|
+
}
|
|
9303
|
+
const refresh = () => resolveProfile(profileName, env);
|
|
9304
|
+
if (manifest.dataDir) {
|
|
9305
|
+
const descriptorFile = path8.join(manifest.dataDir, BACKEND_INSTANCE_DESCRIPTOR_FILE);
|
|
9306
|
+
const descriptor = await readDescriptor(descriptorFile).catch(() => null);
|
|
9307
|
+
if (descriptor) {
|
|
9308
|
+
if (descriptor.pid !== backendPid) {
|
|
9309
|
+
throw new CliError("profile-unavailable", `Profile '${located.id}' has a descriptor from a different backend process`);
|
|
8330
9310
|
}
|
|
8331
|
-
return
|
|
8332
|
-
}, { timeoutMs, signal: context.signal });
|
|
8333
|
-
} catch (error) {
|
|
8334
|
-
if (error instanceof ObservationStopped) {
|
|
8335
|
-
throw stoppedError(error, latestReceipt?.action ?? "environment.get", latestReceipt, `the environment (${condition})`);
|
|
9311
|
+
return descriptorTarget(located.id, "profile", descriptor, undefined, refresh);
|
|
8336
9312
|
}
|
|
8337
|
-
throw error;
|
|
8338
9313
|
}
|
|
9314
|
+
if (!manifest.browserUrl || !manifest.authFile) {
|
|
9315
|
+
throw new CliError("profile-unavailable", `Profile '${located.id}' does not publish a gateway endpoint`);
|
|
9316
|
+
}
|
|
9317
|
+
const baseUrl = normalizeBaseUrl(manifest.browserUrl);
|
|
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
|
+
};
|
|
8339
9325
|
}
|
|
8340
9326
|
|
|
8341
9327
|
// src/client/commands/connection.ts
|
|
@@ -8345,7 +9331,7 @@ function store(context) {
|
|
|
8345
9331
|
async function removeManagedCredential(configStore, name, saved) {
|
|
8346
9332
|
if (saved?.kind !== "endpoint")
|
|
8347
9333
|
return;
|
|
8348
|
-
const legacyFile =
|
|
9334
|
+
const legacyFile = path9.join(configStore.credentialsDirectory, `${name}.json`);
|
|
8349
9335
|
if (saved.managedCredential !== true && saved.credentialFile !== legacyFile)
|
|
8350
9336
|
return;
|
|
8351
9337
|
await configStore.removeOwnedCredential(saved.credentialFile).catch(() => {
|
|
@@ -8505,7 +9491,7 @@ var connectionCommands = [
|
|
|
8505
9491
|
const normalizedUrl = normalizeBaseUrl(url);
|
|
8506
9492
|
let file;
|
|
8507
9493
|
if (credentialFile) {
|
|
8508
|
-
file =
|
|
9494
|
+
file = path9.resolve(context.io.cwd, credentialFile);
|
|
8509
9495
|
await readTokenFile(file, true);
|
|
8510
9496
|
} else {
|
|
8511
9497
|
const bytes = await context.io.readStdin(16 * 1024);
|
|
@@ -8524,7 +9510,7 @@ var connectionCommands = [
|
|
|
8524
9510
|
addedAt: now
|
|
8525
9511
|
};
|
|
8526
9512
|
} else {
|
|
8527
|
-
const descriptorPath =
|
|
9513
|
+
const descriptorPath = path9.resolve(context.io.cwd, descriptorOption ?? path9.join(dataDir, BACKEND_INSTANCE_DESCRIPTOR_FILE));
|
|
8528
9514
|
saved = { kind: "descriptor", descriptorPath, addedAt: now };
|
|
8529
9515
|
}
|
|
8530
9516
|
let checked = null;
|
|
@@ -8648,7 +9634,7 @@ var connectionCommands = [
|
|
|
8648
9634
|
|
|
8649
9635
|
// src/client/inputs.ts
|
|
8650
9636
|
import { promises as fs5 } from "fs";
|
|
8651
|
-
import
|
|
9637
|
+
import path10 from "path";
|
|
8652
9638
|
function decodeUtf8(bytes, label) {
|
|
8653
9639
|
try {
|
|
8654
9640
|
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
@@ -8657,7 +9643,7 @@ function decodeUtf8(bytes, label) {
|
|
|
8657
9643
|
}
|
|
8658
9644
|
}
|
|
8659
9645
|
async function readFileBounded(io, file, label, maxBytes) {
|
|
8660
|
-
const resolved =
|
|
9646
|
+
const resolved = path10.resolve(io.cwd, file);
|
|
8661
9647
|
let handle;
|
|
8662
9648
|
try {
|
|
8663
9649
|
handle = await fs5.open(resolved, "r");
|
|
@@ -8766,121 +9752,6 @@ async function readPatch(io, options2) {
|
|
|
8766
9752
|
return parsed;
|
|
8767
9753
|
}
|
|
8768
9754
|
|
|
8769
|
-
// ../protocol/src/public-api-resources.ts
|
|
8770
|
-
var AGENT_PLATFORM_KEYS = ["claude", "codex", "cursor", "grok", "opencode", "pi"];
|
|
8771
|
-
function agentSettingDescriptors(scope) {
|
|
8772
|
-
const application = "next-session";
|
|
8773
|
-
return [
|
|
8774
|
-
{
|
|
8775
|
-
key: "agent.defaultAgent",
|
|
8776
|
-
scope,
|
|
8777
|
-
type: "agent",
|
|
8778
|
-
inherits: true,
|
|
8779
|
-
application,
|
|
8780
|
-
description: "Agent platform used when a launch does not name one."
|
|
8781
|
-
},
|
|
8782
|
-
...AGENT_PLATFORM_KEYS.flatMap((platform) => [
|
|
8783
|
-
{
|
|
8784
|
-
key: `agent.${platform}.mode`,
|
|
8785
|
-
scope,
|
|
8786
|
-
type: "agent-mode",
|
|
8787
|
-
inherits: true,
|
|
8788
|
-
application,
|
|
8789
|
-
description: `Launch mode (native or terminal) for ${platform}.`
|
|
8790
|
-
},
|
|
8791
|
-
{
|
|
8792
|
-
key: `agent.${platform}.model`,
|
|
8793
|
-
scope,
|
|
8794
|
-
type: "string",
|
|
8795
|
-
inherits: true,
|
|
8796
|
-
application,
|
|
8797
|
-
description: `Default model for new ${platform} sessions.`
|
|
8798
|
-
},
|
|
8799
|
-
{
|
|
8800
|
-
key: `agent.${platform}.reasoningEffort`,
|
|
8801
|
-
scope,
|
|
8802
|
-
type: "string",
|
|
8803
|
-
inherits: true,
|
|
8804
|
-
application,
|
|
8805
|
-
description: `Default reasoning level for new ${platform} sessions.`
|
|
8806
|
-
},
|
|
8807
|
-
{
|
|
8808
|
-
key: `agent.${platform}.fastMode`,
|
|
8809
|
-
scope,
|
|
8810
|
-
type: "boolean",
|
|
8811
|
-
inherits: true,
|
|
8812
|
-
application,
|
|
8813
|
-
description: `Default speed for new ${platform} sessions (true is Fast).`
|
|
8814
|
-
}
|
|
8815
|
-
])
|
|
8816
|
-
];
|
|
8817
|
-
}
|
|
8818
|
-
var PUBLIC_PROJECT_SETTINGS = Object.freeze([
|
|
8819
|
-
{
|
|
8820
|
-
key: "defaultBranch",
|
|
8821
|
-
scope: "project",
|
|
8822
|
-
type: "string",
|
|
8823
|
-
inherits: false,
|
|
8824
|
-
application: "next-environment",
|
|
8825
|
-
description: "Branch new environments are created from."
|
|
8826
|
-
},
|
|
8827
|
-
{
|
|
8828
|
-
key: "prBaseBranch",
|
|
8829
|
-
scope: "project",
|
|
8830
|
-
type: "string",
|
|
8831
|
-
inherits: false,
|
|
8832
|
-
application: "applied",
|
|
8833
|
-
description: "Base branch for pull requests and diff baselines."
|
|
8834
|
-
},
|
|
8835
|
-
{
|
|
8836
|
-
key: "defaultPortMappings",
|
|
8837
|
-
scope: "project",
|
|
8838
|
-
type: "port-mappings",
|
|
8839
|
-
inherits: false,
|
|
8840
|
-
environmentTypes: ["container"],
|
|
8841
|
-
application: "next-environment",
|
|
8842
|
-
description: "Port mappings copied into new container environments."
|
|
8843
|
-
},
|
|
8844
|
-
{
|
|
8845
|
-
key: "filesToCopy",
|
|
8846
|
-
scope: "project",
|
|
8847
|
-
type: "string-list",
|
|
8848
|
-
inherits: false,
|
|
8849
|
-
application: "next-environment",
|
|
8850
|
-
description: "Untracked files copied into new environments."
|
|
8851
|
-
},
|
|
8852
|
-
{
|
|
8853
|
-
key: "entryPort",
|
|
8854
|
-
scope: "project",
|
|
8855
|
-
type: "port",
|
|
8856
|
-
inherits: false,
|
|
8857
|
-
application: "next-environment",
|
|
8858
|
-
description: "Default preview entry port for new environments."
|
|
8859
|
-
},
|
|
8860
|
-
...agentSettingDescriptors("project")
|
|
8861
|
-
]);
|
|
8862
|
-
var PUBLIC_ENVIRONMENT_SETTINGS = Object.freeze([
|
|
8863
|
-
{
|
|
8864
|
-
key: "portMappings",
|
|
8865
|
-
scope: "environment",
|
|
8866
|
-
type: "port-mappings",
|
|
8867
|
-
inherits: false,
|
|
8868
|
-
environmentTypes: ["container"],
|
|
8869
|
-
application: "next-start",
|
|
8870
|
-
description: "Container port mappings; a running container adopts them on recreate."
|
|
8871
|
-
},
|
|
8872
|
-
{
|
|
8873
|
-
key: "allowedDomains",
|
|
8874
|
-
scope: "environment",
|
|
8875
|
-
type: "domain-list",
|
|
8876
|
-
inherits: true,
|
|
8877
|
-
environmentTypes: ["container"],
|
|
8878
|
-
application: "next-start",
|
|
8879
|
-
description: "Restricted-network allowlist; unset inherits the global list. A running container whose image supports it applies the change immediately (reported as applied)."
|
|
8880
|
-
},
|
|
8881
|
-
...agentSettingDescriptors("environment")
|
|
8882
|
-
]);
|
|
8883
|
-
|
|
8884
9755
|
// src/client/commands/settings.ts
|
|
8885
9756
|
function parseAssignment(raw) {
|
|
8886
9757
|
const equals = raw.indexOf("=");
|
|
@@ -9567,7 +10438,7 @@ var WAIT_OPTION = {
|
|
|
9567
10438
|
};
|
|
9568
10439
|
function hostLines(host) {
|
|
9569
10440
|
const updater = host.updater;
|
|
9570
|
-
const lines = [`Version: ${host.version}`];
|
|
10441
|
+
const lines = [`Version: ${host.version}`, `Update channel: ${host.updateChannel}`];
|
|
9571
10442
|
lines.push(updater.available ? `Updater: ${updater.kind}` : `Updater: unavailable${updater.reason ? ` (${updater.reason})` : ""}`);
|
|
9572
10443
|
if (updater.checkedAt) {
|
|
9573
10444
|
const found = updater.latestVersion ?? "unknown";
|
|
@@ -9697,6 +10568,26 @@ var hostCommands = [
|
|
|
9697
10568
|
};
|
|
9698
10569
|
}
|
|
9699
10570
|
},
|
|
10571
|
+
{
|
|
10572
|
+
path: ["host", "update", "channel"],
|
|
10573
|
+
summary: "Choose the host's update channel: stable, beta or unstable.",
|
|
10574
|
+
positionals: [{ name: "channel", required: true, description: "stable, beta or unstable" }],
|
|
10575
|
+
options: [REQUEST_ID_OPTION],
|
|
10576
|
+
async run(context, parsed) {
|
|
10577
|
+
const channel = parsed.positionals.channel;
|
|
10578
|
+
if (!isHostUpdateChannel(channel))
|
|
10579
|
+
throw new CliError("invalid-input", "channel must be stable, beta or unstable");
|
|
10580
|
+
const { session, result, receipt, warnings } = await submit(context, "host.update.channel", { channel }, parsed.options);
|
|
10581
|
+
return {
|
|
10582
|
+
action: "host.update.channel",
|
|
10583
|
+
result,
|
|
10584
|
+
receipt,
|
|
10585
|
+
warnings,
|
|
10586
|
+
connection: session.identity,
|
|
10587
|
+
human: hostLines(result.host)
|
|
10588
|
+
};
|
|
10589
|
+
}
|
|
10590
|
+
},
|
|
9700
10591
|
{
|
|
9701
10592
|
path: ["host", "update", "check"],
|
|
9702
10593
|
summary: "Ask the backend's computer to check for an Orkestrator update.",
|
|
@@ -9850,117 +10741,18 @@ var hostCommands = [
|
|
|
9850
10741
|
|
|
9851
10742
|
// src/client/commands/service.ts
|
|
9852
10743
|
import { spawnSync } from "child_process";
|
|
9853
|
-
import { existsSync as
|
|
10744
|
+
import { existsSync as existsSync4 } from "fs";
|
|
9854
10745
|
import { mkdir as mkdir2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
9855
|
-
import
|
|
9856
|
-
|
|
9857
|
-
// ../../apps/backend/src/data-dir.ts
|
|
9858
|
-
import os3 from "os";
|
|
9859
|
-
import path7 from "path";
|
|
9860
|
-
// ../../apps/backend/package.json
|
|
9861
|
-
var package_default = {
|
|
9862
|
-
name: "@orkestrator/backend",
|
|
9863
|
-
version: "2.22.3",
|
|
9864
|
-
private: true,
|
|
9865
|
-
type: "module",
|
|
9866
|
-
exports: {
|
|
9867
|
-
"./gateway": "./src/gateway.ts"
|
|
9868
|
-
},
|
|
9869
|
-
scripts: {
|
|
9870
|
-
build: "bun scripts/build.ts",
|
|
9871
|
-
dev: "bun --watch src/main.ts",
|
|
9872
|
-
"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",
|
|
9873
|
-
start: "bun dist/main.js",
|
|
9874
|
-
"start:web": "bun dist/main.js --fallback-host 127.0.0.1",
|
|
9875
|
-
"start:web-public": "bun dist/main.js --tailscale-serve --allowed-origins https://orkestrator.dev,https://www.orkestrator.dev",
|
|
9876
|
-
test: "bun test --preload ../../tests/setup-node.ts src tests --parallel",
|
|
9877
|
-
"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}",
|
|
9878
|
-
typecheck: "bunx tsc --noEmit -p tsconfig.json && bunx tsc --noEmit -p tsconfig.test.json"
|
|
9879
|
-
},
|
|
9880
|
-
dependencies: {
|
|
9881
|
-
"@modelcontextprotocol/node": "2.1.1",
|
|
9882
|
-
"@modelcontextprotocol/server": "2.3.1",
|
|
9883
|
-
"@opencode-ai/sdk": "1.19.0",
|
|
9884
|
-
"@orkestrator/protocol": "workspace:*",
|
|
9885
|
-
"@orkestrator/toolchain": "workspace:*",
|
|
9886
|
-
"@xterm/addon-serialize": "0.14.0",
|
|
9887
|
-
"@xterm/headless": "6.0.0",
|
|
9888
|
-
"playwright-core": "1.64.0",
|
|
9889
|
-
sharp: "^0.35.5",
|
|
9890
|
-
ws: "8.22.0",
|
|
9891
|
-
zod: "4.6.7"
|
|
9892
|
-
},
|
|
9893
|
-
devDependencies: {
|
|
9894
|
-
"@modelcontextprotocol/client": "2.3.1",
|
|
9895
|
-
"@types/ws": "8.18.2"
|
|
9896
|
-
}
|
|
9897
|
-
};
|
|
9898
|
-
|
|
9899
|
-
// ../../apps/backend/src/core/constants.ts
|
|
9900
|
-
var APP_SLUG = "orkestrator-v2";
|
|
9901
|
-
var AGENT_NETWORK_DOMAINS_BY_PLATFORM = Object.freeze({
|
|
9902
|
-
codex: Object.freeze(["chatgpt.com", "auth.openai.com"]),
|
|
9903
|
-
cursor: Object.freeze([
|
|
9904
|
-
"api2.cursor.sh",
|
|
9905
|
-
"api3.cursor.sh",
|
|
9906
|
-
"api4.cursor.sh",
|
|
9907
|
-
"api5.cursor.sh",
|
|
9908
|
-
"repo42.cursor.sh",
|
|
9909
|
-
"authenticator.cursor.sh",
|
|
9910
|
-
"marketplace.cursorapi.com",
|
|
9911
|
-
"cursor-cdn.com",
|
|
9912
|
-
"cursor.com"
|
|
9913
|
-
]),
|
|
9914
|
-
grok: Object.freeze([
|
|
9915
|
-
"x.ai",
|
|
9916
|
-
"auth.x.ai",
|
|
9917
|
-
"api.x.ai",
|
|
9918
|
-
"cli-chat-proxy.grok.com"
|
|
9919
|
-
]),
|
|
9920
|
-
pi: Object.freeze([
|
|
9921
|
-
"pi.dev",
|
|
9922
|
-
"radius.pi.dev",
|
|
9923
|
-
"api.anthropic.com",
|
|
9924
|
-
"api.openai.com",
|
|
9925
|
-
"generativelanguage.googleapis.com",
|
|
9926
|
-
"api.x.ai",
|
|
9927
|
-
"api.mistral.ai",
|
|
9928
|
-
"api.groq.com",
|
|
9929
|
-
"api.deepseek.com",
|
|
9930
|
-
"api.cerebras.ai",
|
|
9931
|
-
"api.together.ai",
|
|
9932
|
-
"openrouter.ai"
|
|
9933
|
-
])
|
|
9934
|
-
});
|
|
9935
|
-
var MAX_CODEX_CONCURRENT_THREADS = Number.MAX_SAFE_INTEGER - 1;
|
|
9936
|
-
function sanitizeAppVersion(raw) {
|
|
9937
|
-
const trimmed = raw?.trim() ?? "";
|
|
9938
|
-
return /^[A-Za-z0-9._+-]{1,64}$/.test(trimmed) ? trimmed : "0.0.0";
|
|
9939
|
-
}
|
|
9940
|
-
var BACKEND_PACKAGE_VERSION = sanitizeAppVersion(package_default.version);
|
|
9941
|
-
var APP_VERSION = sanitizeAppVersion(process.env.ORKESTRATOR_VERSION?.trim() || BACKEND_PACKAGE_VERSION);
|
|
9942
|
-
|
|
9943
|
-
// ../../apps/backend/src/data-dir.ts
|
|
9944
|
-
function assertSupportedPlatform(platform = process.platform) {
|
|
9945
|
-
if (platform === "win32") {
|
|
9946
|
-
throw new Error("Orkestrator does not support Windows. Use macOS or Linux.");
|
|
9947
|
-
}
|
|
9948
|
-
}
|
|
9949
|
-
function defaultDataDir(platform = process.platform, env = process.env, home = os3.homedir()) {
|
|
9950
|
-
assertSupportedPlatform(platform);
|
|
9951
|
-
if (platform === "darwin")
|
|
9952
|
-
return path7.join(home, "Library", "Application Support", APP_SLUG);
|
|
9953
|
-
return path7.join(env.XDG_CONFIG_HOME ?? path7.join(home, ".config"), APP_SLUG);
|
|
9954
|
-
}
|
|
10746
|
+
import path13 from "path";
|
|
9955
10747
|
|
|
9956
10748
|
// src/client/service-units.ts
|
|
9957
10749
|
import { createHash as createHash3 } from "crypto";
|
|
9958
|
-
import
|
|
9959
|
-
import
|
|
10750
|
+
import os5 from "os";
|
|
10751
|
+
import path11 from "path";
|
|
9960
10752
|
function suffix(dataDir, defaultDataDir) {
|
|
9961
|
-
if (
|
|
10753
|
+
if (path11.resolve(dataDir) === path11.resolve(defaultDataDir))
|
|
9962
10754
|
return "";
|
|
9963
|
-
return `-${createHash3("sha256").update(
|
|
10755
|
+
return `-${createHash3("sha256").update(path11.resolve(dataDir)).digest("hex").slice(0, 8)}`;
|
|
9964
10756
|
}
|
|
9965
10757
|
function xml(text) {
|
|
9966
10758
|
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
@@ -9969,7 +10761,7 @@ function systemdWord(text) {
|
|
|
9969
10761
|
return `"${text.replaceAll("\\", "\\\\").replaceAll('"', "\\\"").replaceAll("%", "%%")}"`;
|
|
9970
10762
|
}
|
|
9971
10763
|
function serviceUnit(options2) {
|
|
9972
|
-
const home = options2.home ??
|
|
10764
|
+
const home = options2.home ?? os5.homedir();
|
|
9973
10765
|
const tag = suffix(options2.dataDir, options2.defaultDataDir);
|
|
9974
10766
|
const pathValue = options2.env.PATH ?? "/usr/local/bin:/usr/bin:/bin";
|
|
9975
10767
|
if (options2.platform === "darwin") {
|
|
@@ -9979,7 +10771,7 @@ function serviceUnit(options2) {
|
|
|
9979
10771
|
return {
|
|
9980
10772
|
manager: "launchd",
|
|
9981
10773
|
name,
|
|
9982
|
-
file:
|
|
10774
|
+
file: path11.join(home, "Library", "LaunchAgents", `${name}.plist`),
|
|
9983
10775
|
contents: `<?xml version="1.0" encoding="UTF-8"?>
|
|
9984
10776
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
9985
10777
|
<plist version="1.0">
|
|
@@ -10013,11 +10805,11 @@ ${args}
|
|
|
10013
10805
|
};
|
|
10014
10806
|
}
|
|
10015
10807
|
const name = `orkestrator-backend${tag}.service`;
|
|
10016
|
-
const configHome = options2.env.XDG_CONFIG_HOME ??
|
|
10808
|
+
const configHome = options2.env.XDG_CONFIG_HOME ?? path11.join(home, ".config");
|
|
10017
10809
|
return {
|
|
10018
10810
|
manager: "systemd",
|
|
10019
10811
|
name,
|
|
10020
|
-
file:
|
|
10812
|
+
file: path11.join(configHome, "systemd", "user", name),
|
|
10021
10813
|
contents: `[Unit]
|
|
10022
10814
|
Description=Orkestrator backend (${options2.dataDir})
|
|
10023
10815
|
After=network-online.target
|
|
@@ -10072,7 +10864,7 @@ var DATA_DIR_OPTION = {
|
|
|
10072
10864
|
};
|
|
10073
10865
|
var RESERVED_SERVE_FLAGS = new Set(["--data-dir", "--standalone-updater", "--desktop-updater"]);
|
|
10074
10866
|
function dataDirectory(context, parsed) {
|
|
10075
|
-
return
|
|
10867
|
+
return path13.resolve(stringOption(parsed.options, "data-dir") ?? context.io.env.ORKESTRATOR_DATA_DIR ?? defaultDataDir(process.platform, context.io.env));
|
|
10076
10868
|
}
|
|
10077
10869
|
function unitFor(context, dataDir, bun) {
|
|
10078
10870
|
if (process.platform !== "darwin" && process.platform !== "linux") {
|
|
@@ -10089,7 +10881,7 @@ function unitFor(context, dataDir, bun) {
|
|
|
10089
10881
|
defaultDataDir: defaultDataDir(process.platform, context.io.env),
|
|
10090
10882
|
bun,
|
|
10091
10883
|
launcher: layout.launcher,
|
|
10092
|
-
logFile:
|
|
10884
|
+
logFile: path13.join(layout.logs, "service.log"),
|
|
10093
10885
|
env: context.io.env
|
|
10094
10886
|
})
|
|
10095
10887
|
};
|
|
@@ -10204,7 +10996,7 @@ var serviceCommands = [
|
|
|
10204
10996
|
installedAt: new Date().toISOString()
|
|
10205
10997
|
});
|
|
10206
10998
|
await mkdir2(layout.logs, { recursive: true });
|
|
10207
|
-
await mkdir2(
|
|
10999
|
+
await mkdir2(path13.dirname(unit.file), { recursive: true });
|
|
10208
11000
|
await writeFile2(unit.file, unit.contents);
|
|
10209
11001
|
const started = parsed.options["skip-start"] !== true;
|
|
10210
11002
|
if (started) {
|
|
@@ -10218,7 +11010,7 @@ var serviceCommands = [
|
|
|
10218
11010
|
...keyValues([
|
|
10219
11011
|
["release", `${version} (${releaseDir})`],
|
|
10220
11012
|
["unit", `${unit.manager} ${unit.name}`],
|
|
10221
|
-
["logs",
|
|
11013
|
+
["logs", path13.join(layout.logs, "service.log")]
|
|
10222
11014
|
]),
|
|
10223
11015
|
"",
|
|
10224
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(" ")}`,
|
|
@@ -10244,7 +11036,7 @@ var serviceCommands = [
|
|
|
10244
11036
|
const dataDir = dataDirectory(context, parsed);
|
|
10245
11037
|
const { service, layout, unit } = await unitFor(context, dataDir, process.execPath);
|
|
10246
11038
|
const config = service.readServiceConfig(layout);
|
|
10247
|
-
if (
|
|
11039
|
+
if (existsSync4(unit.file)) {
|
|
10248
11040
|
for (const argv of serviceManagerCommands(unit).unload)
|
|
10249
11041
|
runManager(argv, true);
|
|
10250
11042
|
await rm2(unit.file, { force: true });
|
|
@@ -10272,7 +11064,7 @@ var serviceCommands = [
|
|
|
10272
11064
|
const { service, layout, unit } = await unitFor(context, dataDir, process.execPath);
|
|
10273
11065
|
const config = service.readServiceConfig(layout);
|
|
10274
11066
|
const state = service.readLauncherState(layout);
|
|
10275
|
-
const installed =
|
|
11067
|
+
const installed = existsSync4(unit.file);
|
|
10276
11068
|
const running = installed ? runManager(serviceManagerCommands(unit).status, true).ok : false;
|
|
10277
11069
|
const result = {
|
|
10278
11070
|
dataDir,
|
|
@@ -11448,9 +12240,9 @@ var sessionCommands = [
|
|
|
11448
12240
|
];
|
|
11449
12241
|
|
|
11450
12242
|
// src/client/commands/toolchain.ts
|
|
11451
|
-
import
|
|
12243
|
+
import path17 from "path";
|
|
11452
12244
|
function dataDirectory2(context, parsed) {
|
|
11453
|
-
return
|
|
12245
|
+
return path17.resolve(stringOption(parsed.options, "data-dir") ?? context.io.env.ORKESTRATOR_DATA_DIR ?? defaultDataDir(process.platform, context.io.env));
|
|
11454
12246
|
}
|
|
11455
12247
|
function requestedTools(parsed) {
|
|
11456
12248
|
const raw = parsed.options.tool;
|
|
@@ -11553,7 +12345,7 @@ var toolchainCommands = [
|
|
|
11553
12345
|
const rows = installed.tools.map((tool) => ({
|
|
11554
12346
|
tool,
|
|
11555
12347
|
version: installed.versions[tool] ?? "",
|
|
11556
|
-
executable:
|
|
12348
|
+
executable: path17.join(installed.currentBinDir, path17.basename(installed.executables[tool]))
|
|
11557
12349
|
}));
|
|
11558
12350
|
return {
|
|
11559
12351
|
action: "toolchain.install",
|
|
@@ -11590,6 +12382,7 @@ var GROUP_SUMMARIES = {
|
|
|
11590
12382
|
environment: "Create, start, stop, configure, launch and delete environments.",
|
|
11591
12383
|
agent: "Discover agents, models and controls.",
|
|
11592
12384
|
session: "Start, prompt, control and inspect native-agent conversations.",
|
|
12385
|
+
changes: "Read the file and line changes the backend measures (read-only).",
|
|
11593
12386
|
run: "Inspect, wait for and recover individual operations."
|
|
11594
12387
|
};
|
|
11595
12388
|
function optionLine(option) {
|
|
@@ -11639,6 +12432,7 @@ function rootHelp(tree) {
|
|
|
11639
12432
|
"Usage:",
|
|
11640
12433
|
" orkestrator [serve] [service options] Run the backend service in the foreground",
|
|
11641
12434
|
" orkestrator <group> <command> [options] Talk to an already-running backend",
|
|
12435
|
+
" orkestrator --fix AGENT Get an agent's help reporting a problem",
|
|
11642
12436
|
"",
|
|
11643
12437
|
"Client commands never start a backend. Select the backend explicitly with",
|
|
11644
12438
|
"--profile NAME (an isolated dev profile) or --connection NAME, or save a",
|
|
@@ -11649,6 +12443,8 @@ function rootHelp(tree) {
|
|
|
11649
12443
|
"",
|
|
11650
12444
|
"Other:",
|
|
11651
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`)",
|
|
11652
12448
|
" help [COMMAND] Show help for a group or command",
|
|
11653
12449
|
" version Print the client version",
|
|
11654
12450
|
"",
|
|
@@ -11814,6 +12610,7 @@ var CLIENT_COMMANDS = [
|
|
|
11814
12610
|
...projectCommands,
|
|
11815
12611
|
...environmentCommands,
|
|
11816
12612
|
...sessionCommands,
|
|
12613
|
+
...changesCommands,
|
|
11817
12614
|
...runCommands,
|
|
11818
12615
|
...toolchainCommands,
|
|
11819
12616
|
...hostCommands,
|
|
@@ -11958,7 +12755,7 @@ var CLIENT_FLAGS = new Set(GLOBAL_OPTIONS.flatMap((option) => [
|
|
|
11958
12755
|
...option.short ? [`-${option.short}`] : []
|
|
11959
12756
|
]));
|
|
11960
12757
|
var CLIENT_VALUE_FLAGS = new Set(GLOBAL_OPTIONS.filter((option) => option.kind !== "boolean").map((option) => `--${option.name}`));
|
|
11961
|
-
var
|
|
12758
|
+
var SAFE_WORD3 = /^[a-z][a-z0-9-]{0,39}$/;
|
|
11962
12759
|
function flagName(token) {
|
|
11963
12760
|
const equals = token.indexOf("=");
|
|
11964
12761
|
return equals < 0 ? token : token.slice(0, equals);
|
|
@@ -11966,6 +12763,8 @@ function flagName(token) {
|
|
|
11966
12763
|
function classifyInvocation(argv) {
|
|
11967
12764
|
if (argv.length === 0)
|
|
11968
12765
|
return { mode: "serve", args: [] };
|
|
12766
|
+
if (isFixInvocation(argv))
|
|
12767
|
+
return { mode: "fix", argv: [...argv] };
|
|
11969
12768
|
const first = argv[0];
|
|
11970
12769
|
if (first === "serve") {
|
|
11971
12770
|
const rest = argv.slice(1);
|
|
@@ -12011,7 +12810,7 @@ function classifyInvocation(argv) {
|
|
|
12011
12810
|
return { mode: "client", argv: [...argv] };
|
|
12012
12811
|
return {
|
|
12013
12812
|
mode: "error",
|
|
12014
|
-
message: `Unknown command ${
|
|
12813
|
+
message: `Unknown command ${SAFE_WORD3.test(first) ? `'${first}'` : "argument"}; run \`orkestrator help\``
|
|
12015
12814
|
};
|
|
12016
12815
|
}
|
|
12017
12816
|
function renderInvocationError(argv, message) {
|
|
@@ -12036,5 +12835,6 @@ export {
|
|
|
12036
12835
|
classifyInvocation,
|
|
12037
12836
|
renderInvocationError,
|
|
12038
12837
|
runClient,
|
|
12039
|
-
runClientProcess
|
|
12838
|
+
runClientProcess,
|
|
12839
|
+
runFixProcess
|
|
12040
12840
|
};
|