orkestrator 2.30.0-beta → 2.32.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -3
- package/dist/client.js +1728 -158
- package/dist/main.js +227795 -217168
- package/package.json +3 -3
- package/resources/claude-bridge/dist/index.js +1029 -682
- package/resources/codex-bridge/dist/index.js +474 -150
package/dist/client.js
CHANGED
|
@@ -4876,26 +4876,26 @@ import {
|
|
|
4876
4876
|
symlink as symlink2,
|
|
4877
4877
|
utimes
|
|
4878
4878
|
} from "fs/promises";
|
|
4879
|
-
import
|
|
4879
|
+
import path16 from "path";
|
|
4880
4880
|
import { pipeline } from "stream/promises";
|
|
4881
4881
|
import { createGunzip } from "zlib";
|
|
4882
4882
|
function delay(milliseconds) {
|
|
4883
4883
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
4884
4884
|
}
|
|
4885
4885
|
function artifactDirectory(rootDir, artifact) {
|
|
4886
|
-
return
|
|
4886
|
+
return path16.join(rootDir, artifact.name, artifact.version, `${artifact.platform}-${artifact.architecture}`);
|
|
4887
4887
|
}
|
|
4888
4888
|
function artifactExecutablePath(rootDir, artifact) {
|
|
4889
|
-
return
|
|
4889
|
+
return path16.join(artifactDirectory(rootDir, artifact), artifact.executable.fileName);
|
|
4890
4890
|
}
|
|
4891
4891
|
function upstreamExecutablePath(rootDir, artifact) {
|
|
4892
|
-
return
|
|
4892
|
+
return path16.join(artifactDirectory(rootDir, artifact), `.upstream-${artifact.executable.fileName}`);
|
|
4893
4893
|
}
|
|
4894
4894
|
function artifactCompanions(artifact) {
|
|
4895
4895
|
return artifact.companions ?? [];
|
|
4896
4896
|
}
|
|
4897
4897
|
function companionPath(rootDir, artifact, companion) {
|
|
4898
|
-
return
|
|
4898
|
+
return path16.join(artifactDirectory(rootDir, artifact), companion.fileName);
|
|
4899
4899
|
}
|
|
4900
4900
|
function artifactArchiveBytes(artifact) {
|
|
4901
4901
|
return artifactCompanions(artifact).reduce((total, companion) => total + companion.archive.size, artifact.archive.size);
|
|
@@ -4919,7 +4919,7 @@ function assertValidArtifactMetadata(artifact) {
|
|
|
4919
4919
|
}
|
|
4920
4920
|
const paths = new Set;
|
|
4921
4921
|
for (const file of artifact.archive.bundleFiles) {
|
|
4922
|
-
const normalized =
|
|
4922
|
+
const normalized = path16.posix.normalize(file.path);
|
|
4923
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)) {
|
|
4924
4924
|
throw new Error(`${artifact.name} manifest has an unsafe bundle file`);
|
|
4925
4925
|
}
|
|
@@ -4945,7 +4945,7 @@ function assertValidArtifactMetadata(artifact) {
|
|
|
4945
4945
|
}
|
|
4946
4946
|
const activationNames = new Set([artifact.name, artifact.executable.fileName]);
|
|
4947
4947
|
for (const alias of artifact.activationAliases ?? []) {
|
|
4948
|
-
if (!alias || alias.startsWith(".") || alias.includes("/") || alias.includes("\\") || alias !==
|
|
4948
|
+
if (!alias || alias.startsWith(".") || alias.includes("/") || alias.includes("\\") || alias !== path16.basename(alias) || activationNames.has(alias)) {
|
|
4949
4949
|
throw new Error(`${artifact.name} manifest has an unsafe or duplicate activation alias`);
|
|
4950
4950
|
}
|
|
4951
4951
|
activationNames.add(alias);
|
|
@@ -4955,7 +4955,7 @@ function assertValidArtifactMetadata(artifact) {
|
|
|
4955
4955
|
if (companion.archive.format !== "zip" && companion.archive.format !== "tar.gz") {
|
|
4956
4956
|
throw new Error(`${artifact.name} companion has an unsupported archive format`);
|
|
4957
4957
|
}
|
|
4958
|
-
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 !== path16.basename(companion.fileName)) {
|
|
4959
4959
|
throw new Error(`${artifact.name} companion has an unsafe file name`);
|
|
4960
4960
|
}
|
|
4961
4961
|
if (companionNames.has(companion.fileName)) {
|
|
@@ -5021,7 +5021,7 @@ async function isValidExecutable(rootDir, artifact) {
|
|
|
5021
5021
|
if (!await isValidUpstreamExecutable(rootDir, artifact))
|
|
5022
5022
|
return false;
|
|
5023
5023
|
for (const bundled of artifact.archive.bundleFiles ?? []) {
|
|
5024
|
-
if (!await isValidFile(
|
|
5024
|
+
if (!await isValidFile(path16.join(artifactDirectory(rootDir, artifact), ...bundled.path.split("/")), bundled)) {
|
|
5025
5025
|
return false;
|
|
5026
5026
|
}
|
|
5027
5027
|
}
|
|
@@ -5038,7 +5038,7 @@ async function isValidExecutable(rootDir, artifact) {
|
|
|
5038
5038
|
if ((file.mode & 511) !== 320)
|
|
5039
5039
|
await chmod(executablePath, 320);
|
|
5040
5040
|
for (const bundled of artifact.archive.bundleFiles ?? []) {
|
|
5041
|
-
if (!await isValidFile(
|
|
5041
|
+
if (!await isValidFile(path16.join(artifactDirectory(rootDir, artifact), ...bundled.path.split("/")), bundled)) {
|
|
5042
5042
|
return false;
|
|
5043
5043
|
}
|
|
5044
5044
|
}
|
|
@@ -5059,7 +5059,7 @@ async function bundleTreeEntries(directory, artifact) {
|
|
|
5059
5059
|
const visit = async (currentDirectory, prefix = "") => {
|
|
5060
5060
|
for (const entry of await readdir2(currentDirectory, { withFileTypes: true })) {
|
|
5061
5061
|
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
5062
|
-
const filePath =
|
|
5062
|
+
const filePath = path16.join(currentDirectory, entry.name);
|
|
5063
5063
|
if (entry.isDirectory()) {
|
|
5064
5064
|
await visit(filePath, relativePath);
|
|
5065
5065
|
} else if (entry.isFile() && !entry.isSymbolicLink()) {
|
|
@@ -5174,7 +5174,7 @@ async function initializeInstallLock(lockPath, owner, openLockFile) {
|
|
|
5174
5174
|
}
|
|
5175
5175
|
}
|
|
5176
5176
|
async function acquireInstallLock(rootDir, onProgress, timings, openLockFile, ownerProcessExists) {
|
|
5177
|
-
const lockPath =
|
|
5177
|
+
const lockPath = path16.join(rootDir, INSTALL_LOCK);
|
|
5178
5178
|
const startedAt = Date.now();
|
|
5179
5179
|
let announcedWait = false;
|
|
5180
5180
|
while (Date.now() - startedAt < timings.lockWaitTimeoutMs) {
|
|
@@ -5355,8 +5355,8 @@ async function extractTarGzipBundle(archivePath, destinationDirectory, label, ar
|
|
|
5355
5355
|
stream.resume();
|
|
5356
5356
|
return;
|
|
5357
5357
|
}
|
|
5358
|
-
const normalized =
|
|
5359
|
-
if (normalized.startsWith("../") || normalized.startsWith("/") || normalized.includes("\\") ||
|
|
5358
|
+
const normalized = path16.posix.normalize(relative);
|
|
5359
|
+
if (normalized.startsWith("../") || normalized.startsWith("/") || normalized.includes("\\") || path16.isAbsolute(normalized)) {
|
|
5360
5360
|
extract2.destroy(new Error(`Unsafe path in ${label} bundle`));
|
|
5361
5361
|
stream.resume();
|
|
5362
5362
|
return;
|
|
@@ -5368,7 +5368,7 @@ async function extractTarGzipBundle(archivePath, destinationDirectory, label, ar
|
|
|
5368
5368
|
stream.resume();
|
|
5369
5369
|
return;
|
|
5370
5370
|
}
|
|
5371
|
-
const destination =
|
|
5371
|
+
const destination = path16.join(destinationDirectory, ...normalized.split("/"));
|
|
5372
5372
|
if (header.type === "directory") {
|
|
5373
5373
|
stream.once("end", next);
|
|
5374
5374
|
stream.resume();
|
|
@@ -5386,7 +5386,7 @@ async function extractTarGzipBundle(archivePath, destinationDirectory, label, ar
|
|
|
5386
5386
|
}
|
|
5387
5387
|
if (header.name === archive.entryPath)
|
|
5388
5388
|
foundExecutable = true;
|
|
5389
|
-
mkdir3(
|
|
5389
|
+
mkdir3(path16.dirname(destination), { recursive: true, mode: 448 }).then(() => pipeline(stream, createWriteStream(destination, {
|
|
5390
5390
|
flags: "wx",
|
|
5391
5391
|
mode: (header.mode ?? 0) & 73 ? 320 : 256
|
|
5392
5392
|
}))).then(() => next(), (error) => extract2.destroy(error instanceof Error ? error : new Error(String(error))));
|
|
@@ -5548,7 +5548,7 @@ async function repairInvalidMacSignature(executablePath, artifact, timeoutMs, sp
|
|
|
5548
5548
|
await runCodesign(["--sign", "-", "--force", executablePath], `${artifact.name} could not be ad-hoc signed after source verification`, timeoutMs, spawnProcess);
|
|
5549
5549
|
}
|
|
5550
5550
|
async function prepareRepairableExecutable(sourcePath, executablePath, artifact, skipExecutableProbeForTests, processTimeoutMs, spawnProcess) {
|
|
5551
|
-
const temporaryPath =
|
|
5551
|
+
const temporaryPath = path16.join(path16.dirname(executablePath), `.repair-${artifact.executable.fileName}-${randomUUID4()}.tmp`);
|
|
5552
5552
|
try {
|
|
5553
5553
|
await copyFile2(sourcePath, temporaryPath);
|
|
5554
5554
|
await chmod(temporaryPath, 448);
|
|
@@ -5580,8 +5580,8 @@ async function refreshRepairableArtifact(rootDir, artifact, skipExecutableProbeF
|
|
|
5580
5580
|
async function stageCompanions(stagingDirectory, artifact, companions, fetchImpl, onBytes, startBytes, allowInsecureDownloadsForTests, skipExecutableProbeForTests, timings, spawnProcess) {
|
|
5581
5581
|
let downloadedBytes = startBytes;
|
|
5582
5582
|
for (const companion of companions) {
|
|
5583
|
-
const companionArchivePath =
|
|
5584
|
-
const companionFilePath =
|
|
5583
|
+
const companionArchivePath = path16.join(stagingDirectory, `.archive-${companion.fileName}.${archiveExtension(companion.archive)}`);
|
|
5584
|
+
const companionFilePath = path16.join(stagingDirectory, companion.fileName);
|
|
5585
5585
|
const precedingBytes = downloadedBytes;
|
|
5586
5586
|
await downloadArchive(companion.fileName, companion.archive, companionArchivePath, fetchImpl, (received) => onBytes(precedingBytes + received), allowInsecureDownloadsForTests, timings.downloadTimeoutMs);
|
|
5587
5587
|
downloadedBytes += companion.archive.size;
|
|
@@ -5606,28 +5606,28 @@ async function stageCompanions(stagingDirectory, artifact, companions, fetchImpl
|
|
|
5606
5606
|
}
|
|
5607
5607
|
}
|
|
5608
5608
|
async function repairArtifactCompanions(rootDir, artifact, companions, fetchImpl, onBytes, onVerify, allowInsecureDownloadsForTests, skipExecutableProbeForTests, timings, spawnProcess) {
|
|
5609
|
-
const stagingDirectory = await mkdtemp2(
|
|
5609
|
+
const stagingDirectory = await mkdtemp2(path16.join(rootDir, `.staging-${artifact.name}-`));
|
|
5610
5610
|
try {
|
|
5611
5611
|
await stageCompanions(stagingDirectory, artifact, companions, fetchImpl, onBytes, 0, allowInsecureDownloadsForTests, skipExecutableProbeForTests, timings, spawnProcess);
|
|
5612
5612
|
onVerify();
|
|
5613
5613
|
const destinationDirectory = artifactDirectory(rootDir, artifact);
|
|
5614
5614
|
for (const companion of companions) {
|
|
5615
|
-
await rename2(
|
|
5615
|
+
await rename2(path16.join(stagingDirectory, companion.fileName), path16.join(destinationDirectory, companion.fileName));
|
|
5616
5616
|
}
|
|
5617
5617
|
} finally {
|
|
5618
5618
|
await rm3(stagingDirectory, { recursive: true, force: true });
|
|
5619
5619
|
}
|
|
5620
5620
|
}
|
|
5621
5621
|
async function installArtifact(rootDir, artifact, fetchImpl, onBytes, onVerify, allowInsecureDownloadsForTests, skipExecutableProbeForTests, timings, spawnProcess) {
|
|
5622
|
-
const stagingDirectory = await mkdtemp2(
|
|
5623
|
-
const archivePath =
|
|
5624
|
-
const executablePath =
|
|
5625
|
-
const extractedPath = artifact.executable.repairInvalidMacSignature ?
|
|
5622
|
+
const stagingDirectory = await mkdtemp2(path16.join(rootDir, `.staging-${artifact.name}-`));
|
|
5623
|
+
const archivePath = path16.join(stagingDirectory, `archive.${archiveExtension(artifact.archive)}`);
|
|
5624
|
+
const executablePath = path16.join(stagingDirectory, artifact.executable.fileName);
|
|
5625
|
+
const extractedPath = artifact.executable.repairInvalidMacSignature ? path16.join(stagingDirectory, `.upstream-${artifact.executable.fileName}`) : executablePath;
|
|
5626
5626
|
try {
|
|
5627
5627
|
await downloadArchive(artifact.name, artifact.archive, archivePath, fetchImpl, onBytes, allowInsecureDownloadsForTests, timings.downloadTimeoutMs);
|
|
5628
5628
|
if (artifact.archive.bundleRoot) {
|
|
5629
5629
|
await extractTarGzipBundle(archivePath, stagingDirectory, artifact.name, artifact.archive);
|
|
5630
|
-
const bundledExecutable =
|
|
5630
|
+
const bundledExecutable = path16.join(stagingDirectory, ...artifact.archive.entryPath.slice(artifact.archive.bundleRoot.length).split("/"));
|
|
5631
5631
|
if (bundledExecutable !== extractedPath) {
|
|
5632
5632
|
await rename2(bundledExecutable, extractedPath);
|
|
5633
5633
|
}
|
|
@@ -5678,7 +5678,7 @@ async function installArtifact(rootDir, artifact, fetchImpl, onBytes, onVerify,
|
|
|
5678
5678
|
await stageCompanions(stagingDirectory, artifact, artifactCompanions(artifact), fetchImpl, onBytes, artifact.archive.size, allowInsecureDownloadsForTests, skipExecutableProbeForTests, timings, spawnProcess);
|
|
5679
5679
|
onVerify();
|
|
5680
5680
|
const destinationDirectory = artifactDirectory(rootDir, artifact);
|
|
5681
|
-
await mkdir3(
|
|
5681
|
+
await mkdir3(path16.dirname(destinationDirectory), { recursive: true, mode: 448 });
|
|
5682
5682
|
await rm3(destinationDirectory, { recursive: true, force: true });
|
|
5683
5683
|
await rename2(stagingDirectory, destinationDirectory);
|
|
5684
5684
|
await chmod(destinationDirectory, 448);
|
|
@@ -5690,16 +5690,16 @@ async function installArtifact(rootDir, artifact, fetchImpl, onBytes, onVerify,
|
|
|
5690
5690
|
}
|
|
5691
5691
|
async function cleanStagingDirectories(rootDir) {
|
|
5692
5692
|
const entries = await readdir2(rootDir, { withFileTypes: true });
|
|
5693
|
-
await Promise.all(entries.filter((entry) => entry.isDirectory() && entry.name.startsWith(".staging-")).map((entry) => rm3(
|
|
5693
|
+
await Promise.all(entries.filter((entry) => entry.isDirectory() && entry.name.startsWith(".staging-")).map((entry) => rm3(path16.join(rootDir, entry.name), { recursive: true, force: true })));
|
|
5694
5694
|
}
|
|
5695
5695
|
async function touchArtifactDirectory(rootDir, artifact) {
|
|
5696
5696
|
const now = new Date;
|
|
5697
|
-
await utimes(
|
|
5697
|
+
await utimes(path16.dirname(artifactDirectory(rootDir, artifact)), now, now).catch(() => {
|
|
5698
5698
|
return;
|
|
5699
5699
|
});
|
|
5700
5700
|
}
|
|
5701
5701
|
function versionLeaseDirectory(rootDir, name, version) {
|
|
5702
|
-
return
|
|
5702
|
+
return path16.join(rootDir, LEASE_DIRECTORY, name, version);
|
|
5703
5703
|
}
|
|
5704
5704
|
async function acquireVersionLease(rootDir, artifact, openLeaseFile) {
|
|
5705
5705
|
const leaseKey = `${rootDir}\x00${artifact.name}\x00${artifact.version}`;
|
|
@@ -5714,7 +5714,7 @@ async function acquireVersionLease(rootDir, artifact, openLeaseFile) {
|
|
|
5714
5714
|
};
|
|
5715
5715
|
const directory = versionLeaseDirectory(rootDir, artifact.name, artifact.version);
|
|
5716
5716
|
await mkdir3(directory, { recursive: true, mode: 448 });
|
|
5717
|
-
const leasePath =
|
|
5717
|
+
const leasePath = path16.join(directory, `${owner.pid}-${owner.token}.json`);
|
|
5718
5718
|
const handle = await openLeaseFile(leasePath, "wx", 384);
|
|
5719
5719
|
try {
|
|
5720
5720
|
await handle.writeFile(JSON.stringify(owner), "utf8");
|
|
@@ -5735,7 +5735,7 @@ async function versionHasLiveLease(rootDir, name, version, ownerProcessExists) {
|
|
|
5735
5735
|
const entries = await readdir2(directory, { withFileTypes: true }).catch(() => []);
|
|
5736
5736
|
let live = false;
|
|
5737
5737
|
await Promise.all(entries.filter((entry) => entry.isFile()).map(async (entry) => {
|
|
5738
|
-
const leasePath =
|
|
5738
|
+
const leasePath = path16.join(directory, entry.name);
|
|
5739
5739
|
try {
|
|
5740
5740
|
const parsed = JSON.parse(await readFile2(leasePath, "utf8"));
|
|
5741
5741
|
if (typeof parsed.token === "string" && typeof parsed.pid === "number" && typeof parsed.createdAt === "string" && ownerProcessExists(parsed.pid)) {
|
|
@@ -5758,11 +5758,11 @@ async function pruneSupersededVersions(rootDir, artifacts, retainSupersededMs, o
|
|
|
5758
5758
|
}
|
|
5759
5759
|
const staleBefore = Date.now() - retainSupersededMs;
|
|
5760
5760
|
await Promise.all(Array.from(keepByName, async ([name, keep]) => {
|
|
5761
|
-
const toolDir =
|
|
5761
|
+
const toolDir = path16.join(rootDir, name);
|
|
5762
5762
|
try {
|
|
5763
5763
|
const entries = await readdir2(toolDir, { withFileTypes: true });
|
|
5764
5764
|
await Promise.all(entries.filter((entry) => entry.isDirectory() && !keep.has(entry.name)).map(async (entry) => {
|
|
5765
|
-
const versionDir =
|
|
5765
|
+
const versionDir = path16.join(toolDir, entry.name);
|
|
5766
5766
|
try {
|
|
5767
5767
|
if ((await stat2(versionDir)).mtimeMs > staleBefore)
|
|
5768
5768
|
return;
|
|
@@ -5803,8 +5803,8 @@ function activationSetId(artifacts) {
|
|
|
5803
5803
|
return createHash4("sha256").update(JSON.stringify(identity)).digest("hex");
|
|
5804
5804
|
}
|
|
5805
5805
|
async function activateExecutables(rootDir, artifacts) {
|
|
5806
|
-
const binRoot =
|
|
5807
|
-
const binDir =
|
|
5806
|
+
const binRoot = path16.join(rootDir, "bin");
|
|
5807
|
+
const binDir = path16.join(binRoot, activationSetId(artifacts));
|
|
5808
5808
|
await mkdir3(binRoot, { recursive: true, mode: 448 });
|
|
5809
5809
|
const binRootEntry = await lstat(binRoot);
|
|
5810
5810
|
if (!binRootEntry.isDirectory() || binRootEntry.isSymbolicLink()) {
|
|
@@ -5819,8 +5819,8 @@ async function activateExecutables(rootDir, artifacts) {
|
|
|
5819
5819
|
await chmod(binDir, 448);
|
|
5820
5820
|
const executables = {};
|
|
5821
5821
|
const activate = async (linkName, target) => {
|
|
5822
|
-
const activePath =
|
|
5823
|
-
const temporaryLink =
|
|
5822
|
+
const activePath = path16.join(binDir, linkName);
|
|
5823
|
+
const temporaryLink = path16.join(binDir, `.${linkName}-${randomUUID4()}.tmp`);
|
|
5824
5824
|
const existingTarget = await readlink(activePath).catch(() => null);
|
|
5825
5825
|
if (existingTarget !== target) {
|
|
5826
5826
|
await symlink2(target, temporaryLink, "file");
|
|
@@ -5868,7 +5868,7 @@ async function ensurePinnedToolchains(options2) {
|
|
|
5868
5868
|
const timings = { ...DEFAULT_TIMINGS, ...options2.timingsForTests };
|
|
5869
5869
|
const spawnProcess = options2.spawnForTests ?? spawn;
|
|
5870
5870
|
const ownerProcessExists = options2.processExistsForTests ?? ((pid) => processExists(pid, options2.processKillForTests));
|
|
5871
|
-
const rootDir =
|
|
5871
|
+
const rootDir = path16.join(options2.dataDir, TOOLCHAIN_DIRECTORY);
|
|
5872
5872
|
const totalTools = artifacts.length;
|
|
5873
5873
|
const toolFractions = new Map(artifacts.map((artifact) => [artifact.name, 0]));
|
|
5874
5874
|
const progress = (value) => {
|
|
@@ -6033,7 +6033,7 @@ var init_agent_platforms = __esm(() => {
|
|
|
6033
6033
|
|
|
6034
6034
|
// ../toolchain/src/platform-selection.ts
|
|
6035
6035
|
import { promises as fs7 } from "fs";
|
|
6036
|
-
import
|
|
6036
|
+
import path17 from "path";
|
|
6037
6037
|
async function readJsonFile(filePath) {
|
|
6038
6038
|
try {
|
|
6039
6039
|
return JSON.parse(await fs7.readFile(filePath, "utf8"));
|
|
@@ -6042,7 +6042,7 @@ async function readJsonFile(filePath) {
|
|
|
6042
6042
|
}
|
|
6043
6043
|
}
|
|
6044
6044
|
async function loadAgentPlatformSelection(dataDir) {
|
|
6045
|
-
const config = await readJsonFile(
|
|
6045
|
+
const config = await readJsonFile(path17.join(dataDir, AGENT_CONFIG_FILE));
|
|
6046
6046
|
if (config && typeof config === "object" && !Array.isArray(config)) {
|
|
6047
6047
|
const global2 = config.global;
|
|
6048
6048
|
if (global2 && typeof global2 === "object" && !Array.isArray(global2)) {
|
|
@@ -6058,7 +6058,7 @@ async function loadAgentPlatformSelection(dataDir) {
|
|
|
6058
6058
|
needsFirstRunChoice: false
|
|
6059
6059
|
};
|
|
6060
6060
|
}
|
|
6061
|
-
const sidecar = await readJsonFile(
|
|
6061
|
+
const sidecar = await readJsonFile(path17.join(dataDir, AGENT_PLATFORM_SELECTION_FILE));
|
|
6062
6062
|
if (sidecar && typeof sidecar === "object" && !Array.isArray(sidecar)) {
|
|
6063
6063
|
const enabled = normalizeAgentPlatforms(sidecar.enabled, []);
|
|
6064
6064
|
if (enabled.length > 0)
|
|
@@ -6074,7 +6074,7 @@ var init_platform_selection = __esm(() => {
|
|
|
6074
6074
|
// ../toolchain/src/install.ts
|
|
6075
6075
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
6076
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
|
|
6077
|
+
import path18 from "path";
|
|
6078
6078
|
async function resolveToolchainSelection(options2) {
|
|
6079
6079
|
const known = Object.keys(PINNED_TOOL_VERSIONS);
|
|
6080
6080
|
const withSupportTools = (chosen) => known.filter((name) => chosen.has(name) || isSupportToolName(name));
|
|
@@ -6133,16 +6133,16 @@ async function installPinnedToolchains(options2) {
|
|
|
6133
6133
|
};
|
|
6134
6134
|
}
|
|
6135
6135
|
async function toolsInCurrent(rootDir) {
|
|
6136
|
-
const entries = await readdir3(
|
|
6136
|
+
const entries = await readdir3(path18.join(rootDir, "bin", CURRENT_TOOLCHAIN_LINK)).catch(() => []);
|
|
6137
6137
|
return Object.keys(PINNED_TOOL_VERSIONS).filter((tool) => entries.includes(tool));
|
|
6138
6138
|
}
|
|
6139
6139
|
async function pointCurrentAt(rootDir, binDir) {
|
|
6140
|
-
const binRoot =
|
|
6141
|
-
if (
|
|
6140
|
+
const binRoot = path18.join(rootDir, "bin");
|
|
6141
|
+
if (path18.dirname(binDir) !== binRoot) {
|
|
6142
6142
|
throw new Error(`Activation directory ${binDir} is not directly inside ${binRoot}`);
|
|
6143
6143
|
}
|
|
6144
|
-
const link =
|
|
6145
|
-
const target =
|
|
6144
|
+
const link = path18.join(binRoot, CURRENT_TOOLCHAIN_LINK);
|
|
6145
|
+
const target = path18.basename(binDir);
|
|
6146
6146
|
await mkdir4(binRoot, { recursive: true, mode: 448 });
|
|
6147
6147
|
const existing = await lstat2(link).catch(() => null);
|
|
6148
6148
|
if (existing && !existing.isSymbolicLink()) {
|
|
@@ -6150,7 +6150,7 @@ async function pointCurrentAt(rootDir, binDir) {
|
|
|
6150
6150
|
}
|
|
6151
6151
|
if (existing && await readlink2(link) === target)
|
|
6152
6152
|
return link;
|
|
6153
|
-
const temporary =
|
|
6153
|
+
const temporary = path18.join(binRoot, `.${CURRENT_TOOLCHAIN_LINK}-${randomUUID5()}.tmp`);
|
|
6154
6154
|
await symlink3(target, temporary, "dir");
|
|
6155
6155
|
try {
|
|
6156
6156
|
await rename3(temporary, link);
|
|
@@ -6294,7 +6294,15 @@ var PUBLIC_ACTIONS = {
|
|
|
6294
6294
|
effects: ["metadata"],
|
|
6295
6295
|
observation: "receipt",
|
|
6296
6296
|
milestone: "B",
|
|
6297
|
-
summary: "Remove
|
|
6297
|
+
summary: "Remove a project registration (with cascade, after deleting its environments); never deletes checkouts or remotes."
|
|
6298
|
+
},
|
|
6299
|
+
"project.init": {
|
|
6300
|
+
version: 1,
|
|
6301
|
+
mutation: true,
|
|
6302
|
+
effects: ["metadata", "filesystem"],
|
|
6303
|
+
observation: "receipt",
|
|
6304
|
+
milestone: "B",
|
|
6305
|
+
summary: "Initialize a local-only Git repository (initial commit, no remote) at a backend path and register it."
|
|
6298
6306
|
},
|
|
6299
6307
|
"project.config.get": {
|
|
6300
6308
|
version: 1,
|
|
@@ -6459,7 +6467,23 @@ var PUBLIC_ACTIONS = {
|
|
|
6459
6467
|
effects: ["provider-session"],
|
|
6460
6468
|
observation: "run",
|
|
6461
6469
|
milestone: "C",
|
|
6462
|
-
summary: "Send a follow-up prompt to one explicit idle conversation."
|
|
6470
|
+
summary: "Send a follow-up prompt to one explicit idle conversation, or queue it behind a busy one."
|
|
6471
|
+
},
|
|
6472
|
+
"session.commands": {
|
|
6473
|
+
version: 1,
|
|
6474
|
+
mutation: false,
|
|
6475
|
+
effects: [],
|
|
6476
|
+
observation: "immediate",
|
|
6477
|
+
milestone: "C",
|
|
6478
|
+
summary: "List the session's slash-command catalogue (IDs, availability, argument policy)."
|
|
6479
|
+
},
|
|
6480
|
+
"session.command": {
|
|
6481
|
+
version: 1,
|
|
6482
|
+
mutation: true,
|
|
6483
|
+
effects: ["provider-session"],
|
|
6484
|
+
observation: "run",
|
|
6485
|
+
milestone: "C",
|
|
6486
|
+
summary: "Run one selected slash command; a stale or unavailable selection fails, never sends text."
|
|
6463
6487
|
},
|
|
6464
6488
|
"session.stop": {
|
|
6465
6489
|
version: 1,
|
|
@@ -6533,6 +6557,14 @@ var PUBLIC_ACTIONS = {
|
|
|
6533
6557
|
milestone: "C",
|
|
6534
6558
|
summary: "Answer one exact pending interaction at an expected revision."
|
|
6535
6559
|
},
|
|
6560
|
+
"session.interaction.hold": {
|
|
6561
|
+
version: 1,
|
|
6562
|
+
mutation: true,
|
|
6563
|
+
effects: ["provider-session"],
|
|
6564
|
+
observation: "receipt",
|
|
6565
|
+
milestone: "C",
|
|
6566
|
+
summary: "Pause or resume one pending interaction's countdown where its deadline is enforced."
|
|
6567
|
+
},
|
|
6536
6568
|
"session.transcript": {
|
|
6537
6569
|
version: 1,
|
|
6538
6570
|
mutation: false,
|
|
@@ -6557,6 +6589,70 @@ var PUBLIC_ACTIONS = {
|
|
|
6557
6589
|
milestone: "D",
|
|
6558
6590
|
summary: "Read one page of a session's turns, each with the backend's change projection."
|
|
6559
6591
|
},
|
|
6592
|
+
"review.launch": {
|
|
6593
|
+
version: 1,
|
|
6594
|
+
mutation: true,
|
|
6595
|
+
effects: ["metadata", "provider-session"],
|
|
6596
|
+
observation: "receipt",
|
|
6597
|
+
milestone: "D",
|
|
6598
|
+
summary: "Start an environment's Multi Review, as the Multi Review button does: reviewers, consolidation, then optional Fix and PR, with saved defaults for what is omitted; an active review is reused."
|
|
6599
|
+
},
|
|
6600
|
+
"review.list": {
|
|
6601
|
+
version: 1,
|
|
6602
|
+
mutation: false,
|
|
6603
|
+
effects: [],
|
|
6604
|
+
observation: "immediate",
|
|
6605
|
+
milestone: "D",
|
|
6606
|
+
summary: "List an environment's Multi Reviews, newest first."
|
|
6607
|
+
},
|
|
6608
|
+
"review.get": {
|
|
6609
|
+
version: 1,
|
|
6610
|
+
mutation: false,
|
|
6611
|
+
effects: [],
|
|
6612
|
+
observation: "immediate",
|
|
6613
|
+
milestone: "D",
|
|
6614
|
+
summary: "Read one Multi Review: phase, reviewers, consolidation, Fix and PR steps."
|
|
6615
|
+
},
|
|
6616
|
+
"review.open": {
|
|
6617
|
+
version: 1,
|
|
6618
|
+
mutation: true,
|
|
6619
|
+
effects: ["metadata"],
|
|
6620
|
+
observation: "receipt",
|
|
6621
|
+
milestone: "D",
|
|
6622
|
+
summary: "Open or focus a Multi Review's root tab or its Fix tab; starts no work."
|
|
6623
|
+
},
|
|
6624
|
+
"review.address": {
|
|
6625
|
+
version: 1,
|
|
6626
|
+
mutation: true,
|
|
6627
|
+
effects: ["provider-session"],
|
|
6628
|
+
observation: "receipt",
|
|
6629
|
+
milestone: "D",
|
|
6630
|
+
summary: "Address a Multi Review's findings: hand them to its Fix session (the Address findings button)."
|
|
6631
|
+
},
|
|
6632
|
+
"review.cancel": {
|
|
6633
|
+
version: 1,
|
|
6634
|
+
mutation: true,
|
|
6635
|
+
effects: ["process", "provider-session"],
|
|
6636
|
+
observation: "receipt",
|
|
6637
|
+
milestone: "D",
|
|
6638
|
+
summary: "Cancel a running Multi Review; its record and tabs are kept."
|
|
6639
|
+
},
|
|
6640
|
+
"pr.create": {
|
|
6641
|
+
version: 1,
|
|
6642
|
+
mutation: true,
|
|
6643
|
+
effects: ["provider-session", "external-repository"],
|
|
6644
|
+
observation: "run",
|
|
6645
|
+
milestone: "D",
|
|
6646
|
+
summary: "Start the Create PR agent session in an environment (the PR tab): commit, push and open a pull request against the target branch. It never merges."
|
|
6647
|
+
},
|
|
6648
|
+
"pr.resolve": {
|
|
6649
|
+
version: 1,
|
|
6650
|
+
mutation: true,
|
|
6651
|
+
effects: ["provider-session", "external-repository"],
|
|
6652
|
+
observation: "run",
|
|
6653
|
+
milestone: "D",
|
|
6654
|
+
summary: "Start the Resolve Conflicts agent session in an environment (the Resolve tab): merge the target branch, resolve and validate conflicts, commit and push."
|
|
6655
|
+
},
|
|
6560
6656
|
"attention.list": {
|
|
6561
6657
|
version: 1,
|
|
6562
6658
|
mutation: false,
|
|
@@ -6621,6 +6717,78 @@ var PUBLIC_ACTIONS = {
|
|
|
6621
6717
|
milestone: "D",
|
|
6622
6718
|
summary: "Place a message in one tab's inbox; external messages are never injected into a turn."
|
|
6623
6719
|
},
|
|
6720
|
+
"node.list": {
|
|
6721
|
+
version: 1,
|
|
6722
|
+
mutation: false,
|
|
6723
|
+
effects: [],
|
|
6724
|
+
observation: "immediate",
|
|
6725
|
+
milestone: "D",
|
|
6726
|
+
summary: "List this machine and the leaf nodes (remote machines) it controls, and whether each can run actions; pass its nodeId to any other action to run it there."
|
|
6727
|
+
},
|
|
6728
|
+
"schedule.list": {
|
|
6729
|
+
version: 1,
|
|
6730
|
+
mutation: false,
|
|
6731
|
+
effects: [],
|
|
6732
|
+
observation: "immediate",
|
|
6733
|
+
milestone: "D",
|
|
6734
|
+
summary: "List scheduled jobs with their next fire time, owner and last outcome."
|
|
6735
|
+
},
|
|
6736
|
+
"schedule.get": {
|
|
6737
|
+
version: 1,
|
|
6738
|
+
mutation: false,
|
|
6739
|
+
effects: [],
|
|
6740
|
+
observation: "immediate",
|
|
6741
|
+
milestone: "D",
|
|
6742
|
+
summary: "Read one scheduled job with its bounded fire history."
|
|
6743
|
+
},
|
|
6744
|
+
"schedule.create": {
|
|
6745
|
+
version: 1,
|
|
6746
|
+
mutation: true,
|
|
6747
|
+
effects: ["metadata"],
|
|
6748
|
+
observation: "receipt",
|
|
6749
|
+
milestone: "D",
|
|
6750
|
+
summary: "Schedule an action, a prompt to an agent tab or an environment launch, once or on a cron or interval."
|
|
6751
|
+
},
|
|
6752
|
+
"schedule.update": {
|
|
6753
|
+
version: 1,
|
|
6754
|
+
mutation: true,
|
|
6755
|
+
effects: ["metadata"],
|
|
6756
|
+
observation: "receipt",
|
|
6757
|
+
milestone: "D",
|
|
6758
|
+
summary: "Change a scheduled job's name, time, time zone, catch-up window or action."
|
|
6759
|
+
},
|
|
6760
|
+
"schedule.pause": {
|
|
6761
|
+
version: 1,
|
|
6762
|
+
mutation: true,
|
|
6763
|
+
effects: ["metadata"],
|
|
6764
|
+
observation: "receipt",
|
|
6765
|
+
milestone: "D",
|
|
6766
|
+
summary: "Pause a scheduled job; nothing fires until it is resumed."
|
|
6767
|
+
},
|
|
6768
|
+
"schedule.resume": {
|
|
6769
|
+
version: 1,
|
|
6770
|
+
mutation: true,
|
|
6771
|
+
effects: ["metadata"],
|
|
6772
|
+
observation: "receipt",
|
|
6773
|
+
milestone: "D",
|
|
6774
|
+
summary: "Resume a paused scheduled job from its next future slot."
|
|
6775
|
+
},
|
|
6776
|
+
"schedule.run": {
|
|
6777
|
+
version: 1,
|
|
6778
|
+
mutation: true,
|
|
6779
|
+
effects: ["metadata"],
|
|
6780
|
+
observation: "receipt",
|
|
6781
|
+
milestone: "D",
|
|
6782
|
+
summary: "Fire a scheduled job now, once, without changing its schedule."
|
|
6783
|
+
},
|
|
6784
|
+
"schedule.delete": {
|
|
6785
|
+
version: 1,
|
|
6786
|
+
mutation: true,
|
|
6787
|
+
effects: ["metadata"],
|
|
6788
|
+
observation: "receipt",
|
|
6789
|
+
milestone: "D",
|
|
6790
|
+
summary: "Delete a scheduled job and its history."
|
|
6791
|
+
},
|
|
6624
6792
|
"run.get": {
|
|
6625
6793
|
version: 1,
|
|
6626
6794
|
mutation: false,
|
|
@@ -6760,6 +6928,7 @@ var PUBLIC_ERROR_EXIT = {
|
|
|
6760
6928
|
"not-found": PUBLIC_EXIT.notFound,
|
|
6761
6929
|
"ambiguous-target": PUBLIC_EXIT.notFound,
|
|
6762
6930
|
"history-expired": PUBLIC_EXIT.notFound,
|
|
6931
|
+
"node-not-found": PUBLIC_EXIT.notFound,
|
|
6763
6932
|
"connection-failed": PUBLIC_EXIT.connection,
|
|
6764
6933
|
"connection-not-configured": PUBLIC_EXIT.connection,
|
|
6765
6934
|
"profile-unavailable": PUBLIC_EXIT.connection,
|
|
@@ -6767,11 +6936,13 @@ var PUBLIC_ERROR_EXIT = {
|
|
|
6767
6936
|
"identity-mismatch": PUBLIC_EXIT.connection,
|
|
6768
6937
|
"response-invalid": PUBLIC_EXIT.connection,
|
|
6769
6938
|
"transport-uncertain": PUBLIC_EXIT.connection,
|
|
6939
|
+
"node-unavailable": PUBLIC_EXIT.connection,
|
|
6770
6940
|
"deadline-exceeded": PUBLIC_EXIT.deadline,
|
|
6771
6941
|
"observation-interrupted": PUBLIC_SIGNAL_EXIT.SIGINT,
|
|
6772
6942
|
"interaction-required": PUBLIC_EXIT.interactionRequired,
|
|
6773
6943
|
"dispatch-unknown": PUBLIC_EXIT.unknownDispatch,
|
|
6774
6944
|
"run-unknown": PUBLIC_EXIT.unknownDispatch,
|
|
6945
|
+
"node-outcome-unknown": PUBLIC_EXIT.unknownDispatch,
|
|
6775
6946
|
conflict: PUBLIC_EXIT.conflict,
|
|
6776
6947
|
"request-conflict": PUBLIC_EXIT.conflict,
|
|
6777
6948
|
"revision-conflict": PUBLIC_EXIT.conflict,
|
|
@@ -6787,7 +6958,8 @@ var PUBLIC_ERROR_EXIT = {
|
|
|
6787
6958
|
"target-mismatch": PUBLIC_EXIT.conflict,
|
|
6788
6959
|
"session-idle": PUBLIC_EXIT.conflict,
|
|
6789
6960
|
"interaction-stale": PUBLIC_EXIT.conflict,
|
|
6790
|
-
"dispatch-parked": PUBLIC_EXIT.conflict
|
|
6961
|
+
"dispatch-parked": PUBLIC_EXIT.conflict,
|
|
6962
|
+
"node-unsupported": PUBLIC_EXIT.conflict
|
|
6791
6963
|
};
|
|
6792
6964
|
function isPublicErrorCode(value) {
|
|
6793
6965
|
return typeof value === "string" && Object.hasOwn(PUBLIC_ERROR_EXIT, value);
|
|
@@ -6820,6 +6992,10 @@ var PUBLIC_API_LIMITS = Object.freeze({
|
|
|
6820
6992
|
attentionMaxItems: 50,
|
|
6821
6993
|
responseMaxBytes: 8 * 1024 * 1024,
|
|
6822
6994
|
requestMaxBytes: 1024 * 1024,
|
|
6995
|
+
promptAttachmentsMaxItems: 8,
|
|
6996
|
+
promptAttachmentsMaxBytes: 256 * 1024,
|
|
6997
|
+
promptAttachmentNameMaxChars: 128,
|
|
6998
|
+
commandArgumentsMaxBytes: 64 * 1024,
|
|
6823
6999
|
execArgvMaxItems: 256,
|
|
6824
7000
|
execArgvMaxBytes: 256 * 1024,
|
|
6825
7001
|
execEnvMaxEntries: 64,
|
|
@@ -6858,6 +7034,17 @@ var TERMINAL_EXECUTION_PHASES = Object.freeze([
|
|
|
6858
7034
|
"cancelled",
|
|
6859
7035
|
"interrupted"
|
|
6860
7036
|
]);
|
|
7037
|
+
var PUBLIC_ALL_NODES = "all";
|
|
7038
|
+
var PUBLIC_AGGREGATE_ACTIONS = Object.freeze([
|
|
7039
|
+
"project.list",
|
|
7040
|
+
"environment.list",
|
|
7041
|
+
"attention.list"
|
|
7042
|
+
]);
|
|
7043
|
+
var PUBLIC_LOCAL_ONLY_ACTIONS = Object.freeze(["node.list"]);
|
|
7044
|
+
var NODE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/;
|
|
7045
|
+
function isPublicNodeTarget(value) {
|
|
7046
|
+
return typeof value === "string" && NODE_ID_PATTERN.test(value);
|
|
7047
|
+
}
|
|
6861
7048
|
function isPublicActionAvailable(capabilities, action) {
|
|
6862
7049
|
const entry = capabilities.actions[action];
|
|
6863
7050
|
return entry?.available === true && entry.version === PUBLIC_ACTIONS[action].version;
|
|
@@ -6878,7 +7065,7 @@ var OPERATION_STATES = [
|
|
|
6878
7065
|
function isPublicReceipt(value) {
|
|
6879
7066
|
if (!isRecord(value))
|
|
6880
7067
|
return false;
|
|
6881
|
-
return typeof value.operationId === "string" && value.operationId.length > 0 && typeof value.namespace === "string" && isPublicRequestId(value.requestId) && isPublicActionName(value.action) && OPERATION_STATES.includes(value.state) && typeof value.stage === "string" && typeof value.replayed === "boolean" && isRecord(value.resources) && typeof value.createdAt === "string" && typeof value.updatedAt === "string" && typeof value.retainedUntil === "string" && (value.dispatch === undefined || isRecord(value.dispatch)) && (value.execution === undefined || isRecord(value.execution));
|
|
7068
|
+
return typeof value.operationId === "string" && value.operationId.length > 0 && typeof value.namespace === "string" && isPublicRequestId(value.requestId) && isPublicActionName(value.action) && OPERATION_STATES.includes(value.state) && typeof value.stage === "string" && typeof value.replayed === "boolean" && isRecord(value.resources) && typeof value.createdAt === "string" && typeof value.updatedAt === "string" && typeof value.retainedUntil === "string" && (value.dispatch === undefined || isRecord(value.dispatch)) && (value.execution === undefined || isRecord(value.execution)) && (value.result === undefined || isRecord(value.result));
|
|
6882
7069
|
}
|
|
6883
7070
|
function isEnvelopeError(value) {
|
|
6884
7071
|
return isRecord(value) && isPublicErrorCode(value.code) && typeof value.message === "string" && typeof value.exitCode === "number" && (value.exitCode === exitCodeForError(value.code) || value.code === "observation-interrupted" && value.exitCode === PUBLIC_SIGNAL_EXIT.SIGTERM) && (value.details === undefined || isRecord(value.details));
|
|
@@ -6895,6 +7082,9 @@ function isPublicActionResponse(value) {
|
|
|
6895
7082
|
if (value.backend !== undefined && !(isRecord(value.backend) && typeof value.backend.installationId === "string" && typeof value.backend.generation === "string")) {
|
|
6896
7083
|
return false;
|
|
6897
7084
|
}
|
|
7085
|
+
if (value.node !== undefined && !(isRecord(value.node) && typeof value.node.nodeId === "string" && typeof value.node.name === "string")) {
|
|
7086
|
+
return false;
|
|
7087
|
+
}
|
|
6898
7088
|
if (value.ok === true) {
|
|
6899
7089
|
return Object.hasOwn(value, "result") && value.error === undefined && (value.warnings === undefined || Array.isArray(value.warnings) && value.warnings.every((w) => typeof w === "string"));
|
|
6900
7090
|
}
|
|
@@ -6939,6 +7129,7 @@ function parsePublicDuration(value) {
|
|
|
6939
7129
|
// src/client/errors.ts
|
|
6940
7130
|
class CliError extends Error {
|
|
6941
7131
|
code;
|
|
7132
|
+
node;
|
|
6942
7133
|
details;
|
|
6943
7134
|
receipt;
|
|
6944
7135
|
retryable;
|
|
@@ -6947,6 +7138,7 @@ class CliError extends Error {
|
|
|
6947
7138
|
super(message);
|
|
6948
7139
|
this.name = "CliError";
|
|
6949
7140
|
this.code = code;
|
|
7141
|
+
this.node = options2.node;
|
|
6950
7142
|
this.details = options2.details;
|
|
6951
7143
|
this.receipt = options2.receipt;
|
|
6952
7144
|
this.retryable = options2.retryable;
|
|
@@ -6968,6 +7160,12 @@ var GLOBAL_OPTIONS = [
|
|
|
6968
7160
|
valueName: "NAME",
|
|
6969
7161
|
description: "Use a running isolated development profile (`mise run dev:test`)."
|
|
6970
7162
|
},
|
|
7163
|
+
{
|
|
7164
|
+
name: "node",
|
|
7165
|
+
kind: "string",
|
|
7166
|
+
valueName: "NODE_ID|all",
|
|
7167
|
+
description: "Run on a leaf node the connected command node controls (`node list`); `all` for project list, environment list and attention across nodes."
|
|
7168
|
+
},
|
|
6971
7169
|
{ name: "json", kind: "boolean", description: "Print exactly one JSON envelope on stdout." },
|
|
6972
7170
|
{
|
|
6973
7171
|
name: "output",
|
|
@@ -7166,6 +7364,12 @@ function parseInvocation(tree, argv) {
|
|
|
7166
7364
|
}
|
|
7167
7365
|
if (typeof globalState.values.profile === "string")
|
|
7168
7366
|
global2.profile = globalState.values.profile;
|
|
7367
|
+
if (typeof globalState.values.node === "string") {
|
|
7368
|
+
if (!isPublicNodeTarget(globalState.values.node)) {
|
|
7369
|
+
throw new CliError("invalid-input", "--node expects a node ID from `node list`, or all");
|
|
7370
|
+
}
|
|
7371
|
+
global2.node = globalState.values.node;
|
|
7372
|
+
}
|
|
7169
7373
|
if (global2.connection && global2.profile) {
|
|
7170
7374
|
throw new CliError("invalid-input", "--connection and --profile select different backends; pass only one");
|
|
7171
7375
|
}
|
|
@@ -7510,7 +7714,7 @@ import path4 from "path";
|
|
|
7510
7714
|
// ../../apps/backend/package.json
|
|
7511
7715
|
var package_default = {
|
|
7512
7716
|
name: "@orkestrator/backend",
|
|
7513
|
-
version: "2.
|
|
7717
|
+
version: "2.32.0-beta",
|
|
7514
7718
|
private: true,
|
|
7515
7719
|
type: "module",
|
|
7516
7720
|
exports: {
|
|
@@ -7585,6 +7789,7 @@ var AGENT_NETWORK_DOMAINS_BY_PLATFORM = Object.freeze({
|
|
|
7585
7789
|
"cursor-cdn.com",
|
|
7586
7790
|
"cursor.com"
|
|
7587
7791
|
]),
|
|
7792
|
+
opencode: Object.freeze(["opencode.ai"]),
|
|
7588
7793
|
grok: Object.freeze([
|
|
7589
7794
|
"x.ai",
|
|
7590
7795
|
"auth.x.ai",
|
|
@@ -8232,7 +8437,7 @@ async function submitForWait(context, action, input, options2, timeoutMs) {
|
|
|
8232
8437
|
} catch (error) {
|
|
8233
8438
|
const requestId = error instanceof CliError ? error.details?.requestId : undefined;
|
|
8234
8439
|
const namespace = error instanceof CliError ? error.details?.namespace : undefined;
|
|
8235
|
-
if (!(error instanceof CliError) ||
|
|
8440
|
+
if (!(error instanceof CliError) || !["transport-uncertain", "node-outcome-unknown"].includes(error.code) || typeof requestId !== "string" || typeof namespace !== "string")
|
|
8236
8441
|
throw error;
|
|
8237
8442
|
let receipt;
|
|
8238
8443
|
try {
|
|
@@ -8405,6 +8610,11 @@ async function waitForEnvironment(context, session, environmentId, condition, re
|
|
|
8405
8610
|
|
|
8406
8611
|
// src/client/commands/attention.ts
|
|
8407
8612
|
function attentionLine(item) {
|
|
8613
|
+
const tagged = item;
|
|
8614
|
+
const prefix = tagged.nodeId ? `[${tagged.nodeName ?? tagged.nodeId} --node ${tagged.nodeId}] ` : "";
|
|
8615
|
+
return prefix + attentionDetail(item);
|
|
8616
|
+
}
|
|
8617
|
+
function attentionDetail(item) {
|
|
8408
8618
|
if (item.kind === "interaction") {
|
|
8409
8619
|
return `${item.blocking ? "waiting" : "pending"} ${item.agent} in ${item.environmentName}: ${item.title} (${item.sessionId} ${item.interactionId} rev ${item.revision})`;
|
|
8410
8620
|
}
|
|
@@ -8415,10 +8625,10 @@ function attentionLine(item) {
|
|
|
8415
8625
|
}
|
|
8416
8626
|
function itemKey(item) {
|
|
8417
8627
|
if (item.kind === "interaction")
|
|
8418
|
-
return
|
|
8628
|
+
return `${item.nodeId ?? ""}:interaction:${item.interactionId}:${item.revision}`;
|
|
8419
8629
|
if (item.kind === "setup")
|
|
8420
|
-
return
|
|
8421
|
-
return
|
|
8630
|
+
return `${item.nodeId ?? ""}:setup:${item.environmentId}:${item.at}`;
|
|
8631
|
+
return `${item.nodeId ?? ""}:run:${item.operationId}:${item.state}`;
|
|
8422
8632
|
}
|
|
8423
8633
|
var attentionCommands = [
|
|
8424
8634
|
{
|
|
@@ -8450,6 +8660,9 @@ var attentionCommands = [
|
|
|
8450
8660
|
input.cursor = parsed.options.cursor;
|
|
8451
8661
|
if (parsed.options.limit !== undefined)
|
|
8452
8662
|
input.limit = parsed.options.limit;
|
|
8663
|
+
if (parsed.options.follow === true && context.global.node === "all") {
|
|
8664
|
+
throw new CliError("invalid-input", "--follow follows one node; pass its --node");
|
|
8665
|
+
}
|
|
8453
8666
|
if (parsed.options.follow !== true) {
|
|
8454
8667
|
if (context.global.output === "jsonl") {
|
|
8455
8668
|
throw new CliError("invalid-input", "--jsonl needs --follow");
|
|
@@ -8462,7 +8675,7 @@ var attentionCommands = [
|
|
|
8462
8675
|
human: [
|
|
8463
8676
|
...page.items.length ? page.items.map(attentionLine) : ["Nothing needs attention."],
|
|
8464
8677
|
...page.more ? [`and ${page.more} more (raise --limit)`] : [],
|
|
8465
|
-
`cursor: ${page.cursor}`
|
|
8678
|
+
...page.cursor ? [`cursor: ${page.cursor}`] : (page.nodes ?? []).filter((node) => node.cursor).map((node) => `cursor (${node.name}): ${node.cursor}`)
|
|
8466
8679
|
]
|
|
8467
8680
|
};
|
|
8468
8681
|
}
|
|
@@ -8656,8 +8869,14 @@ function projectLines(project) {
|
|
|
8656
8869
|
["revision", project.revision]
|
|
8657
8870
|
]);
|
|
8658
8871
|
}
|
|
8872
|
+
function withNodeColumn(items, headers, rows) {
|
|
8873
|
+
const names = items.map((item) => item.nodeName);
|
|
8874
|
+
if (!names.some((name) => typeof name === "string"))
|
|
8875
|
+
return table(headers, rows);
|
|
8876
|
+
return table(["NODE", ...headers], rows.map((row, index) => [String(names[index] ?? ""), ...row]));
|
|
8877
|
+
}
|
|
8659
8878
|
function projectRows(projects) {
|
|
8660
|
-
return
|
|
8879
|
+
return withNodeColumn(projects, ["ID", "NAME", "ENVS", "PATH"], projects.map((project) => [
|
|
8661
8880
|
project.id,
|
|
8662
8881
|
project.name,
|
|
8663
8882
|
String(project.environmentCount),
|
|
@@ -8682,7 +8901,7 @@ function environmentLines(environment) {
|
|
|
8682
8901
|
]);
|
|
8683
8902
|
}
|
|
8684
8903
|
function environmentRows(environments) {
|
|
8685
|
-
return
|
|
8904
|
+
return withNodeColumn(environments, ["ID", "NAME", "TYPE", "STATUS", "READY"], environments.map((environment) => [
|
|
8686
8905
|
environment.id,
|
|
8687
8906
|
environment.name,
|
|
8688
8907
|
environment.environmentType,
|
|
@@ -8933,6 +9152,201 @@ var changesCommands = [
|
|
|
8933
9152
|
}
|
|
8934
9153
|
];
|
|
8935
9154
|
|
|
9155
|
+
// src/client/commands/completion.ts
|
|
9156
|
+
var SHELLS = ["bash", "zsh", "fish"];
|
|
9157
|
+
function flagsOf(options2) {
|
|
9158
|
+
return options2.flatMap((option) => [
|
|
9159
|
+
`--${option.name}`,
|
|
9160
|
+
...option.short ? [`-${option.short}`] : [],
|
|
9161
|
+
...option.negatable ? [`--no-${option.name}`] : []
|
|
9162
|
+
]);
|
|
9163
|
+
}
|
|
9164
|
+
function completionTable(commands) {
|
|
9165
|
+
const next = new Map;
|
|
9166
|
+
const flags = new Map;
|
|
9167
|
+
const values = new Map;
|
|
9168
|
+
const valueFlags = (options2) => flagsOf(options2.filter((option) => option.kind !== "boolean"));
|
|
9169
|
+
values.set("", valueFlags(GLOBAL_OPTIONS));
|
|
9170
|
+
const add = (prefix, word) => {
|
|
9171
|
+
const words = next.get(prefix) ?? [];
|
|
9172
|
+
if (!words.includes(word))
|
|
9173
|
+
words.push(word);
|
|
9174
|
+
next.set(prefix, words);
|
|
9175
|
+
};
|
|
9176
|
+
for (const command of commands) {
|
|
9177
|
+
command.path.forEach((word, index) => {
|
|
9178
|
+
const prefix = command.path.slice(0, index).join(" ");
|
|
9179
|
+
add(prefix, word);
|
|
9180
|
+
if (!values.has(prefix))
|
|
9181
|
+
values.set(prefix, valueFlags(GLOBAL_OPTIONS));
|
|
9182
|
+
});
|
|
9183
|
+
flags.set(command.path.join(" "), flagsOf(command.options));
|
|
9184
|
+
values.set(command.path.join(" "), [
|
|
9185
|
+
...valueFlags(GLOBAL_OPTIONS),
|
|
9186
|
+
...valueFlags(command.options)
|
|
9187
|
+
]);
|
|
9188
|
+
}
|
|
9189
|
+
add("", "help");
|
|
9190
|
+
add("", "version");
|
|
9191
|
+
return { next, flags, globals: flagsOf(GLOBAL_OPTIONS), values };
|
|
9192
|
+
}
|
|
9193
|
+
function words(list) {
|
|
9194
|
+
return list.join(" ");
|
|
9195
|
+
}
|
|
9196
|
+
function bashScript(table) {
|
|
9197
|
+
const cases = [...table.next].map(([prefix, list]) => ` "${prefix}") echo "${words(list)}" ;;`);
|
|
9198
|
+
const flagCases = [...table.flags].map(([path, list]) => ` "${path}") flags="${words(list)}" ;;`);
|
|
9199
|
+
return [
|
|
9200
|
+
"# orkestrator bash completion. Install: orkestrator completion bash > ~/.local/share/bash-completion/completions/orkestrator",
|
|
9201
|
+
"_orkestrator_next() {",
|
|
9202
|
+
' case "$1" in',
|
|
9203
|
+
...cases,
|
|
9204
|
+
" esac",
|
|
9205
|
+
"}",
|
|
9206
|
+
"_orkestrator_takes_value() {",
|
|
9207
|
+
' case "$1:$2" in',
|
|
9208
|
+
...[...table.values].filter(([, flags]) => flags.length).map(([path, flags]) => ` ${flags.map((flag) => `"${path}:${flag}"`).join("|")}) return 0 ;;`),
|
|
9209
|
+
" esac",
|
|
9210
|
+
" return 1",
|
|
9211
|
+
"}",
|
|
9212
|
+
"_orkestrator() {",
|
|
9213
|
+
' local cur="${COMP_WORDS[COMP_CWORD]}" command_path="" word i flags="" skip=0',
|
|
9214
|
+
" COMPREPLY=()",
|
|
9215
|
+
" for ((i = 1; i < COMP_CWORD; i++)); do",
|
|
9216
|
+
' word="${COMP_WORDS[i]}"',
|
|
9217
|
+
' if ((skip)); then [[ "$word" == "=" ]] || skip=0; continue; fi',
|
|
9218
|
+
' [[ "$word" == "--" ]] && return 0',
|
|
9219
|
+
' [[ "$word" == --*=* ]] && continue',
|
|
9220
|
+
' if _orkestrator_takes_value "$command_path" "$word"; then skip=1; continue; fi',
|
|
9221
|
+
' [[ "$word" == -* ]] && continue',
|
|
9222
|
+
' if [[ " $(_orkestrator_next "$command_path") " == *" $word "* ]]; then command_path="${command_path:+$command_path }$word"; fi',
|
|
9223
|
+
" done",
|
|
9224
|
+
" ((skip)) && return 0",
|
|
9225
|
+
' case "$command_path" in',
|
|
9226
|
+
...flagCases,
|
|
9227
|
+
" esac",
|
|
9228
|
+
" local candidates candidate",
|
|
9229
|
+
' if [[ "$cur" == -* ]]; then',
|
|
9230
|
+
` candidates="$flags ${words(table.globals)}"`,
|
|
9231
|
+
" else",
|
|
9232
|
+
' candidates="$(_orkestrator_next "$command_path")"',
|
|
9233
|
+
" fi",
|
|
9234
|
+
" for candidate in $candidates; do",
|
|
9235
|
+
' [[ "$candidate" == "$cur"* ]] && COMPREPLY+=("$candidate")',
|
|
9236
|
+
" done",
|
|
9237
|
+
" return 0",
|
|
9238
|
+
"}",
|
|
9239
|
+
"complete -o default -F _orkestrator orkestrator",
|
|
9240
|
+
""
|
|
9241
|
+
].join(`
|
|
9242
|
+
`);
|
|
9243
|
+
}
|
|
9244
|
+
function zshScript(table) {
|
|
9245
|
+
return [
|
|
9246
|
+
"#compdef orkestrator",
|
|
9247
|
+
'# orkestrator zsh completion (through bashcompinit). Install: orkestrator completion zsh > "${fpath[1]}/_orkestrator"',
|
|
9248
|
+
"autoload -U +X bashcompinit && bashcompinit",
|
|
9249
|
+
bashScript(table).split(`
|
|
9250
|
+
`).slice(1).join(`
|
|
9251
|
+
`).replace(`_orkestrator() {
|
|
9252
|
+
`, `_orkestrator() {
|
|
9253
|
+
emulate -L ksh
|
|
9254
|
+
`)
|
|
9255
|
+
].join(`
|
|
9256
|
+
`);
|
|
9257
|
+
}
|
|
9258
|
+
function fishScript(table) {
|
|
9259
|
+
const lines = [
|
|
9260
|
+
"# orkestrator fish completion. Install: orkestrator completion fish > ~/.config/fish/completions/orkestrator.fish",
|
|
9261
|
+
"function __orkestrator_next",
|
|
9262
|
+
" switch $argv[1]",
|
|
9263
|
+
...[...table.next].map(([prefix, list]) => ` case '${prefix}'
|
|
9264
|
+
echo '${words(list)}'`),
|
|
9265
|
+
" end",
|
|
9266
|
+
"end",
|
|
9267
|
+
"function __orkestrator_takes_value",
|
|
9268
|
+
' switch "$argv[1]:$argv[2]"',
|
|
9269
|
+
...[...table.values].filter(([, flags]) => flags.length).map(([path, flags]) => ` case ${flags.map((flag) => `'${path}:${flag}'`).join(" ")}
|
|
9270
|
+
return 0`),
|
|
9271
|
+
" end",
|
|
9272
|
+
" return 1",
|
|
9273
|
+
"end",
|
|
9274
|
+
"function __orkestrator_path",
|
|
9275
|
+
" set -l tokens (commandline -opc)",
|
|
9276
|
+
" set -e tokens[1]",
|
|
9277
|
+
" set -l path ''",
|
|
9278
|
+
" set -l skip 0",
|
|
9279
|
+
" for token in $tokens",
|
|
9280
|
+
" if test $skip = 1",
|
|
9281
|
+
" set skip 0",
|
|
9282
|
+
" continue",
|
|
9283
|
+
" end",
|
|
9284
|
+
' if test "$token" = "--"',
|
|
9285
|
+
" echo __rest",
|
|
9286
|
+
" return",
|
|
9287
|
+
" end",
|
|
9288
|
+
" string match -q -- '--*=*' $token; and continue",
|
|
9289
|
+
' if __orkestrator_takes_value "$path" "$token"',
|
|
9290
|
+
" set skip 1",
|
|
9291
|
+
" continue",
|
|
9292
|
+
" end",
|
|
9293
|
+
" string match -q -- '-*' $token; and continue",
|
|
9294
|
+
" if contains -- $token (string split ' ' -- (__orkestrator_next $path))",
|
|
9295
|
+
' set path (string trim -- "$path $token")',
|
|
9296
|
+
" end",
|
|
9297
|
+
" end",
|
|
9298
|
+
" if test $skip = 1",
|
|
9299
|
+
" echo __value",
|
|
9300
|
+
" return",
|
|
9301
|
+
" end",
|
|
9302
|
+
' echo "$path"',
|
|
9303
|
+
"end",
|
|
9304
|
+
"complete -c orkestrator -f"
|
|
9305
|
+
];
|
|
9306
|
+
for (const [prefix, list] of table.next) {
|
|
9307
|
+
lines.push(`complete -c orkestrator -n 'test (__orkestrator_path) = "${prefix}"' -a '${words(list)}'`);
|
|
9308
|
+
}
|
|
9309
|
+
for (const [path, list] of table.flags) {
|
|
9310
|
+
for (const flag of list) {
|
|
9311
|
+
lines.push(`complete -c orkestrator -n 'test (__orkestrator_path) = "${path}"' ${flag.startsWith("--") ? `-l '${flag.slice(2)}'` : `-s '${flag.slice(1)}'`}${table.values.get(path)?.includes(flag) ? " -r" : ""}`);
|
|
9312
|
+
}
|
|
9313
|
+
}
|
|
9314
|
+
for (const flag of table.globals) {
|
|
9315
|
+
lines.push(`complete -c orkestrator ${flag.startsWith("--") ? `-l '${flag.slice(2)}'` : `-s '${flag.slice(1)}'`}${table.values.get("")?.includes(flag) ? " -r" : ""}`);
|
|
9316
|
+
}
|
|
9317
|
+
return `${lines.join(`
|
|
9318
|
+
`)}
|
|
9319
|
+
`;
|
|
9320
|
+
}
|
|
9321
|
+
function completionScript(shell, commands) {
|
|
9322
|
+
const table = completionTable(commands);
|
|
9323
|
+
return shell === "bash" ? bashScript(table) : shell === "zsh" ? zshScript(table) : fishScript(table);
|
|
9324
|
+
}
|
|
9325
|
+
function completionCommands(commands) {
|
|
9326
|
+
return [
|
|
9327
|
+
{
|
|
9328
|
+
path: ["completion"],
|
|
9329
|
+
summary: "Print a shell completion script (bash, zsh or fish).",
|
|
9330
|
+
description: "Completes nouns, verbs and flags from this build's command table; it never contacts a backend, so IDs are not completed. Example: `orkestrator completion bash > ~/.local/share/bash-completion/completions/orkestrator`.",
|
|
9331
|
+
positionals: [{ name: "shell", required: true, description: "bash, zsh or fish." }],
|
|
9332
|
+
options: [],
|
|
9333
|
+
local: true,
|
|
9334
|
+
async run(_context, parsed) {
|
|
9335
|
+
const shell = parsed.positionals.shell;
|
|
9336
|
+
if (!SHELLS.includes(shell)) {
|
|
9337
|
+
throw new CliError("invalid-input", "Shell must be one of: bash, zsh, fish");
|
|
9338
|
+
}
|
|
9339
|
+
const script = completionScript(shell, commands());
|
|
9340
|
+
return {
|
|
9341
|
+
action: "completion",
|
|
9342
|
+
result: { shell, script },
|
|
9343
|
+
human: [script.replace(/\n$/, "")]
|
|
9344
|
+
};
|
|
9345
|
+
}
|
|
9346
|
+
}
|
|
9347
|
+
];
|
|
9348
|
+
}
|
|
9349
|
+
|
|
8936
9350
|
// src/client/commands/connection.ts
|
|
8937
9351
|
import path9 from "path";
|
|
8938
9352
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
@@ -9020,8 +9434,11 @@ import { promises as fs2 } from "fs";
|
|
|
9020
9434
|
import path6 from "path";
|
|
9021
9435
|
var MAX_LOCAL_RECEIPTS_PER_INSTALLATION = 1000;
|
|
9022
9436
|
var MAX_RECEIPT_BYTES = 8 * 1024;
|
|
9023
|
-
function receiptKey(action, requestId) {
|
|
9024
|
-
|
|
9437
|
+
function receiptKey(action, requestId, node) {
|
|
9438
|
+
const hash = createHash("sha256").update(action).update("\x00").update(requestId);
|
|
9439
|
+
if (node)
|
|
9440
|
+
hash.update("\x00node:").update(node);
|
|
9441
|
+
return hash.digest("hex");
|
|
9025
9442
|
}
|
|
9026
9443
|
function safeSegment(value) {
|
|
9027
9444
|
return /^[A-Za-z0-9._-]{1,200}$/.test(value) ? value : createHash("sha256").update(value).digest("hex");
|
|
@@ -9035,18 +9452,18 @@ class LocalReceiptStore {
|
|
|
9035
9452
|
directory(installationId) {
|
|
9036
9453
|
return path6.join(this.root, safeSegment(installationId));
|
|
9037
9454
|
}
|
|
9038
|
-
file(installationId, action, requestId) {
|
|
9039
|
-
return path6.join(this.directory(installationId), `${receiptKey(action, requestId)}.json`);
|
|
9455
|
+
file(installationId, action, requestId, node) {
|
|
9456
|
+
return path6.join(this.directory(installationId), `${receiptKey(action, requestId, node)}.json`);
|
|
9040
9457
|
}
|
|
9041
|
-
async find(installationId, action, requestId) {
|
|
9042
|
-
return this.readFile(this.file(installationId, action, requestId));
|
|
9458
|
+
async find(installationId, action, requestId, node) {
|
|
9459
|
+
return this.readFile(this.file(installationId, action, requestId, node));
|
|
9043
9460
|
}
|
|
9044
9461
|
async save(receipt) {
|
|
9045
9462
|
const text = `${JSON.stringify(receipt)}
|
|
9046
9463
|
`;
|
|
9047
9464
|
if (Buffer.byteLength(text) > MAX_RECEIPT_BYTES)
|
|
9048
9465
|
return;
|
|
9049
|
-
await writePrivateFile(this.file(receipt.installationId, receipt.action, receipt.requestId), text);
|
|
9466
|
+
await writePrivateFile(this.file(receipt.installationId, receipt.action, receipt.requestId, receipt.node), text);
|
|
9050
9467
|
await this.prune(receipt.installationId);
|
|
9051
9468
|
}
|
|
9052
9469
|
async update(receipt, remote) {
|
|
@@ -9081,7 +9498,7 @@ class LocalReceiptStore {
|
|
|
9081
9498
|
if (!stat.isFile() || stat.size > MAX_RECEIPT_BYTES)
|
|
9082
9499
|
return null;
|
|
9083
9500
|
const value = JSON.parse(await fs2.readFile(file, "utf8"));
|
|
9084
|
-
if (value.version !== 1 || typeof value.installationId !== "string" || typeof value.action !== "string" || !isPublicRequestId(value.requestId) || !isPublicNamespace(value.namespace) || typeof value.intentDigest !== "string" || typeof value.createdAt !== "string" || typeof value.updatedAt !== "string") {
|
|
9501
|
+
if (value.version !== 1 || typeof value.installationId !== "string" || value.node !== undefined && typeof value.node !== "string" || typeof value.action !== "string" || !isPublicRequestId(value.requestId) || !isPublicNamespace(value.namespace) || typeof value.intentDigest !== "string" || typeof value.createdAt !== "string" || typeof value.updatedAt !== "string") {
|
|
9085
9502
|
return null;
|
|
9086
9503
|
}
|
|
9087
9504
|
return value;
|
|
@@ -9263,6 +9680,14 @@ class ClientSession {
|
|
|
9263
9680
|
options;
|
|
9264
9681
|
capabilitiesPromise = null;
|
|
9265
9682
|
transport;
|
|
9683
|
+
readWarnings = new Set;
|
|
9684
|
+
responseNode;
|
|
9685
|
+
get warnings() {
|
|
9686
|
+
return [...this.readWarnings];
|
|
9687
|
+
}
|
|
9688
|
+
get nodeTag() {
|
|
9689
|
+
return this.responseNode ?? (this.options.node && this.options.node !== PUBLIC_ALL_NODES ? { nodeId: this.options.node, name: this.options.node } : undefined);
|
|
9690
|
+
}
|
|
9266
9691
|
constructor(target, receipts, options2 = {}) {
|
|
9267
9692
|
this.target = target;
|
|
9268
9693
|
this.receipts = receipts;
|
|
@@ -9272,6 +9697,27 @@ class ClientSession {
|
|
|
9272
9697
|
get identity() {
|
|
9273
9698
|
return this.target.identity;
|
|
9274
9699
|
}
|
|
9700
|
+
get node() {
|
|
9701
|
+
return this.options.node;
|
|
9702
|
+
}
|
|
9703
|
+
routingChecked = null;
|
|
9704
|
+
ensureNodeRouting() {
|
|
9705
|
+
this.routingChecked ??= (async () => {
|
|
9706
|
+
const response = await this.call("capabilities", {}, undefined, false, undefined, false);
|
|
9707
|
+
if (!response.ok)
|
|
9708
|
+
throw envelopeError(response);
|
|
9709
|
+
if (!isPublicCapabilities(response.result)) {
|
|
9710
|
+
throw new CliError("response-invalid", "The backend returned invalid capabilities");
|
|
9711
|
+
}
|
|
9712
|
+
if (response.result.features.nodeRouting !== true) {
|
|
9713
|
+
throw new CliError("backend-incompatible", "The connected backend cannot run actions on other nodes; upgrade it to use --node");
|
|
9714
|
+
}
|
|
9715
|
+
})().catch((error) => {
|
|
9716
|
+
this.routingChecked = null;
|
|
9717
|
+
throw error;
|
|
9718
|
+
});
|
|
9719
|
+
return this.routingChecked;
|
|
9720
|
+
}
|
|
9275
9721
|
capabilities() {
|
|
9276
9722
|
this.capabilitiesPromise ??= this.loadCapabilities().catch((error) => {
|
|
9277
9723
|
this.capabilitiesPromise = null;
|
|
@@ -9305,6 +9751,9 @@ class ClientSession {
|
|
|
9305
9751
|
async mutate(action, input, requestId) {
|
|
9306
9752
|
if (!PUBLIC_ACTIONS[action].mutation)
|
|
9307
9753
|
throw new Error(`${action} is not a mutation`);
|
|
9754
|
+
if (this.options.node === PUBLIC_ALL_NODES) {
|
|
9755
|
+
throw new CliError("invalid-input", "--node all is only for project list, environment list and attention; name one node");
|
|
9756
|
+
}
|
|
9308
9757
|
if (requestId !== undefined && !isPublicRequestId(requestId)) {
|
|
9309
9758
|
throw new CliError("invalid-input", "--request-id must be 1\u2013200 characters of letters, digits and . _ : @ / + = -, starting with a letter or digit");
|
|
9310
9759
|
}
|
|
@@ -9316,7 +9765,7 @@ class ClientSession {
|
|
|
9316
9765
|
const installationId = capabilities.backend.installationId;
|
|
9317
9766
|
const key = requestId ?? `cli-${randomUUID2()}`;
|
|
9318
9767
|
const intentDigest = createHash2("sha256").update(canonicalJson({ action, input })).digest("hex");
|
|
9319
|
-
const existing = await this.receipts.find(installationId, action, key);
|
|
9768
|
+
const existing = await this.receipts.find(installationId, action, key, this.options.node);
|
|
9320
9769
|
if (existing && existing.intentDigest !== intentDigest) {
|
|
9321
9770
|
throw new CliError("request-conflict", "This saved request ID was used for a different intent", {
|
|
9322
9771
|
action,
|
|
@@ -9329,6 +9778,7 @@ class ClientSession {
|
|
|
9329
9778
|
version: 1,
|
|
9330
9779
|
installationId,
|
|
9331
9780
|
connection: this.target.identity.name,
|
|
9781
|
+
...this.options.node ? { node: this.options.node } : {},
|
|
9332
9782
|
action,
|
|
9333
9783
|
requestId: key,
|
|
9334
9784
|
namespace,
|
|
@@ -9345,10 +9795,12 @@ class ClientSession {
|
|
|
9345
9795
|
if (error instanceof CliError && error.code === "transport-uncertain") {
|
|
9346
9796
|
throw new CliError(error.code, error.message, {
|
|
9347
9797
|
action,
|
|
9798
|
+
node: this.nodeTag,
|
|
9348
9799
|
details: {
|
|
9349
9800
|
requestId: key,
|
|
9350
9801
|
namespace,
|
|
9351
|
-
|
|
9802
|
+
...this.nodeTag ? { node: this.nodeTag } : {},
|
|
9803
|
+
recovery: `orkestrator run get --request-id ${key} --action ${action}${this.options.node ? ` --node ${quoteArgument(this.options.node)}` : ""}`
|
|
9352
9804
|
}
|
|
9353
9805
|
});
|
|
9354
9806
|
}
|
|
@@ -9358,27 +9810,56 @@ class ClientSession {
|
|
|
9358
9810
|
await this.receipts.update(local, response.receipt).catch(() => {
|
|
9359
9811
|
return;
|
|
9360
9812
|
});
|
|
9361
|
-
if (!response.ok)
|
|
9813
|
+
if (!response.ok) {
|
|
9814
|
+
if (response.error.code === "node-outcome-unknown") {
|
|
9815
|
+
throw new CliError(response.error.code, response.error.message, {
|
|
9816
|
+
action,
|
|
9817
|
+
node: this.nodeTag,
|
|
9818
|
+
retryable: response.error.retryable,
|
|
9819
|
+
details: {
|
|
9820
|
+
...response.error.details,
|
|
9821
|
+
requestId: key,
|
|
9822
|
+
namespace,
|
|
9823
|
+
recovery: `orkestrator run get --request-id ${key} --action ${action}${this.options.node ? ` --node ${quoteArgument(this.options.node)}` : ""}`
|
|
9824
|
+
}
|
|
9825
|
+
});
|
|
9826
|
+
}
|
|
9362
9827
|
throw envelopeError(response);
|
|
9828
|
+
}
|
|
9363
9829
|
return {
|
|
9364
9830
|
result: response.result,
|
|
9831
|
+
...this.nodeTag ? { node: this.nodeTag } : {},
|
|
9365
9832
|
...response.receipt ? { receipt: response.receipt } : {},
|
|
9366
9833
|
...response.warnings ? { warnings: response.warnings } : {}
|
|
9367
9834
|
};
|
|
9368
9835
|
}
|
|
9369
|
-
async call(action, input, request, mutation, timeoutMs) {
|
|
9836
|
+
async call(action, input, request, mutation, timeoutMs, routed = true) {
|
|
9837
|
+
const node = routed && this.options.node && !PUBLIC_LOCAL_ONLY_ACTIONS.includes(action) ? this.options.node : undefined;
|
|
9838
|
+
if (node)
|
|
9839
|
+
await this.ensureNodeRouting();
|
|
9370
9840
|
const body = {
|
|
9371
9841
|
schemaVersion: PUBLIC_API_SCHEMA_VERSION,
|
|
9372
9842
|
action,
|
|
9373
9843
|
actionVersion: PUBLIC_ACTIONS[action].version,
|
|
9374
9844
|
input,
|
|
9375
|
-
...request ? { request } : {}
|
|
9845
|
+
...request ? { request } : {},
|
|
9846
|
+
...node && !(node === PUBLIC_ALL_NODES && action === "capabilities") ? { node } : {}
|
|
9376
9847
|
};
|
|
9377
9848
|
const raw = await this.transport.invoke(PUBLIC_ACTION_COMMAND, body, { mutation, signal: this.options.signal, timeoutMs });
|
|
9378
9849
|
if (!isPublicActionResponse(raw)) {
|
|
9379
9850
|
throw new CliError(mutation ? "transport-uncertain" : "response-invalid", "The backend returned a response that does not match the public contract");
|
|
9380
9851
|
}
|
|
9381
9852
|
await this.verifyIdentity(raw);
|
|
9853
|
+
if (raw.node)
|
|
9854
|
+
this.responseNode = raw.node;
|
|
9855
|
+
if (!mutation && raw.ok) {
|
|
9856
|
+
for (const warning of raw.warnings ?? [])
|
|
9857
|
+
this.readWarnings.add(warning);
|
|
9858
|
+
const nodes = raw.result?.nodes;
|
|
9859
|
+
for (const failed of nodes?.filter((entry) => !entry.ok) ?? []) {
|
|
9860
|
+
this.readWarnings.add(`${failed.name} (${failed.nodeId}): ${failed.error?.message ?? "not checked"}`);
|
|
9861
|
+
}
|
|
9862
|
+
}
|
|
9382
9863
|
return raw;
|
|
9383
9864
|
}
|
|
9384
9865
|
async verifyIdentity(response) {
|
|
@@ -9397,11 +9878,15 @@ class ClientSession {
|
|
|
9397
9878
|
function envelopeError(response) {
|
|
9398
9879
|
return new CliError(response.error.code, response.error.message, {
|
|
9399
9880
|
action: response.action,
|
|
9881
|
+
...response.node ? { node: response.node } : {},
|
|
9400
9882
|
...response.error.details ? { details: response.error.details } : {},
|
|
9401
9883
|
...response.receipt ? { receipt: response.receipt } : {},
|
|
9402
9884
|
...response.error.retryable !== undefined ? { retryable: response.error.retryable } : {}
|
|
9403
9885
|
});
|
|
9404
9886
|
}
|
|
9887
|
+
function quoteArgument(value) {
|
|
9888
|
+
return /^[A-Za-z0-9_.:@/+=-]+$/.test(value) ? value : "'" + value.replaceAll("'", `'"'"'`) + "'";
|
|
9889
|
+
}
|
|
9405
9890
|
|
|
9406
9891
|
// src/client/targets.ts
|
|
9407
9892
|
import { promises as fs3 } from "fs";
|
|
@@ -10819,7 +11304,8 @@ function hostLines(host) {
|
|
|
10819
11304
|
}
|
|
10820
11305
|
if (host.impact) {
|
|
10821
11306
|
const impact = host.impact;
|
|
10822
|
-
|
|
11307
|
+
const inContainers = impact.containerTerminals !== undefined ? ` (${impact.containerTerminals} in containers)` : "";
|
|
11308
|
+
lines.push(`Running: ${impact.workingAgents} agents working, ${impact.waitingAgents} waiting, ${impact.activePipelines} pipelines, ${impact.terminals} terminals${inContainers}`);
|
|
10823
11309
|
}
|
|
10824
11310
|
if (host.tools?.length) {
|
|
10825
11311
|
lines.push("Tools:");
|
|
@@ -11113,63 +11599,532 @@ var hostCommands = [
|
|
|
11113
11599
|
})
|
|
11114
11600
|
];
|
|
11115
11601
|
|
|
11116
|
-
// src/
|
|
11117
|
-
var
|
|
11118
|
-
|
|
11119
|
-
|
|
11120
|
-
|
|
11121
|
-
|
|
11122
|
-
|
|
11123
|
-
|
|
11124
|
-
|
|
11125
|
-
|
|
11126
|
-
|
|
11127
|
-
|
|
11128
|
-
|
|
11129
|
-
|
|
11130
|
-
|
|
11131
|
-
|
|
11132
|
-
|
|
11133
|
-
|
|
11134
|
-
|
|
11135
|
-
|
|
11136
|
-
|
|
11137
|
-
|
|
11138
|
-
|
|
11139
|
-
|
|
11140
|
-
|
|
11141
|
-
|
|
11142
|
-
|
|
11143
|
-
|
|
11144
|
-
|
|
11145
|
-
|
|
11602
|
+
// ../protocol/src/schedule-time.ts
|
|
11603
|
+
var CRON_SEARCH_DAYS = 366 * 5;
|
|
11604
|
+
var formatters = new Map;
|
|
11605
|
+
|
|
11606
|
+
// ../protocol/src/scheduled-jobs.ts
|
|
11607
|
+
var SCHEDULED_JOB_LIMITS = Object.freeze({
|
|
11608
|
+
maxSchedules: 1000,
|
|
11609
|
+
maxPerOwner: 50,
|
|
11610
|
+
historyEntries: 20,
|
|
11611
|
+
nameMaxChars: 120,
|
|
11612
|
+
promptMaxChars: 1e5,
|
|
11613
|
+
minIntervalSeconds: 60,
|
|
11614
|
+
maxIntervalSeconds: 366 * 24 * 60 * 60,
|
|
11615
|
+
defaultCatchUpMinutes: 15,
|
|
11616
|
+
maxCatchUpMinutes: 7 * 24 * 60,
|
|
11617
|
+
onTimeToleranceMs: 60000,
|
|
11618
|
+
actionInputMaxBytes: 64 * 1024,
|
|
11619
|
+
busyRetryWindowMs: 15 * 60000,
|
|
11620
|
+
busyRetryDelayMs: 30000,
|
|
11621
|
+
fireStaleMs: 90000,
|
|
11622
|
+
fireHeartbeatMs: 20000,
|
|
11623
|
+
maxFireAttempts: 3
|
|
11624
|
+
});
|
|
11625
|
+
function describeScheduleTrigger(trigger, timeZone) {
|
|
11626
|
+
switch (trigger.kind) {
|
|
11627
|
+
case "once":
|
|
11628
|
+
return `once at ${trigger.at}`;
|
|
11629
|
+
case "cron":
|
|
11630
|
+
return `cron "${trigger.expression}" (${timeZone})`;
|
|
11631
|
+
case "interval": {
|
|
11632
|
+
const seconds = trigger.everySeconds;
|
|
11633
|
+
const text = seconds % 86400 === 0 ? `${seconds / 86400}d` : seconds % 3600 === 0 ? `${seconds / 3600}h` : seconds % 60 === 0 ? `${seconds / 60}m` : `${seconds}s`;
|
|
11634
|
+
return `every ${text}`;
|
|
11146
11635
|
}
|
|
11636
|
+
}
|
|
11637
|
+
}
|
|
11638
|
+
|
|
11639
|
+
// src/client/commands/schedule.ts
|
|
11640
|
+
var SCHEDULE_ID = { name: "schedule", required: true, description: "Schedule ID (sch_...)." };
|
|
11641
|
+
var TIMING_OPTIONS = [
|
|
11642
|
+
{
|
|
11643
|
+
name: "at",
|
|
11644
|
+
kind: "string",
|
|
11645
|
+
valueName: "TIME",
|
|
11646
|
+
description: "Once: HH:MM (next occurrence), YYYY-MM-DDTHH:MM, an ISO time with an offset, or +15m."
|
|
11147
11647
|
},
|
|
11148
11648
|
{
|
|
11149
|
-
|
|
11150
|
-
|
|
11151
|
-
|
|
11152
|
-
|
|
11153
|
-
|
|
11154
|
-
|
|
11155
|
-
|
|
11156
|
-
|
|
11157
|
-
|
|
11158
|
-
|
|
11159
|
-
|
|
11160
|
-
|
|
11161
|
-
|
|
11162
|
-
|
|
11163
|
-
|
|
11164
|
-
|
|
11165
|
-
|
|
11166
|
-
|
|
11167
|
-
|
|
11168
|
-
|
|
11169
|
-
|
|
11170
|
-
|
|
11171
|
-
|
|
11172
|
-
|
|
11649
|
+
name: "cron",
|
|
11650
|
+
kind: "string",
|
|
11651
|
+
valueName: "EXPR",
|
|
11652
|
+
description: 'Recurring: five-field cron, e.g. "0 9 * * 1-5", or @daily.'
|
|
11653
|
+
},
|
|
11654
|
+
{
|
|
11655
|
+
name: "every",
|
|
11656
|
+
kind: "duration",
|
|
11657
|
+
valueName: "DURATION",
|
|
11658
|
+
description: "Recurring: a fixed interval (at least 1m)."
|
|
11659
|
+
},
|
|
11660
|
+
{
|
|
11661
|
+
name: "start-at",
|
|
11662
|
+
kind: "string",
|
|
11663
|
+
valueName: "TIME",
|
|
11664
|
+
description: "With --every: the first fire."
|
|
11665
|
+
},
|
|
11666
|
+
{
|
|
11667
|
+
name: "time-zone",
|
|
11668
|
+
kind: "string",
|
|
11669
|
+
valueName: "ZONE",
|
|
11670
|
+
description: "IANA zone for --at and --cron, e.g. Europe/London. Default: the backend host's."
|
|
11671
|
+
},
|
|
11672
|
+
{
|
|
11673
|
+
name: "catch-up-minutes",
|
|
11674
|
+
kind: "integer",
|
|
11675
|
+
valueName: "N",
|
|
11676
|
+
description: "Run a fire missed while the backend was down if at most N minutes late (default 15)."
|
|
11677
|
+
},
|
|
11678
|
+
{ name: "name", kind: "string", valueName: "TEXT", description: "Label shown in lists." }
|
|
11679
|
+
];
|
|
11680
|
+
var ACTION_OPTIONS = [
|
|
11681
|
+
{
|
|
11682
|
+
name: "mail",
|
|
11683
|
+
kind: "string",
|
|
11684
|
+
valueName: "ENV:TAB",
|
|
11685
|
+
description: "Action: send agent mail (mail.send) to this mailbox."
|
|
11686
|
+
},
|
|
11687
|
+
{ name: "subject", kind: "string", valueName: "TEXT", description: "With --mail: subject." },
|
|
11688
|
+
{ name: "body", kind: "string", valueName: "TEXT", description: "With --mail: message text." },
|
|
11689
|
+
{
|
|
11690
|
+
name: "body-file",
|
|
11691
|
+
kind: "string",
|
|
11692
|
+
valueName: "PATH",
|
|
11693
|
+
description: "With --mail: read the text from a file."
|
|
11694
|
+
},
|
|
11695
|
+
{
|
|
11696
|
+
name: "session",
|
|
11697
|
+
kind: "string",
|
|
11698
|
+
valueName: "SESSION",
|
|
11699
|
+
description: "Action: prompt this idle session (session.prompt); give the prompt with --prompt\u2026"
|
|
11700
|
+
},
|
|
11701
|
+
{
|
|
11702
|
+
name: "launch-project",
|
|
11703
|
+
kind: "string",
|
|
11704
|
+
valueName: "PROJECT_ID",
|
|
11705
|
+
description: "Action: launch an environment in this project (environment.launch)."
|
|
11706
|
+
},
|
|
11707
|
+
{
|
|
11708
|
+
name: "agent",
|
|
11709
|
+
kind: "string",
|
|
11710
|
+
valueName: "AGENT",
|
|
11711
|
+
description: "With --launch-project: agent platform."
|
|
11712
|
+
},
|
|
11713
|
+
{
|
|
11714
|
+
name: "type",
|
|
11715
|
+
kind: "string",
|
|
11716
|
+
valueName: "TYPE",
|
|
11717
|
+
description: "With --launch-project: local (default) or container."
|
|
11718
|
+
},
|
|
11719
|
+
{
|
|
11720
|
+
name: "model",
|
|
11721
|
+
kind: "string",
|
|
11722
|
+
valueName: "MODEL",
|
|
11723
|
+
description: "With --launch-project: model ID."
|
|
11724
|
+
},
|
|
11725
|
+
{
|
|
11726
|
+
name: "base-branch",
|
|
11727
|
+
kind: "string",
|
|
11728
|
+
valueName: "BRANCH",
|
|
11729
|
+
description: "With --launch-project: base branch."
|
|
11730
|
+
},
|
|
11731
|
+
{
|
|
11732
|
+
name: "base-commit",
|
|
11733
|
+
kind: "string",
|
|
11734
|
+
valueName: "SHA",
|
|
11735
|
+
description: "With --launch-project: pin this commit; omitted, the branch's newest commit at fire time."
|
|
11736
|
+
},
|
|
11737
|
+
{
|
|
11738
|
+
name: "mode",
|
|
11739
|
+
kind: "string",
|
|
11740
|
+
valueName: "MODE",
|
|
11741
|
+
description: "With --session or --launch-project: plan or build."
|
|
11742
|
+
},
|
|
11743
|
+
{
|
|
11744
|
+
name: "action",
|
|
11745
|
+
kind: "string",
|
|
11746
|
+
valueName: "ACTION",
|
|
11747
|
+
description: "Action: any public mutation by name (e.g. ticket.comment) with --input-json."
|
|
11748
|
+
},
|
|
11749
|
+
{
|
|
11750
|
+
name: "input-json",
|
|
11751
|
+
kind: "string",
|
|
11752
|
+
valueName: "JSON",
|
|
11753
|
+
description: "With --action: its input object."
|
|
11754
|
+
},
|
|
11755
|
+
...PROMPT_OPTIONS
|
|
11756
|
+
];
|
|
11757
|
+
var ACTION_INPUT_FIELDS = Object.fromEntries(ACTION_OPTIONS.map((option) => [option.name, "action"]));
|
|
11758
|
+
function timingInput(options2) {
|
|
11759
|
+
const every = options2.every;
|
|
11760
|
+
if (typeof every === "number" && every % 1000 !== 0) {
|
|
11761
|
+
throw new CliError("invalid-input", "--every must be whole seconds");
|
|
11762
|
+
}
|
|
11763
|
+
const at = stringOption(options2, "at");
|
|
11764
|
+
const cron = stringOption(options2, "cron");
|
|
11765
|
+
const startAt = stringOption(options2, "start-at");
|
|
11766
|
+
const timeZone = stringOption(options2, "time-zone");
|
|
11767
|
+
const name = stringOption(options2, "name");
|
|
11768
|
+
return {
|
|
11769
|
+
...at !== undefined ? { at } : {},
|
|
11770
|
+
...cron !== undefined ? { cron } : {},
|
|
11771
|
+
...typeof every === "number" ? { everySeconds: every / 1000 } : {},
|
|
11772
|
+
...startAt !== undefined ? { startAt } : {},
|
|
11773
|
+
...timeZone !== undefined ? { timeZone } : {},
|
|
11774
|
+
...typeof options2["catch-up-minutes"] === "number" ? { catchUpMinutes: options2["catch-up-minutes"] } : {},
|
|
11775
|
+
...name !== undefined ? { name } : {}
|
|
11776
|
+
};
|
|
11777
|
+
}
|
|
11778
|
+
async function actionInput(io, options2) {
|
|
11779
|
+
const chosen = ["mail", "session", "launch-project", "action"].filter((name) => options2[name] !== undefined);
|
|
11780
|
+
if (chosen.length === 0)
|
|
11781
|
+
return;
|
|
11782
|
+
if (chosen.length > 1) {
|
|
11783
|
+
throw new CliError("invalid-input", `--${chosen.join(", --")} are mutually exclusive`);
|
|
11784
|
+
}
|
|
11785
|
+
const prompt = () => readTextSource(io, {
|
|
11786
|
+
file: options2["prompt-file"],
|
|
11787
|
+
stdin: options2["prompt-stdin"],
|
|
11788
|
+
inline: options2.prompt,
|
|
11789
|
+
label: "Prompt",
|
|
11790
|
+
maxBytes: 400000,
|
|
11791
|
+
names: { file: "--prompt-file", stdin: "--prompt-stdin", inline: "--prompt" },
|
|
11792
|
+
required: true
|
|
11793
|
+
});
|
|
11794
|
+
const mode = options2.mode !== undefined ? { mode: oneOf(options2.mode, ["plan", "build"], "--mode") } : {};
|
|
11795
|
+
switch (chosen[0]) {
|
|
11796
|
+
case "mail": {
|
|
11797
|
+
const [environmentId, tabId, extra] = String(options2.mail).split(":");
|
|
11798
|
+
if (!environmentId || !tabId || extra !== undefined) {
|
|
11799
|
+
throw new CliError("invalid-input", "--mail expects ENVIRONMENT_ID:TAB_ID");
|
|
11800
|
+
}
|
|
11801
|
+
const body = await readTextSource(io, {
|
|
11802
|
+
file: options2["body-file"],
|
|
11803
|
+
inline: options2.body,
|
|
11804
|
+
label: "Message",
|
|
11805
|
+
maxBytes: 32 * 1024,
|
|
11806
|
+
names: { file: "--body-file", stdin: "--body-stdin", inline: "--body" },
|
|
11807
|
+
required: true
|
|
11808
|
+
});
|
|
11809
|
+
const subject = stringOption(options2, "subject");
|
|
11810
|
+
return {
|
|
11811
|
+
kind: "action",
|
|
11812
|
+
action: "mail.send",
|
|
11813
|
+
input: {
|
|
11814
|
+
toEnvironmentId: environmentId,
|
|
11815
|
+
toTabId: tabId,
|
|
11816
|
+
body,
|
|
11817
|
+
...subject ? { subject } : {}
|
|
11818
|
+
}
|
|
11819
|
+
};
|
|
11820
|
+
}
|
|
11821
|
+
case "session":
|
|
11822
|
+
return { kind: "prompt", sessionId: options2.session, prompt: await prompt(), ...mode };
|
|
11823
|
+
case "launch-project": {
|
|
11824
|
+
const agent = stringOption(options2, "agent");
|
|
11825
|
+
if (!agent)
|
|
11826
|
+
throw new CliError("invalid-input", "--launch-project needs --agent");
|
|
11827
|
+
const type = options2.type === undefined ? "local" : oneOf(options2.type, ["local", "container"], "--type");
|
|
11828
|
+
const model = stringOption(options2, "model");
|
|
11829
|
+
const baseBranch = stringOption(options2, "base-branch");
|
|
11830
|
+
const baseCommit = stringOption(options2, "base-commit");
|
|
11831
|
+
return {
|
|
11832
|
+
kind: "launch",
|
|
11833
|
+
projectId: options2["launch-project"],
|
|
11834
|
+
agent,
|
|
11835
|
+
type,
|
|
11836
|
+
prompt: await prompt(),
|
|
11837
|
+
...mode,
|
|
11838
|
+
...model ? { model } : {},
|
|
11839
|
+
...baseBranch ? { baseBranch } : {},
|
|
11840
|
+
...baseCommit ? { baseCommit } : {}
|
|
11841
|
+
};
|
|
11842
|
+
}
|
|
11843
|
+
default: {
|
|
11844
|
+
let input = {};
|
|
11845
|
+
const raw = stringOption(options2, "input-json");
|
|
11846
|
+
if (raw !== undefined) {
|
|
11847
|
+
try {
|
|
11848
|
+
input = JSON.parse(raw);
|
|
11849
|
+
} catch {
|
|
11850
|
+
throw new CliError("invalid-input", "--input-json is not valid JSON");
|
|
11851
|
+
}
|
|
11852
|
+
}
|
|
11853
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
11854
|
+
throw new CliError("invalid-input", "--input-json must be a JSON object");
|
|
11855
|
+
}
|
|
11856
|
+
return { kind: "action", action: options2.action, input };
|
|
11857
|
+
}
|
|
11858
|
+
}
|
|
11859
|
+
}
|
|
11860
|
+
function lastOutcome(job) {
|
|
11861
|
+
const fire = job.activeFire ?? job.lastFire;
|
|
11862
|
+
if (!fire)
|
|
11863
|
+
return "-";
|
|
11864
|
+
return fire.error ? `${fire.state}: ${fire.error.code}` : fire.state;
|
|
11865
|
+
}
|
|
11866
|
+
function scheduleLines(job) {
|
|
11867
|
+
return [
|
|
11868
|
+
...keyValues([
|
|
11869
|
+
["id", job.id],
|
|
11870
|
+
["name", job.name],
|
|
11871
|
+
["owner", job.ownerLabel],
|
|
11872
|
+
["state", job.pausedReason ? `${job.state} (${job.pausedReason})` : job.state],
|
|
11873
|
+
["when", describeScheduleTrigger(job.trigger, job.timeZone)],
|
|
11874
|
+
["time zone", job.timeZone],
|
|
11875
|
+
["next fire", job.nextFireAt ?? "-"],
|
|
11876
|
+
["catch-up", `${job.catchUpMinutes} min`],
|
|
11877
|
+
["action", job.action.kind === "action" ? job.action.action : job.action.kind]
|
|
11878
|
+
]),
|
|
11879
|
+
"",
|
|
11880
|
+
...table(["FIRE", "SLOT", "STATE", "ERROR"], [...job.fires].reverse().map((fire) => [
|
|
11881
|
+
fire.fireId,
|
|
11882
|
+
fire.scheduledFor,
|
|
11883
|
+
fire.state,
|
|
11884
|
+
fire.error ? `${fire.error.code}: ${fire.error.message}` : ""
|
|
11885
|
+
]))
|
|
11886
|
+
];
|
|
11887
|
+
}
|
|
11888
|
+
function simpleMutation(verb, summary) {
|
|
11889
|
+
const action = `schedule.${verb}`;
|
|
11890
|
+
return {
|
|
11891
|
+
path: ["schedule", verb],
|
|
11892
|
+
action,
|
|
11893
|
+
summary,
|
|
11894
|
+
positionals: [SCHEDULE_ID],
|
|
11895
|
+
options: [REQUEST_ID_OPTION],
|
|
11896
|
+
inputFields: { schedule: "scheduleId" },
|
|
11897
|
+
idOutput: "schedule ID",
|
|
11898
|
+
async run(context, parsed) {
|
|
11899
|
+
const { session, result, receipt, warnings } = await submit(context, action, { scheduleId: parsed.positionals.schedule }, parsed.options);
|
|
11900
|
+
return {
|
|
11901
|
+
action,
|
|
11902
|
+
result,
|
|
11903
|
+
receipt,
|
|
11904
|
+
warnings,
|
|
11905
|
+
connection: session.identity,
|
|
11906
|
+
ids: [String(parsed.positionals.schedule)],
|
|
11907
|
+
human: keyValues(Object.entries(result))
|
|
11908
|
+
};
|
|
11909
|
+
}
|
|
11910
|
+
};
|
|
11911
|
+
}
|
|
11912
|
+
var scheduleCommands = [
|
|
11913
|
+
{
|
|
11914
|
+
path: ["schedule", "list"],
|
|
11915
|
+
action: "schedule.list",
|
|
11916
|
+
summary: "List scheduled jobs with their next fire, owner and last outcome.",
|
|
11917
|
+
positionals: [],
|
|
11918
|
+
options: [
|
|
11919
|
+
{
|
|
11920
|
+
name: "project",
|
|
11921
|
+
kind: "string",
|
|
11922
|
+
valueName: "PROJECT_ID",
|
|
11923
|
+
description: "Owned in or aimed at this project."
|
|
11924
|
+
},
|
|
11925
|
+
{
|
|
11926
|
+
name: "state",
|
|
11927
|
+
kind: "string",
|
|
11928
|
+
valueName: "STATE",
|
|
11929
|
+
description: "active, paused or completed."
|
|
11930
|
+
},
|
|
11931
|
+
{
|
|
11932
|
+
name: "owner-kind",
|
|
11933
|
+
kind: "string",
|
|
11934
|
+
valueName: "KIND",
|
|
11935
|
+
description: "operator, control-mcp, agent or coordinator."
|
|
11936
|
+
},
|
|
11937
|
+
{ name: "offset", kind: "integer", valueName: "N", description: "Rows to skip." },
|
|
11938
|
+
{ name: "limit", kind: "integer", valueName: "N", description: "Rows (max 200)." }
|
|
11939
|
+
],
|
|
11940
|
+
async run(context, parsed) {
|
|
11941
|
+
const session = await context.session();
|
|
11942
|
+
const options2 = parsed.options;
|
|
11943
|
+
const page = await session.read("schedule.list", {
|
|
11944
|
+
...options2.project !== undefined ? { projectId: options2.project } : {},
|
|
11945
|
+
...options2.state !== undefined ? { state: options2.state } : {},
|
|
11946
|
+
...options2["owner-kind"] !== undefined ? { ownerKind: options2["owner-kind"] } : {},
|
|
11947
|
+
...options2.offset !== undefined ? { offset: options2.offset } : {},
|
|
11948
|
+
...options2.limit !== undefined ? { limit: options2.limit } : {}
|
|
11949
|
+
});
|
|
11950
|
+
return {
|
|
11951
|
+
action: "schedule.list",
|
|
11952
|
+
result: page,
|
|
11953
|
+
connection: session.identity,
|
|
11954
|
+
human: table(["ID", "NAME", "STATE", "NEXT FIRE", "OWNER", "LAST"], page.schedules.map((job) => [
|
|
11955
|
+
job.id,
|
|
11956
|
+
job.name,
|
|
11957
|
+
job.state,
|
|
11958
|
+
job.nextFireAt ?? "-",
|
|
11959
|
+
job.ownerLabel,
|
|
11960
|
+
lastOutcome(job)
|
|
11961
|
+
]))
|
|
11962
|
+
};
|
|
11963
|
+
}
|
|
11964
|
+
},
|
|
11965
|
+
{
|
|
11966
|
+
path: ["schedule", "get"],
|
|
11967
|
+
action: "schedule.get",
|
|
11968
|
+
summary: "Show one scheduled job and its fire history.",
|
|
11969
|
+
positionals: [SCHEDULE_ID],
|
|
11970
|
+
options: [],
|
|
11971
|
+
inputFields: { schedule: "scheduleId" },
|
|
11972
|
+
async run(context, parsed) {
|
|
11973
|
+
const session = await context.session();
|
|
11974
|
+
const result = await session.read("schedule.get", {
|
|
11975
|
+
scheduleId: parsed.positionals.schedule
|
|
11976
|
+
});
|
|
11977
|
+
return {
|
|
11978
|
+
action: "schedule.get",
|
|
11979
|
+
result,
|
|
11980
|
+
connection: session.identity,
|
|
11981
|
+
human: scheduleLines(result.schedule)
|
|
11982
|
+
};
|
|
11983
|
+
}
|
|
11984
|
+
},
|
|
11985
|
+
{
|
|
11986
|
+
path: ["schedule", "create"],
|
|
11987
|
+
action: "schedule.create",
|
|
11988
|
+
summary: "Schedule mail, a prompt, an environment launch or another action.",
|
|
11989
|
+
description: "Give one of --at, --cron or --every, and one action: --mail ENV:TAB with --body, --session SES with --prompt, --launch-project P with --agent and --prompt, or --action NAME with --input-json.",
|
|
11990
|
+
positionals: [],
|
|
11991
|
+
options: [
|
|
11992
|
+
...TIMING_OPTIONS,
|
|
11993
|
+
{ name: "paused", kind: "boolean", description: "Create it paused." },
|
|
11994
|
+
...ACTION_OPTIONS,
|
|
11995
|
+
REQUEST_ID_OPTION
|
|
11996
|
+
],
|
|
11997
|
+
inputFields: { ...ACTION_INPUT_FIELDS, every: "everySeconds", "start-at": "startAt" },
|
|
11998
|
+
idOutput: "schedule ID",
|
|
11999
|
+
examples: [
|
|
12000
|
+
'orkestrator schedule create --at 03:12 --mail env-1:startup-agent --body "Resume the stalled workers."',
|
|
12001
|
+
'orkestrator schedule create --cron "0 9 * * 1-5" --session ses_... --prompt "Summarise CI failures."'
|
|
12002
|
+
],
|
|
12003
|
+
async run(context, parsed) {
|
|
12004
|
+
const action = await actionInput(context.io, parsed.options);
|
|
12005
|
+
if (!action)
|
|
12006
|
+
throw new CliError("invalid-input", "Choose an action: --mail, --session, --launch-project or --action");
|
|
12007
|
+
const { session, result, receipt, warnings } = await submit(context, "schedule.create", {
|
|
12008
|
+
...timingInput(parsed.options),
|
|
12009
|
+
...parsed.options.paused === true ? { paused: true } : {},
|
|
12010
|
+
action
|
|
12011
|
+
}, parsed.options);
|
|
12012
|
+
return {
|
|
12013
|
+
action: "schedule.create",
|
|
12014
|
+
result,
|
|
12015
|
+
receipt,
|
|
12016
|
+
warnings,
|
|
12017
|
+
connection: session.identity,
|
|
12018
|
+
ids: [result.scheduleId],
|
|
12019
|
+
human: [`scheduled ${result.scheduleId}; next fire ${result.nextFireAt ?? "none"}`]
|
|
12020
|
+
};
|
|
12021
|
+
}
|
|
12022
|
+
},
|
|
12023
|
+
{
|
|
12024
|
+
path: ["schedule", "update"],
|
|
12025
|
+
action: "schedule.update",
|
|
12026
|
+
summary: "Change a schedule's time, zone, catch-up window, name or action.",
|
|
12027
|
+
positionals: [SCHEDULE_ID],
|
|
12028
|
+
options: [
|
|
12029
|
+
...TIMING_OPTIONS,
|
|
12030
|
+
...ACTION_OPTIONS,
|
|
12031
|
+
{
|
|
12032
|
+
name: "expected-revision",
|
|
12033
|
+
kind: "integer",
|
|
12034
|
+
valueName: "N",
|
|
12035
|
+
description: "Apply only at this revision."
|
|
12036
|
+
},
|
|
12037
|
+
REQUEST_ID_OPTION
|
|
12038
|
+
],
|
|
12039
|
+
inputFields: {
|
|
12040
|
+
...ACTION_INPUT_FIELDS,
|
|
12041
|
+
schedule: "scheduleId",
|
|
12042
|
+
every: "everySeconds",
|
|
12043
|
+
"start-at": "startAt"
|
|
12044
|
+
},
|
|
12045
|
+
idOutput: "schedule ID",
|
|
12046
|
+
async run(context, parsed) {
|
|
12047
|
+
const action = await actionInput(context.io, parsed.options);
|
|
12048
|
+
const { session, result, receipt, warnings } = await submit(context, "schedule.update", {
|
|
12049
|
+
scheduleId: parsed.positionals.schedule,
|
|
12050
|
+
...timingInput(parsed.options),
|
|
12051
|
+
...action ? { action } : {},
|
|
12052
|
+
...typeof parsed.options["expected-revision"] === "number" ? { expectedRevision: parsed.options["expected-revision"] } : {}
|
|
12053
|
+
}, parsed.options);
|
|
12054
|
+
return {
|
|
12055
|
+
action: "schedule.update",
|
|
12056
|
+
result,
|
|
12057
|
+
receipt,
|
|
12058
|
+
warnings,
|
|
12059
|
+
connection: session.identity,
|
|
12060
|
+
ids: [String(parsed.positionals.schedule)],
|
|
12061
|
+
human: keyValues(Object.entries(result))
|
|
12062
|
+
};
|
|
12063
|
+
}
|
|
12064
|
+
},
|
|
12065
|
+
simpleMutation("pause", "Pause a schedule; nothing fires until it is resumed."),
|
|
12066
|
+
simpleMutation("resume", "Resume a paused schedule from its next future slot."),
|
|
12067
|
+
simpleMutation("run", "Fire a schedule once now, leaving its regular slots unchanged."),
|
|
12068
|
+
simpleMutation("delete", "Delete a schedule and its history.")
|
|
12069
|
+
];
|
|
12070
|
+
|
|
12071
|
+
// src/client/commands/mail.ts
|
|
12072
|
+
var mailCommands = [
|
|
12073
|
+
{
|
|
12074
|
+
path: ["mail", "mailboxes"],
|
|
12075
|
+
action: "mail.mailboxes",
|
|
12076
|
+
summary: "List agent mailboxes a message can be sent to.",
|
|
12077
|
+
positionals: [],
|
|
12078
|
+
options: [
|
|
12079
|
+
{ name: "q", kind: "string", valueName: "TEXT", description: "Filter by name." },
|
|
12080
|
+
{ name: "offset", kind: "integer", valueName: "N", description: "Rows to skip." },
|
|
12081
|
+
{ name: "limit", kind: "integer", valueName: "N", description: "Rows (max 200)." }
|
|
12082
|
+
],
|
|
12083
|
+
async run(context, parsed) {
|
|
12084
|
+
const session = await context.session();
|
|
12085
|
+
const page = await session.read("mail.mailboxes", {
|
|
12086
|
+
...parsed.options.q !== undefined ? { q: parsed.options.q } : {},
|
|
12087
|
+
...parsed.options.offset !== undefined ? { offset: parsed.options.offset } : {},
|
|
12088
|
+
...parsed.options.limit !== undefined ? { limit: parsed.options.limit } : {}
|
|
12089
|
+
});
|
|
12090
|
+
return {
|
|
12091
|
+
action: "mail.mailboxes",
|
|
12092
|
+
result: page,
|
|
12093
|
+
connection: session.identity,
|
|
12094
|
+
human: table(["ENVIRONMENT", "TAB", "NAME", "PRESENCE"], page.mailboxes.map((mailbox) => [
|
|
12095
|
+
mailbox.environmentId,
|
|
12096
|
+
mailbox.tabId,
|
|
12097
|
+
`${mailbox.environmentName} / ${mailbox.displayName}`,
|
|
12098
|
+
mailbox.presence
|
|
12099
|
+
]))
|
|
12100
|
+
};
|
|
12101
|
+
}
|
|
12102
|
+
},
|
|
12103
|
+
{
|
|
12104
|
+
path: ["mail", "send"],
|
|
12105
|
+
action: "mail.send",
|
|
12106
|
+
summary: "Place a message in one tab's inbox (never injected into a turn).",
|
|
12107
|
+
positionals: [
|
|
12108
|
+
{ name: "environment", required: true },
|
|
12109
|
+
{ name: "tab", required: true }
|
|
12110
|
+
],
|
|
12111
|
+
options: [
|
|
12112
|
+
{ name: "subject", kind: "string", valueName: "TEXT", description: "Subject line." },
|
|
12113
|
+
{ name: "body", kind: "string", valueName: "TEXT", description: "Message text." },
|
|
12114
|
+
{
|
|
12115
|
+
name: "body-file",
|
|
12116
|
+
kind: "string",
|
|
12117
|
+
valueName: "PATH",
|
|
12118
|
+
description: "Read the message from a local file."
|
|
12119
|
+
},
|
|
12120
|
+
{ name: "body-stdin", kind: "boolean", description: "Read the message from stdin." },
|
|
12121
|
+
REQUEST_ID_OPTION
|
|
12122
|
+
],
|
|
12123
|
+
inputFields: {
|
|
12124
|
+
environment: "toEnvironmentId",
|
|
12125
|
+
tab: "toTabId",
|
|
12126
|
+
"body-file": "body",
|
|
12127
|
+
"body-stdin": "body"
|
|
11173
12128
|
},
|
|
11174
12129
|
idOutput: "message ID",
|
|
11175
12130
|
async run(context, parsed) {
|
|
@@ -11908,7 +12863,7 @@ var projectCommands = [
|
|
|
11908
12863
|
path: ["project", "create"],
|
|
11909
12864
|
action: "project.create",
|
|
11910
12865
|
summary: "Create a new repository: git init, a PRIVATE GitHub repository, and a push.",
|
|
11911
|
-
description: "This creates an external repository on GitHub using the backend's GitHub CLI credentials, pushes the initial commit, and registers the project. It requires --github-private to confirm that effect.
|
|
12866
|
+
description: "This creates an external repository on GitHub using the backend's GitHub CLI credentials, pushes the initial commit, and registers the project. It requires --github-private to confirm that effect. For a repository with no remote, use `project init`.",
|
|
11912
12867
|
positionals: [],
|
|
11913
12868
|
options: [
|
|
11914
12869
|
{
|
|
@@ -11944,6 +12899,38 @@ var projectCommands = [
|
|
|
11944
12899
|
};
|
|
11945
12900
|
}
|
|
11946
12901
|
},
|
|
12902
|
+
{
|
|
12903
|
+
path: ["project", "init"],
|
|
12904
|
+
action: "project.init",
|
|
12905
|
+
summary: "Create a local-only repository (git init, initial commit, no remote) and register it.",
|
|
12906
|
+
description: "Runs on the BACKEND host: the path must be new or an empty directory there. Nothing reaches GitHub or the network. Environments start from the repository's own branch; without a remote, container environments cannot clone it and pull requests are unavailable.",
|
|
12907
|
+
positionals: [],
|
|
12908
|
+
options: [
|
|
12909
|
+
{
|
|
12910
|
+
name: "path",
|
|
12911
|
+
kind: "string",
|
|
12912
|
+
valueName: "BACKEND_PATH",
|
|
12913
|
+
description: "New, empty directory on the backend host."
|
|
12914
|
+
},
|
|
12915
|
+
REQUEST_ID_OPTION
|
|
12916
|
+
],
|
|
12917
|
+
idOutput: "project ID",
|
|
12918
|
+
async run(context, parsed) {
|
|
12919
|
+
const backendPath = stringOption(parsed.options, "path");
|
|
12920
|
+
if (!backendPath)
|
|
12921
|
+
throw new CliError("invalid-input", "--path is required");
|
|
12922
|
+
const { session, result, receipt, warnings } = await submit(context, "project.init", { path: backendPath }, parsed.options);
|
|
12923
|
+
return {
|
|
12924
|
+
action: "project.init",
|
|
12925
|
+
result,
|
|
12926
|
+
receipt,
|
|
12927
|
+
warnings,
|
|
12928
|
+
connection: session.identity,
|
|
12929
|
+
ids: [result.project.id],
|
|
12930
|
+
human: projectLines(result.project)
|
|
12931
|
+
};
|
|
12932
|
+
}
|
|
12933
|
+
},
|
|
11947
12934
|
{
|
|
11948
12935
|
path: ["project", "update"],
|
|
11949
12936
|
action: "project.update",
|
|
@@ -12025,13 +13012,46 @@ var projectCommands = [
|
|
|
12025
13012
|
{
|
|
12026
13013
|
path: ["project", "remove"],
|
|
12027
13014
|
action: "project.remove",
|
|
12028
|
-
summary: "Remove
|
|
12029
|
-
description: "
|
|
13015
|
+
summary: "Remove a project registration; --cascade deletes its environments first.",
|
|
13016
|
+
description: "Without --cascade a project that still has environments is refused (exit 8). With --cascade the backend deletes each environment through the normal lifecycle, one by one, reports a result for each, and removes the registration only if all were deleted (otherwise the operation is partial and the project is kept). The checkout on disk and any remote repository are never deleted. --wait observes a cascade until it ends.",
|
|
12030
13017
|
positionals: [{ name: "project", required: true }],
|
|
12031
|
-
options: [
|
|
13018
|
+
options: [
|
|
13019
|
+
{
|
|
13020
|
+
name: "cascade",
|
|
13021
|
+
kind: "boolean",
|
|
13022
|
+
description: "Delete every environment of the project first."
|
|
13023
|
+
},
|
|
13024
|
+
{ name: "wait", kind: "boolean", description: "Observe the removal until it ends." },
|
|
13025
|
+
TIMEOUT_OPTION,
|
|
13026
|
+
REQUEST_ID_OPTION
|
|
13027
|
+
],
|
|
12032
13028
|
idOutput: "operation ID",
|
|
12033
13029
|
async run(context, parsed) {
|
|
12034
|
-
const
|
|
13030
|
+
const options2 = parsed.options;
|
|
13031
|
+
if (options2.timeout !== undefined && options2.wait !== true) {
|
|
13032
|
+
throw new CliError("invalid-input", "--timeout needs --wait");
|
|
13033
|
+
}
|
|
13034
|
+
if (typeof options2.timeout === "number" && options2.timeout > PUBLIC_API_LIMITS.waitMaxMs) {
|
|
13035
|
+
throw new CliError("invalid-input", "--timeout may be at most 24h");
|
|
13036
|
+
}
|
|
13037
|
+
const { session, result, receipt, warnings } = await submit(context, "project.remove", {
|
|
13038
|
+
projectId: parsed.positionals.project,
|
|
13039
|
+
...options2.cascade === true ? { cascade: true } : {}
|
|
13040
|
+
}, options2);
|
|
13041
|
+
if (options2.wait === true && receipt) {
|
|
13042
|
+
const final = await waitForOperation(context, session, receipt, options2.timeout ?? DEFAULT_WAIT_MS);
|
|
13043
|
+
const failure = receiptFailure(final);
|
|
13044
|
+
return {
|
|
13045
|
+
action: "project.remove",
|
|
13046
|
+
result: final.result ?? result,
|
|
13047
|
+
receipt: final,
|
|
13048
|
+
warnings,
|
|
13049
|
+
connection: session.identity,
|
|
13050
|
+
ids: [final.operationId],
|
|
13051
|
+
human: receiptLines(final),
|
|
13052
|
+
...failure ? { failure } : {}
|
|
13053
|
+
};
|
|
13054
|
+
}
|
|
12035
13055
|
return {
|
|
12036
13056
|
action: "project.remove",
|
|
12037
13057
|
result,
|
|
@@ -12089,7 +13109,7 @@ var runCommands = [
|
|
|
12089
13109
|
let namespace = stringOption(parsed.options, "namespace");
|
|
12090
13110
|
if (requestId && !namespace && action) {
|
|
12091
13111
|
const capabilities = await session.capabilities();
|
|
12092
|
-
const local = await new LocalReceiptStore(context.configStore.receiptsDirectory).find(capabilities.backend.installationId, action, requestId);
|
|
13112
|
+
const local = await new LocalReceiptStore(context.configStore.receiptsDirectory).find(capabilities.backend.installationId, action, requestId, session.node);
|
|
12093
13113
|
namespace = local?.namespace;
|
|
12094
13114
|
}
|
|
12095
13115
|
const { receipt } = await session.readWithReceipt("run.get", operationId ? { operationId } : { requestId, ...action ? { action } : {}, ...namespace ? { namespace } : {} });
|
|
@@ -12097,7 +13117,7 @@ var runCommands = [
|
|
|
12097
13117
|
throw new CliError("response-invalid", "run.get returned no receipt");
|
|
12098
13118
|
return {
|
|
12099
13119
|
action: "run.get",
|
|
12100
|
-
result: null,
|
|
13120
|
+
result: receipt.result ?? null,
|
|
12101
13121
|
receipt,
|
|
12102
13122
|
connection: session.identity,
|
|
12103
13123
|
ids: [receipt.operationId],
|
|
@@ -12127,7 +13147,7 @@ var runCommands = [
|
|
|
12127
13147
|
const failure = receiptFailure(final);
|
|
12128
13148
|
return {
|
|
12129
13149
|
action: "run.wait",
|
|
12130
|
-
result: null,
|
|
13150
|
+
result: final.result ?? null,
|
|
12131
13151
|
receipt: final,
|
|
12132
13152
|
connection: session.identity,
|
|
12133
13153
|
ids: [final.operationId],
|
|
@@ -12277,9 +13297,10 @@ var runCommands = [
|
|
|
12277
13297
|
return {
|
|
12278
13298
|
action: "run.receipts",
|
|
12279
13299
|
result: { items: receipts },
|
|
12280
|
-
human: table(["REQUEST", "ACTION", "OPERATION", "STATE", "UPDATED"], receipts.map((receipt) => [
|
|
13300
|
+
human: table(["REQUEST", "ACTION", "NODE", "OPERATION", "STATE", "UPDATED"], receipts.map((receipt) => [
|
|
12281
13301
|
receipt.requestId,
|
|
12282
13302
|
receipt.action,
|
|
13303
|
+
receipt.node ?? "(connected node)",
|
|
12283
13304
|
receipt.operationId ?? "(no response)",
|
|
12284
13305
|
receipt.state ?? "-",
|
|
12285
13306
|
receipt.updatedAt
|
|
@@ -12290,6 +13311,7 @@ var runCommands = [
|
|
|
12290
13311
|
];
|
|
12291
13312
|
|
|
12292
13313
|
// src/client/commands/session.ts
|
|
13314
|
+
import path15 from "path";
|
|
12293
13315
|
var WAIT_OPTIONS = [
|
|
12294
13316
|
{ name: "wait", kind: "boolean", description: "Observe this run until its turn ends." },
|
|
12295
13317
|
TIMEOUT_OPTION,
|
|
@@ -12324,7 +13346,45 @@ var CONTROL_OPTIONS = [
|
|
|
12324
13346
|
valueName: "plan|build",
|
|
12325
13347
|
description: "Conversation mode."
|
|
12326
13348
|
}
|
|
12327
|
-
];
|
|
13349
|
+
];
|
|
13350
|
+
var LITERAL_OPTION = {
|
|
13351
|
+
name: "literal",
|
|
13352
|
+
kind: "boolean",
|
|
13353
|
+
description: "Send the prompt as plain text: a leading /name never runs a provider command (by default it does)."
|
|
13354
|
+
};
|
|
13355
|
+
var ENQUEUE_OPTION = {
|
|
13356
|
+
name: "enqueue",
|
|
13357
|
+
kind: "boolean",
|
|
13358
|
+
description: "If the session is busy, queue this behind its current work (the backend sends it later) instead of refusing."
|
|
13359
|
+
};
|
|
13360
|
+
var ATTACH_OPTION = {
|
|
13361
|
+
name: "attach",
|
|
13362
|
+
kind: "list",
|
|
13363
|
+
valueName: "FILE",
|
|
13364
|
+
description: `Send a local file with the prompt (repeatable; at most ${PUBLIC_API_LIMITS.promptAttachmentsMaxItems} files, ${PUBLIC_API_LIMITS.promptAttachmentsMaxBytes / 1024} KiB in total).`
|
|
13365
|
+
};
|
|
13366
|
+
async function readAttachments(context, options2) {
|
|
13367
|
+
const files = options2.attach ?? [];
|
|
13368
|
+
if (files.length === 0)
|
|
13369
|
+
return;
|
|
13370
|
+
if (files.length > PUBLIC_API_LIMITS.promptAttachmentsMaxItems) {
|
|
13371
|
+
throw new CliError("input-too-large", `At most ${PUBLIC_API_LIMITS.promptAttachmentsMaxItems} attachments are allowed`);
|
|
13372
|
+
}
|
|
13373
|
+
let remaining = PUBLIC_API_LIMITS.promptAttachmentsMaxBytes;
|
|
13374
|
+
const names = new Set;
|
|
13375
|
+
const attachments = [];
|
|
13376
|
+
for (const file of files) {
|
|
13377
|
+
const name = path15.basename(file);
|
|
13378
|
+
if (names.has(name)) {
|
|
13379
|
+
throw new CliError("invalid-input", `Two attachments are both named ${name}`);
|
|
13380
|
+
}
|
|
13381
|
+
names.add(name);
|
|
13382
|
+
const bytes = await readBinaryFile(context.io, file, `Attachment ${name}`, remaining);
|
|
13383
|
+
remaining -= bytes.byteLength;
|
|
13384
|
+
attachments.push({ name, dataBase64: Buffer.from(bytes).toString("base64") });
|
|
13385
|
+
}
|
|
13386
|
+
return attachments;
|
|
13387
|
+
}
|
|
12328
13388
|
function waitBudget(options2) {
|
|
12329
13389
|
return options2.timeout ?? DEFAULT_WAIT_MS;
|
|
12330
13390
|
}
|
|
@@ -12371,6 +13431,17 @@ function controlsInput(options2) {
|
|
|
12371
13431
|
input.mode = oneOf(options2.mode, ["plan", "build"], "--mode");
|
|
12372
13432
|
return input;
|
|
12373
13433
|
}
|
|
13434
|
+
function holdLabel(hold) {
|
|
13435
|
+
if (!hold)
|
|
13436
|
+
return "";
|
|
13437
|
+
if (hold.heldAt !== undefined) {
|
|
13438
|
+
return ` (paused${hold.reason ? `, ${hold.reason}` : ""}; resume with interactions hold --resume)`;
|
|
13439
|
+
}
|
|
13440
|
+
if (hold.available) {
|
|
13441
|
+
return ` (pausable${hold.reason ? `, ${hold.reason}` : ""} with interactions hold)`;
|
|
13442
|
+
}
|
|
13443
|
+
return ` (not pausable${hold.reason ? `: ${hold.reason}` : ""})`;
|
|
13444
|
+
}
|
|
12374
13445
|
function parseAnswers(options2) {
|
|
12375
13446
|
const answers = new Map;
|
|
12376
13447
|
const entry = (questionId) => {
|
|
@@ -12519,9 +13590,12 @@ var sessionCommands = [
|
|
|
12519
13590
|
{ name: "title", kind: "string", valueName: "TITLE", description: "Tab title." },
|
|
12520
13591
|
...CONTROL_OPTIONS,
|
|
12521
13592
|
...PROMPT_OPTIONS,
|
|
13593
|
+
LITERAL_OPTION,
|
|
13594
|
+
ATTACH_OPTION,
|
|
12522
13595
|
...WAIT_OPTIONS,
|
|
12523
13596
|
REQUEST_ID_OPTION
|
|
12524
13597
|
],
|
|
13598
|
+
inputFields: { attach: "attachments" },
|
|
12525
13599
|
idOutput: "session ID",
|
|
12526
13600
|
async run(context, parsed) {
|
|
12527
13601
|
const options2 = parsed.options;
|
|
@@ -12532,12 +13606,15 @@ var sessionCommands = [
|
|
|
12532
13606
|
throw new CliError("invalid-input", "--environment and --agent are required");
|
|
12533
13607
|
}
|
|
12534
13608
|
const prompt = await readPrompt(context.io, options2);
|
|
13609
|
+
const attachments = await readAttachments(context, options2);
|
|
12535
13610
|
const { session, result, receipt, warnings, recoveryDeadline } = await submitRun(context, "session.start", {
|
|
12536
13611
|
environmentId,
|
|
12537
13612
|
agent,
|
|
12538
13613
|
prompt,
|
|
12539
13614
|
...options2.title !== undefined ? { title: options2.title } : {},
|
|
12540
|
-
...controlsInput(options2)
|
|
13615
|
+
...controlsInput(options2),
|
|
13616
|
+
...options2.literal === true ? { literal: true } : {},
|
|
13617
|
+
...attachments ? { attachments } : {}
|
|
12541
13618
|
}, options2);
|
|
12542
13619
|
const fromReceipt = (final) => ({
|
|
12543
13620
|
result: {
|
|
@@ -12563,8 +13640,8 @@ var sessionCommands = [
|
|
|
12563
13640
|
{
|
|
12564
13641
|
path: ["session", "prompt"],
|
|
12565
13642
|
action: "session.prompt",
|
|
12566
|
-
summary: "Send a follow-up prompt to an idle session.",
|
|
12567
|
-
description: "Busy sessions are refused (exit 8); use `session steer` to redirect an active turn. The target must be the exact session: a vanished session is never replaced by a new one.",
|
|
13643
|
+
summary: "Send a follow-up prompt to an idle session, or queue it behind a busy one.",
|
|
13644
|
+
description: "Busy sessions are refused (exit 8) unless --enqueue puts the prompt in the backend's durable queue; use `session steer` to redirect an active turn. A leading /name runs that provider command unless --literal is given. The target must be the exact session: a vanished session is never replaced by a new one.",
|
|
12568
13645
|
positionals: [{ name: "session", required: true }],
|
|
12569
13646
|
options: [
|
|
12570
13647
|
{
|
|
@@ -12574,18 +13651,26 @@ var sessionCommands = [
|
|
|
12574
13651
|
description: "Conversation mode for this turn."
|
|
12575
13652
|
},
|
|
12576
13653
|
...PROMPT_OPTIONS,
|
|
13654
|
+
LITERAL_OPTION,
|
|
13655
|
+
ENQUEUE_OPTION,
|
|
13656
|
+
ATTACH_OPTION,
|
|
12577
13657
|
...WAIT_OPTIONS,
|
|
12578
13658
|
REQUEST_ID_OPTION
|
|
12579
13659
|
],
|
|
13660
|
+
inputFields: { attach: "attachments" },
|
|
12580
13661
|
idOutput: "operation (run) ID",
|
|
12581
13662
|
async run(context, parsed) {
|
|
12582
13663
|
const options2 = parsed.options;
|
|
12583
13664
|
validateWaitOptions(options2);
|
|
12584
13665
|
const prompt = await readPrompt(context.io, options2);
|
|
13666
|
+
const attachments = await readAttachments(context, options2);
|
|
12585
13667
|
const { session, result, receipt, warnings, recoveryDeadline } = await submitRun(context, "session.prompt", {
|
|
12586
13668
|
sessionId: parsed.positionals.session,
|
|
12587
13669
|
prompt,
|
|
12588
|
-
...options2.mode !== undefined ? { mode: oneOf(options2.mode, ["plan", "build"], "--mode") } : {}
|
|
13670
|
+
...options2.mode !== undefined ? { mode: oneOf(options2.mode, ["plan", "build"], "--mode") } : {},
|
|
13671
|
+
...options2.literal === true ? { literal: true } : {},
|
|
13672
|
+
...options2.enqueue === true ? { enqueue: true } : {},
|
|
13673
|
+
...attachments ? { attachments } : {}
|
|
12589
13674
|
}, options2);
|
|
12590
13675
|
const runId = result?.runId ?? receipt.operationId;
|
|
12591
13676
|
return finishRun(context, parsed, {
|
|
@@ -12599,6 +13684,98 @@ var sessionCommands = [
|
|
|
12599
13684
|
}, recoveryDeadline);
|
|
12600
13685
|
}
|
|
12601
13686
|
},
|
|
13687
|
+
{
|
|
13688
|
+
path: ["session", "commands"],
|
|
13689
|
+
action: "session.commands",
|
|
13690
|
+
summary: "List the session's slash commands (ID, name, availability, argument policy).",
|
|
13691
|
+
description: "Reads the catalogue the backend already holds; --refresh asks the provider again. Use an ID (or a unique name) with `session command`.",
|
|
13692
|
+
positionals: [{ name: "session", required: true }],
|
|
13693
|
+
options: [
|
|
13694
|
+
{ name: "refresh", kind: "boolean", description: "Ask the provider for its list again." }
|
|
13695
|
+
],
|
|
13696
|
+
idOutput: "command IDs, one per line",
|
|
13697
|
+
async run(context, parsed) {
|
|
13698
|
+
const session = await context.session();
|
|
13699
|
+
const list = await session.read("session.commands", {
|
|
13700
|
+
sessionId: parsed.positionals.session,
|
|
13701
|
+
...parsed.options.refresh === true ? { refresh: true } : {}
|
|
13702
|
+
});
|
|
13703
|
+
return {
|
|
13704
|
+
action: "session.commands",
|
|
13705
|
+
result: list,
|
|
13706
|
+
connection: session.identity,
|
|
13707
|
+
ids: list.commands.map((command) => command.id),
|
|
13708
|
+
human: [
|
|
13709
|
+
`catalogue ${list.catalogue.status}${list.catalogue.error ? ` (${list.catalogue.error})` : ""}`,
|
|
13710
|
+
...table(["NAME", "ID", "ARGS", "AVAILABLE", "DESCRIPTION"], list.commands.map((command) => [
|
|
13711
|
+
command.name,
|
|
13712
|
+
command.id,
|
|
13713
|
+
command.arguments,
|
|
13714
|
+
command.available ? "yes" : `no: ${command.unavailableReason ?? ""}`,
|
|
13715
|
+
(command.description ?? "").slice(0, 60)
|
|
13716
|
+
]))
|
|
13717
|
+
]
|
|
13718
|
+
};
|
|
13719
|
+
}
|
|
13720
|
+
},
|
|
13721
|
+
{
|
|
13722
|
+
path: ["session", "command"],
|
|
13723
|
+
action: "session.command",
|
|
13724
|
+
summary: "Run one selected slash command in a session.",
|
|
13725
|
+
description: "Pass the command's ID or unique name with --command and its arguments after `--`. The command is sent as a selection: if it is missing, ambiguous, unavailable or changed (--expected-binding-revision), the request fails (exit 3 or 8) and nothing is sent as text. Busy sessions are refused unless --enqueue.",
|
|
13726
|
+
positionals: [{ name: "session", required: true }],
|
|
13727
|
+
options: [
|
|
13728
|
+
{
|
|
13729
|
+
name: "command",
|
|
13730
|
+
kind: "string",
|
|
13731
|
+
valueName: "ID|NAME",
|
|
13732
|
+
description: "Command ID or name from `session commands`."
|
|
13733
|
+
},
|
|
13734
|
+
{
|
|
13735
|
+
name: "expected-binding-revision",
|
|
13736
|
+
kind: "string",
|
|
13737
|
+
valueName: "REV",
|
|
13738
|
+
description: "Refuse if the command changed since `session commands` listed this revision."
|
|
13739
|
+
},
|
|
13740
|
+
ENQUEUE_OPTION,
|
|
13741
|
+
...WAIT_OPTIONS,
|
|
13742
|
+
REQUEST_ID_OPTION
|
|
13743
|
+
],
|
|
13744
|
+
acceptsRest: true,
|
|
13745
|
+
idOutput: "operation (run) ID",
|
|
13746
|
+
examples: [
|
|
13747
|
+
"orkestrator session command ses_\u2026 --command /review -- the open diff",
|
|
13748
|
+
"orkestrator session command ses_\u2026 --command /compact --wait"
|
|
13749
|
+
],
|
|
13750
|
+
async run(context, parsed) {
|
|
13751
|
+
const options2 = parsed.options;
|
|
13752
|
+
validateWaitOptions(options2);
|
|
13753
|
+
const command = stringOption(options2, "command");
|
|
13754
|
+
if (!command)
|
|
13755
|
+
throw new CliError("invalid-input", "--command is required");
|
|
13756
|
+
const args = parsed.rest.join(" ");
|
|
13757
|
+
if (Buffer.byteLength(args) > PUBLIC_API_LIMITS.commandArgumentsMaxBytes) {
|
|
13758
|
+
throw new CliError("input-too-large", "Command arguments are too large");
|
|
13759
|
+
}
|
|
13760
|
+
const { session, result, receipt, warnings, recoveryDeadline } = await submitRun(context, "session.command", {
|
|
13761
|
+
sessionId: parsed.positionals.session,
|
|
13762
|
+
command,
|
|
13763
|
+
...args.trim() ? { arguments: args } : {},
|
|
13764
|
+
...options2["expected-binding-revision"] !== undefined ? { expectedBindingRevision: options2["expected-binding-revision"] } : {},
|
|
13765
|
+
...options2.enqueue === true ? { enqueue: true } : {}
|
|
13766
|
+
}, options2);
|
|
13767
|
+
const runId = result?.runId ?? receipt.operationId;
|
|
13768
|
+
return finishRun(context, parsed, {
|
|
13769
|
+
action: "session.command",
|
|
13770
|
+
result: result ?? { runId },
|
|
13771
|
+
receipt,
|
|
13772
|
+
warnings,
|
|
13773
|
+
connection: session.identity,
|
|
13774
|
+
ids: [runId],
|
|
13775
|
+
human: receipt ? receiptLines(receipt) : [`run ${runId}`]
|
|
13776
|
+
}, recoveryDeadline);
|
|
13777
|
+
}
|
|
13778
|
+
},
|
|
12602
13779
|
{
|
|
12603
13780
|
path: ["session", "stop"],
|
|
12604
13781
|
action: "session.stop",
|
|
@@ -12810,6 +13987,7 @@ var sessionCommands = [
|
|
|
12810
13987
|
ids: interactions.items.map((item) => item.id),
|
|
12811
13988
|
human: interactions.items.length ? interactions.items.flatMap((item) => [
|
|
12812
13989
|
`${item.id} ${item.kind} revision ${item.revision} ${item.title}`,
|
|
13990
|
+
...item.expiresAt !== undefined ? [` expires ${new Date(item.expiresAt).toISOString()}${holdLabel(item.hold)}`] : [],
|
|
12813
13991
|
...item.questions.map((question) => ` ${question.id}: ${question.prompt} [${question.options.map((option) => option.id).join(", ")}]${question.allowFreeText ? " (free text)" : ""}`),
|
|
12814
13992
|
` actions: ${item.actions.join(", ")}`
|
|
12815
13993
|
]) : ["(no pending interactions)"]
|
|
@@ -12889,6 +14067,41 @@ var sessionCommands = [
|
|
|
12889
14067
|
};
|
|
12890
14068
|
}
|
|
12891
14069
|
},
|
|
14070
|
+
{
|
|
14071
|
+
path: ["session", "interactions", "hold"],
|
|
14072
|
+
action: "session.interaction.hold",
|
|
14073
|
+
inputFields: { resume: "held" },
|
|
14074
|
+
summary: "Pause one pending interaction's countdown; --resume restarts a fresh window.",
|
|
14075
|
+
description: "The pause is enforced by the provider bridge: a paused request still ends at the 24-hour hold ceiling or the provider's own sooner limit (the moved expiresAt). An interaction with no countdown, or whose bridge predates pausing, is refused (exit 8). Nothing is answered.",
|
|
14076
|
+
positionals: [
|
|
14077
|
+
{ name: "session", required: true },
|
|
14078
|
+
{ name: "interaction", required: true }
|
|
14079
|
+
],
|
|
14080
|
+
options: [
|
|
14081
|
+
{
|
|
14082
|
+
name: "resume",
|
|
14083
|
+
kind: "boolean",
|
|
14084
|
+
description: "Resume the countdown instead of pausing it."
|
|
14085
|
+
},
|
|
14086
|
+
REQUEST_ID_OPTION
|
|
14087
|
+
],
|
|
14088
|
+
async run(context, parsed) {
|
|
14089
|
+
const held = parsed.options.resume !== true;
|
|
14090
|
+
const { session, result, receipt, warnings } = await submit(context, "session.interaction.hold", {
|
|
14091
|
+
sessionId: parsed.positionals.session,
|
|
14092
|
+
interactionId: parsed.positionals.interaction,
|
|
14093
|
+
held
|
|
14094
|
+
}, parsed.options);
|
|
14095
|
+
return {
|
|
14096
|
+
action: "session.interaction.hold",
|
|
14097
|
+
result,
|
|
14098
|
+
receipt,
|
|
14099
|
+
warnings,
|
|
14100
|
+
connection: session.identity,
|
|
14101
|
+
human: receipt ? receiptLines(receipt) : [held ? "paused" : "resumed"]
|
|
14102
|
+
};
|
|
14103
|
+
}
|
|
14104
|
+
},
|
|
12892
14105
|
{
|
|
12893
14106
|
path: ["session", "transcript"],
|
|
12894
14107
|
action: "session.transcript",
|
|
@@ -13240,9 +14453,9 @@ var ticketCommands = [
|
|
|
13240
14453
|
];
|
|
13241
14454
|
|
|
13242
14455
|
// src/client/commands/toolchain.ts
|
|
13243
|
-
import
|
|
14456
|
+
import path19 from "path";
|
|
13244
14457
|
function dataDirectory2(context, parsed) {
|
|
13245
|
-
return
|
|
14458
|
+
return path19.resolve(stringOption(parsed.options, "data-dir") ?? context.io.env.ORKESTRATOR_DATA_DIR ?? defaultDataDir(process.platform, context.io.env));
|
|
13246
14459
|
}
|
|
13247
14460
|
function requestedTools(parsed) {
|
|
13248
14461
|
const raw = parsed.options.tool;
|
|
@@ -13345,7 +14558,7 @@ var toolchainCommands = [
|
|
|
13345
14558
|
const rows = installed.tools.map((tool) => ({
|
|
13346
14559
|
tool,
|
|
13347
14560
|
version: installed.versions[tool] ?? "",
|
|
13348
|
-
executable:
|
|
14561
|
+
executable: path19.join(installed.currentBinDir, path19.basename(installed.executables[tool]))
|
|
13349
14562
|
}));
|
|
13350
14563
|
return {
|
|
13351
14564
|
action: "toolchain.install",
|
|
@@ -13375,6 +14588,337 @@ var toolchainCommands = [
|
|
|
13375
14588
|
}
|
|
13376
14589
|
];
|
|
13377
14590
|
|
|
14591
|
+
// src/client/commands/workflows.ts
|
|
14592
|
+
init_agent_platforms();
|
|
14593
|
+
function parseSelection(raw, flag) {
|
|
14594
|
+
const equals = raw.indexOf("=");
|
|
14595
|
+
const agent = raw.slice(0, equals);
|
|
14596
|
+
if (equals <= 0 || !AGENT_PLATFORMS.includes(agent)) {
|
|
14597
|
+
throw new CliError("invalid-input", `${flag} expects AGENT=MODEL[@EFFORT] with AGENT one of ${AGENT_PLATFORMS.join(", ")}`);
|
|
14598
|
+
}
|
|
14599
|
+
const rest = raw.slice(equals + 1);
|
|
14600
|
+
const at = rest.lastIndexOf("@");
|
|
14601
|
+
const model = at > 0 ? rest.slice(0, at) : rest;
|
|
14602
|
+
const reasoningEffort = at > 0 ? rest.slice(at + 1) : undefined;
|
|
14603
|
+
if (!model)
|
|
14604
|
+
throw new CliError("invalid-input", `${flag} needs a model`);
|
|
14605
|
+
return { agent, model, ...reasoningEffort ? { reasoningEffort } : {} };
|
|
14606
|
+
}
|
|
14607
|
+
var SELECTION_HELP = "AGENT=MODEL[@EFFORT], e.g. codex=gpt-5.5@high.";
|
|
14608
|
+
var AGENT_OPTIONS = [
|
|
14609
|
+
{
|
|
14610
|
+
name: "agent",
|
|
14611
|
+
kind: "string",
|
|
14612
|
+
valueName: "AGENT",
|
|
14613
|
+
description: "Agent platform. Omitted: the action's configured default."
|
|
14614
|
+
},
|
|
14615
|
+
{ name: "model", kind: "string", valueName: "MODEL", description: "Requires --agent." },
|
|
14616
|
+
{
|
|
14617
|
+
name: "reasoning",
|
|
14618
|
+
kind: "string",
|
|
14619
|
+
valueName: "LEVEL",
|
|
14620
|
+
description: "Reasoning level; requires --model."
|
|
14621
|
+
},
|
|
14622
|
+
{
|
|
14623
|
+
name: "fast",
|
|
14624
|
+
kind: "boolean",
|
|
14625
|
+
negatable: true,
|
|
14626
|
+
description: "Fast speed where supported."
|
|
14627
|
+
}
|
|
14628
|
+
];
|
|
14629
|
+
function reviewLines(review) {
|
|
14630
|
+
return keyValues([
|
|
14631
|
+
["review", review.id],
|
|
14632
|
+
["environment", review.environmentId],
|
|
14633
|
+
["phase", review.phase],
|
|
14634
|
+
...review.targetBranch ? [["target", review.targetBranch]] : [],
|
|
14635
|
+
...review.error ? [["error", review.error]] : []
|
|
14636
|
+
]);
|
|
14637
|
+
}
|
|
14638
|
+
function reviewMutation(path, action, summary) {
|
|
14639
|
+
return {
|
|
14640
|
+
path: ["review", path],
|
|
14641
|
+
action,
|
|
14642
|
+
summary,
|
|
14643
|
+
positionals: [{ name: "workflow", required: true, description: "Multi Review ID." }],
|
|
14644
|
+
options: [REQUEST_ID_OPTION],
|
|
14645
|
+
inputFields: { workflow: "workflowId" },
|
|
14646
|
+
idOutput: "workflow ID",
|
|
14647
|
+
async run(context, parsed) {
|
|
14648
|
+
const workflowId = parsed.positionals.workflow;
|
|
14649
|
+
const { session, result, receipt, warnings } = await submit(context, action, { workflowId }, parsed.options);
|
|
14650
|
+
return {
|
|
14651
|
+
action,
|
|
14652
|
+
result,
|
|
14653
|
+
receipt,
|
|
14654
|
+
warnings,
|
|
14655
|
+
connection: session.identity,
|
|
14656
|
+
ids: [workflowId],
|
|
14657
|
+
human: keyValues([
|
|
14658
|
+
["review", workflowId],
|
|
14659
|
+
["phase", result.phase ?? receipt?.state ?? ""]
|
|
14660
|
+
])
|
|
14661
|
+
};
|
|
14662
|
+
}
|
|
14663
|
+
};
|
|
14664
|
+
}
|
|
14665
|
+
function prCommand(verb) {
|
|
14666
|
+
const action = verb === "create" ? "pr.create" : "pr.resolve";
|
|
14667
|
+
return {
|
|
14668
|
+
path: ["pr", verb],
|
|
14669
|
+
action,
|
|
14670
|
+
summary: verb === "create" ? "Start the Create PR agent: commit, push and open a pull request (never merges)." : "Start the Resolve Conflicts agent: merge the target branch, resolve, validate and push.",
|
|
14671
|
+
description: "Opens the same agent tab as the environment's button, without focusing it. The response is a run receipt; add --wait (or `run wait`) to observe it.",
|
|
14672
|
+
positionals: [{ name: "environment", required: true, description: "Ready environment." }],
|
|
14673
|
+
options: [
|
|
14674
|
+
{
|
|
14675
|
+
name: "target-branch",
|
|
14676
|
+
kind: "string",
|
|
14677
|
+
valueName: "BRANCH",
|
|
14678
|
+
description: verb === "create" ? "Pull request base. Omitted: the project's PR base branch." : "Branch to merge in. Omitted: the environment's PR base branch."
|
|
14679
|
+
},
|
|
14680
|
+
...AGENT_OPTIONS,
|
|
14681
|
+
...WAIT_OPTIONS,
|
|
14682
|
+
REQUEST_ID_OPTION
|
|
14683
|
+
],
|
|
14684
|
+
idOutput: "session ID",
|
|
14685
|
+
async run(context, parsed) {
|
|
14686
|
+
const options2 = parsed.options;
|
|
14687
|
+
validateWaitOptions(options2);
|
|
14688
|
+
const fast = optionalBoolean(options2, "fast");
|
|
14689
|
+
const input = {
|
|
14690
|
+
environmentId: parsed.positionals.environment,
|
|
14691
|
+
...options2["target-branch"] !== undefined ? { targetBranch: options2["target-branch"] } : {},
|
|
14692
|
+
...options2.agent !== undefined ? { agent: options2.agent } : {},
|
|
14693
|
+
...options2.model !== undefined ? { model: options2.model } : {},
|
|
14694
|
+
...options2.reasoning !== undefined ? { reasoning: options2.reasoning } : {},
|
|
14695
|
+
...fast !== undefined ? { fastMode: fast } : {}
|
|
14696
|
+
};
|
|
14697
|
+
const { session, result, receipt, warnings, recoveryDeadline } = await submitRun(context, action, input, options2);
|
|
14698
|
+
const fromReceipt = (final) => ({
|
|
14699
|
+
result: {
|
|
14700
|
+
sessionId: final.resources.sessionId ?? null,
|
|
14701
|
+
tabId: final.resources.tabId ?? null,
|
|
14702
|
+
runId: final.operationId
|
|
14703
|
+
},
|
|
14704
|
+
ids: final.resources.sessionId ? [final.resources.sessionId] : []
|
|
14705
|
+
});
|
|
14706
|
+
return finishRun(context, parsed, {
|
|
14707
|
+
action,
|
|
14708
|
+
...result ? { result, ids: [result.sessionId] } : fromReceipt(receipt),
|
|
14709
|
+
receipt,
|
|
14710
|
+
warnings,
|
|
14711
|
+
connection: session.identity,
|
|
14712
|
+
human: result ? keyValues([
|
|
14713
|
+
["session", result.sessionId],
|
|
14714
|
+
["run", result.runId]
|
|
14715
|
+
]) : []
|
|
14716
|
+
}, recoveryDeadline, result ? undefined : fromReceipt);
|
|
14717
|
+
}
|
|
14718
|
+
};
|
|
14719
|
+
}
|
|
14720
|
+
var workflowCommands = [
|
|
14721
|
+
{
|
|
14722
|
+
path: ["node", "list"],
|
|
14723
|
+
action: "node.list",
|
|
14724
|
+
summary: "List this node and the leaf nodes it controls; pass a node's ID with --node.",
|
|
14725
|
+
positionals: [],
|
|
14726
|
+
options: [],
|
|
14727
|
+
idOutput: "node IDs",
|
|
14728
|
+
async run(context) {
|
|
14729
|
+
if (context.global.node) {
|
|
14730
|
+
throw new CliError("invalid-input", "node list always lists the connected node; omit --node");
|
|
14731
|
+
}
|
|
14732
|
+
const session = await context.session();
|
|
14733
|
+
const list = await session.read("node.list", {});
|
|
14734
|
+
return {
|
|
14735
|
+
action: "node.list",
|
|
14736
|
+
result: list,
|
|
14737
|
+
connection: session.identity,
|
|
14738
|
+
ids: list.items.map((node) => node.nodeId),
|
|
14739
|
+
human: table(["NODE", "NAME", "STATE", "VERSION", "ACTIONS"], list.items.map((node) => [
|
|
14740
|
+
node.nodeId,
|
|
14741
|
+
node.self ? `${node.name} (this node)` : node.name,
|
|
14742
|
+
node.state,
|
|
14743
|
+
node.version ?? "",
|
|
14744
|
+
node.publicActions ? "yes" : node.reason ?? "no"
|
|
14745
|
+
]))
|
|
14746
|
+
};
|
|
14747
|
+
}
|
|
14748
|
+
},
|
|
14749
|
+
{
|
|
14750
|
+
path: ["review", "launch"],
|
|
14751
|
+
action: "review.launch",
|
|
14752
|
+
summary: "Start an environment's Multi Review, as its button does (an active one is reused).",
|
|
14753
|
+
description: "Omitted reviewers, models and auto-actions use the saved Multi Review settings. The review continues in the backend; follow it with `review get`.",
|
|
14754
|
+
positionals: [{ name: "environment", required: true, description: "Environment to review." }],
|
|
14755
|
+
options: [
|
|
14756
|
+
{
|
|
14757
|
+
name: "reviewer",
|
|
14758
|
+
kind: "list",
|
|
14759
|
+
valueName: "SELECTION",
|
|
14760
|
+
description: `Reviewer row (repeatable): ${SELECTION_HELP}`
|
|
14761
|
+
},
|
|
14762
|
+
{
|
|
14763
|
+
name: "review-model",
|
|
14764
|
+
kind: "string",
|
|
14765
|
+
valueName: "SELECTION",
|
|
14766
|
+
description: `Preparation and consolidation model: ${SELECTION_HELP}`
|
|
14767
|
+
},
|
|
14768
|
+
{
|
|
14769
|
+
name: "fix-model",
|
|
14770
|
+
kind: "string",
|
|
14771
|
+
valueName: "SELECTION",
|
|
14772
|
+
description: `Fix model: ${SELECTION_HELP}`
|
|
14773
|
+
},
|
|
14774
|
+
{
|
|
14775
|
+
name: "auto-fix",
|
|
14776
|
+
kind: "boolean",
|
|
14777
|
+
negatable: true,
|
|
14778
|
+
description: "Start the Fix when consolidation reports findings."
|
|
14779
|
+
},
|
|
14780
|
+
{
|
|
14781
|
+
name: "auto-pr",
|
|
14782
|
+
kind: "boolean",
|
|
14783
|
+
negatable: true,
|
|
14784
|
+
description: "Open the pull request after the Fix (never merges)."
|
|
14785
|
+
},
|
|
14786
|
+
{
|
|
14787
|
+
name: "target-branch",
|
|
14788
|
+
kind: "string",
|
|
14789
|
+
valueName: "BRANCH",
|
|
14790
|
+
description: "Branch to review against. Omitted: the project's PR base branch."
|
|
14791
|
+
},
|
|
14792
|
+
{
|
|
14793
|
+
name: "instruction-file",
|
|
14794
|
+
kind: "string",
|
|
14795
|
+
valueName: "PATH",
|
|
14796
|
+
description: "Read the review instruction from a local file (empty: none)."
|
|
14797
|
+
},
|
|
14798
|
+
REQUEST_ID_OPTION
|
|
14799
|
+
],
|
|
14800
|
+
inputFields: {
|
|
14801
|
+
reviewer: "reviewers",
|
|
14802
|
+
"instruction-file": "reviewInstruction"
|
|
14803
|
+
},
|
|
14804
|
+
idOutput: "workflow ID",
|
|
14805
|
+
async run(context, parsed) {
|
|
14806
|
+
const options2 = parsed.options;
|
|
14807
|
+
const reviewers = (options2.reviewer ?? []).map((raw) => parseSelection(raw, "--reviewer"));
|
|
14808
|
+
const reviewModel = stringOption(options2, "review-model");
|
|
14809
|
+
const fixModel = stringOption(options2, "fix-model");
|
|
14810
|
+
const autoFix = optionalBoolean(options2, "auto-fix");
|
|
14811
|
+
const autoPr = optionalBoolean(options2, "auto-pr");
|
|
14812
|
+
const instructionFile = stringOption(options2, "instruction-file");
|
|
14813
|
+
const reviewInstruction = instructionFile !== undefined ? await readTextSource(context.io, {
|
|
14814
|
+
file: instructionFile,
|
|
14815
|
+
label: "Review instruction",
|
|
14816
|
+
maxBytes: 1e5,
|
|
14817
|
+
names: { file: "--instruction-file", stdin: "--instruction-file -" },
|
|
14818
|
+
required: false
|
|
14819
|
+
}) : undefined;
|
|
14820
|
+
const { session, result, receipt, warnings } = await submit(context, "review.launch", {
|
|
14821
|
+
environmentId: parsed.positionals.environment,
|
|
14822
|
+
...reviewers.length ? { reviewers } : {},
|
|
14823
|
+
...reviewModel ? { reviewModel: parseSelection(reviewModel, "--review-model") } : {},
|
|
14824
|
+
...fixModel ? { fixModel: parseSelection(fixModel, "--fix-model") } : {},
|
|
14825
|
+
...autoFix !== undefined ? { autoFix } : {},
|
|
14826
|
+
...autoPr !== undefined ? { autoPr } : {},
|
|
14827
|
+
...options2["target-branch"] !== undefined ? { targetBranch: options2["target-branch"] } : {},
|
|
14828
|
+
...reviewInstruction !== undefined ? { reviewInstruction } : {}
|
|
14829
|
+
}, options2);
|
|
14830
|
+
return {
|
|
14831
|
+
action: "review.launch",
|
|
14832
|
+
result,
|
|
14833
|
+
receipt,
|
|
14834
|
+
warnings,
|
|
14835
|
+
connection: session.identity,
|
|
14836
|
+
ids: [result.workflowId],
|
|
14837
|
+
human: keyValues([
|
|
14838
|
+
["review", result.workflowId],
|
|
14839
|
+
["phase", result.phase ?? ""],
|
|
14840
|
+
["reused", result.reused ? "yes" : "no"]
|
|
14841
|
+
])
|
|
14842
|
+
};
|
|
14843
|
+
}
|
|
14844
|
+
},
|
|
14845
|
+
{
|
|
14846
|
+
path: ["review", "list"],
|
|
14847
|
+
action: "review.list",
|
|
14848
|
+
summary: "List an environment's Multi Reviews, newest first.",
|
|
14849
|
+
positionals: [{ name: "environment", required: true }],
|
|
14850
|
+
options: [],
|
|
14851
|
+
idOutput: "workflow IDs",
|
|
14852
|
+
async run(context, parsed) {
|
|
14853
|
+
const session = await context.session();
|
|
14854
|
+
const page = await session.read("review.list", {
|
|
14855
|
+
environmentId: parsed.positionals.environment
|
|
14856
|
+
});
|
|
14857
|
+
return {
|
|
14858
|
+
action: "review.list",
|
|
14859
|
+
result: page,
|
|
14860
|
+
connection: session.identity,
|
|
14861
|
+
ids: page.items.map((review) => review.id),
|
|
14862
|
+
human: table(["REVIEW", "PHASE", "TARGET", "CREATED"], page.items.map((review) => [
|
|
14863
|
+
review.id,
|
|
14864
|
+
review.phase,
|
|
14865
|
+
review.targetBranch ?? "",
|
|
14866
|
+
review.createdAt ?? ""
|
|
14867
|
+
]))
|
|
14868
|
+
};
|
|
14869
|
+
}
|
|
14870
|
+
},
|
|
14871
|
+
{
|
|
14872
|
+
path: ["review", "get"],
|
|
14873
|
+
action: "review.get",
|
|
14874
|
+
summary: "Read one Multi Review.",
|
|
14875
|
+
positionals: [{ name: "workflow", required: true, description: "Multi Review ID." }],
|
|
14876
|
+
options: [],
|
|
14877
|
+
inputFields: { workflow: "workflowId" },
|
|
14878
|
+
async run(context, parsed) {
|
|
14879
|
+
const session = await context.session();
|
|
14880
|
+
const review = await session.read("review.get", {
|
|
14881
|
+
workflowId: parsed.positionals.workflow
|
|
14882
|
+
});
|
|
14883
|
+
return {
|
|
14884
|
+
action: "review.get",
|
|
14885
|
+
result: review,
|
|
14886
|
+
connection: session.identity,
|
|
14887
|
+
human: reviewLines(review)
|
|
14888
|
+
};
|
|
14889
|
+
}
|
|
14890
|
+
},
|
|
14891
|
+
{
|
|
14892
|
+
path: ["review", "open"],
|
|
14893
|
+
action: "review.open",
|
|
14894
|
+
summary: "Open or focus a Multi Review's tab (--fix: its Fix tab); starts no work.",
|
|
14895
|
+
positionals: [{ name: "workflow", required: true, description: "Multi Review ID." }],
|
|
14896
|
+
options: [
|
|
14897
|
+
{ name: "fix", kind: "boolean", description: "Open the Fix session tab instead." },
|
|
14898
|
+
REQUEST_ID_OPTION
|
|
14899
|
+
],
|
|
14900
|
+
inputFields: { workflow: "workflowId", fix: "surface" },
|
|
14901
|
+
idOutput: "workflow ID",
|
|
14902
|
+
async run(context, parsed) {
|
|
14903
|
+
const workflowId = parsed.positionals.workflow;
|
|
14904
|
+
const { session, result, receipt, warnings } = await submit(context, "review.open", { workflowId, ...parsed.options.fix === true ? { surface: "fix" } : {} }, parsed.options);
|
|
14905
|
+
return {
|
|
14906
|
+
action: "review.open",
|
|
14907
|
+
result,
|
|
14908
|
+
receipt,
|
|
14909
|
+
warnings,
|
|
14910
|
+
connection: session.identity,
|
|
14911
|
+
ids: [workflowId],
|
|
14912
|
+
human: [`opened review ${workflowId}`]
|
|
14913
|
+
};
|
|
14914
|
+
}
|
|
14915
|
+
},
|
|
14916
|
+
reviewMutation("address", "review.address", "Address a Multi Review's findings: hand them to its Fix session."),
|
|
14917
|
+
reviewMutation("cancel", "review.cancel", "Cancel a running Multi Review; its record is kept."),
|
|
14918
|
+
prCommand("create"),
|
|
14919
|
+
prCommand("resolve")
|
|
14920
|
+
];
|
|
14921
|
+
|
|
13378
14922
|
// src/client/help.ts
|
|
13379
14923
|
var GROUP_SUMMARIES = {
|
|
13380
14924
|
connection: "Select and verify the backend this client talks to.",
|
|
@@ -13509,6 +15053,7 @@ class OutputWriter {
|
|
|
13509
15053
|
ok: true,
|
|
13510
15054
|
...outcome.connection ? { connection: outcome.connection } : {},
|
|
13511
15055
|
result: outcome.result ?? null,
|
|
15056
|
+
...outcome.node ? { node: outcome.node } : {},
|
|
13512
15057
|
...outcome.receipt ? { receipt: outcome.receipt } : {},
|
|
13513
15058
|
...outcome.warnings && outcome.warnings.length > 0 ? { warnings: outcome.warnings } : {}
|
|
13514
15059
|
};
|
|
@@ -13532,7 +15077,7 @@ class OutputWriter {
|
|
|
13532
15077
|
this.io.stderr(`warning: ${warning}
|
|
13533
15078
|
`);
|
|
13534
15079
|
if (outcome.receipt)
|
|
13535
|
-
this.io.stderr(`${receiptHint(outcome.receipt)}
|
|
15080
|
+
this.io.stderr(`${receiptHint(outcome.receipt, outcome.node)}
|
|
13536
15081
|
`);
|
|
13537
15082
|
return outcome.exitCodeOverride ?? 0;
|
|
13538
15083
|
}
|
|
@@ -13540,12 +15085,14 @@ class OutputWriter {
|
|
|
13540
15085
|
const cliError = error instanceof CliError ? error : new CliError("internal-error", error instanceof Error ? boundMessage(error.message) : "Unexpected client failure");
|
|
13541
15086
|
const exitCode = exitCodeForError(cliError.code);
|
|
13542
15087
|
const receipt = cliError.receipt ?? context.receipt;
|
|
15088
|
+
const node = cliError.node ?? context.node;
|
|
13543
15089
|
if (this.mode === "json" || this.mode === "jsonl") {
|
|
13544
15090
|
const envelope = {
|
|
13545
15091
|
schemaVersion: PUBLIC_API_SCHEMA_VERSION,
|
|
13546
15092
|
action: cliError.action ?? context.action ?? "cli",
|
|
13547
15093
|
ok: false,
|
|
13548
15094
|
...context.connection ? { connection: context.connection } : {},
|
|
15095
|
+
...node ? { node } : {},
|
|
13549
15096
|
error: {
|
|
13550
15097
|
code: cliError.code,
|
|
13551
15098
|
message: cliError.message,
|
|
@@ -13560,9 +15107,12 @@ class OutputWriter {
|
|
|
13560
15107
|
return exitCode;
|
|
13561
15108
|
}
|
|
13562
15109
|
this.io.stderr(`error [${cliError.code}]: ${cliError.message}
|
|
15110
|
+
`);
|
|
15111
|
+
if (typeof cliError.details?.recovery === "string")
|
|
15112
|
+
this.io.stderr(`recover with \`${cliError.details.recovery}\`
|
|
13563
15113
|
`);
|
|
13564
15114
|
if (receipt)
|
|
13565
|
-
this.io.stderr(`${receiptHint(receipt)}
|
|
15115
|
+
this.io.stderr(`${receiptHint(receipt, node)}
|
|
13566
15116
|
`);
|
|
13567
15117
|
return exitCode;
|
|
13568
15118
|
}
|
|
@@ -13573,6 +15123,7 @@ class OutputWriter {
|
|
|
13573
15123
|
this.io.stdout(`${JSON.stringify({
|
|
13574
15124
|
schemaVersion: PUBLIC_API_SCHEMA_VERSION,
|
|
13575
15125
|
action: context.action,
|
|
15126
|
+
...context.node ? { node: context.node } : {},
|
|
13576
15127
|
ok: false,
|
|
13577
15128
|
...context.connection ? { connection: context.connection } : {},
|
|
13578
15129
|
error: {
|
|
@@ -13589,13 +15140,13 @@ class OutputWriter {
|
|
|
13589
15140
|
this.io.stderr(`${message}
|
|
13590
15141
|
`);
|
|
13591
15142
|
if (context.receipt)
|
|
13592
|
-
this.io.stderr(`${receiptHint(context.receipt)}
|
|
15143
|
+
this.io.stderr(`${receiptHint(context.receipt, context.node)}
|
|
13593
15144
|
`);
|
|
13594
15145
|
return code;
|
|
13595
15146
|
}
|
|
13596
15147
|
}
|
|
13597
|
-
function receiptHint(receipt) {
|
|
13598
|
-
return `operation ${receipt.operationId} (${receipt.action}) is ${receipt.state}; inspect with \`orkestrator run get ${receipt.operationId}\``;
|
|
15148
|
+
function receiptHint(receipt, node) {
|
|
15149
|
+
return `operation ${receipt.operationId} (${receipt.action}) is ${receipt.state}; inspect with \`orkestrator run get ${receipt.operationId}${node ? ` --node ${node.nodeId}` : ""}\``;
|
|
13599
15150
|
}
|
|
13600
15151
|
function boundMessage(message, max = 500) {
|
|
13601
15152
|
const single = message.replace(/\s+/g, " ").trim();
|
|
@@ -13664,13 +15215,16 @@ var CLIENT_COMMANDS = [
|
|
|
13664
15215
|
...environmentCommands,
|
|
13665
15216
|
...sessionCommands,
|
|
13666
15217
|
...changesCommands,
|
|
15218
|
+
...workflowCommands,
|
|
13667
15219
|
...attentionCommands,
|
|
13668
15220
|
...ticketCommands,
|
|
13669
15221
|
...mailCommands,
|
|
15222
|
+
...scheduleCommands,
|
|
13670
15223
|
...runCommands,
|
|
13671
15224
|
...toolchainCommands,
|
|
13672
15225
|
...hostCommands,
|
|
13673
|
-
...serviceCommands
|
|
15226
|
+
...serviceCommands,
|
|
15227
|
+
...completionCommands(() => CLIENT_COMMANDS)
|
|
13674
15228
|
];
|
|
13675
15229
|
var COMMAND_TREE = { commands: CLIENT_COMMANDS };
|
|
13676
15230
|
function stripHelpWord(argv) {
|
|
@@ -13736,11 +15290,15 @@ async function runClient(argv, options2) {
|
|
|
13736
15290
|
session = new ClientSession(target, new LocalReceiptStore(configStore.receiptsDirectory), {
|
|
13737
15291
|
fetchImpl: options2.fetchImpl,
|
|
13738
15292
|
requestTimeoutMs: parsed.global.requestTimeoutMs,
|
|
13739
|
-
signal
|
|
15293
|
+
signal,
|
|
15294
|
+
...parsed.global.node ? { node: parsed.global.node } : {}
|
|
13740
15295
|
});
|
|
13741
15296
|
return session;
|
|
13742
15297
|
}
|
|
13743
15298
|
};
|
|
15299
|
+
if (command.spec.local && parsed.global.node) {
|
|
15300
|
+
return output.failure(new CliError("invalid-input", `'${command.spec.path.join(" ")}' does not use --node`), { action });
|
|
15301
|
+
}
|
|
13744
15302
|
if (command.spec.local && (parsed.global.connection || parsed.global.profile)) {
|
|
13745
15303
|
if (!(command.spec.path[0] === "connection" && command.spec.path[1] === "check")) {
|
|
13746
15304
|
return output.failure(new CliError("invalid-input", `'${command.spec.path.join(" ")}' does not use --connection or --profile`), { action });
|
|
@@ -13750,12 +15308,14 @@ async function runClient(argv, options2) {
|
|
|
13750
15308
|
try {
|
|
13751
15309
|
outcome = await command.spec.run(context, command);
|
|
13752
15310
|
} catch (error) {
|
|
13753
|
-
const
|
|
15311
|
+
const failedSession = session;
|
|
15312
|
+
const connection = failedSession?.identity;
|
|
13754
15313
|
if (signal.aborted && !(error instanceof CliError && error.code === "observation-interrupted")) {
|
|
13755
15314
|
return output.interrupted(signalName(signal), {
|
|
13756
15315
|
action,
|
|
13757
15316
|
...error instanceof CliError && error.receipt ? { receipt: error.receipt } : {},
|
|
13758
|
-
...connection ? { connection } : {}
|
|
15317
|
+
...connection ? { connection } : {},
|
|
15318
|
+
...failedSession?.nodeTag ? { node: failedSession.nodeTag } : {}
|
|
13759
15319
|
});
|
|
13760
15320
|
}
|
|
13761
15321
|
if (error instanceof CliError && error.code === "observation-interrupted") {
|
|
@@ -13763,12 +15323,22 @@ async function runClient(argv, options2) {
|
|
|
13763
15323
|
action: error.action ?? action,
|
|
13764
15324
|
details: error.details,
|
|
13765
15325
|
...error.receipt ? { receipt: error.receipt } : {},
|
|
13766
|
-
...connection ? { connection } : {}
|
|
15326
|
+
...connection ? { connection } : {},
|
|
15327
|
+
...failedSession?.nodeTag ? { node: failedSession.nodeTag } : {}
|
|
13767
15328
|
});
|
|
13768
15329
|
}
|
|
13769
|
-
return output.failure(error, {
|
|
15330
|
+
return output.failure(error, {
|
|
15331
|
+
action,
|
|
15332
|
+
...connection ? { connection } : {},
|
|
15333
|
+
...failedSession?.nodeTag ? { node: failedSession.nodeTag } : {}
|
|
15334
|
+
});
|
|
13770
15335
|
}
|
|
13771
|
-
|
|
15336
|
+
const activeSession = session;
|
|
15337
|
+
return output.success({
|
|
15338
|
+
...outcome,
|
|
15339
|
+
...activeSession?.nodeTag ? { node: activeSession.nodeTag } : {},
|
|
15340
|
+
warnings: [...new Set([...outcome.warnings ?? [], ...activeSession?.warnings ?? []])]
|
|
15341
|
+
});
|
|
13772
15342
|
}
|
|
13773
15343
|
async function runClientProcess(argv, version) {
|
|
13774
15344
|
const controller = new AbortController;
|