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