orkestrator 2.29.0-beta → 2.31.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 +2706 -517
- package/dist/main.js +220796 -192672
- package/package.json +5 -5
- package/resources/claude-bridge/dist/index.js +1038 -857
- package/resources/codex-bridge/dist/index.js +412 -81
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,206 @@ 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
|
+
},
|
|
6656
|
+
"attention.list": {
|
|
6657
|
+
version: 1,
|
|
6658
|
+
mutation: false,
|
|
6659
|
+
effects: [],
|
|
6660
|
+
observation: "immediate",
|
|
6661
|
+
milestone: "D",
|
|
6662
|
+
summary: "What needs attention: pending questions and approvals, the caller's runs that settled and environments whose setup finished, since a cursor."
|
|
6663
|
+
},
|
|
6664
|
+
"ticket.list": {
|
|
6665
|
+
version: 1,
|
|
6666
|
+
mutation: false,
|
|
6667
|
+
effects: [],
|
|
6668
|
+
observation: "immediate",
|
|
6669
|
+
milestone: "D",
|
|
6670
|
+
summary: "List a project's Kanban tickets."
|
|
6671
|
+
},
|
|
6672
|
+
"ticket.get": {
|
|
6673
|
+
version: 1,
|
|
6674
|
+
mutation: false,
|
|
6675
|
+
effects: [],
|
|
6676
|
+
observation: "immediate",
|
|
6677
|
+
milestone: "D",
|
|
6678
|
+
summary: "Read one Kanban ticket with its description, criteria and latest comments."
|
|
6679
|
+
},
|
|
6680
|
+
"ticket.create": {
|
|
6681
|
+
version: 1,
|
|
6682
|
+
mutation: true,
|
|
6683
|
+
effects: ["metadata"],
|
|
6684
|
+
observation: "receipt",
|
|
6685
|
+
milestone: "D",
|
|
6686
|
+
summary: "Create a Kanban ticket in a project."
|
|
6687
|
+
},
|
|
6688
|
+
"ticket.update": {
|
|
6689
|
+
version: 1,
|
|
6690
|
+
mutation: true,
|
|
6691
|
+
effects: ["metadata"],
|
|
6692
|
+
observation: "receipt",
|
|
6693
|
+
milestone: "D",
|
|
6694
|
+
summary: "Change a Kanban ticket's title, description, criteria or column."
|
|
6695
|
+
},
|
|
6696
|
+
"ticket.comment": {
|
|
6697
|
+
version: 1,
|
|
6698
|
+
mutation: true,
|
|
6699
|
+
effects: ["metadata"],
|
|
6700
|
+
observation: "receipt",
|
|
6701
|
+
milestone: "D",
|
|
6702
|
+
summary: "Append a comment to a Kanban ticket."
|
|
6703
|
+
},
|
|
6704
|
+
"mail.mailboxes": {
|
|
6705
|
+
version: 1,
|
|
6706
|
+
mutation: false,
|
|
6707
|
+
effects: [],
|
|
6708
|
+
observation: "immediate",
|
|
6709
|
+
milestone: "D",
|
|
6710
|
+
summary: "List agent mailboxes a message can be sent to."
|
|
6711
|
+
},
|
|
6712
|
+
"mail.send": {
|
|
6713
|
+
version: 1,
|
|
6714
|
+
mutation: true,
|
|
6715
|
+
effects: ["metadata"],
|
|
6716
|
+
observation: "receipt",
|
|
6717
|
+
milestone: "D",
|
|
6718
|
+
summary: "Place a message in one tab's inbox; external messages are never injected into a turn."
|
|
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
|
+
},
|
|
6560
6792
|
"run.get": {
|
|
6561
6793
|
version: 1,
|
|
6562
6794
|
mutation: false,
|
|
@@ -6696,6 +6928,7 @@ var PUBLIC_ERROR_EXIT = {
|
|
|
6696
6928
|
"not-found": PUBLIC_EXIT.notFound,
|
|
6697
6929
|
"ambiguous-target": PUBLIC_EXIT.notFound,
|
|
6698
6930
|
"history-expired": PUBLIC_EXIT.notFound,
|
|
6931
|
+
"node-not-found": PUBLIC_EXIT.notFound,
|
|
6699
6932
|
"connection-failed": PUBLIC_EXIT.connection,
|
|
6700
6933
|
"connection-not-configured": PUBLIC_EXIT.connection,
|
|
6701
6934
|
"profile-unavailable": PUBLIC_EXIT.connection,
|
|
@@ -6703,11 +6936,13 @@ var PUBLIC_ERROR_EXIT = {
|
|
|
6703
6936
|
"identity-mismatch": PUBLIC_EXIT.connection,
|
|
6704
6937
|
"response-invalid": PUBLIC_EXIT.connection,
|
|
6705
6938
|
"transport-uncertain": PUBLIC_EXIT.connection,
|
|
6939
|
+
"node-unavailable": PUBLIC_EXIT.connection,
|
|
6706
6940
|
"deadline-exceeded": PUBLIC_EXIT.deadline,
|
|
6707
6941
|
"observation-interrupted": PUBLIC_SIGNAL_EXIT.SIGINT,
|
|
6708
6942
|
"interaction-required": PUBLIC_EXIT.interactionRequired,
|
|
6709
6943
|
"dispatch-unknown": PUBLIC_EXIT.unknownDispatch,
|
|
6710
6944
|
"run-unknown": PUBLIC_EXIT.unknownDispatch,
|
|
6945
|
+
"node-outcome-unknown": PUBLIC_EXIT.unknownDispatch,
|
|
6711
6946
|
conflict: PUBLIC_EXIT.conflict,
|
|
6712
6947
|
"request-conflict": PUBLIC_EXIT.conflict,
|
|
6713
6948
|
"revision-conflict": PUBLIC_EXIT.conflict,
|
|
@@ -6723,7 +6958,8 @@ var PUBLIC_ERROR_EXIT = {
|
|
|
6723
6958
|
"target-mismatch": PUBLIC_EXIT.conflict,
|
|
6724
6959
|
"session-idle": PUBLIC_EXIT.conflict,
|
|
6725
6960
|
"interaction-stale": PUBLIC_EXIT.conflict,
|
|
6726
|
-
"dispatch-parked": PUBLIC_EXIT.conflict
|
|
6961
|
+
"dispatch-parked": PUBLIC_EXIT.conflict,
|
|
6962
|
+
"node-unsupported": PUBLIC_EXIT.conflict
|
|
6727
6963
|
};
|
|
6728
6964
|
function isPublicErrorCode(value) {
|
|
6729
6965
|
return typeof value === "string" && Object.hasOwn(PUBLIC_ERROR_EXIT, value);
|
|
@@ -6752,8 +6988,14 @@ var PUBLIC_API_LIMITS = Object.freeze({
|
|
|
6752
6988
|
transcriptTextMaxChars: 20000,
|
|
6753
6989
|
interactionsMax: 32,
|
|
6754
6990
|
historyMaxEntries: 100,
|
|
6991
|
+
attentionDefaultItems: 20,
|
|
6992
|
+
attentionMaxItems: 50,
|
|
6755
6993
|
responseMaxBytes: 8 * 1024 * 1024,
|
|
6756
6994
|
requestMaxBytes: 1024 * 1024,
|
|
6995
|
+
promptAttachmentsMaxItems: 8,
|
|
6996
|
+
promptAttachmentsMaxBytes: 256 * 1024,
|
|
6997
|
+
promptAttachmentNameMaxChars: 128,
|
|
6998
|
+
commandArgumentsMaxBytes: 64 * 1024,
|
|
6757
6999
|
execArgvMaxItems: 256,
|
|
6758
7000
|
execArgvMaxBytes: 256 * 1024,
|
|
6759
7001
|
execEnvMaxEntries: 64,
|
|
@@ -6792,6 +7034,17 @@ var TERMINAL_EXECUTION_PHASES = Object.freeze([
|
|
|
6792
7034
|
"cancelled",
|
|
6793
7035
|
"interrupted"
|
|
6794
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
|
+
}
|
|
6795
7048
|
function isPublicActionAvailable(capabilities, action) {
|
|
6796
7049
|
const entry = capabilities.actions[action];
|
|
6797
7050
|
return entry?.available === true && entry.version === PUBLIC_ACTIONS[action].version;
|
|
@@ -6812,7 +7065,7 @@ var OPERATION_STATES = [
|
|
|
6812
7065
|
function isPublicReceipt(value) {
|
|
6813
7066
|
if (!isRecord(value))
|
|
6814
7067
|
return false;
|
|
6815
|
-
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));
|
|
6816
7069
|
}
|
|
6817
7070
|
function isEnvelopeError(value) {
|
|
6818
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));
|
|
@@ -6829,6 +7082,9 @@ function isPublicActionResponse(value) {
|
|
|
6829
7082
|
if (value.backend !== undefined && !(isRecord(value.backend) && typeof value.backend.installationId === "string" && typeof value.backend.generation === "string")) {
|
|
6830
7083
|
return false;
|
|
6831
7084
|
}
|
|
7085
|
+
if (value.node !== undefined && !(isRecord(value.node) && typeof value.node.nodeId === "string" && typeof value.node.name === "string")) {
|
|
7086
|
+
return false;
|
|
7087
|
+
}
|
|
6832
7088
|
if (value.ok === true) {
|
|
6833
7089
|
return Object.hasOwn(value, "result") && value.error === undefined && (value.warnings === undefined || Array.isArray(value.warnings) && value.warnings.every((w) => typeof w === "string"));
|
|
6834
7090
|
}
|
|
@@ -6873,6 +7129,7 @@ function parsePublicDuration(value) {
|
|
|
6873
7129
|
// src/client/errors.ts
|
|
6874
7130
|
class CliError extends Error {
|
|
6875
7131
|
code;
|
|
7132
|
+
node;
|
|
6876
7133
|
details;
|
|
6877
7134
|
receipt;
|
|
6878
7135
|
retryable;
|
|
@@ -6881,6 +7138,7 @@ class CliError extends Error {
|
|
|
6881
7138
|
super(message);
|
|
6882
7139
|
this.name = "CliError";
|
|
6883
7140
|
this.code = code;
|
|
7141
|
+
this.node = options2.node;
|
|
6884
7142
|
this.details = options2.details;
|
|
6885
7143
|
this.receipt = options2.receipt;
|
|
6886
7144
|
this.retryable = options2.retryable;
|
|
@@ -6902,6 +7160,12 @@ var GLOBAL_OPTIONS = [
|
|
|
6902
7160
|
valueName: "NAME",
|
|
6903
7161
|
description: "Use a running isolated development profile (`mise run dev:test`)."
|
|
6904
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
|
+
},
|
|
6905
7169
|
{ name: "json", kind: "boolean", description: "Print exactly one JSON envelope on stdout." },
|
|
6906
7170
|
{
|
|
6907
7171
|
name: "output",
|
|
@@ -7100,6 +7364,12 @@ function parseInvocation(tree, argv) {
|
|
|
7100
7364
|
}
|
|
7101
7365
|
if (typeof globalState.values.profile === "string")
|
|
7102
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
|
+
}
|
|
7103
7373
|
if (global2.connection && global2.profile) {
|
|
7104
7374
|
throw new CliError("invalid-input", "--connection and --profile select different backends; pass only one");
|
|
7105
7375
|
}
|
|
@@ -7444,7 +7714,7 @@ import path4 from "path";
|
|
|
7444
7714
|
// ../../apps/backend/package.json
|
|
7445
7715
|
var package_default = {
|
|
7446
7716
|
name: "@orkestrator/backend",
|
|
7447
|
-
version: "2.
|
|
7717
|
+
version: "2.31.0-beta",
|
|
7448
7718
|
private: true,
|
|
7449
7719
|
type: "module",
|
|
7450
7720
|
exports: {
|
|
@@ -7459,7 +7729,7 @@ var package_default = {
|
|
|
7459
7729
|
"start:web-public": "bun dist/main.js --tailscale-serve --allowed-origins https://orkestrator.dev,https://www.orkestrator.dev",
|
|
7460
7730
|
test: "bun test --preload ../../tests/setup-node.ts src tests --parallel",
|
|
7461
7731
|
"test:workspace": "bun test --preload ../../tests/setup-node.ts src tests --only-failures ${ORKESTRATOR_TEST_TIMINGS_DIR:+--timings=$ORKESTRATOR_TEST_TIMINGS_DIR/backend.json --update-timings} $ORKESTRATOR_BACKEND_TEST_IGNORE --parallel=${ORKESTRATOR_BACKEND_TEST_WORKERS:-${ORKESTRATOR_TEST_WORKERS:-2}}",
|
|
7462
|
-
typecheck: "
|
|
7732
|
+
typecheck: "bun check -p tsconfig.json && bun check -p tsconfig.test.json"
|
|
7463
7733
|
},
|
|
7464
7734
|
dependencies: {
|
|
7465
7735
|
"@modelcontextprotocol/node": "2.1.1",
|
|
@@ -7998,247 +8268,15 @@ function runFixProcess(argv, version) {
|
|
|
7998
8268
|
});
|
|
7999
8269
|
}
|
|
8000
8270
|
|
|
8001
|
-
//
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
type: "agent",
|
|
8011
|
-
inherits: true,
|
|
8012
|
-
application,
|
|
8013
|
-
description: "Agent platform used when a launch does not name one."
|
|
8014
|
-
},
|
|
8015
|
-
...AGENT_PLATFORM_KEYS.flatMap((platform) => [
|
|
8016
|
-
{
|
|
8017
|
-
key: `agent.${platform}.mode`,
|
|
8018
|
-
scope,
|
|
8019
|
-
type: "agent-mode",
|
|
8020
|
-
inherits: true,
|
|
8021
|
-
application,
|
|
8022
|
-
description: `Launch mode (native or terminal) for ${platform}.`
|
|
8023
|
-
},
|
|
8024
|
-
{
|
|
8025
|
-
key: `agent.${platform}.model`,
|
|
8026
|
-
scope,
|
|
8027
|
-
type: "string",
|
|
8028
|
-
inherits: true,
|
|
8029
|
-
application,
|
|
8030
|
-
description: `Default model for new ${platform} sessions.`
|
|
8031
|
-
},
|
|
8032
|
-
{
|
|
8033
|
-
key: `agent.${platform}.reasoningEffort`,
|
|
8034
|
-
scope,
|
|
8035
|
-
type: "string",
|
|
8036
|
-
inherits: true,
|
|
8037
|
-
application,
|
|
8038
|
-
description: `Default reasoning level for new ${platform} sessions.`
|
|
8039
|
-
},
|
|
8040
|
-
{
|
|
8041
|
-
key: `agent.${platform}.fastMode`,
|
|
8042
|
-
scope,
|
|
8043
|
-
type: "boolean",
|
|
8044
|
-
inherits: true,
|
|
8045
|
-
application,
|
|
8046
|
-
description: `Default speed for new ${platform} sessions (true is Fast).`
|
|
8047
|
-
}
|
|
8048
|
-
])
|
|
8049
|
-
];
|
|
8050
|
-
}
|
|
8051
|
-
var PUBLIC_PROJECT_SETTINGS = Object.freeze([
|
|
8052
|
-
{
|
|
8053
|
-
key: "defaultBranch",
|
|
8054
|
-
scope: "project",
|
|
8055
|
-
type: "string",
|
|
8056
|
-
inherits: false,
|
|
8057
|
-
application: "next-environment",
|
|
8058
|
-
description: "Branch new environments are created from."
|
|
8059
|
-
},
|
|
8060
|
-
{
|
|
8061
|
-
key: "prBaseBranch",
|
|
8062
|
-
scope: "project",
|
|
8063
|
-
type: "string",
|
|
8064
|
-
inherits: false,
|
|
8065
|
-
application: "applied",
|
|
8066
|
-
description: "Base branch for pull requests and diff baselines."
|
|
8067
|
-
},
|
|
8068
|
-
{
|
|
8069
|
-
key: "defaultPortMappings",
|
|
8070
|
-
scope: "project",
|
|
8071
|
-
type: "port-mappings",
|
|
8072
|
-
inherits: false,
|
|
8073
|
-
environmentTypes: ["container"],
|
|
8074
|
-
application: "next-environment",
|
|
8075
|
-
description: "Port mappings copied into new container environments."
|
|
8076
|
-
},
|
|
8077
|
-
{
|
|
8078
|
-
key: "filesToCopy",
|
|
8079
|
-
scope: "project",
|
|
8080
|
-
type: "string-list",
|
|
8081
|
-
inherits: false,
|
|
8082
|
-
application: "next-environment",
|
|
8083
|
-
description: "Untracked files copied into new environments."
|
|
8084
|
-
},
|
|
8085
|
-
{
|
|
8086
|
-
key: "entryPort",
|
|
8087
|
-
scope: "project",
|
|
8088
|
-
type: "port",
|
|
8089
|
-
inherits: false,
|
|
8090
|
-
application: "next-environment",
|
|
8091
|
-
description: "Default preview entry port for new environments."
|
|
8092
|
-
},
|
|
8093
|
-
...agentSettingDescriptors("project")
|
|
8094
|
-
]);
|
|
8095
|
-
var PUBLIC_ENVIRONMENT_SETTINGS = Object.freeze([
|
|
8096
|
-
{
|
|
8097
|
-
key: "portMappings",
|
|
8098
|
-
scope: "environment",
|
|
8099
|
-
type: "port-mappings",
|
|
8100
|
-
inherits: false,
|
|
8101
|
-
environmentTypes: ["container"],
|
|
8102
|
-
application: "next-start",
|
|
8103
|
-
description: "Container port mappings; a running container adopts them on recreate."
|
|
8104
|
-
},
|
|
8105
|
-
{
|
|
8106
|
-
key: "allowedDomains",
|
|
8107
|
-
scope: "environment",
|
|
8108
|
-
type: "domain-list",
|
|
8109
|
-
inherits: true,
|
|
8110
|
-
environmentTypes: ["container"],
|
|
8111
|
-
application: "next-start",
|
|
8112
|
-
description: "Restricted-network allowlist; unset inherits the global list. A running container whose image supports it applies the change immediately (reported as applied)."
|
|
8113
|
-
},
|
|
8114
|
-
...agentSettingDescriptors("environment")
|
|
8115
|
-
]);
|
|
8116
|
-
|
|
8117
|
-
// src/client/format.ts
|
|
8118
|
-
function table(headers, rows) {
|
|
8119
|
-
if (rows.length === 0)
|
|
8120
|
-
return ["(none)"];
|
|
8121
|
-
const widths = headers.map((header, index) => Math.min(60, Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length))));
|
|
8122
|
-
const line = (cells) => cells.map((cell, index) => {
|
|
8123
|
-
const text = cell.length > widths[index] ? `${cell.slice(0, widths[index] - 1)}\u2026` : cell;
|
|
8124
|
-
return text.padEnd(widths[index]);
|
|
8125
|
-
}).join(" ").trimEnd();
|
|
8126
|
-
return [line(headers), ...rows.map(line)];
|
|
8127
|
-
}
|
|
8128
|
-
function keyValues(entries) {
|
|
8129
|
-
const width = Math.max(...entries.map(([key]) => key.length));
|
|
8130
|
-
return entries.filter(([, value]) => value !== undefined).map(([key, value]) => {
|
|
8131
|
-
const text = value === null ? "-" : typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
8132
|
-
return `${key.padEnd(width)} ${text}`;
|
|
8133
|
-
});
|
|
8134
|
-
}
|
|
8135
|
-
function projectLines(project) {
|
|
8136
|
-
return keyValues([
|
|
8137
|
-
["id", project.id],
|
|
8138
|
-
["name", project.name],
|
|
8139
|
-
["remote", project.gitUrl],
|
|
8140
|
-
["path", project.localPath],
|
|
8141
|
-
["folder", project.folder],
|
|
8142
|
-
["environments", project.environmentCount],
|
|
8143
|
-
["revision", project.revision]
|
|
8144
|
-
]);
|
|
8145
|
-
}
|
|
8146
|
-
function projectRows(projects) {
|
|
8147
|
-
return table(["ID", "NAME", "ENVS", "PATH"], projects.map((project) => [
|
|
8148
|
-
project.id,
|
|
8149
|
-
project.name,
|
|
8150
|
-
String(project.environmentCount),
|
|
8151
|
-
project.localPath ?? "-"
|
|
8152
|
-
]));
|
|
8153
|
-
}
|
|
8154
|
-
function environmentLines(environment) {
|
|
8155
|
-
return keyValues([
|
|
8156
|
-
["id", environment.id],
|
|
8157
|
-
["project", environment.projectId],
|
|
8158
|
-
["name", environment.name],
|
|
8159
|
-
["branch", environment.branch],
|
|
8160
|
-
["type", environment.environmentType],
|
|
8161
|
-
["status", environment.status],
|
|
8162
|
-
["ready", environment.ready],
|
|
8163
|
-
["setup", `${environment.setup.phase}${environment.setup.overridden ? " (overridden)" : ""}`],
|
|
8164
|
-
["activity", environment.activity.state],
|
|
8165
|
-
["lifecycle error", environment.lifecycle.error],
|
|
8166
|
-
["workspace", environment.workspacePath],
|
|
8167
|
-
["base", environment.base.commit ?? environment.base.branch],
|
|
8168
|
-
["startup session", environment.startupSession?.sessionId]
|
|
8169
|
-
]);
|
|
8170
|
-
}
|
|
8171
|
-
function environmentRows(environments) {
|
|
8172
|
-
return table(["ID", "NAME", "TYPE", "STATUS", "READY"], environments.map((environment) => [
|
|
8173
|
-
environment.id,
|
|
8174
|
-
environment.name,
|
|
8175
|
-
environment.environmentType,
|
|
8176
|
-
environment.status,
|
|
8177
|
-
environment.ready ? "yes" : "no"
|
|
8178
|
-
]));
|
|
8179
|
-
}
|
|
8180
|
-
function sessionRows(sessions) {
|
|
8181
|
-
return table(["SESSION", "AGENT", "TAB", "ACTIVITY", "TITLE"], sessions.map((session) => [
|
|
8182
|
-
session.id,
|
|
8183
|
-
session.agent,
|
|
8184
|
-
session.tabId,
|
|
8185
|
-
session.activity,
|
|
8186
|
-
session.title ?? ""
|
|
8187
|
-
]));
|
|
8188
|
-
}
|
|
8189
|
-
function sessionLines(session) {
|
|
8190
|
-
return keyValues([
|
|
8191
|
-
["id", session.id],
|
|
8192
|
-
["environment", session.environmentId],
|
|
8193
|
-
["tab", session.tabId],
|
|
8194
|
-
["agent", session.agent],
|
|
8195
|
-
["title", session.title],
|
|
8196
|
-
["activity", session.activity],
|
|
8197
|
-
["latest request", session.latestRequestId],
|
|
8198
|
-
["pending interactions", session.pendingInteractionCount],
|
|
8199
|
-
["recoverable dispatch", session.recoverableDispatch]
|
|
8200
|
-
]);
|
|
8201
|
-
}
|
|
8202
|
-
function receiptLines(receipt) {
|
|
8203
|
-
return keyValues([
|
|
8204
|
-
["operation", receipt.operationId],
|
|
8205
|
-
["action", receipt.action],
|
|
8206
|
-
["state", receipt.state],
|
|
8207
|
-
["stage", receipt.stage],
|
|
8208
|
-
["request", `${receipt.requestId} (${receipt.namespace})`],
|
|
8209
|
-
["replayed", receipt.replayed],
|
|
8210
|
-
["resources", receipt.resources],
|
|
8211
|
-
["dispatch", receipt.dispatch?.state],
|
|
8212
|
-
["execution", receipt.execution?.state],
|
|
8213
|
-
["evidence", receipt.execution?.evidence],
|
|
8214
|
-
["exit code", receipt.execution?.exitCode],
|
|
8215
|
-
["error", receipt.error ? `${receipt.error.code}: ${receipt.error.message}` : undefined],
|
|
8216
|
-
["updated", receipt.updatedAt],
|
|
8217
|
-
["retained until", receipt.retainedUntil]
|
|
8218
|
-
]);
|
|
8219
|
-
}
|
|
8220
|
-
function settingsLines(snapshot) {
|
|
8221
|
-
return [
|
|
8222
|
-
`revision ${snapshot.revision}`,
|
|
8223
|
-
...table(["KEY", "VALUE", "EFFECTIVE", "SOURCE", "APPLIES"], snapshot.settings.map((setting) => [
|
|
8224
|
-
setting.key,
|
|
8225
|
-
setting.value === null || setting.value === undefined ? "-" : JSON.stringify(setting.value),
|
|
8226
|
-
setting.effective === null || setting.effective === undefined ? "-" : JSON.stringify(setting.effective),
|
|
8227
|
-
setting.source,
|
|
8228
|
-
setting.application
|
|
8229
|
-
]))
|
|
8230
|
-
];
|
|
8231
|
-
}
|
|
8232
|
-
|
|
8233
|
-
// src/client/wait.ts
|
|
8234
|
-
class ObservationStopped extends Error {
|
|
8235
|
-
reason;
|
|
8236
|
-
signalName;
|
|
8237
|
-
constructor(reason, signalName) {
|
|
8238
|
-
super(reason === "deadline" ? "Observation deadline passed" : "Observation interrupted");
|
|
8239
|
-
this.reason = reason;
|
|
8240
|
-
this.signalName = signalName;
|
|
8241
|
-
}
|
|
8271
|
+
// src/client/wait.ts
|
|
8272
|
+
class ObservationStopped extends Error {
|
|
8273
|
+
reason;
|
|
8274
|
+
signalName;
|
|
8275
|
+
constructor(reason, signalName) {
|
|
8276
|
+
super(reason === "deadline" ? "Observation deadline passed" : "Observation interrupted");
|
|
8277
|
+
this.reason = reason;
|
|
8278
|
+
this.signalName = signalName;
|
|
8279
|
+
}
|
|
8242
8280
|
}
|
|
8243
8281
|
function defaultSleep(ms, signal) {
|
|
8244
8282
|
return new Promise((resolve) => {
|
|
@@ -8284,6 +8322,61 @@ async function observe(check, options2) {
|
|
|
8284
8322
|
}
|
|
8285
8323
|
}
|
|
8286
8324
|
|
|
8325
|
+
// ../protocol/src/public-api-wait.ts
|
|
8326
|
+
function isTerminalPublicReceipt(receipt) {
|
|
8327
|
+
return !["admitted", "running", "unknown"].includes(receipt.state);
|
|
8328
|
+
}
|
|
8329
|
+
function classifyReceiptForWait(receipt, options2 = {}) {
|
|
8330
|
+
if (isTerminalPublicReceipt(receipt)) {
|
|
8331
|
+
return { kind: "terminal", succeeded: receipt.state === "succeeded" };
|
|
8332
|
+
}
|
|
8333
|
+
const execution = receipt.execution;
|
|
8334
|
+
if (execution?.state === "waiting-for-input" && !options2.continueOnInteraction) {
|
|
8335
|
+
return {
|
|
8336
|
+
kind: "stopped",
|
|
8337
|
+
code: "interaction-required",
|
|
8338
|
+
message: "The run is waiting for an answer to a pending interaction",
|
|
8339
|
+
interactions: execution.interactions ?? []
|
|
8340
|
+
};
|
|
8341
|
+
}
|
|
8342
|
+
if (receipt.state === "unknown" && receipt.dispatch?.state === "unknown") {
|
|
8343
|
+
return {
|
|
8344
|
+
kind: "stopped",
|
|
8345
|
+
code: "dispatch-unknown",
|
|
8346
|
+
message: "The provider may or may not have received the prompt; retry it under the same key or discard it",
|
|
8347
|
+
retryable: true
|
|
8348
|
+
};
|
|
8349
|
+
}
|
|
8350
|
+
if (receipt.state === "unknown") {
|
|
8351
|
+
return {
|
|
8352
|
+
kind: "stopped",
|
|
8353
|
+
code: "run-unknown",
|
|
8354
|
+
message: receipt.error?.message ?? "The outcome of this run is not known"
|
|
8355
|
+
};
|
|
8356
|
+
}
|
|
8357
|
+
if (execution?.state === "unsupported") {
|
|
8358
|
+
return {
|
|
8359
|
+
kind: "stopped",
|
|
8360
|
+
code: "capability-unavailable",
|
|
8361
|
+
message: execution.reason ?? "Completion of this run cannot be observed for its provider"
|
|
8362
|
+
};
|
|
8363
|
+
}
|
|
8364
|
+
return { kind: "pending" };
|
|
8365
|
+
}
|
|
8366
|
+
function terminalReceiptErrorCode(receipt) {
|
|
8367
|
+
if (receipt.state === "succeeded")
|
|
8368
|
+
return null;
|
|
8369
|
+
if (receipt.state === "partial")
|
|
8370
|
+
return "partial-failure";
|
|
8371
|
+
if (receipt.state === "cancelled")
|
|
8372
|
+
return "run-cancelled";
|
|
8373
|
+
if (receipt.state === "interrupted")
|
|
8374
|
+
return "run-interrupted";
|
|
8375
|
+
if (receipt.state === "unknown")
|
|
8376
|
+
return "run-unknown";
|
|
8377
|
+
return receipt.error?.code ?? "operation-failed";
|
|
8378
|
+
}
|
|
8379
|
+
|
|
8287
8380
|
// src/client/commands/common.ts
|
|
8288
8381
|
var REQUEST_ID_OPTION = {
|
|
8289
8382
|
name: "request-id",
|
|
@@ -8343,7 +8436,7 @@ async function submitForWait(context, action, input, options2, timeoutMs) {
|
|
|
8343
8436
|
} catch (error) {
|
|
8344
8437
|
const requestId = error instanceof CliError ? error.details?.requestId : undefined;
|
|
8345
8438
|
const namespace = error instanceof CliError ? error.details?.namespace : undefined;
|
|
8346
|
-
if (!(error instanceof CliError) ||
|
|
8439
|
+
if (!(error instanceof CliError) || !["transport-uncertain", "node-outcome-unknown"].includes(error.code) || typeof requestId !== "string" || typeof namespace !== "string")
|
|
8347
8440
|
throw error;
|
|
8348
8441
|
let receipt;
|
|
8349
8442
|
try {
|
|
@@ -8387,139 +8480,485 @@ async function submitForWait(context, action, input, options2, timeoutMs) {
|
|
|
8387
8480
|
};
|
|
8388
8481
|
}
|
|
8389
8482
|
}
|
|
8390
|
-
async function readOperation(session, operationId, timeoutMs) {
|
|
8391
|
-
const { receipt } = await session.readWithReceipt("run.get", { operationId }, timeoutMs);
|
|
8392
|
-
if (!receipt)
|
|
8393
|
-
throw new CliError("response-invalid", "run.get returned no receipt");
|
|
8394
|
-
return receipt;
|
|
8483
|
+
async function readOperation(session, operationId, timeoutMs) {
|
|
8484
|
+
const { receipt } = await session.readWithReceipt("run.get", { operationId }, timeoutMs);
|
|
8485
|
+
if (!receipt)
|
|
8486
|
+
throw new CliError("response-invalid", "run.get returned no receipt");
|
|
8487
|
+
return receipt;
|
|
8488
|
+
}
|
|
8489
|
+
function isTerminalReceipt(receipt) {
|
|
8490
|
+
return isTerminalPublicReceipt(receipt);
|
|
8491
|
+
}
|
|
8492
|
+
function stoppedError(error, action, receipt, what) {
|
|
8493
|
+
if (error.reason === "signal") {
|
|
8494
|
+
return new CliError("observation-interrupted", `Stopped observing ${what}; it continues in the backend`, {
|
|
8495
|
+
action,
|
|
8496
|
+
...receipt ? { receipt } : {},
|
|
8497
|
+
details: { signal: error.signalName ?? "SIGINT" }
|
|
8498
|
+
});
|
|
8499
|
+
}
|
|
8500
|
+
return new CliError("deadline-exceeded", `Timed out observing ${what}; it continues in the backend`, {
|
|
8501
|
+
action,
|
|
8502
|
+
...receipt ? { receipt } : {}
|
|
8503
|
+
});
|
|
8504
|
+
}
|
|
8505
|
+
function receiptFailure(receipt) {
|
|
8506
|
+
const code = terminalReceiptErrorCode(receipt);
|
|
8507
|
+
if (!code)
|
|
8508
|
+
return;
|
|
8509
|
+
const message = receipt.error?.message ?? `Operation ended ${receipt.state}`;
|
|
8510
|
+
return new CliError(code, message, { receipt, action: receipt.action });
|
|
8511
|
+
}
|
|
8512
|
+
var CLI_STOP_MESSAGES = {
|
|
8513
|
+
"dispatch-unknown": "The provider may or may not have received the prompt; use `run retry` (same key) or `run discard`"
|
|
8514
|
+
};
|
|
8515
|
+
async function waitForOperation(context, session, initial, timeoutMs, options2 = {}) {
|
|
8516
|
+
let latest = initial;
|
|
8517
|
+
const deadline = Date.now() + timeoutMs;
|
|
8518
|
+
try {
|
|
8519
|
+
return await observe(async () => {
|
|
8520
|
+
if (Date.now() >= deadline)
|
|
8521
|
+
throw new ObservationStopped("deadline");
|
|
8522
|
+
try {
|
|
8523
|
+
latest = await readOperation(session, initial.operationId, Math.max(1, deadline - Date.now()));
|
|
8524
|
+
} catch (error) {
|
|
8525
|
+
if (context.signal?.aborted)
|
|
8526
|
+
throw new ObservationStopped("signal", signalName(context.signal));
|
|
8527
|
+
if (Date.now() >= deadline && error instanceof CliError && error.code === "connection-failed")
|
|
8528
|
+
throw new ObservationStopped("deadline");
|
|
8529
|
+
throw error;
|
|
8530
|
+
}
|
|
8531
|
+
const verdict = classifyReceiptForWait(latest, options2);
|
|
8532
|
+
if (verdict.kind === "terminal")
|
|
8533
|
+
return { done: true, value: latest };
|
|
8534
|
+
if (verdict.kind === "stopped") {
|
|
8535
|
+
throw new CliError(verdict.code, CLI_STOP_MESSAGES[verdict.code] ?? verdict.message, {
|
|
8536
|
+
receipt: latest,
|
|
8537
|
+
action: latest.action,
|
|
8538
|
+
...verdict.retryable ? { retryable: true } : {},
|
|
8539
|
+
...verdict.interactions ? { details: { interactions: verdict.interactions } } : {}
|
|
8540
|
+
});
|
|
8541
|
+
}
|
|
8542
|
+
return { done: false };
|
|
8543
|
+
}, { timeoutMs, signal: context.signal });
|
|
8544
|
+
} catch (error) {
|
|
8545
|
+
if (error instanceof ObservationStopped) {
|
|
8546
|
+
throw stoppedError(error, latest.action, latest, "the operation");
|
|
8547
|
+
}
|
|
8548
|
+
throw error;
|
|
8549
|
+
}
|
|
8550
|
+
}
|
|
8551
|
+
async function waitForEnvironment(context, session, environmentId, condition, receipt, timeoutMs) {
|
|
8552
|
+
let latestReceipt = receipt;
|
|
8553
|
+
const conditionMet = (environment) => {
|
|
8554
|
+
if (condition === "deleted")
|
|
8555
|
+
return environment === null;
|
|
8556
|
+
if (!environment)
|
|
8557
|
+
return false;
|
|
8558
|
+
if (condition === "running")
|
|
8559
|
+
return environment.status === "running";
|
|
8560
|
+
if (condition === "stopped")
|
|
8561
|
+
return environment.status === "stopped";
|
|
8562
|
+
return environment.ready;
|
|
8563
|
+
};
|
|
8564
|
+
try {
|
|
8565
|
+
return await observe(async () => {
|
|
8566
|
+
if (latestReceipt && !isTerminalReceipt(latestReceipt)) {
|
|
8567
|
+
latestReceipt = await readOperation(session, latestReceipt.operationId);
|
|
8568
|
+
}
|
|
8569
|
+
if (latestReceipt && isTerminalReceipt(latestReceipt)) {
|
|
8570
|
+
const failure = receiptFailure(latestReceipt);
|
|
8571
|
+
if (failure)
|
|
8572
|
+
throw failure;
|
|
8573
|
+
}
|
|
8574
|
+
let environment;
|
|
8575
|
+
try {
|
|
8576
|
+
environment = await session.read("environment.get", {
|
|
8577
|
+
environmentId
|
|
8578
|
+
});
|
|
8579
|
+
} catch (error) {
|
|
8580
|
+
if (error instanceof CliError && error.code === "not-found")
|
|
8581
|
+
environment = null;
|
|
8582
|
+
else
|
|
8583
|
+
throw error;
|
|
8584
|
+
}
|
|
8585
|
+
if (condition !== "deleted" && !environment) {
|
|
8586
|
+
throw new CliError("not-found", "The environment no longer exists", latestReceipt ? { receipt: latestReceipt } : {});
|
|
8587
|
+
}
|
|
8588
|
+
if (environment && (condition === "ready" || condition === "running")) {
|
|
8589
|
+
if (environment.setup.phase === "failed" || environment.status === "error") {
|
|
8590
|
+
throw new CliError("setup-failed", environment.lifecycle.error ?? "Environment setup failed", { ...latestReceipt ? { receipt: latestReceipt } : {}, details: { environmentId } });
|
|
8591
|
+
}
|
|
8592
|
+
}
|
|
8593
|
+
const operationSettled = !latestReceipt || isTerminalReceipt(latestReceipt) || condition === "running" && latestReceipt.stage === "setup";
|
|
8594
|
+
if (operationSettled && conditionMet(environment)) {
|
|
8595
|
+
return {
|
|
8596
|
+
done: true,
|
|
8597
|
+
value: { environment, ...latestReceipt ? { receipt: latestReceipt } : {} }
|
|
8598
|
+
};
|
|
8599
|
+
}
|
|
8600
|
+
return { done: false };
|
|
8601
|
+
}, { timeoutMs, signal: context.signal });
|
|
8602
|
+
} catch (error) {
|
|
8603
|
+
if (error instanceof ObservationStopped) {
|
|
8604
|
+
throw stoppedError(error, latestReceipt?.action ?? "environment.get", latestReceipt, `the environment (${condition})`);
|
|
8605
|
+
}
|
|
8606
|
+
throw error;
|
|
8607
|
+
}
|
|
8608
|
+
}
|
|
8609
|
+
|
|
8610
|
+
// src/client/commands/attention.ts
|
|
8611
|
+
function attentionLine(item) {
|
|
8612
|
+
const tagged = item;
|
|
8613
|
+
const prefix = tagged.nodeId ? `[${tagged.nodeName ?? tagged.nodeId} --node ${tagged.nodeId}] ` : "";
|
|
8614
|
+
return prefix + attentionDetail(item);
|
|
8615
|
+
}
|
|
8616
|
+
function attentionDetail(item) {
|
|
8617
|
+
if (item.kind === "interaction") {
|
|
8618
|
+
return `${item.blocking ? "waiting" : "pending"} ${item.agent} in ${item.environmentName}: ${item.title} (${item.sessionId} ${item.interactionId} rev ${item.revision})`;
|
|
8619
|
+
}
|
|
8620
|
+
if (item.kind === "setup") {
|
|
8621
|
+
return `setup ${item.phase} ${item.environmentName} (${item.environmentId})`;
|
|
8622
|
+
}
|
|
8623
|
+
return `run ${item.state} ${item.action}${item.errorCode ? ` ${item.errorCode}` : ""} (${item.operationId})`;
|
|
8624
|
+
}
|
|
8625
|
+
function itemKey(item) {
|
|
8626
|
+
if (item.kind === "interaction")
|
|
8627
|
+
return `${item.nodeId ?? ""}:interaction:${item.interactionId}:${item.revision}`;
|
|
8628
|
+
if (item.kind === "setup")
|
|
8629
|
+
return `${item.nodeId ?? ""}:setup:${item.environmentId}:${item.at}`;
|
|
8630
|
+
return `${item.nodeId ?? ""}:run:${item.operationId}:${item.state}`;
|
|
8631
|
+
}
|
|
8632
|
+
var attentionCommands = [
|
|
8633
|
+
{
|
|
8634
|
+
path: ["attention"],
|
|
8635
|
+
action: "attention.list",
|
|
8636
|
+
summary: "What needs attention: questions, approvals, settled runs, finished setups.",
|
|
8637
|
+
description: "Lists pending questions and approvals (always), and the runs this client started and setups that finished since --cursor (default: the last hour). --follow --jsonl polls and prints each new item once, which is how a voice runtime or notifier can hear about work without asking; it stops at --timeout or Ctrl+C and never changes anything.",
|
|
8638
|
+
positionals: [],
|
|
8639
|
+
options: [
|
|
8640
|
+
{
|
|
8641
|
+
name: "cursor",
|
|
8642
|
+
kind: "string",
|
|
8643
|
+
valueName: "CURSOR",
|
|
8644
|
+
description: "cursor from a previous read: only what changed since."
|
|
8645
|
+
},
|
|
8646
|
+
{ name: "limit", kind: "integer", valueName: "N", description: "Most items (max 50)." },
|
|
8647
|
+
{
|
|
8648
|
+
name: "follow",
|
|
8649
|
+
kind: "boolean",
|
|
8650
|
+
description: "Keep polling and print new items (use --jsonl for machines)."
|
|
8651
|
+
},
|
|
8652
|
+
TIMEOUT_OPTION
|
|
8653
|
+
],
|
|
8654
|
+
jsonl: true,
|
|
8655
|
+
async run(context, parsed) {
|
|
8656
|
+
const session = await context.session();
|
|
8657
|
+
const input = {};
|
|
8658
|
+
if (parsed.options.cursor !== undefined)
|
|
8659
|
+
input.cursor = parsed.options.cursor;
|
|
8660
|
+
if (parsed.options.limit !== undefined)
|
|
8661
|
+
input.limit = parsed.options.limit;
|
|
8662
|
+
if (parsed.options.follow === true && context.global.node === "all") {
|
|
8663
|
+
throw new CliError("invalid-input", "--follow follows one node; pass its --node");
|
|
8664
|
+
}
|
|
8665
|
+
if (parsed.options.follow !== true) {
|
|
8666
|
+
if (context.global.output === "jsonl") {
|
|
8667
|
+
throw new CliError("invalid-input", "--jsonl needs --follow");
|
|
8668
|
+
}
|
|
8669
|
+
const page = await session.read("attention.list", input);
|
|
8670
|
+
return {
|
|
8671
|
+
action: "attention.list",
|
|
8672
|
+
result: page,
|
|
8673
|
+
connection: session.identity,
|
|
8674
|
+
human: [
|
|
8675
|
+
...page.items.length ? page.items.map(attentionLine) : ["Nothing needs attention."],
|
|
8676
|
+
...page.more ? [`and ${page.more} more (raise --limit)`] : [],
|
|
8677
|
+
...page.cursor ? [`cursor: ${page.cursor}`] : (page.nodes ?? []).filter((node) => node.cursor).map((node) => `cursor (${node.name}): ${node.cursor}`)
|
|
8678
|
+
]
|
|
8679
|
+
};
|
|
8680
|
+
}
|
|
8681
|
+
const seen = new Set;
|
|
8682
|
+
let cursor = input.cursor;
|
|
8683
|
+
let emitted = 0;
|
|
8684
|
+
try {
|
|
8685
|
+
await observe(async () => {
|
|
8686
|
+
const page = await session.read("attention.list", {
|
|
8687
|
+
...input,
|
|
8688
|
+
...cursor ? { cursor } : {}
|
|
8689
|
+
});
|
|
8690
|
+
cursor = page.cursor;
|
|
8691
|
+
for (const item of page.items) {
|
|
8692
|
+
const key = itemKey(item);
|
|
8693
|
+
if (seen.has(key))
|
|
8694
|
+
continue;
|
|
8695
|
+
seen.add(key);
|
|
8696
|
+
emitted += 1;
|
|
8697
|
+
if (context.global.output === "jsonl") {
|
|
8698
|
+
context.io.stdout(`${JSON.stringify({ type: "attention", item })}
|
|
8699
|
+
`);
|
|
8700
|
+
} else if (context.global.output === "human") {
|
|
8701
|
+
context.io.stdout(`${attentionLine(item)}
|
|
8702
|
+
`);
|
|
8703
|
+
}
|
|
8704
|
+
}
|
|
8705
|
+
if (seen.size > 1e4)
|
|
8706
|
+
seen.clear();
|
|
8707
|
+
return { done: false };
|
|
8708
|
+
}, {
|
|
8709
|
+
timeoutMs: parsed.options.timeout ?? DEFAULT_WAIT_MS,
|
|
8710
|
+
signal: context.signal
|
|
8711
|
+
});
|
|
8712
|
+
} catch (error) {
|
|
8713
|
+
if (!(error instanceof ObservationStopped))
|
|
8714
|
+
throw error;
|
|
8715
|
+
}
|
|
8716
|
+
return {
|
|
8717
|
+
action: "attention.list",
|
|
8718
|
+
result: { followed: true, emitted, cursor: cursor ?? null },
|
|
8719
|
+
connection: session.identity,
|
|
8720
|
+
human: []
|
|
8721
|
+
};
|
|
8722
|
+
}
|
|
8723
|
+
}
|
|
8724
|
+
];
|
|
8725
|
+
|
|
8726
|
+
// ../protocol/src/public-api-resources.ts
|
|
8727
|
+
var PUBLIC_CHANGE_SCOPES = Object.freeze(["environment"]);
|
|
8728
|
+
var AGENT_PLATFORM_KEYS = ["claude", "codex", "cursor", "grok", "opencode", "pi"];
|
|
8729
|
+
function agentSettingDescriptors(scope) {
|
|
8730
|
+
const application = "next-session";
|
|
8731
|
+
return [
|
|
8732
|
+
{
|
|
8733
|
+
key: "agent.defaultAgent",
|
|
8734
|
+
scope,
|
|
8735
|
+
type: "agent",
|
|
8736
|
+
inherits: true,
|
|
8737
|
+
application,
|
|
8738
|
+
description: "Agent platform used when a launch does not name one."
|
|
8739
|
+
},
|
|
8740
|
+
...AGENT_PLATFORM_KEYS.flatMap((platform) => [
|
|
8741
|
+
{
|
|
8742
|
+
key: `agent.${platform}.mode`,
|
|
8743
|
+
scope,
|
|
8744
|
+
type: "agent-mode",
|
|
8745
|
+
inherits: true,
|
|
8746
|
+
application,
|
|
8747
|
+
description: `Launch mode (native or terminal) for ${platform}.`
|
|
8748
|
+
},
|
|
8749
|
+
{
|
|
8750
|
+
key: `agent.${platform}.model`,
|
|
8751
|
+
scope,
|
|
8752
|
+
type: "string",
|
|
8753
|
+
inherits: true,
|
|
8754
|
+
application,
|
|
8755
|
+
description: `Default model for new ${platform} sessions.`
|
|
8756
|
+
},
|
|
8757
|
+
{
|
|
8758
|
+
key: `agent.${platform}.reasoningEffort`,
|
|
8759
|
+
scope,
|
|
8760
|
+
type: "string",
|
|
8761
|
+
inherits: true,
|
|
8762
|
+
application,
|
|
8763
|
+
description: `Default reasoning level for new ${platform} sessions.`
|
|
8764
|
+
},
|
|
8765
|
+
{
|
|
8766
|
+
key: `agent.${platform}.fastMode`,
|
|
8767
|
+
scope,
|
|
8768
|
+
type: "boolean",
|
|
8769
|
+
inherits: true,
|
|
8770
|
+
application,
|
|
8771
|
+
description: `Default speed for new ${platform} sessions (true is Fast).`
|
|
8772
|
+
}
|
|
8773
|
+
])
|
|
8774
|
+
];
|
|
8775
|
+
}
|
|
8776
|
+
var PUBLIC_PROJECT_SETTINGS = Object.freeze([
|
|
8777
|
+
{
|
|
8778
|
+
key: "defaultBranch",
|
|
8779
|
+
scope: "project",
|
|
8780
|
+
type: "string",
|
|
8781
|
+
inherits: false,
|
|
8782
|
+
application: "next-environment",
|
|
8783
|
+
description: "Branch new environments are created from."
|
|
8784
|
+
},
|
|
8785
|
+
{
|
|
8786
|
+
key: "prBaseBranch",
|
|
8787
|
+
scope: "project",
|
|
8788
|
+
type: "string",
|
|
8789
|
+
inherits: false,
|
|
8790
|
+
application: "applied",
|
|
8791
|
+
description: "Base branch for pull requests and diff baselines."
|
|
8792
|
+
},
|
|
8793
|
+
{
|
|
8794
|
+
key: "defaultPortMappings",
|
|
8795
|
+
scope: "project",
|
|
8796
|
+
type: "port-mappings",
|
|
8797
|
+
inherits: false,
|
|
8798
|
+
environmentTypes: ["container"],
|
|
8799
|
+
application: "next-environment",
|
|
8800
|
+
description: "Port mappings copied into new container environments."
|
|
8801
|
+
},
|
|
8802
|
+
{
|
|
8803
|
+
key: "filesToCopy",
|
|
8804
|
+
scope: "project",
|
|
8805
|
+
type: "string-list",
|
|
8806
|
+
inherits: false,
|
|
8807
|
+
application: "next-environment",
|
|
8808
|
+
description: "Untracked files copied into new environments."
|
|
8809
|
+
},
|
|
8810
|
+
{
|
|
8811
|
+
key: "entryPort",
|
|
8812
|
+
scope: "project",
|
|
8813
|
+
type: "port",
|
|
8814
|
+
inherits: false,
|
|
8815
|
+
application: "next-environment",
|
|
8816
|
+
description: "Default preview entry port for new environments."
|
|
8817
|
+
},
|
|
8818
|
+
...agentSettingDescriptors("project")
|
|
8819
|
+
]);
|
|
8820
|
+
var PUBLIC_ENVIRONMENT_SETTINGS = Object.freeze([
|
|
8821
|
+
{
|
|
8822
|
+
key: "portMappings",
|
|
8823
|
+
scope: "environment",
|
|
8824
|
+
type: "port-mappings",
|
|
8825
|
+
inherits: false,
|
|
8826
|
+
environmentTypes: ["container"],
|
|
8827
|
+
application: "next-start",
|
|
8828
|
+
description: "Container port mappings; a running container adopts them on recreate."
|
|
8829
|
+
},
|
|
8830
|
+
{
|
|
8831
|
+
key: "allowedDomains",
|
|
8832
|
+
scope: "environment",
|
|
8833
|
+
type: "domain-list",
|
|
8834
|
+
inherits: true,
|
|
8835
|
+
environmentTypes: ["container"],
|
|
8836
|
+
application: "next-start",
|
|
8837
|
+
description: "Restricted-network allowlist; unset inherits the global list. A running container whose image supports it applies the change immediately (reported as applied)."
|
|
8838
|
+
},
|
|
8839
|
+
...agentSettingDescriptors("environment")
|
|
8840
|
+
]);
|
|
8841
|
+
|
|
8842
|
+
// src/client/format.ts
|
|
8843
|
+
function table(headers, rows) {
|
|
8844
|
+
if (rows.length === 0)
|
|
8845
|
+
return ["(none)"];
|
|
8846
|
+
const widths = headers.map((header, index) => Math.min(60, Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length))));
|
|
8847
|
+
const line = (cells) => cells.map((cell, index) => {
|
|
8848
|
+
const text = cell.length > widths[index] ? `${cell.slice(0, widths[index] - 1)}\u2026` : cell;
|
|
8849
|
+
return text.padEnd(widths[index]);
|
|
8850
|
+
}).join(" ").trimEnd();
|
|
8851
|
+
return [line(headers), ...rows.map(line)];
|
|
8852
|
+
}
|
|
8853
|
+
function keyValues(entries) {
|
|
8854
|
+
const width = Math.max(...entries.map(([key]) => key.length));
|
|
8855
|
+
return entries.filter(([, value]) => value !== undefined).map(([key, value]) => {
|
|
8856
|
+
const text = value === null ? "-" : typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
8857
|
+
return `${key.padEnd(width)} ${text}`;
|
|
8858
|
+
});
|
|
8859
|
+
}
|
|
8860
|
+
function projectLines(project) {
|
|
8861
|
+
return keyValues([
|
|
8862
|
+
["id", project.id],
|
|
8863
|
+
["name", project.name],
|
|
8864
|
+
["remote", project.gitUrl],
|
|
8865
|
+
["path", project.localPath],
|
|
8866
|
+
["folder", project.folder],
|
|
8867
|
+
["environments", project.environmentCount],
|
|
8868
|
+
["revision", project.revision]
|
|
8869
|
+
]);
|
|
8395
8870
|
}
|
|
8396
|
-
function
|
|
8397
|
-
|
|
8871
|
+
function withNodeColumn(items, headers, rows) {
|
|
8872
|
+
const names = items.map((item) => item.nodeName);
|
|
8873
|
+
if (!names.some((name) => typeof name === "string"))
|
|
8874
|
+
return table(headers, rows);
|
|
8875
|
+
return table(["NODE", ...headers], rows.map((row, index) => [String(names[index] ?? ""), ...row]));
|
|
8398
8876
|
}
|
|
8399
|
-
function
|
|
8400
|
-
|
|
8401
|
-
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
}
|
|
8407
|
-
return new CliError("deadline-exceeded", `Timed out observing ${what}; it continues in the backend`, {
|
|
8408
|
-
action,
|
|
8409
|
-
...receipt ? { receipt } : {}
|
|
8410
|
-
});
|
|
8877
|
+
function projectRows(projects) {
|
|
8878
|
+
return withNodeColumn(projects, ["ID", "NAME", "ENVS", "PATH"], projects.map((project) => [
|
|
8879
|
+
project.id,
|
|
8880
|
+
project.name,
|
|
8881
|
+
String(project.environmentCount),
|
|
8882
|
+
project.localPath ?? "-"
|
|
8883
|
+
]));
|
|
8411
8884
|
}
|
|
8412
|
-
function
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8885
|
+
function environmentLines(environment) {
|
|
8886
|
+
return keyValues([
|
|
8887
|
+
["id", environment.id],
|
|
8888
|
+
["project", environment.projectId],
|
|
8889
|
+
["name", environment.name],
|
|
8890
|
+
["branch", environment.branch],
|
|
8891
|
+
["type", environment.environmentType],
|
|
8892
|
+
["status", environment.status],
|
|
8893
|
+
["ready", environment.ready],
|
|
8894
|
+
["setup", `${environment.setup.phase}${environment.setup.overridden ? " (overridden)" : ""}`],
|
|
8895
|
+
["activity", environment.activity.state],
|
|
8896
|
+
["lifecycle error", environment.lifecycle.error],
|
|
8897
|
+
["workspace", environment.workspacePath],
|
|
8898
|
+
["base", environment.base.commit ?? environment.base.branch],
|
|
8899
|
+
["startup session", environment.startupSession?.sessionId]
|
|
8900
|
+
]);
|
|
8418
8901
|
}
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
latest = await readOperation(session, initial.operationId, Math.max(1, deadline - Date.now()));
|
|
8428
|
-
} catch (error) {
|
|
8429
|
-
if (context.signal?.aborted)
|
|
8430
|
-
throw new ObservationStopped("signal", signalName(context.signal));
|
|
8431
|
-
if (Date.now() >= deadline && error instanceof CliError && error.code === "connection-failed")
|
|
8432
|
-
throw new ObservationStopped("deadline");
|
|
8433
|
-
throw error;
|
|
8434
|
-
}
|
|
8435
|
-
if (isTerminalReceipt(latest))
|
|
8436
|
-
return { done: true, value: latest };
|
|
8437
|
-
const execution = latest.execution;
|
|
8438
|
-
if (execution?.state === "waiting-for-input" && !options2.continueOnInteraction) {
|
|
8439
|
-
throw new CliError("interaction-required", "The run is waiting for an answer to a pending interaction", {
|
|
8440
|
-
receipt: latest,
|
|
8441
|
-
action: latest.action,
|
|
8442
|
-
details: { interactions: execution.interactions ?? [] }
|
|
8443
|
-
});
|
|
8444
|
-
}
|
|
8445
|
-
if (latest.state === "unknown" && latest.dispatch?.state === "unknown") {
|
|
8446
|
-
throw new CliError("dispatch-unknown", "The provider may or may not have received the prompt; use `run retry` (same key) or `run discard`", { receipt: latest, action: latest.action, retryable: true });
|
|
8447
|
-
}
|
|
8448
|
-
if (latest.state === "unknown") {
|
|
8449
|
-
throw new CliError("run-unknown", latest.error?.message ?? "The outcome of this run is not known", {
|
|
8450
|
-
receipt: latest,
|
|
8451
|
-
action: latest.action
|
|
8452
|
-
});
|
|
8453
|
-
}
|
|
8454
|
-
if (execution?.state === "unsupported") {
|
|
8455
|
-
throw new CliError("capability-unavailable", execution.reason ?? "Completion of this run cannot be observed for its provider", { receipt: latest, action: latest.action });
|
|
8456
|
-
}
|
|
8457
|
-
return { done: false };
|
|
8458
|
-
}, { timeoutMs, signal: context.signal });
|
|
8459
|
-
} catch (error) {
|
|
8460
|
-
if (error instanceof ObservationStopped) {
|
|
8461
|
-
throw stoppedError(error, latest.action, latest, "the operation");
|
|
8462
|
-
}
|
|
8463
|
-
throw error;
|
|
8464
|
-
}
|
|
8902
|
+
function environmentRows(environments) {
|
|
8903
|
+
return withNodeColumn(environments, ["ID", "NAME", "TYPE", "STATUS", "READY"], environments.map((environment) => [
|
|
8904
|
+
environment.id,
|
|
8905
|
+
environment.name,
|
|
8906
|
+
environment.environmentType,
|
|
8907
|
+
environment.status,
|
|
8908
|
+
environment.ready ? "yes" : "no"
|
|
8909
|
+
]));
|
|
8465
8910
|
}
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
} catch (error) {
|
|
8518
|
-
if (error instanceof ObservationStopped) {
|
|
8519
|
-
throw stoppedError(error, latestReceipt?.action ?? "environment.get", latestReceipt, `the environment (${condition})`);
|
|
8520
|
-
}
|
|
8521
|
-
throw error;
|
|
8522
|
-
}
|
|
8911
|
+
function sessionRows(sessions) {
|
|
8912
|
+
return table(["SESSION", "AGENT", "TAB", "ACTIVITY", "TITLE"], sessions.map((session) => [
|
|
8913
|
+
session.id,
|
|
8914
|
+
session.agent,
|
|
8915
|
+
session.tabId,
|
|
8916
|
+
session.activity,
|
|
8917
|
+
session.title ?? ""
|
|
8918
|
+
]));
|
|
8919
|
+
}
|
|
8920
|
+
function sessionLines(session) {
|
|
8921
|
+
return keyValues([
|
|
8922
|
+
["id", session.id],
|
|
8923
|
+
["environment", session.environmentId],
|
|
8924
|
+
["tab", session.tabId],
|
|
8925
|
+
["agent", session.agent],
|
|
8926
|
+
["title", session.title],
|
|
8927
|
+
["activity", session.activity],
|
|
8928
|
+
["latest request", session.latestRequestId],
|
|
8929
|
+
["pending interactions", session.pendingInteractionCount],
|
|
8930
|
+
["recoverable dispatch", session.recoverableDispatch]
|
|
8931
|
+
]);
|
|
8932
|
+
}
|
|
8933
|
+
function receiptLines(receipt) {
|
|
8934
|
+
return keyValues([
|
|
8935
|
+
["operation", receipt.operationId],
|
|
8936
|
+
["action", receipt.action],
|
|
8937
|
+
["state", receipt.state],
|
|
8938
|
+
["stage", receipt.stage],
|
|
8939
|
+
["request", `${receipt.requestId} (${receipt.namespace})`],
|
|
8940
|
+
["replayed", receipt.replayed],
|
|
8941
|
+
["resources", receipt.resources],
|
|
8942
|
+
["dispatch", receipt.dispatch?.state],
|
|
8943
|
+
["execution", receipt.execution?.state],
|
|
8944
|
+
["evidence", receipt.execution?.evidence],
|
|
8945
|
+
["exit code", receipt.execution?.exitCode],
|
|
8946
|
+
["error", receipt.error ? `${receipt.error.code}: ${receipt.error.message}` : undefined],
|
|
8947
|
+
["updated", receipt.updatedAt],
|
|
8948
|
+
["retained until", receipt.retainedUntil]
|
|
8949
|
+
]);
|
|
8950
|
+
}
|
|
8951
|
+
function settingsLines(snapshot) {
|
|
8952
|
+
return [
|
|
8953
|
+
`revision ${snapshot.revision}`,
|
|
8954
|
+
...table(["KEY", "VALUE", "EFFECTIVE", "SOURCE", "APPLIES"], snapshot.settings.map((setting) => [
|
|
8955
|
+
setting.key,
|
|
8956
|
+
setting.value === null || setting.value === undefined ? "-" : JSON.stringify(setting.value),
|
|
8957
|
+
setting.effective === null || setting.effective === undefined ? "-" : JSON.stringify(setting.effective),
|
|
8958
|
+
setting.source,
|
|
8959
|
+
setting.application
|
|
8960
|
+
]))
|
|
8961
|
+
];
|
|
8523
8962
|
}
|
|
8524
8963
|
|
|
8525
8964
|
// src/client/commands/changes.ts
|
|
@@ -8625,6 +9064,7 @@ function changesHistoryLines(history) {
|
|
|
8625
9064
|
var changesCommands = [
|
|
8626
9065
|
{
|
|
8627
9066
|
path: ["changes", "view"],
|
|
9067
|
+
action: "changes.view",
|
|
8628
9068
|
summary: "Show an environment's file and line changes as the backend measures them.",
|
|
8629
9069
|
description: "Read-only: prints the backend's change projection for the environment \u2014 the numbers the desktop shows \u2014 and one page of changed files. Counts carry their quality: ~ partial or estimated, ? unknown, \u2265/\u2264 a bound; binary files and submodules have no line counts. Nothing is ever shown as zero unless the backend measured zero. Pass the printed cursor as --cursor for the next page; a cursor stops working once the changes move on. --json prints the action result verbatim ({projection, files}).",
|
|
8630
9070
|
positionals: [{ name: "environment", required: true, description: "Environment ID." }],
|
|
@@ -8674,6 +9114,7 @@ var changesCommands = [
|
|
|
8674
9114
|
},
|
|
8675
9115
|
{
|
|
8676
9116
|
path: ["changes", "history"],
|
|
9117
|
+
action: "changes.history",
|
|
8677
9118
|
summary: "List a session's turns, oldest first, with the changes each one made.",
|
|
8678
9119
|
description: "Read-only: each turn's backend measurement from its start to its end, with the same count qualities as `changes view`. No workspace scan, transcript or provider read. Pass the printed cursor as --cursor for newer turns.",
|
|
8679
9120
|
positionals: [{ name: "session", required: true, description: "Session ID (ses_\u2026)." }],
|
|
@@ -8710,6 +9151,201 @@ var changesCommands = [
|
|
|
8710
9151
|
}
|
|
8711
9152
|
];
|
|
8712
9153
|
|
|
9154
|
+
// src/client/commands/completion.ts
|
|
9155
|
+
var SHELLS = ["bash", "zsh", "fish"];
|
|
9156
|
+
function flagsOf(options2) {
|
|
9157
|
+
return options2.flatMap((option) => [
|
|
9158
|
+
`--${option.name}`,
|
|
9159
|
+
...option.short ? [`-${option.short}`] : [],
|
|
9160
|
+
...option.negatable ? [`--no-${option.name}`] : []
|
|
9161
|
+
]);
|
|
9162
|
+
}
|
|
9163
|
+
function completionTable(commands) {
|
|
9164
|
+
const next = new Map;
|
|
9165
|
+
const flags = new Map;
|
|
9166
|
+
const values = new Map;
|
|
9167
|
+
const valueFlags = (options2) => flagsOf(options2.filter((option) => option.kind !== "boolean"));
|
|
9168
|
+
values.set("", valueFlags(GLOBAL_OPTIONS));
|
|
9169
|
+
const add = (prefix, word) => {
|
|
9170
|
+
const words = next.get(prefix) ?? [];
|
|
9171
|
+
if (!words.includes(word))
|
|
9172
|
+
words.push(word);
|
|
9173
|
+
next.set(prefix, words);
|
|
9174
|
+
};
|
|
9175
|
+
for (const command of commands) {
|
|
9176
|
+
command.path.forEach((word, index) => {
|
|
9177
|
+
const prefix = command.path.slice(0, index).join(" ");
|
|
9178
|
+
add(prefix, word);
|
|
9179
|
+
if (!values.has(prefix))
|
|
9180
|
+
values.set(prefix, valueFlags(GLOBAL_OPTIONS));
|
|
9181
|
+
});
|
|
9182
|
+
flags.set(command.path.join(" "), flagsOf(command.options));
|
|
9183
|
+
values.set(command.path.join(" "), [
|
|
9184
|
+
...valueFlags(GLOBAL_OPTIONS),
|
|
9185
|
+
...valueFlags(command.options)
|
|
9186
|
+
]);
|
|
9187
|
+
}
|
|
9188
|
+
add("", "help");
|
|
9189
|
+
add("", "version");
|
|
9190
|
+
return { next, flags, globals: flagsOf(GLOBAL_OPTIONS), values };
|
|
9191
|
+
}
|
|
9192
|
+
function words(list) {
|
|
9193
|
+
return list.join(" ");
|
|
9194
|
+
}
|
|
9195
|
+
function bashScript(table) {
|
|
9196
|
+
const cases = [...table.next].map(([prefix, list]) => ` "${prefix}") echo "${words(list)}" ;;`);
|
|
9197
|
+
const flagCases = [...table.flags].map(([path, list]) => ` "${path}") flags="${words(list)}" ;;`);
|
|
9198
|
+
return [
|
|
9199
|
+
"# orkestrator bash completion. Install: orkestrator completion bash > ~/.local/share/bash-completion/completions/orkestrator",
|
|
9200
|
+
"_orkestrator_next() {",
|
|
9201
|
+
' case "$1" in',
|
|
9202
|
+
...cases,
|
|
9203
|
+
" esac",
|
|
9204
|
+
"}",
|
|
9205
|
+
"_orkestrator_takes_value() {",
|
|
9206
|
+
' case "$1:$2" in',
|
|
9207
|
+
...[...table.values].filter(([, flags]) => flags.length).map(([path, flags]) => ` ${flags.map((flag) => `"${path}:${flag}"`).join("|")}) return 0 ;;`),
|
|
9208
|
+
" esac",
|
|
9209
|
+
" return 1",
|
|
9210
|
+
"}",
|
|
9211
|
+
"_orkestrator() {",
|
|
9212
|
+
' local cur="${COMP_WORDS[COMP_CWORD]}" command_path="" word i flags="" skip=0',
|
|
9213
|
+
" COMPREPLY=()",
|
|
9214
|
+
" for ((i = 1; i < COMP_CWORD; i++)); do",
|
|
9215
|
+
' word="${COMP_WORDS[i]}"',
|
|
9216
|
+
' if ((skip)); then [[ "$word" == "=" ]] || skip=0; continue; fi',
|
|
9217
|
+
' [[ "$word" == "--" ]] && return 0',
|
|
9218
|
+
' [[ "$word" == --*=* ]] && continue',
|
|
9219
|
+
' if _orkestrator_takes_value "$command_path" "$word"; then skip=1; continue; fi',
|
|
9220
|
+
' [[ "$word" == -* ]] && continue',
|
|
9221
|
+
' if [[ " $(_orkestrator_next "$command_path") " == *" $word "* ]]; then command_path="${command_path:+$command_path }$word"; fi',
|
|
9222
|
+
" done",
|
|
9223
|
+
" ((skip)) && return 0",
|
|
9224
|
+
' case "$command_path" in',
|
|
9225
|
+
...flagCases,
|
|
9226
|
+
" esac",
|
|
9227
|
+
" local candidates candidate",
|
|
9228
|
+
' if [[ "$cur" == -* ]]; then',
|
|
9229
|
+
` candidates="$flags ${words(table.globals)}"`,
|
|
9230
|
+
" else",
|
|
9231
|
+
' candidates="$(_orkestrator_next "$command_path")"',
|
|
9232
|
+
" fi",
|
|
9233
|
+
" for candidate in $candidates; do",
|
|
9234
|
+
' [[ "$candidate" == "$cur"* ]] && COMPREPLY+=("$candidate")',
|
|
9235
|
+
" done",
|
|
9236
|
+
" return 0",
|
|
9237
|
+
"}",
|
|
9238
|
+
"complete -o default -F _orkestrator orkestrator",
|
|
9239
|
+
""
|
|
9240
|
+
].join(`
|
|
9241
|
+
`);
|
|
9242
|
+
}
|
|
9243
|
+
function zshScript(table) {
|
|
9244
|
+
return [
|
|
9245
|
+
"#compdef orkestrator",
|
|
9246
|
+
'# orkestrator zsh completion (through bashcompinit). Install: orkestrator completion zsh > "${fpath[1]}/_orkestrator"',
|
|
9247
|
+
"autoload -U +X bashcompinit && bashcompinit",
|
|
9248
|
+
bashScript(table).split(`
|
|
9249
|
+
`).slice(1).join(`
|
|
9250
|
+
`).replace(`_orkestrator() {
|
|
9251
|
+
`, `_orkestrator() {
|
|
9252
|
+
emulate -L ksh
|
|
9253
|
+
`)
|
|
9254
|
+
].join(`
|
|
9255
|
+
`);
|
|
9256
|
+
}
|
|
9257
|
+
function fishScript(table) {
|
|
9258
|
+
const lines = [
|
|
9259
|
+
"# orkestrator fish completion. Install: orkestrator completion fish > ~/.config/fish/completions/orkestrator.fish",
|
|
9260
|
+
"function __orkestrator_next",
|
|
9261
|
+
" switch $argv[1]",
|
|
9262
|
+
...[...table.next].map(([prefix, list]) => ` case '${prefix}'
|
|
9263
|
+
echo '${words(list)}'`),
|
|
9264
|
+
" end",
|
|
9265
|
+
"end",
|
|
9266
|
+
"function __orkestrator_takes_value",
|
|
9267
|
+
' switch "$argv[1]:$argv[2]"',
|
|
9268
|
+
...[...table.values].filter(([, flags]) => flags.length).map(([path, flags]) => ` case ${flags.map((flag) => `'${path}:${flag}'`).join(" ")}
|
|
9269
|
+
return 0`),
|
|
9270
|
+
" end",
|
|
9271
|
+
" return 1",
|
|
9272
|
+
"end",
|
|
9273
|
+
"function __orkestrator_path",
|
|
9274
|
+
" set -l tokens (commandline -opc)",
|
|
9275
|
+
" set -e tokens[1]",
|
|
9276
|
+
" set -l path ''",
|
|
9277
|
+
" set -l skip 0",
|
|
9278
|
+
" for token in $tokens",
|
|
9279
|
+
" if test $skip = 1",
|
|
9280
|
+
" set skip 0",
|
|
9281
|
+
" continue",
|
|
9282
|
+
" end",
|
|
9283
|
+
' if test "$token" = "--"',
|
|
9284
|
+
" echo __rest",
|
|
9285
|
+
" return",
|
|
9286
|
+
" end",
|
|
9287
|
+
" string match -q -- '--*=*' $token; and continue",
|
|
9288
|
+
' if __orkestrator_takes_value "$path" "$token"',
|
|
9289
|
+
" set skip 1",
|
|
9290
|
+
" continue",
|
|
9291
|
+
" end",
|
|
9292
|
+
" string match -q -- '-*' $token; and continue",
|
|
9293
|
+
" if contains -- $token (string split ' ' -- (__orkestrator_next $path))",
|
|
9294
|
+
' set path (string trim -- "$path $token")',
|
|
9295
|
+
" end",
|
|
9296
|
+
" end",
|
|
9297
|
+
" if test $skip = 1",
|
|
9298
|
+
" echo __value",
|
|
9299
|
+
" return",
|
|
9300
|
+
" end",
|
|
9301
|
+
' echo "$path"',
|
|
9302
|
+
"end",
|
|
9303
|
+
"complete -c orkestrator -f"
|
|
9304
|
+
];
|
|
9305
|
+
for (const [prefix, list] of table.next) {
|
|
9306
|
+
lines.push(`complete -c orkestrator -n 'test (__orkestrator_path) = "${prefix}"' -a '${words(list)}'`);
|
|
9307
|
+
}
|
|
9308
|
+
for (const [path, list] of table.flags) {
|
|
9309
|
+
for (const flag of list) {
|
|
9310
|
+
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" : ""}`);
|
|
9311
|
+
}
|
|
9312
|
+
}
|
|
9313
|
+
for (const flag of table.globals) {
|
|
9314
|
+
lines.push(`complete -c orkestrator ${flag.startsWith("--") ? `-l '${flag.slice(2)}'` : `-s '${flag.slice(1)}'`}${table.values.get("")?.includes(flag) ? " -r" : ""}`);
|
|
9315
|
+
}
|
|
9316
|
+
return `${lines.join(`
|
|
9317
|
+
`)}
|
|
9318
|
+
`;
|
|
9319
|
+
}
|
|
9320
|
+
function completionScript(shell, commands) {
|
|
9321
|
+
const table = completionTable(commands);
|
|
9322
|
+
return shell === "bash" ? bashScript(table) : shell === "zsh" ? zshScript(table) : fishScript(table);
|
|
9323
|
+
}
|
|
9324
|
+
function completionCommands(commands) {
|
|
9325
|
+
return [
|
|
9326
|
+
{
|
|
9327
|
+
path: ["completion"],
|
|
9328
|
+
summary: "Print a shell completion script (bash, zsh or fish).",
|
|
9329
|
+
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`.",
|
|
9330
|
+
positionals: [{ name: "shell", required: true, description: "bash, zsh or fish." }],
|
|
9331
|
+
options: [],
|
|
9332
|
+
local: true,
|
|
9333
|
+
async run(_context, parsed) {
|
|
9334
|
+
const shell = parsed.positionals.shell;
|
|
9335
|
+
if (!SHELLS.includes(shell)) {
|
|
9336
|
+
throw new CliError("invalid-input", "Shell must be one of: bash, zsh, fish");
|
|
9337
|
+
}
|
|
9338
|
+
const script = completionScript(shell, commands());
|
|
9339
|
+
return {
|
|
9340
|
+
action: "completion",
|
|
9341
|
+
result: { shell, script },
|
|
9342
|
+
human: [script.replace(/\n$/, "")]
|
|
9343
|
+
};
|
|
9344
|
+
}
|
|
9345
|
+
}
|
|
9346
|
+
];
|
|
9347
|
+
}
|
|
9348
|
+
|
|
8713
9349
|
// src/client/commands/connection.ts
|
|
8714
9350
|
import path9 from "path";
|
|
8715
9351
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
@@ -8797,8 +9433,11 @@ import { promises as fs2 } from "fs";
|
|
|
8797
9433
|
import path6 from "path";
|
|
8798
9434
|
var MAX_LOCAL_RECEIPTS_PER_INSTALLATION = 1000;
|
|
8799
9435
|
var MAX_RECEIPT_BYTES = 8 * 1024;
|
|
8800
|
-
function receiptKey(action, requestId) {
|
|
8801
|
-
|
|
9436
|
+
function receiptKey(action, requestId, node) {
|
|
9437
|
+
const hash = createHash("sha256").update(action).update("\x00").update(requestId);
|
|
9438
|
+
if (node)
|
|
9439
|
+
hash.update("\x00node:").update(node);
|
|
9440
|
+
return hash.digest("hex");
|
|
8802
9441
|
}
|
|
8803
9442
|
function safeSegment(value) {
|
|
8804
9443
|
return /^[A-Za-z0-9._-]{1,200}$/.test(value) ? value : createHash("sha256").update(value).digest("hex");
|
|
@@ -8812,18 +9451,18 @@ class LocalReceiptStore {
|
|
|
8812
9451
|
directory(installationId) {
|
|
8813
9452
|
return path6.join(this.root, safeSegment(installationId));
|
|
8814
9453
|
}
|
|
8815
|
-
file(installationId, action, requestId) {
|
|
8816
|
-
return path6.join(this.directory(installationId), `${receiptKey(action, requestId)}.json`);
|
|
9454
|
+
file(installationId, action, requestId, node) {
|
|
9455
|
+
return path6.join(this.directory(installationId), `${receiptKey(action, requestId, node)}.json`);
|
|
8817
9456
|
}
|
|
8818
|
-
async find(installationId, action, requestId) {
|
|
8819
|
-
return this.readFile(this.file(installationId, action, requestId));
|
|
9457
|
+
async find(installationId, action, requestId, node) {
|
|
9458
|
+
return this.readFile(this.file(installationId, action, requestId, node));
|
|
8820
9459
|
}
|
|
8821
9460
|
async save(receipt) {
|
|
8822
9461
|
const text = `${JSON.stringify(receipt)}
|
|
8823
9462
|
`;
|
|
8824
9463
|
if (Buffer.byteLength(text) > MAX_RECEIPT_BYTES)
|
|
8825
9464
|
return;
|
|
8826
|
-
await writePrivateFile(this.file(receipt.installationId, receipt.action, receipt.requestId), text);
|
|
9465
|
+
await writePrivateFile(this.file(receipt.installationId, receipt.action, receipt.requestId, receipt.node), text);
|
|
8827
9466
|
await this.prune(receipt.installationId);
|
|
8828
9467
|
}
|
|
8829
9468
|
async update(receipt, remote) {
|
|
@@ -8858,7 +9497,7 @@ class LocalReceiptStore {
|
|
|
8858
9497
|
if (!stat.isFile() || stat.size > MAX_RECEIPT_BYTES)
|
|
8859
9498
|
return null;
|
|
8860
9499
|
const value = JSON.parse(await fs2.readFile(file, "utf8"));
|
|
8861
|
-
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") {
|
|
9500
|
+
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") {
|
|
8862
9501
|
return null;
|
|
8863
9502
|
}
|
|
8864
9503
|
return value;
|
|
@@ -9040,6 +9679,14 @@ class ClientSession {
|
|
|
9040
9679
|
options;
|
|
9041
9680
|
capabilitiesPromise = null;
|
|
9042
9681
|
transport;
|
|
9682
|
+
readWarnings = new Set;
|
|
9683
|
+
responseNode;
|
|
9684
|
+
get warnings() {
|
|
9685
|
+
return [...this.readWarnings];
|
|
9686
|
+
}
|
|
9687
|
+
get nodeTag() {
|
|
9688
|
+
return this.responseNode ?? (this.options.node && this.options.node !== PUBLIC_ALL_NODES ? { nodeId: this.options.node, name: this.options.node } : undefined);
|
|
9689
|
+
}
|
|
9043
9690
|
constructor(target, receipts, options2 = {}) {
|
|
9044
9691
|
this.target = target;
|
|
9045
9692
|
this.receipts = receipts;
|
|
@@ -9049,6 +9696,27 @@ class ClientSession {
|
|
|
9049
9696
|
get identity() {
|
|
9050
9697
|
return this.target.identity;
|
|
9051
9698
|
}
|
|
9699
|
+
get node() {
|
|
9700
|
+
return this.options.node;
|
|
9701
|
+
}
|
|
9702
|
+
routingChecked = null;
|
|
9703
|
+
ensureNodeRouting() {
|
|
9704
|
+
this.routingChecked ??= (async () => {
|
|
9705
|
+
const response = await this.call("capabilities", {}, undefined, false, undefined, false);
|
|
9706
|
+
if (!response.ok)
|
|
9707
|
+
throw envelopeError(response);
|
|
9708
|
+
if (!isPublicCapabilities(response.result)) {
|
|
9709
|
+
throw new CliError("response-invalid", "The backend returned invalid capabilities");
|
|
9710
|
+
}
|
|
9711
|
+
if (response.result.features.nodeRouting !== true) {
|
|
9712
|
+
throw new CliError("backend-incompatible", "The connected backend cannot run actions on other nodes; upgrade it to use --node");
|
|
9713
|
+
}
|
|
9714
|
+
})().catch((error) => {
|
|
9715
|
+
this.routingChecked = null;
|
|
9716
|
+
throw error;
|
|
9717
|
+
});
|
|
9718
|
+
return this.routingChecked;
|
|
9719
|
+
}
|
|
9052
9720
|
capabilities() {
|
|
9053
9721
|
this.capabilitiesPromise ??= this.loadCapabilities().catch((error) => {
|
|
9054
9722
|
this.capabilitiesPromise = null;
|
|
@@ -9082,6 +9750,9 @@ class ClientSession {
|
|
|
9082
9750
|
async mutate(action, input, requestId) {
|
|
9083
9751
|
if (!PUBLIC_ACTIONS[action].mutation)
|
|
9084
9752
|
throw new Error(`${action} is not a mutation`);
|
|
9753
|
+
if (this.options.node === PUBLIC_ALL_NODES) {
|
|
9754
|
+
throw new CliError("invalid-input", "--node all is only for project list, environment list and attention; name one node");
|
|
9755
|
+
}
|
|
9085
9756
|
if (requestId !== undefined && !isPublicRequestId(requestId)) {
|
|
9086
9757
|
throw new CliError("invalid-input", "--request-id must be 1\u2013200 characters of letters, digits and . _ : @ / + = -, starting with a letter or digit");
|
|
9087
9758
|
}
|
|
@@ -9093,7 +9764,7 @@ class ClientSession {
|
|
|
9093
9764
|
const installationId = capabilities.backend.installationId;
|
|
9094
9765
|
const key = requestId ?? `cli-${randomUUID2()}`;
|
|
9095
9766
|
const intentDigest = createHash2("sha256").update(canonicalJson({ action, input })).digest("hex");
|
|
9096
|
-
const existing = await this.receipts.find(installationId, action, key);
|
|
9767
|
+
const existing = await this.receipts.find(installationId, action, key, this.options.node);
|
|
9097
9768
|
if (existing && existing.intentDigest !== intentDigest) {
|
|
9098
9769
|
throw new CliError("request-conflict", "This saved request ID was used for a different intent", {
|
|
9099
9770
|
action,
|
|
@@ -9106,6 +9777,7 @@ class ClientSession {
|
|
|
9106
9777
|
version: 1,
|
|
9107
9778
|
installationId,
|
|
9108
9779
|
connection: this.target.identity.name,
|
|
9780
|
+
...this.options.node ? { node: this.options.node } : {},
|
|
9109
9781
|
action,
|
|
9110
9782
|
requestId: key,
|
|
9111
9783
|
namespace,
|
|
@@ -9122,10 +9794,12 @@ class ClientSession {
|
|
|
9122
9794
|
if (error instanceof CliError && error.code === "transport-uncertain") {
|
|
9123
9795
|
throw new CliError(error.code, error.message, {
|
|
9124
9796
|
action,
|
|
9797
|
+
node: this.nodeTag,
|
|
9125
9798
|
details: {
|
|
9126
9799
|
requestId: key,
|
|
9127
9800
|
namespace,
|
|
9128
|
-
|
|
9801
|
+
...this.nodeTag ? { node: this.nodeTag } : {},
|
|
9802
|
+
recovery: `orkestrator run get --request-id ${key} --action ${action}${this.options.node ? ` --node ${quoteArgument(this.options.node)}` : ""}`
|
|
9129
9803
|
}
|
|
9130
9804
|
});
|
|
9131
9805
|
}
|
|
@@ -9135,27 +9809,56 @@ class ClientSession {
|
|
|
9135
9809
|
await this.receipts.update(local, response.receipt).catch(() => {
|
|
9136
9810
|
return;
|
|
9137
9811
|
});
|
|
9138
|
-
if (!response.ok)
|
|
9812
|
+
if (!response.ok) {
|
|
9813
|
+
if (response.error.code === "node-outcome-unknown") {
|
|
9814
|
+
throw new CliError(response.error.code, response.error.message, {
|
|
9815
|
+
action,
|
|
9816
|
+
node: this.nodeTag,
|
|
9817
|
+
retryable: response.error.retryable,
|
|
9818
|
+
details: {
|
|
9819
|
+
...response.error.details,
|
|
9820
|
+
requestId: key,
|
|
9821
|
+
namespace,
|
|
9822
|
+
recovery: `orkestrator run get --request-id ${key} --action ${action}${this.options.node ? ` --node ${quoteArgument(this.options.node)}` : ""}`
|
|
9823
|
+
}
|
|
9824
|
+
});
|
|
9825
|
+
}
|
|
9139
9826
|
throw envelopeError(response);
|
|
9827
|
+
}
|
|
9140
9828
|
return {
|
|
9141
9829
|
result: response.result,
|
|
9830
|
+
...this.nodeTag ? { node: this.nodeTag } : {},
|
|
9142
9831
|
...response.receipt ? { receipt: response.receipt } : {},
|
|
9143
9832
|
...response.warnings ? { warnings: response.warnings } : {}
|
|
9144
9833
|
};
|
|
9145
9834
|
}
|
|
9146
|
-
async call(action, input, request, mutation, timeoutMs) {
|
|
9835
|
+
async call(action, input, request, mutation, timeoutMs, routed = true) {
|
|
9836
|
+
const node = routed && this.options.node && !PUBLIC_LOCAL_ONLY_ACTIONS.includes(action) ? this.options.node : undefined;
|
|
9837
|
+
if (node)
|
|
9838
|
+
await this.ensureNodeRouting();
|
|
9147
9839
|
const body = {
|
|
9148
9840
|
schemaVersion: PUBLIC_API_SCHEMA_VERSION,
|
|
9149
9841
|
action,
|
|
9150
9842
|
actionVersion: PUBLIC_ACTIONS[action].version,
|
|
9151
9843
|
input,
|
|
9152
|
-
...request ? { request } : {}
|
|
9844
|
+
...request ? { request } : {},
|
|
9845
|
+
...node && !(node === PUBLIC_ALL_NODES && action === "capabilities") ? { node } : {}
|
|
9153
9846
|
};
|
|
9154
9847
|
const raw = await this.transport.invoke(PUBLIC_ACTION_COMMAND, body, { mutation, signal: this.options.signal, timeoutMs });
|
|
9155
9848
|
if (!isPublicActionResponse(raw)) {
|
|
9156
9849
|
throw new CliError(mutation ? "transport-uncertain" : "response-invalid", "The backend returned a response that does not match the public contract");
|
|
9157
9850
|
}
|
|
9158
9851
|
await this.verifyIdentity(raw);
|
|
9852
|
+
if (raw.node)
|
|
9853
|
+
this.responseNode = raw.node;
|
|
9854
|
+
if (!mutation && raw.ok) {
|
|
9855
|
+
for (const warning of raw.warnings ?? [])
|
|
9856
|
+
this.readWarnings.add(warning);
|
|
9857
|
+
const nodes = raw.result?.nodes;
|
|
9858
|
+
for (const failed of nodes?.filter((entry) => !entry.ok) ?? []) {
|
|
9859
|
+
this.readWarnings.add(`${failed.name} (${failed.nodeId}): ${failed.error?.message ?? "not checked"}`);
|
|
9860
|
+
}
|
|
9861
|
+
}
|
|
9159
9862
|
return raw;
|
|
9160
9863
|
}
|
|
9161
9864
|
async verifyIdentity(response) {
|
|
@@ -9174,11 +9877,15 @@ class ClientSession {
|
|
|
9174
9877
|
function envelopeError(response) {
|
|
9175
9878
|
return new CliError(response.error.code, response.error.message, {
|
|
9176
9879
|
action: response.action,
|
|
9880
|
+
...response.node ? { node: response.node } : {},
|
|
9177
9881
|
...response.error.details ? { details: response.error.details } : {},
|
|
9178
9882
|
...response.receipt ? { receipt: response.receipt } : {},
|
|
9179
9883
|
...response.error.retryable !== undefined ? { retryable: response.error.retryable } : {}
|
|
9180
9884
|
});
|
|
9181
9885
|
}
|
|
9886
|
+
function quoteArgument(value) {
|
|
9887
|
+
return /^[A-Za-z0-9_.:@/+=-]+$/.test(value) ? value : "'" + value.replaceAll("'", `'"'"'`) + "'";
|
|
9888
|
+
}
|
|
9182
9889
|
|
|
9183
9890
|
// src/client/targets.ts
|
|
9184
9891
|
import { promises as fs3 } from "fs";
|
|
@@ -9900,6 +10607,7 @@ function settingsCommands(scope) {
|
|
|
9900
10607
|
return [
|
|
9901
10608
|
{
|
|
9902
10609
|
path: [scope, "config", "get"],
|
|
10610
|
+
action: `${scope}.config.get`,
|
|
9903
10611
|
summary: `Show ${scope} settings with effective values, sources and application timing.`,
|
|
9904
10612
|
positionals: [{ name: idName, required: true }],
|
|
9905
10613
|
options: [],
|
|
@@ -9918,6 +10626,7 @@ function settingsCommands(scope) {
|
|
|
9918
10626
|
},
|
|
9919
10627
|
{
|
|
9920
10628
|
path: [scope, "config", "set"],
|
|
10629
|
+
action: `${scope}.config.set`,
|
|
9921
10630
|
summary: `Set and/or unset ${scope} settings atomically.`,
|
|
9922
10631
|
description: `Settable keys:
|
|
9923
10632
|
${keysHelp}
|
|
@@ -10015,6 +10724,8 @@ ${applies}`,
|
|
|
10015
10724
|
},
|
|
10016
10725
|
{
|
|
10017
10726
|
path: [scope, "config", "unset"],
|
|
10727
|
+
action: `${scope}.config.set`,
|
|
10728
|
+
inputFields: { key: "unset" },
|
|
10018
10729
|
summary: `Unset ${scope} settings, restoring inheritance.`,
|
|
10019
10730
|
positionals: [
|
|
10020
10731
|
{ name: idName, required: true },
|
|
@@ -10092,6 +10803,7 @@ async function lifecycle(context, parsed, action, conditions, extraInput = {}) {
|
|
|
10092
10803
|
var environmentCommands = [
|
|
10093
10804
|
{
|
|
10094
10805
|
path: ["environment", "list"],
|
|
10806
|
+
action: "environment.list",
|
|
10095
10807
|
summary: "List environments from the backend snapshot.",
|
|
10096
10808
|
positionals: [],
|
|
10097
10809
|
options: [
|
|
@@ -10138,6 +10850,7 @@ var environmentCommands = [
|
|
|
10138
10850
|
},
|
|
10139
10851
|
{
|
|
10140
10852
|
path: ["environment", "get"],
|
|
10853
|
+
action: "environment.get",
|
|
10141
10854
|
summary: "Show one environment by ID, or by --project and --name when unique.",
|
|
10142
10855
|
positionals: [{ name: "environment", required: false }],
|
|
10143
10856
|
options: [
|
|
@@ -10173,6 +10886,7 @@ var environmentCommands = [
|
|
|
10173
10886
|
},
|
|
10174
10887
|
{
|
|
10175
10888
|
path: ["environment", "create"],
|
|
10889
|
+
action: "environment.create",
|
|
10176
10890
|
summary: "Create an environment record (does not start it).",
|
|
10177
10891
|
description: "--base-branch with --base-commit (a full 40-character SHA) pins the exact base; the backend validates it and records what was used. Without them the project's default branch is used at start time.",
|
|
10178
10892
|
positionals: [],
|
|
@@ -10231,6 +10945,7 @@ var environmentCommands = [
|
|
|
10231
10945
|
},
|
|
10232
10946
|
{
|
|
10233
10947
|
path: ["environment", "start"],
|
|
10948
|
+
action: "environment.start",
|
|
10234
10949
|
summary: "Start an environment; setup continues in the backend.",
|
|
10235
10950
|
description: "`--wait running` returns once processes run; `--wait ready` also requires setup to complete (an explicit setup override is reported, never applied by the CLI). Setup failure exits 1 with the recorded reason.",
|
|
10236
10951
|
positionals: [{ name: "environment", required: true }],
|
|
@@ -10240,6 +10955,7 @@ var environmentCommands = [
|
|
|
10240
10955
|
},
|
|
10241
10956
|
{
|
|
10242
10957
|
path: ["environment", "stop"],
|
|
10958
|
+
action: "environment.stop",
|
|
10243
10959
|
summary: "Stop an environment's processes (and container).",
|
|
10244
10960
|
positionals: [{ name: "environment", required: true }],
|
|
10245
10961
|
options: [waitOption(["stopped"]), TIMEOUT_OPTION, REQUEST_ID_OPTION],
|
|
@@ -10248,6 +10964,7 @@ var environmentCommands = [
|
|
|
10248
10964
|
},
|
|
10249
10965
|
{
|
|
10250
10966
|
path: ["environment", "recreate"],
|
|
10967
|
+
action: "environment.recreate",
|
|
10251
10968
|
summary: "Rebuild the container, keeping its files; with --discard, DESTRUCTIVELY reset it (container environments only).",
|
|
10252
10969
|
description: "Without --discard the workspace (tracked, untracked and ignored files, unpushed commits) and the preserved agent session state are copied to a new container and verified before it replaces the old one, which is kept stopped as a recovery copy. The rebuild is refused, with nothing changed, when the image or Docker engine cannot preserve them. --discard instead deletes the container's local files, installed tools and container-local agent sessions. Local worktree environments cannot be recreated.",
|
|
10253
10970
|
positionals: [{ name: "environment", required: true }],
|
|
@@ -10266,6 +10983,7 @@ var environmentCommands = [
|
|
|
10266
10983
|
},
|
|
10267
10984
|
{
|
|
10268
10985
|
path: ["environment", "delete"],
|
|
10986
|
+
action: "environment.delete",
|
|
10269
10987
|
summary: "DESTRUCTIVE: stop and delete the environment, its worktree or container, and its record.",
|
|
10270
10988
|
description: "Uncommitted work in the environment is lost. Cleanup is tracked by the operation; a failed cleanup stays inspectable and can be retried with a new request.",
|
|
10271
10989
|
positionals: [{ name: "environment", required: true }],
|
|
@@ -10275,6 +10993,7 @@ var environmentCommands = [
|
|
|
10275
10993
|
},
|
|
10276
10994
|
{
|
|
10277
10995
|
path: ["environment", "fork"],
|
|
10996
|
+
action: "environment.fork",
|
|
10278
10997
|
summary: "Create a new environment from this environment's current state.",
|
|
10279
10998
|
positionals: [{ name: "environment", required: true }],
|
|
10280
10999
|
options: [TYPE_OPTION, REQUEST_ID_OPTION],
|
|
@@ -10295,6 +11014,7 @@ var environmentCommands = [
|
|
|
10295
11014
|
},
|
|
10296
11015
|
{
|
|
10297
11016
|
path: ["environment", "rename"],
|
|
11017
|
+
action: "environment.rename",
|
|
10298
11018
|
summary: "Rename an environment (and its branch).",
|
|
10299
11019
|
positionals: [
|
|
10300
11020
|
{ name: "environment", required: true },
|
|
@@ -10317,6 +11037,7 @@ var environmentCommands = [
|
|
|
10317
11037
|
},
|
|
10318
11038
|
{
|
|
10319
11039
|
path: ["environment", "launch"],
|
|
11040
|
+
action: "environment.launch",
|
|
10320
11041
|
summary: "Create and start an environment whose startup agent receives one first prompt.",
|
|
10321
11042
|
description: "The backend owns the whole flow: create, start, setup, then exactly one initial prompt to the startup session. Do not follow it with `session start`. --wait ready observes setup; follow the prompt's run with `run wait`.",
|
|
10322
11043
|
positionals: [],
|
|
@@ -10427,6 +11148,8 @@ var environmentCommands = [
|
|
|
10427
11148
|
},
|
|
10428
11149
|
{
|
|
10429
11150
|
path: ["environment", "exec"],
|
|
11151
|
+
action: "environment.exec",
|
|
11152
|
+
inputFields: { "stdin-file": "stdinBase64", "exec-timeout": "timeoutMs" },
|
|
10430
11153
|
summary: "Run one command in the environment workspace and report its exit status.",
|
|
10431
11154
|
description: "Arguments after `--` are passed as argv without a shell; use `-- sh -c '...'` to opt into shell expansion. The command runs in a backend-owned worker that outlives this client; --wait observes it and exits with the operation's result (`--exit-code` passes the child's exit code through instead). Output is kept in bounded private artifacts; read it with `run output`.",
|
|
10432
11155
|
positionals: [{ name: "environment", required: true }],
|
|
@@ -10646,6 +11369,7 @@ async function waitForReceipt(context, session, receipt, limit, action) {
|
|
|
10646
11369
|
function taskCommand(spec) {
|
|
10647
11370
|
return {
|
|
10648
11371
|
path: spec.path,
|
|
11372
|
+
action: spec.action,
|
|
10649
11373
|
summary: spec.summary,
|
|
10650
11374
|
...spec.description ? { description: spec.description } : {},
|
|
10651
11375
|
positionals: [],
|
|
@@ -10677,6 +11401,7 @@ function taskCommand(spec) {
|
|
|
10677
11401
|
var hostCommands = [
|
|
10678
11402
|
{
|
|
10679
11403
|
path: ["host", "status"],
|
|
11404
|
+
action: "host.status",
|
|
10680
11405
|
summary: "Show the Orkestrator version on the backend's computer, its updater and any update.",
|
|
10681
11406
|
positionals: [],
|
|
10682
11407
|
options: [],
|
|
@@ -10693,6 +11418,7 @@ var hostCommands = [
|
|
|
10693
11418
|
},
|
|
10694
11419
|
{
|
|
10695
11420
|
path: ["host", "update", "channel"],
|
|
11421
|
+
action: "host.update.channel",
|
|
10696
11422
|
summary: "Choose the host's update channel: stable, beta or unstable.",
|
|
10697
11423
|
positionals: [{ name: "channel", required: true, description: "stable, beta or unstable" }],
|
|
10698
11424
|
options: [REQUEST_ID_OPTION],
|
|
@@ -10713,6 +11439,7 @@ var hostCommands = [
|
|
|
10713
11439
|
},
|
|
10714
11440
|
{
|
|
10715
11441
|
path: ["host", "update", "check"],
|
|
11442
|
+
action: "host.update.check",
|
|
10716
11443
|
summary: "Ask the backend's computer to check for an Orkestrator update.",
|
|
10717
11444
|
positionals: [],
|
|
10718
11445
|
options: [WAIT_OPTION, TIMEOUT_OPTION, REQUEST_ID_OPTION],
|
|
@@ -10747,6 +11474,13 @@ var hostCommands = [
|
|
|
10747
11474
|
},
|
|
10748
11475
|
{
|
|
10749
11476
|
path: ["host", "update", "apply"],
|
|
11477
|
+
action: "host.update.apply",
|
|
11478
|
+
inputFields: {
|
|
11479
|
+
release: "version",
|
|
11480
|
+
yes: "confirm",
|
|
11481
|
+
"when-idle": "drain",
|
|
11482
|
+
"max-wait": "maxWaitSeconds"
|
|
11483
|
+
},
|
|
10750
11484
|
summary: "Install an Orkestrator update on the backend's computer. It restarts there after a 60 s countdown.",
|
|
10751
11485
|
description: "Someone at that computer can postpone the restart; the update then installs the next time the app quits. Restarting ends agent turns and terminals running on that computer; containers keep running.",
|
|
10752
11486
|
positionals: [],
|
|
@@ -10796,70 +11530,632 @@ var hostCommands = [
|
|
|
10796
11530
|
...whenIdle ? { drain: "when-idle" } : {},
|
|
10797
11531
|
...typeof maxWait === "number" ? { maxWaitSeconds: Math.round(maxWait / 1000) } : {}
|
|
10798
11532
|
}, parsed.options);
|
|
10799
|
-
const latest = wait && receipt ? await waitForReceipt(context, session, receipt, limit, "host.update.apply") : receipt;
|
|
10800
|
-
const failure = latest && isTerminalReceipt(latest) ? receiptFailure(latest) : undefined;
|
|
10801
|
-
const host = wait ? await readHost(session).catch(() => null) : null;
|
|
10802
|
-
const human = [
|
|
10803
|
-
`Update ${result.jobId}: ${latest?.state ?? "accepted"} (${latest?.stage ?? ""})`
|
|
10804
|
-
];
|
|
10805
|
-
if (host)
|
|
10806
|
-
human.push(...hostLines(host));
|
|
10807
|
-
else if (!wait)
|
|
10808
|
-
human.push("Follow it with `orkestrator host status`.");
|
|
11533
|
+
const latest = wait && receipt ? await waitForReceipt(context, session, receipt, limit, "host.update.apply") : receipt;
|
|
11534
|
+
const failure = latest && isTerminalReceipt(latest) ? receiptFailure(latest) : undefined;
|
|
11535
|
+
const host = wait ? await readHost(session).catch(() => null) : null;
|
|
11536
|
+
const human = [
|
|
11537
|
+
`Update ${result.jobId}: ${latest?.state ?? "accepted"} (${latest?.stage ?? ""})`
|
|
11538
|
+
];
|
|
11539
|
+
if (host)
|
|
11540
|
+
human.push(...hostLines(host));
|
|
11541
|
+
else if (!wait)
|
|
11542
|
+
human.push("Follow it with `orkestrator host status`.");
|
|
11543
|
+
return {
|
|
11544
|
+
action: "host.update.apply",
|
|
11545
|
+
result: { ...result, ...host ? { host } : {} },
|
|
11546
|
+
receipt: latest,
|
|
11547
|
+
warnings,
|
|
11548
|
+
connection: session.identity,
|
|
11549
|
+
ids: receipt ? [receipt.operationId] : [],
|
|
11550
|
+
human,
|
|
11551
|
+
...failure ? { failure } : {}
|
|
11552
|
+
};
|
|
11553
|
+
}
|
|
11554
|
+
},
|
|
11555
|
+
{
|
|
11556
|
+
path: ["host", "update", "cancel"],
|
|
11557
|
+
action: "host.update.cancel",
|
|
11558
|
+
summary: "Cancel the host's update before it restarts.",
|
|
11559
|
+
positionals: [],
|
|
11560
|
+
options: [REQUEST_ID_OPTION],
|
|
11561
|
+
async run(context, parsed) {
|
|
11562
|
+
const { session, result, receipt, warnings } = await submit(context, "host.update.cancel", {}, parsed.options);
|
|
11563
|
+
return {
|
|
11564
|
+
action: "host.update.cancel",
|
|
11565
|
+
result,
|
|
11566
|
+
receipt,
|
|
11567
|
+
warnings,
|
|
11568
|
+
connection: session.identity,
|
|
11569
|
+
human: [`Cancelled update ${result.jobId}.`]
|
|
11570
|
+
};
|
|
11571
|
+
}
|
|
11572
|
+
},
|
|
11573
|
+
taskCommand({
|
|
11574
|
+
path: ["host", "toolchain", "repair"],
|
|
11575
|
+
action: "host.toolchain.repair",
|
|
11576
|
+
summary: "Reinstall whatever of the host's pinned agent tools is missing or fails its digest.",
|
|
11577
|
+
input: {}
|
|
11578
|
+
}),
|
|
11579
|
+
taskCommand({
|
|
11580
|
+
path: ["host", "image", "update"],
|
|
11581
|
+
action: "host.image.update",
|
|
11582
|
+
summary: "Pull the release container image matching the backend and use it for new environments.",
|
|
11583
|
+
description: "Running environments keep their image; `host status` lists those to rebuild afterwards.",
|
|
11584
|
+
input: {}
|
|
11585
|
+
}),
|
|
11586
|
+
taskCommand({
|
|
11587
|
+
path: ["host", "gh", "latest"],
|
|
11588
|
+
action: "host.gh.update",
|
|
11589
|
+
summary: "Make the host use the newest gh release instead of its pin (unqualified).",
|
|
11590
|
+
input: { channel: "latest" }
|
|
11591
|
+
}),
|
|
11592
|
+
taskCommand({
|
|
11593
|
+
path: ["host", "gh", "pinned"],
|
|
11594
|
+
action: "host.gh.update",
|
|
11595
|
+
summary: "Make the host use the gh this Orkestrator release pins.",
|
|
11596
|
+
input: { channel: "pinned" }
|
|
11597
|
+
})
|
|
11598
|
+
];
|
|
11599
|
+
|
|
11600
|
+
// ../protocol/src/schedule-time.ts
|
|
11601
|
+
var CRON_SEARCH_DAYS = 366 * 5;
|
|
11602
|
+
var formatters = new Map;
|
|
11603
|
+
|
|
11604
|
+
// ../protocol/src/scheduled-jobs.ts
|
|
11605
|
+
var SCHEDULED_JOB_LIMITS = Object.freeze({
|
|
11606
|
+
maxSchedules: 1000,
|
|
11607
|
+
maxPerOwner: 50,
|
|
11608
|
+
historyEntries: 20,
|
|
11609
|
+
nameMaxChars: 120,
|
|
11610
|
+
promptMaxChars: 1e5,
|
|
11611
|
+
minIntervalSeconds: 60,
|
|
11612
|
+
maxIntervalSeconds: 366 * 24 * 60 * 60,
|
|
11613
|
+
defaultCatchUpMinutes: 15,
|
|
11614
|
+
maxCatchUpMinutes: 7 * 24 * 60,
|
|
11615
|
+
onTimeToleranceMs: 60000,
|
|
11616
|
+
actionInputMaxBytes: 64 * 1024,
|
|
11617
|
+
busyRetryWindowMs: 15 * 60000,
|
|
11618
|
+
busyRetryDelayMs: 30000,
|
|
11619
|
+
fireStaleMs: 90000,
|
|
11620
|
+
fireHeartbeatMs: 20000,
|
|
11621
|
+
maxFireAttempts: 3
|
|
11622
|
+
});
|
|
11623
|
+
function describeScheduleTrigger(trigger, timeZone) {
|
|
11624
|
+
switch (trigger.kind) {
|
|
11625
|
+
case "once":
|
|
11626
|
+
return `once at ${trigger.at}`;
|
|
11627
|
+
case "cron":
|
|
11628
|
+
return `cron "${trigger.expression}" (${timeZone})`;
|
|
11629
|
+
case "interval": {
|
|
11630
|
+
const seconds = trigger.everySeconds;
|
|
11631
|
+
const text = seconds % 86400 === 0 ? `${seconds / 86400}d` : seconds % 3600 === 0 ? `${seconds / 3600}h` : seconds % 60 === 0 ? `${seconds / 60}m` : `${seconds}s`;
|
|
11632
|
+
return `every ${text}`;
|
|
11633
|
+
}
|
|
11634
|
+
}
|
|
11635
|
+
}
|
|
11636
|
+
|
|
11637
|
+
// src/client/commands/schedule.ts
|
|
11638
|
+
var SCHEDULE_ID = { name: "schedule", required: true, description: "Schedule ID (sch_...)." };
|
|
11639
|
+
var TIMING_OPTIONS = [
|
|
11640
|
+
{
|
|
11641
|
+
name: "at",
|
|
11642
|
+
kind: "string",
|
|
11643
|
+
valueName: "TIME",
|
|
11644
|
+
description: "Once: HH:MM (next occurrence), YYYY-MM-DDTHH:MM, an ISO time with an offset, or +15m."
|
|
11645
|
+
},
|
|
11646
|
+
{
|
|
11647
|
+
name: "cron",
|
|
11648
|
+
kind: "string",
|
|
11649
|
+
valueName: "EXPR",
|
|
11650
|
+
description: 'Recurring: five-field cron, e.g. "0 9 * * 1-5", or @daily.'
|
|
11651
|
+
},
|
|
11652
|
+
{
|
|
11653
|
+
name: "every",
|
|
11654
|
+
kind: "duration",
|
|
11655
|
+
valueName: "DURATION",
|
|
11656
|
+
description: "Recurring: a fixed interval (at least 1m)."
|
|
11657
|
+
},
|
|
11658
|
+
{
|
|
11659
|
+
name: "start-at",
|
|
11660
|
+
kind: "string",
|
|
11661
|
+
valueName: "TIME",
|
|
11662
|
+
description: "With --every: the first fire."
|
|
11663
|
+
},
|
|
11664
|
+
{
|
|
11665
|
+
name: "time-zone",
|
|
11666
|
+
kind: "string",
|
|
11667
|
+
valueName: "ZONE",
|
|
11668
|
+
description: "IANA zone for --at and --cron, e.g. Europe/London. Default: the backend host's."
|
|
11669
|
+
},
|
|
11670
|
+
{
|
|
11671
|
+
name: "catch-up-minutes",
|
|
11672
|
+
kind: "integer",
|
|
11673
|
+
valueName: "N",
|
|
11674
|
+
description: "Run a fire missed while the backend was down if at most N minutes late (default 15)."
|
|
11675
|
+
},
|
|
11676
|
+
{ name: "name", kind: "string", valueName: "TEXT", description: "Label shown in lists." }
|
|
11677
|
+
];
|
|
11678
|
+
var ACTION_OPTIONS = [
|
|
11679
|
+
{
|
|
11680
|
+
name: "mail",
|
|
11681
|
+
kind: "string",
|
|
11682
|
+
valueName: "ENV:TAB",
|
|
11683
|
+
description: "Action: send agent mail (mail.send) to this mailbox."
|
|
11684
|
+
},
|
|
11685
|
+
{ name: "subject", kind: "string", valueName: "TEXT", description: "With --mail: subject." },
|
|
11686
|
+
{ name: "body", kind: "string", valueName: "TEXT", description: "With --mail: message text." },
|
|
11687
|
+
{
|
|
11688
|
+
name: "body-file",
|
|
11689
|
+
kind: "string",
|
|
11690
|
+
valueName: "PATH",
|
|
11691
|
+
description: "With --mail: read the text from a file."
|
|
11692
|
+
},
|
|
11693
|
+
{
|
|
11694
|
+
name: "session",
|
|
11695
|
+
kind: "string",
|
|
11696
|
+
valueName: "SESSION",
|
|
11697
|
+
description: "Action: prompt this idle session (session.prompt); give the prompt with --prompt\u2026"
|
|
11698
|
+
},
|
|
11699
|
+
{
|
|
11700
|
+
name: "launch-project",
|
|
11701
|
+
kind: "string",
|
|
11702
|
+
valueName: "PROJECT_ID",
|
|
11703
|
+
description: "Action: launch an environment in this project (environment.launch)."
|
|
11704
|
+
},
|
|
11705
|
+
{
|
|
11706
|
+
name: "agent",
|
|
11707
|
+
kind: "string",
|
|
11708
|
+
valueName: "AGENT",
|
|
11709
|
+
description: "With --launch-project: agent platform."
|
|
11710
|
+
},
|
|
11711
|
+
{
|
|
11712
|
+
name: "type",
|
|
11713
|
+
kind: "string",
|
|
11714
|
+
valueName: "TYPE",
|
|
11715
|
+
description: "With --launch-project: local (default) or container."
|
|
11716
|
+
},
|
|
11717
|
+
{
|
|
11718
|
+
name: "model",
|
|
11719
|
+
kind: "string",
|
|
11720
|
+
valueName: "MODEL",
|
|
11721
|
+
description: "With --launch-project: model ID."
|
|
11722
|
+
},
|
|
11723
|
+
{
|
|
11724
|
+
name: "base-branch",
|
|
11725
|
+
kind: "string",
|
|
11726
|
+
valueName: "BRANCH",
|
|
11727
|
+
description: "With --launch-project: base branch."
|
|
11728
|
+
},
|
|
11729
|
+
{
|
|
11730
|
+
name: "base-commit",
|
|
11731
|
+
kind: "string",
|
|
11732
|
+
valueName: "SHA",
|
|
11733
|
+
description: "With --launch-project: pin this commit; omitted, the branch's newest commit at fire time."
|
|
11734
|
+
},
|
|
11735
|
+
{
|
|
11736
|
+
name: "mode",
|
|
11737
|
+
kind: "string",
|
|
11738
|
+
valueName: "MODE",
|
|
11739
|
+
description: "With --session or --launch-project: plan or build."
|
|
11740
|
+
},
|
|
11741
|
+
{
|
|
11742
|
+
name: "action",
|
|
11743
|
+
kind: "string",
|
|
11744
|
+
valueName: "ACTION",
|
|
11745
|
+
description: "Action: any public mutation by name (e.g. ticket.comment) with --input-json."
|
|
11746
|
+
},
|
|
11747
|
+
{
|
|
11748
|
+
name: "input-json",
|
|
11749
|
+
kind: "string",
|
|
11750
|
+
valueName: "JSON",
|
|
11751
|
+
description: "With --action: its input object."
|
|
11752
|
+
},
|
|
11753
|
+
...PROMPT_OPTIONS
|
|
11754
|
+
];
|
|
11755
|
+
var ACTION_INPUT_FIELDS = Object.fromEntries(ACTION_OPTIONS.map((option) => [option.name, "action"]));
|
|
11756
|
+
function timingInput(options2) {
|
|
11757
|
+
const every = options2.every;
|
|
11758
|
+
if (typeof every === "number" && every % 1000 !== 0) {
|
|
11759
|
+
throw new CliError("invalid-input", "--every must be whole seconds");
|
|
11760
|
+
}
|
|
11761
|
+
const at = stringOption(options2, "at");
|
|
11762
|
+
const cron = stringOption(options2, "cron");
|
|
11763
|
+
const startAt = stringOption(options2, "start-at");
|
|
11764
|
+
const timeZone = stringOption(options2, "time-zone");
|
|
11765
|
+
const name = stringOption(options2, "name");
|
|
11766
|
+
return {
|
|
11767
|
+
...at !== undefined ? { at } : {},
|
|
11768
|
+
...cron !== undefined ? { cron } : {},
|
|
11769
|
+
...typeof every === "number" ? { everySeconds: every / 1000 } : {},
|
|
11770
|
+
...startAt !== undefined ? { startAt } : {},
|
|
11771
|
+
...timeZone !== undefined ? { timeZone } : {},
|
|
11772
|
+
...typeof options2["catch-up-minutes"] === "number" ? { catchUpMinutes: options2["catch-up-minutes"] } : {},
|
|
11773
|
+
...name !== undefined ? { name } : {}
|
|
11774
|
+
};
|
|
11775
|
+
}
|
|
11776
|
+
async function actionInput(io, options2) {
|
|
11777
|
+
const chosen = ["mail", "session", "launch-project", "action"].filter((name) => options2[name] !== undefined);
|
|
11778
|
+
if (chosen.length === 0)
|
|
11779
|
+
return;
|
|
11780
|
+
if (chosen.length > 1) {
|
|
11781
|
+
throw new CliError("invalid-input", `--${chosen.join(", --")} are mutually exclusive`);
|
|
11782
|
+
}
|
|
11783
|
+
const prompt = () => readTextSource(io, {
|
|
11784
|
+
file: options2["prompt-file"],
|
|
11785
|
+
stdin: options2["prompt-stdin"],
|
|
11786
|
+
inline: options2.prompt,
|
|
11787
|
+
label: "Prompt",
|
|
11788
|
+
maxBytes: 400000,
|
|
11789
|
+
names: { file: "--prompt-file", stdin: "--prompt-stdin", inline: "--prompt" },
|
|
11790
|
+
required: true
|
|
11791
|
+
});
|
|
11792
|
+
const mode = options2.mode !== undefined ? { mode: oneOf(options2.mode, ["plan", "build"], "--mode") } : {};
|
|
11793
|
+
switch (chosen[0]) {
|
|
11794
|
+
case "mail": {
|
|
11795
|
+
const [environmentId, tabId, extra] = String(options2.mail).split(":");
|
|
11796
|
+
if (!environmentId || !tabId || extra !== undefined) {
|
|
11797
|
+
throw new CliError("invalid-input", "--mail expects ENVIRONMENT_ID:TAB_ID");
|
|
11798
|
+
}
|
|
11799
|
+
const body = await readTextSource(io, {
|
|
11800
|
+
file: options2["body-file"],
|
|
11801
|
+
inline: options2.body,
|
|
11802
|
+
label: "Message",
|
|
11803
|
+
maxBytes: 32 * 1024,
|
|
11804
|
+
names: { file: "--body-file", stdin: "--body-stdin", inline: "--body" },
|
|
11805
|
+
required: true
|
|
11806
|
+
});
|
|
11807
|
+
const subject = stringOption(options2, "subject");
|
|
11808
|
+
return {
|
|
11809
|
+
kind: "action",
|
|
11810
|
+
action: "mail.send",
|
|
11811
|
+
input: {
|
|
11812
|
+
toEnvironmentId: environmentId,
|
|
11813
|
+
toTabId: tabId,
|
|
11814
|
+
body,
|
|
11815
|
+
...subject ? { subject } : {}
|
|
11816
|
+
}
|
|
11817
|
+
};
|
|
11818
|
+
}
|
|
11819
|
+
case "session":
|
|
11820
|
+
return { kind: "prompt", sessionId: options2.session, prompt: await prompt(), ...mode };
|
|
11821
|
+
case "launch-project": {
|
|
11822
|
+
const agent = stringOption(options2, "agent");
|
|
11823
|
+
if (!agent)
|
|
11824
|
+
throw new CliError("invalid-input", "--launch-project needs --agent");
|
|
11825
|
+
const type = options2.type === undefined ? "local" : oneOf(options2.type, ["local", "container"], "--type");
|
|
11826
|
+
const model = stringOption(options2, "model");
|
|
11827
|
+
const baseBranch = stringOption(options2, "base-branch");
|
|
11828
|
+
const baseCommit = stringOption(options2, "base-commit");
|
|
11829
|
+
return {
|
|
11830
|
+
kind: "launch",
|
|
11831
|
+
projectId: options2["launch-project"],
|
|
11832
|
+
agent,
|
|
11833
|
+
type,
|
|
11834
|
+
prompt: await prompt(),
|
|
11835
|
+
...mode,
|
|
11836
|
+
...model ? { model } : {},
|
|
11837
|
+
...baseBranch ? { baseBranch } : {},
|
|
11838
|
+
...baseCommit ? { baseCommit } : {}
|
|
11839
|
+
};
|
|
11840
|
+
}
|
|
11841
|
+
default: {
|
|
11842
|
+
let input = {};
|
|
11843
|
+
const raw = stringOption(options2, "input-json");
|
|
11844
|
+
if (raw !== undefined) {
|
|
11845
|
+
try {
|
|
11846
|
+
input = JSON.parse(raw);
|
|
11847
|
+
} catch {
|
|
11848
|
+
throw new CliError("invalid-input", "--input-json is not valid JSON");
|
|
11849
|
+
}
|
|
11850
|
+
}
|
|
11851
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
11852
|
+
throw new CliError("invalid-input", "--input-json must be a JSON object");
|
|
11853
|
+
}
|
|
11854
|
+
return { kind: "action", action: options2.action, input };
|
|
11855
|
+
}
|
|
11856
|
+
}
|
|
11857
|
+
}
|
|
11858
|
+
function lastOutcome(job) {
|
|
11859
|
+
const fire = job.activeFire ?? job.lastFire;
|
|
11860
|
+
if (!fire)
|
|
11861
|
+
return "-";
|
|
11862
|
+
return fire.error ? `${fire.state}: ${fire.error.code}` : fire.state;
|
|
11863
|
+
}
|
|
11864
|
+
function scheduleLines(job) {
|
|
11865
|
+
return [
|
|
11866
|
+
...keyValues([
|
|
11867
|
+
["id", job.id],
|
|
11868
|
+
["name", job.name],
|
|
11869
|
+
["owner", job.ownerLabel],
|
|
11870
|
+
["state", job.pausedReason ? `${job.state} (${job.pausedReason})` : job.state],
|
|
11871
|
+
["when", describeScheduleTrigger(job.trigger, job.timeZone)],
|
|
11872
|
+
["time zone", job.timeZone],
|
|
11873
|
+
["next fire", job.nextFireAt ?? "-"],
|
|
11874
|
+
["catch-up", `${job.catchUpMinutes} min`],
|
|
11875
|
+
["action", job.action.kind === "action" ? job.action.action : job.action.kind]
|
|
11876
|
+
]),
|
|
11877
|
+
"",
|
|
11878
|
+
...table(["FIRE", "SLOT", "STATE", "ERROR"], [...job.fires].reverse().map((fire) => [
|
|
11879
|
+
fire.fireId,
|
|
11880
|
+
fire.scheduledFor,
|
|
11881
|
+
fire.state,
|
|
11882
|
+
fire.error ? `${fire.error.code}: ${fire.error.message}` : ""
|
|
11883
|
+
]))
|
|
11884
|
+
];
|
|
11885
|
+
}
|
|
11886
|
+
function simpleMutation(verb, summary) {
|
|
11887
|
+
const action = `schedule.${verb}`;
|
|
11888
|
+
return {
|
|
11889
|
+
path: ["schedule", verb],
|
|
11890
|
+
action,
|
|
11891
|
+
summary,
|
|
11892
|
+
positionals: [SCHEDULE_ID],
|
|
11893
|
+
options: [REQUEST_ID_OPTION],
|
|
11894
|
+
inputFields: { schedule: "scheduleId" },
|
|
11895
|
+
idOutput: "schedule ID",
|
|
11896
|
+
async run(context, parsed) {
|
|
11897
|
+
const { session, result, receipt, warnings } = await submit(context, action, { scheduleId: parsed.positionals.schedule }, parsed.options);
|
|
11898
|
+
return {
|
|
11899
|
+
action,
|
|
11900
|
+
result,
|
|
11901
|
+
receipt,
|
|
11902
|
+
warnings,
|
|
11903
|
+
connection: session.identity,
|
|
11904
|
+
ids: [String(parsed.positionals.schedule)],
|
|
11905
|
+
human: keyValues(Object.entries(result))
|
|
11906
|
+
};
|
|
11907
|
+
}
|
|
11908
|
+
};
|
|
11909
|
+
}
|
|
11910
|
+
var scheduleCommands = [
|
|
11911
|
+
{
|
|
11912
|
+
path: ["schedule", "list"],
|
|
11913
|
+
action: "schedule.list",
|
|
11914
|
+
summary: "List scheduled jobs with their next fire, owner and last outcome.",
|
|
11915
|
+
positionals: [],
|
|
11916
|
+
options: [
|
|
11917
|
+
{
|
|
11918
|
+
name: "project",
|
|
11919
|
+
kind: "string",
|
|
11920
|
+
valueName: "PROJECT_ID",
|
|
11921
|
+
description: "Owned in or aimed at this project."
|
|
11922
|
+
},
|
|
11923
|
+
{
|
|
11924
|
+
name: "state",
|
|
11925
|
+
kind: "string",
|
|
11926
|
+
valueName: "STATE",
|
|
11927
|
+
description: "active, paused or completed."
|
|
11928
|
+
},
|
|
11929
|
+
{
|
|
11930
|
+
name: "owner-kind",
|
|
11931
|
+
kind: "string",
|
|
11932
|
+
valueName: "KIND",
|
|
11933
|
+
description: "operator, control-mcp, agent or coordinator."
|
|
11934
|
+
},
|
|
11935
|
+
{ name: "offset", kind: "integer", valueName: "N", description: "Rows to skip." },
|
|
11936
|
+
{ name: "limit", kind: "integer", valueName: "N", description: "Rows (max 200)." }
|
|
11937
|
+
],
|
|
11938
|
+
async run(context, parsed) {
|
|
11939
|
+
const session = await context.session();
|
|
11940
|
+
const options2 = parsed.options;
|
|
11941
|
+
const page = await session.read("schedule.list", {
|
|
11942
|
+
...options2.project !== undefined ? { projectId: options2.project } : {},
|
|
11943
|
+
...options2.state !== undefined ? { state: options2.state } : {},
|
|
11944
|
+
...options2["owner-kind"] !== undefined ? { ownerKind: options2["owner-kind"] } : {},
|
|
11945
|
+
...options2.offset !== undefined ? { offset: options2.offset } : {},
|
|
11946
|
+
...options2.limit !== undefined ? { limit: options2.limit } : {}
|
|
11947
|
+
});
|
|
11948
|
+
return {
|
|
11949
|
+
action: "schedule.list",
|
|
11950
|
+
result: page,
|
|
11951
|
+
connection: session.identity,
|
|
11952
|
+
human: table(["ID", "NAME", "STATE", "NEXT FIRE", "OWNER", "LAST"], page.schedules.map((job) => [
|
|
11953
|
+
job.id,
|
|
11954
|
+
job.name,
|
|
11955
|
+
job.state,
|
|
11956
|
+
job.nextFireAt ?? "-",
|
|
11957
|
+
job.ownerLabel,
|
|
11958
|
+
lastOutcome(job)
|
|
11959
|
+
]))
|
|
11960
|
+
};
|
|
11961
|
+
}
|
|
11962
|
+
},
|
|
11963
|
+
{
|
|
11964
|
+
path: ["schedule", "get"],
|
|
11965
|
+
action: "schedule.get",
|
|
11966
|
+
summary: "Show one scheduled job and its fire history.",
|
|
11967
|
+
positionals: [SCHEDULE_ID],
|
|
11968
|
+
options: [],
|
|
11969
|
+
inputFields: { schedule: "scheduleId" },
|
|
11970
|
+
async run(context, parsed) {
|
|
11971
|
+
const session = await context.session();
|
|
11972
|
+
const result = await session.read("schedule.get", {
|
|
11973
|
+
scheduleId: parsed.positionals.schedule
|
|
11974
|
+
});
|
|
11975
|
+
return {
|
|
11976
|
+
action: "schedule.get",
|
|
11977
|
+
result,
|
|
11978
|
+
connection: session.identity,
|
|
11979
|
+
human: scheduleLines(result.schedule)
|
|
11980
|
+
};
|
|
11981
|
+
}
|
|
11982
|
+
},
|
|
11983
|
+
{
|
|
11984
|
+
path: ["schedule", "create"],
|
|
11985
|
+
action: "schedule.create",
|
|
11986
|
+
summary: "Schedule mail, a prompt, an environment launch or another action.",
|
|
11987
|
+
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.",
|
|
11988
|
+
positionals: [],
|
|
11989
|
+
options: [
|
|
11990
|
+
...TIMING_OPTIONS,
|
|
11991
|
+
{ name: "paused", kind: "boolean", description: "Create it paused." },
|
|
11992
|
+
...ACTION_OPTIONS,
|
|
11993
|
+
REQUEST_ID_OPTION
|
|
11994
|
+
],
|
|
11995
|
+
inputFields: { ...ACTION_INPUT_FIELDS, every: "everySeconds", "start-at": "startAt" },
|
|
11996
|
+
idOutput: "schedule ID",
|
|
11997
|
+
examples: [
|
|
11998
|
+
'orkestrator schedule create --at 03:12 --mail env-1:startup-agent --body "Resume the stalled workers."',
|
|
11999
|
+
'orkestrator schedule create --cron "0 9 * * 1-5" --session ses_... --prompt "Summarise CI failures."'
|
|
12000
|
+
],
|
|
12001
|
+
async run(context, parsed) {
|
|
12002
|
+
const action = await actionInput(context.io, parsed.options);
|
|
12003
|
+
if (!action)
|
|
12004
|
+
throw new CliError("invalid-input", "Choose an action: --mail, --session, --launch-project or --action");
|
|
12005
|
+
const { session, result, receipt, warnings } = await submit(context, "schedule.create", {
|
|
12006
|
+
...timingInput(parsed.options),
|
|
12007
|
+
...parsed.options.paused === true ? { paused: true } : {},
|
|
12008
|
+
action
|
|
12009
|
+
}, parsed.options);
|
|
12010
|
+
return {
|
|
12011
|
+
action: "schedule.create",
|
|
12012
|
+
result,
|
|
12013
|
+
receipt,
|
|
12014
|
+
warnings,
|
|
12015
|
+
connection: session.identity,
|
|
12016
|
+
ids: [result.scheduleId],
|
|
12017
|
+
human: [`scheduled ${result.scheduleId}; next fire ${result.nextFireAt ?? "none"}`]
|
|
12018
|
+
};
|
|
12019
|
+
}
|
|
12020
|
+
},
|
|
12021
|
+
{
|
|
12022
|
+
path: ["schedule", "update"],
|
|
12023
|
+
action: "schedule.update",
|
|
12024
|
+
summary: "Change a schedule's time, zone, catch-up window, name or action.",
|
|
12025
|
+
positionals: [SCHEDULE_ID],
|
|
12026
|
+
options: [
|
|
12027
|
+
...TIMING_OPTIONS,
|
|
12028
|
+
...ACTION_OPTIONS,
|
|
12029
|
+
{
|
|
12030
|
+
name: "expected-revision",
|
|
12031
|
+
kind: "integer",
|
|
12032
|
+
valueName: "N",
|
|
12033
|
+
description: "Apply only at this revision."
|
|
12034
|
+
},
|
|
12035
|
+
REQUEST_ID_OPTION
|
|
12036
|
+
],
|
|
12037
|
+
inputFields: {
|
|
12038
|
+
...ACTION_INPUT_FIELDS,
|
|
12039
|
+
schedule: "scheduleId",
|
|
12040
|
+
every: "everySeconds",
|
|
12041
|
+
"start-at": "startAt"
|
|
12042
|
+
},
|
|
12043
|
+
idOutput: "schedule ID",
|
|
12044
|
+
async run(context, parsed) {
|
|
12045
|
+
const action = await actionInput(context.io, parsed.options);
|
|
12046
|
+
const { session, result, receipt, warnings } = await submit(context, "schedule.update", {
|
|
12047
|
+
scheduleId: parsed.positionals.schedule,
|
|
12048
|
+
...timingInput(parsed.options),
|
|
12049
|
+
...action ? { action } : {},
|
|
12050
|
+
...typeof parsed.options["expected-revision"] === "number" ? { expectedRevision: parsed.options["expected-revision"] } : {}
|
|
12051
|
+
}, parsed.options);
|
|
10809
12052
|
return {
|
|
10810
|
-
action: "
|
|
10811
|
-
result
|
|
10812
|
-
receipt
|
|
12053
|
+
action: "schedule.update",
|
|
12054
|
+
result,
|
|
12055
|
+
receipt,
|
|
10813
12056
|
warnings,
|
|
10814
12057
|
connection: session.identity,
|
|
10815
|
-
ids:
|
|
10816
|
-
human
|
|
10817
|
-
...failure ? { failure } : {}
|
|
12058
|
+
ids: [String(parsed.positionals.schedule)],
|
|
12059
|
+
human: keyValues(Object.entries(result))
|
|
10818
12060
|
};
|
|
10819
12061
|
}
|
|
10820
12062
|
},
|
|
12063
|
+
simpleMutation("pause", "Pause a schedule; nothing fires until it is resumed."),
|
|
12064
|
+
simpleMutation("resume", "Resume a paused schedule from its next future slot."),
|
|
12065
|
+
simpleMutation("run", "Fire a schedule once now, leaving its regular slots unchanged."),
|
|
12066
|
+
simpleMutation("delete", "Delete a schedule and its history.")
|
|
12067
|
+
];
|
|
12068
|
+
|
|
12069
|
+
// src/client/commands/mail.ts
|
|
12070
|
+
var mailCommands = [
|
|
10821
12071
|
{
|
|
10822
|
-
path: ["
|
|
10823
|
-
|
|
12072
|
+
path: ["mail", "mailboxes"],
|
|
12073
|
+
action: "mail.mailboxes",
|
|
12074
|
+
summary: "List agent mailboxes a message can be sent to.",
|
|
10824
12075
|
positionals: [],
|
|
10825
|
-
options: [
|
|
12076
|
+
options: [
|
|
12077
|
+
{ name: "q", kind: "string", valueName: "TEXT", description: "Filter by name." },
|
|
12078
|
+
{ name: "offset", kind: "integer", valueName: "N", description: "Rows to skip." },
|
|
12079
|
+
{ name: "limit", kind: "integer", valueName: "N", description: "Rows (max 200)." }
|
|
12080
|
+
],
|
|
10826
12081
|
async run(context, parsed) {
|
|
10827
|
-
const
|
|
12082
|
+
const session = await context.session();
|
|
12083
|
+
const page = await session.read("mail.mailboxes", {
|
|
12084
|
+
...parsed.options.q !== undefined ? { q: parsed.options.q } : {},
|
|
12085
|
+
...parsed.options.offset !== undefined ? { offset: parsed.options.offset } : {},
|
|
12086
|
+
...parsed.options.limit !== undefined ? { limit: parsed.options.limit } : {}
|
|
12087
|
+
});
|
|
10828
12088
|
return {
|
|
10829
|
-
action: "
|
|
12089
|
+
action: "mail.mailboxes",
|
|
12090
|
+
result: page,
|
|
12091
|
+
connection: session.identity,
|
|
12092
|
+
human: table(["ENVIRONMENT", "TAB", "NAME", "PRESENCE"], page.mailboxes.map((mailbox) => [
|
|
12093
|
+
mailbox.environmentId,
|
|
12094
|
+
mailbox.tabId,
|
|
12095
|
+
`${mailbox.environmentName} / ${mailbox.displayName}`,
|
|
12096
|
+
mailbox.presence
|
|
12097
|
+
]))
|
|
12098
|
+
};
|
|
12099
|
+
}
|
|
12100
|
+
},
|
|
12101
|
+
{
|
|
12102
|
+
path: ["mail", "send"],
|
|
12103
|
+
action: "mail.send",
|
|
12104
|
+
summary: "Place a message in one tab's inbox (never injected into a turn).",
|
|
12105
|
+
positionals: [
|
|
12106
|
+
{ name: "environment", required: true },
|
|
12107
|
+
{ name: "tab", required: true }
|
|
12108
|
+
],
|
|
12109
|
+
options: [
|
|
12110
|
+
{ name: "subject", kind: "string", valueName: "TEXT", description: "Subject line." },
|
|
12111
|
+
{ name: "body", kind: "string", valueName: "TEXT", description: "Message text." },
|
|
12112
|
+
{
|
|
12113
|
+
name: "body-file",
|
|
12114
|
+
kind: "string",
|
|
12115
|
+
valueName: "PATH",
|
|
12116
|
+
description: "Read the message from a local file."
|
|
12117
|
+
},
|
|
12118
|
+
{ name: "body-stdin", kind: "boolean", description: "Read the message from stdin." },
|
|
12119
|
+
REQUEST_ID_OPTION
|
|
12120
|
+
],
|
|
12121
|
+
inputFields: {
|
|
12122
|
+
environment: "toEnvironmentId",
|
|
12123
|
+
tab: "toTabId",
|
|
12124
|
+
"body-file": "body",
|
|
12125
|
+
"body-stdin": "body"
|
|
12126
|
+
},
|
|
12127
|
+
idOutput: "message ID",
|
|
12128
|
+
async run(context, parsed) {
|
|
12129
|
+
const body = await readTextSource(context.io, {
|
|
12130
|
+
file: parsed.options["body-file"],
|
|
12131
|
+
stdin: parsed.options["body-stdin"],
|
|
12132
|
+
inline: parsed.options.body,
|
|
12133
|
+
label: "Message",
|
|
12134
|
+
maxBytes: 32 * 1024,
|
|
12135
|
+
names: { file: "--body-file", stdin: "--body-stdin", inline: "--body" },
|
|
12136
|
+
required: true
|
|
12137
|
+
});
|
|
12138
|
+
const subject = stringOption(parsed.options, "subject");
|
|
12139
|
+
if (subject !== undefined && subject.trim().length === 0) {
|
|
12140
|
+
throw new CliError("invalid-input", "--subject is empty");
|
|
12141
|
+
}
|
|
12142
|
+
const { session, result, receipt, warnings } = await submit(context, "mail.send", {
|
|
12143
|
+
toEnvironmentId: parsed.positionals.environment,
|
|
12144
|
+
toTabId: parsed.positionals.tab,
|
|
12145
|
+
body,
|
|
12146
|
+
...subject ? { subject } : {}
|
|
12147
|
+
}, parsed.options);
|
|
12148
|
+
return {
|
|
12149
|
+
action: "mail.send",
|
|
10830
12150
|
result,
|
|
10831
12151
|
receipt,
|
|
10832
12152
|
warnings,
|
|
10833
12153
|
connection: session.identity,
|
|
10834
|
-
|
|
12154
|
+
ids: result.messageId ? [result.messageId] : [],
|
|
12155
|
+
human: [`sent message ${result.messageId ?? ""}`.trim()]
|
|
10835
12156
|
};
|
|
10836
12157
|
}
|
|
10837
|
-
}
|
|
10838
|
-
taskCommand({
|
|
10839
|
-
path: ["host", "toolchain", "repair"],
|
|
10840
|
-
action: "host.toolchain.repair",
|
|
10841
|
-
summary: "Reinstall whatever of the host's pinned agent tools is missing or fails its digest.",
|
|
10842
|
-
input: {}
|
|
10843
|
-
}),
|
|
10844
|
-
taskCommand({
|
|
10845
|
-
path: ["host", "image", "update"],
|
|
10846
|
-
action: "host.image.update",
|
|
10847
|
-
summary: "Pull the release container image matching the backend and use it for new environments.",
|
|
10848
|
-
description: "Running environments keep their image; `host status` lists those to rebuild afterwards.",
|
|
10849
|
-
input: {}
|
|
10850
|
-
}),
|
|
10851
|
-
taskCommand({
|
|
10852
|
-
path: ["host", "gh", "latest"],
|
|
10853
|
-
action: "host.gh.update",
|
|
10854
|
-
summary: "Make the host use the newest gh release instead of its pin (unqualified).",
|
|
10855
|
-
input: { channel: "latest" }
|
|
10856
|
-
}),
|
|
10857
|
-
taskCommand({
|
|
10858
|
-
path: ["host", "gh", "pinned"],
|
|
10859
|
-
action: "host.gh.update",
|
|
10860
|
-
summary: "Make the host use the gh this Orkestrator release pins.",
|
|
10861
|
-
input: { channel: "pinned" }
|
|
10862
|
-
})
|
|
12158
|
+
}
|
|
10863
12159
|
];
|
|
10864
12160
|
|
|
10865
12161
|
// src/client/commands/service.ts
|
|
@@ -11477,6 +12773,7 @@ var PAGE_OPTIONS = [
|
|
|
11477
12773
|
var projectCommands = [
|
|
11478
12774
|
{
|
|
11479
12775
|
path: ["project", "list"],
|
|
12776
|
+
action: "project.list",
|
|
11480
12777
|
summary: "List projects.",
|
|
11481
12778
|
positionals: [],
|
|
11482
12779
|
options: PAGE_OPTIONS,
|
|
@@ -11501,6 +12798,7 @@ var projectCommands = [
|
|
|
11501
12798
|
},
|
|
11502
12799
|
{
|
|
11503
12800
|
path: ["project", "get"],
|
|
12801
|
+
action: "project.get",
|
|
11504
12802
|
summary: "Show one project by ID, or by --name when unique.",
|
|
11505
12803
|
positionals: [{ name: "project", required: false }],
|
|
11506
12804
|
options: [
|
|
@@ -11526,6 +12824,7 @@ var projectCommands = [
|
|
|
11526
12824
|
},
|
|
11527
12825
|
{
|
|
11528
12826
|
path: ["project", "add"],
|
|
12827
|
+
action: "project.add",
|
|
11529
12828
|
summary: "Register a repository, attach a backend checkout, or clone into a backend path.",
|
|
11530
12829
|
description: "--remote alone registers the remote. --path alone attaches an existing checkout on the BACKEND's filesystem and reads its origin there. Both together attach the checkout if it exists, or clone the remote into that backend path when it is missing or empty. No GitHub repository is created.",
|
|
11531
12830
|
positionals: [],
|
|
@@ -11560,8 +12859,9 @@ var projectCommands = [
|
|
|
11560
12859
|
},
|
|
11561
12860
|
{
|
|
11562
12861
|
path: ["project", "create"],
|
|
12862
|
+
action: "project.create",
|
|
11563
12863
|
summary: "Create a new repository: git init, a PRIVATE GitHub repository, and a push.",
|
|
11564
|
-
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.
|
|
12864
|
+
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`.",
|
|
11565
12865
|
positionals: [],
|
|
11566
12866
|
options: [
|
|
11567
12867
|
{
|
|
@@ -11597,8 +12897,49 @@ var projectCommands = [
|
|
|
11597
12897
|
};
|
|
11598
12898
|
}
|
|
11599
12899
|
},
|
|
12900
|
+
{
|
|
12901
|
+
path: ["project", "init"],
|
|
12902
|
+
action: "project.init",
|
|
12903
|
+
summary: "Create a local-only repository (git init, initial commit, no remote) and register it.",
|
|
12904
|
+
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.",
|
|
12905
|
+
positionals: [],
|
|
12906
|
+
options: [
|
|
12907
|
+
{
|
|
12908
|
+
name: "path",
|
|
12909
|
+
kind: "string",
|
|
12910
|
+
valueName: "BACKEND_PATH",
|
|
12911
|
+
description: "New, empty directory on the backend host."
|
|
12912
|
+
},
|
|
12913
|
+
REQUEST_ID_OPTION
|
|
12914
|
+
],
|
|
12915
|
+
idOutput: "project ID",
|
|
12916
|
+
async run(context, parsed) {
|
|
12917
|
+
const backendPath = stringOption(parsed.options, "path");
|
|
12918
|
+
if (!backendPath)
|
|
12919
|
+
throw new CliError("invalid-input", "--path is required");
|
|
12920
|
+
const { session, result, receipt, warnings } = await submit(context, "project.init", { path: backendPath }, parsed.options);
|
|
12921
|
+
return {
|
|
12922
|
+
action: "project.init",
|
|
12923
|
+
result,
|
|
12924
|
+
receipt,
|
|
12925
|
+
warnings,
|
|
12926
|
+
connection: session.identity,
|
|
12927
|
+
ids: [result.project.id],
|
|
12928
|
+
human: projectLines(result.project)
|
|
12929
|
+
};
|
|
12930
|
+
}
|
|
12931
|
+
},
|
|
11600
12932
|
{
|
|
11601
12933
|
path: ["project", "update"],
|
|
12934
|
+
action: "project.update",
|
|
12935
|
+
inputFields: {
|
|
12936
|
+
name: "set",
|
|
12937
|
+
folder: "set",
|
|
12938
|
+
"no-folder": "set",
|
|
12939
|
+
remote: "set",
|
|
12940
|
+
path: "set",
|
|
12941
|
+
"no-path": "set"
|
|
12942
|
+
},
|
|
11602
12943
|
summary: "Edit project metadata (does not move checkouts or rewrite .git/config).",
|
|
11603
12944
|
positionals: [{ name: "project", required: true }],
|
|
11604
12945
|
options: [
|
|
@@ -11668,13 +13009,47 @@ var projectCommands = [
|
|
|
11668
13009
|
},
|
|
11669
13010
|
{
|
|
11670
13011
|
path: ["project", "remove"],
|
|
11671
|
-
|
|
11672
|
-
|
|
13012
|
+
action: "project.remove",
|
|
13013
|
+
summary: "Remove a project registration; --cascade deletes its environments first.",
|
|
13014
|
+
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.",
|
|
11673
13015
|
positionals: [{ name: "project", required: true }],
|
|
11674
|
-
options: [
|
|
13016
|
+
options: [
|
|
13017
|
+
{
|
|
13018
|
+
name: "cascade",
|
|
13019
|
+
kind: "boolean",
|
|
13020
|
+
description: "Delete every environment of the project first."
|
|
13021
|
+
},
|
|
13022
|
+
{ name: "wait", kind: "boolean", description: "Observe the removal until it ends." },
|
|
13023
|
+
TIMEOUT_OPTION,
|
|
13024
|
+
REQUEST_ID_OPTION
|
|
13025
|
+
],
|
|
11675
13026
|
idOutput: "operation ID",
|
|
11676
13027
|
async run(context, parsed) {
|
|
11677
|
-
const
|
|
13028
|
+
const options2 = parsed.options;
|
|
13029
|
+
if (options2.timeout !== undefined && options2.wait !== true) {
|
|
13030
|
+
throw new CliError("invalid-input", "--timeout needs --wait");
|
|
13031
|
+
}
|
|
13032
|
+
if (typeof options2.timeout === "number" && options2.timeout > PUBLIC_API_LIMITS.waitMaxMs) {
|
|
13033
|
+
throw new CliError("invalid-input", "--timeout may be at most 24h");
|
|
13034
|
+
}
|
|
13035
|
+
const { session, result, receipt, warnings } = await submit(context, "project.remove", {
|
|
13036
|
+
projectId: parsed.positionals.project,
|
|
13037
|
+
...options2.cascade === true ? { cascade: true } : {}
|
|
13038
|
+
}, options2);
|
|
13039
|
+
if (options2.wait === true && receipt) {
|
|
13040
|
+
const final = await waitForOperation(context, session, receipt, options2.timeout ?? DEFAULT_WAIT_MS);
|
|
13041
|
+
const failure = receiptFailure(final);
|
|
13042
|
+
return {
|
|
13043
|
+
action: "project.remove",
|
|
13044
|
+
result: final.result ?? result,
|
|
13045
|
+
receipt: final,
|
|
13046
|
+
warnings,
|
|
13047
|
+
connection: session.identity,
|
|
13048
|
+
ids: [final.operationId],
|
|
13049
|
+
human: receiptLines(final),
|
|
13050
|
+
...failure ? { failure } : {}
|
|
13051
|
+
};
|
|
13052
|
+
}
|
|
11678
13053
|
return {
|
|
11679
13054
|
action: "project.remove",
|
|
11680
13055
|
result,
|
|
@@ -11693,6 +13068,7 @@ var projectCommands = [
|
|
|
11693
13068
|
var runCommands = [
|
|
11694
13069
|
{
|
|
11695
13070
|
path: ["run", "get"],
|
|
13071
|
+
action: "run.get",
|
|
11696
13072
|
summary: "Show an operation by ID, or by its original --request-id.",
|
|
11697
13073
|
description: "Lookup by request key finds the operation even when the original response was lost. `history-expired` (exit 3) means the key's namespace was retired and its outcome is no longer retained; it is not evidence that nothing ran.",
|
|
11698
13074
|
positionals: [{ name: "operation", required: false }],
|
|
@@ -11731,7 +13107,7 @@ var runCommands = [
|
|
|
11731
13107
|
let namespace = stringOption(parsed.options, "namespace");
|
|
11732
13108
|
if (requestId && !namespace && action) {
|
|
11733
13109
|
const capabilities = await session.capabilities();
|
|
11734
|
-
const local = await new LocalReceiptStore(context.configStore.receiptsDirectory).find(capabilities.backend.installationId, action, requestId);
|
|
13110
|
+
const local = await new LocalReceiptStore(context.configStore.receiptsDirectory).find(capabilities.backend.installationId, action, requestId, session.node);
|
|
11735
13111
|
namespace = local?.namespace;
|
|
11736
13112
|
}
|
|
11737
13113
|
const { receipt } = await session.readWithReceipt("run.get", operationId ? { operationId } : { requestId, ...action ? { action } : {}, ...namespace ? { namespace } : {} });
|
|
@@ -11739,7 +13115,7 @@ var runCommands = [
|
|
|
11739
13115
|
throw new CliError("response-invalid", "run.get returned no receipt");
|
|
11740
13116
|
return {
|
|
11741
13117
|
action: "run.get",
|
|
11742
|
-
result: null,
|
|
13118
|
+
result: receipt.result ?? null,
|
|
11743
13119
|
receipt,
|
|
11744
13120
|
connection: session.identity,
|
|
11745
13121
|
ids: [receipt.operationId],
|
|
@@ -11749,6 +13125,7 @@ var runCommands = [
|
|
|
11749
13125
|
},
|
|
11750
13126
|
{
|
|
11751
13127
|
path: ["run", "wait"],
|
|
13128
|
+
action: "run.get",
|
|
11752
13129
|
summary: "Observe an operation until it finishes (exit 0 only on success).",
|
|
11753
13130
|
description: "Exit codes: 0 succeeded, 1 failed/cancelled/interrupted, 5 --timeout passed (still running), 6 waiting for an interaction answer, 7 dispatch or outcome unknown. Ctrl+C stops observing only.",
|
|
11754
13131
|
positionals: [{ name: "operation", required: true }],
|
|
@@ -11768,7 +13145,7 @@ var runCommands = [
|
|
|
11768
13145
|
const failure = receiptFailure(final);
|
|
11769
13146
|
return {
|
|
11770
13147
|
action: "run.wait",
|
|
11771
|
-
result: null,
|
|
13148
|
+
result: final.result ?? null,
|
|
11772
13149
|
receipt: final,
|
|
11773
13150
|
connection: session.identity,
|
|
11774
13151
|
ids: [final.operationId],
|
|
@@ -11779,6 +13156,7 @@ var runCommands = [
|
|
|
11779
13156
|
},
|
|
11780
13157
|
{
|
|
11781
13158
|
path: ["run", "retry"],
|
|
13159
|
+
action: "run.retry",
|
|
11782
13160
|
summary: "Retry the exact parked prompt/steer of an unknown dispatch under its original key.",
|
|
11783
13161
|
description: "Never sends a different prompt and never mints a new key.",
|
|
11784
13162
|
positionals: [{ name: "operation", required: true }],
|
|
@@ -11799,6 +13177,7 @@ var runCommands = [
|
|
|
11799
13177
|
},
|
|
11800
13178
|
{
|
|
11801
13179
|
path: ["run", "discard"],
|
|
13180
|
+
action: "run.discard",
|
|
11802
13181
|
summary: "Clear the recovery state of an unknown dispatch (does NOT undo a turn that ran).",
|
|
11803
13182
|
positionals: [{ name: "operation", required: true }],
|
|
11804
13183
|
options: [REQUEST_ID_OPTION],
|
|
@@ -11818,6 +13197,7 @@ var runCommands = [
|
|
|
11818
13197
|
},
|
|
11819
13198
|
{
|
|
11820
13199
|
path: ["run", "cancel"],
|
|
13200
|
+
action: "run.cancel",
|
|
11821
13201
|
summary: "Cancel a running exec operation (its exact worker and descendants).",
|
|
11822
13202
|
positionals: [{ name: "operation", required: true }],
|
|
11823
13203
|
options: [REQUEST_ID_OPTION],
|
|
@@ -11837,6 +13217,8 @@ var runCommands = [
|
|
|
11837
13217
|
},
|
|
11838
13218
|
{
|
|
11839
13219
|
path: ["run", "output"],
|
|
13220
|
+
action: "run.output",
|
|
13221
|
+
inputFields: { tail: "tailBytes" },
|
|
11840
13222
|
summary: "Read a bounded window of an exec operation's stdout or stderr.",
|
|
11841
13223
|
positionals: [{ name: "operation", required: true }],
|
|
11842
13224
|
options: [
|
|
@@ -11913,9 +13295,10 @@ var runCommands = [
|
|
|
11913
13295
|
return {
|
|
11914
13296
|
action: "run.receipts",
|
|
11915
13297
|
result: { items: receipts },
|
|
11916
|
-
human: table(["REQUEST", "ACTION", "OPERATION", "STATE", "UPDATED"], receipts.map((receipt) => [
|
|
13298
|
+
human: table(["REQUEST", "ACTION", "NODE", "OPERATION", "STATE", "UPDATED"], receipts.map((receipt) => [
|
|
11917
13299
|
receipt.requestId,
|
|
11918
13300
|
receipt.action,
|
|
13301
|
+
receipt.node ?? "(connected node)",
|
|
11919
13302
|
receipt.operationId ?? "(no response)",
|
|
11920
13303
|
receipt.state ?? "-",
|
|
11921
13304
|
receipt.updatedAt
|
|
@@ -11926,6 +13309,7 @@ var runCommands = [
|
|
|
11926
13309
|
];
|
|
11927
13310
|
|
|
11928
13311
|
// src/client/commands/session.ts
|
|
13312
|
+
import path15 from "path";
|
|
11929
13313
|
var WAIT_OPTIONS = [
|
|
11930
13314
|
{ name: "wait", kind: "boolean", description: "Observe this run until its turn ends." },
|
|
11931
13315
|
TIMEOUT_OPTION,
|
|
@@ -11961,6 +13345,44 @@ var CONTROL_OPTIONS = [
|
|
|
11961
13345
|
description: "Conversation mode."
|
|
11962
13346
|
}
|
|
11963
13347
|
];
|
|
13348
|
+
var LITERAL_OPTION = {
|
|
13349
|
+
name: "literal",
|
|
13350
|
+
kind: "boolean",
|
|
13351
|
+
description: "Send the prompt as plain text: a leading /name never runs a provider command (by default it does)."
|
|
13352
|
+
};
|
|
13353
|
+
var ENQUEUE_OPTION = {
|
|
13354
|
+
name: "enqueue",
|
|
13355
|
+
kind: "boolean",
|
|
13356
|
+
description: "If the session is busy, queue this behind its current work (the backend sends it later) instead of refusing."
|
|
13357
|
+
};
|
|
13358
|
+
var ATTACH_OPTION = {
|
|
13359
|
+
name: "attach",
|
|
13360
|
+
kind: "list",
|
|
13361
|
+
valueName: "FILE",
|
|
13362
|
+
description: `Send a local file with the prompt (repeatable; at most ${PUBLIC_API_LIMITS.promptAttachmentsMaxItems} files, ${PUBLIC_API_LIMITS.promptAttachmentsMaxBytes / 1024} KiB in total).`
|
|
13363
|
+
};
|
|
13364
|
+
async function readAttachments(context, options2) {
|
|
13365
|
+
const files = options2.attach ?? [];
|
|
13366
|
+
if (files.length === 0)
|
|
13367
|
+
return;
|
|
13368
|
+
if (files.length > PUBLIC_API_LIMITS.promptAttachmentsMaxItems) {
|
|
13369
|
+
throw new CliError("input-too-large", `At most ${PUBLIC_API_LIMITS.promptAttachmentsMaxItems} attachments are allowed`);
|
|
13370
|
+
}
|
|
13371
|
+
let remaining = PUBLIC_API_LIMITS.promptAttachmentsMaxBytes;
|
|
13372
|
+
const names = new Set;
|
|
13373
|
+
const attachments = [];
|
|
13374
|
+
for (const file of files) {
|
|
13375
|
+
const name = path15.basename(file);
|
|
13376
|
+
if (names.has(name)) {
|
|
13377
|
+
throw new CliError("invalid-input", `Two attachments are both named ${name}`);
|
|
13378
|
+
}
|
|
13379
|
+
names.add(name);
|
|
13380
|
+
const bytes = await readBinaryFile(context.io, file, `Attachment ${name}`, remaining);
|
|
13381
|
+
remaining -= bytes.byteLength;
|
|
13382
|
+
attachments.push({ name, dataBase64: Buffer.from(bytes).toString("base64") });
|
|
13383
|
+
}
|
|
13384
|
+
return attachments;
|
|
13385
|
+
}
|
|
11964
13386
|
function waitBudget(options2) {
|
|
11965
13387
|
return options2.timeout ?? DEFAULT_WAIT_MS;
|
|
11966
13388
|
}
|
|
@@ -12007,6 +13429,17 @@ function controlsInput(options2) {
|
|
|
12007
13429
|
input.mode = oneOf(options2.mode, ["plan", "build"], "--mode");
|
|
12008
13430
|
return input;
|
|
12009
13431
|
}
|
|
13432
|
+
function holdLabel(hold) {
|
|
13433
|
+
if (!hold)
|
|
13434
|
+
return "";
|
|
13435
|
+
if (hold.heldAt !== undefined) {
|
|
13436
|
+
return ` (paused${hold.reason ? `, ${hold.reason}` : ""}; resume with interactions hold --resume)`;
|
|
13437
|
+
}
|
|
13438
|
+
if (hold.available) {
|
|
13439
|
+
return ` (pausable${hold.reason ? `, ${hold.reason}` : ""} with interactions hold)`;
|
|
13440
|
+
}
|
|
13441
|
+
return ` (not pausable${hold.reason ? `: ${hold.reason}` : ""})`;
|
|
13442
|
+
}
|
|
12010
13443
|
function parseAnswers(options2) {
|
|
12011
13444
|
const answers = new Map;
|
|
12012
13445
|
const entry = (questionId) => {
|
|
@@ -12039,6 +13472,7 @@ function parseAnswers(options2) {
|
|
|
12039
13472
|
var sessionCommands = [
|
|
12040
13473
|
{
|
|
12041
13474
|
path: ["agent", "options"],
|
|
13475
|
+
action: "agent.options",
|
|
12042
13476
|
summary: "Enabled agents, model catalogues (with freshness), and supported controls.",
|
|
12043
13477
|
description: "Reads the backend's cached catalogues; --refresh asks providers again, which can take a while. An `empty` catalogue is a successful empty answer; `stale`/`unavailable` means the backend could not read it.",
|
|
12044
13478
|
positionals: [],
|
|
@@ -12084,6 +13518,7 @@ var sessionCommands = [
|
|
|
12084
13518
|
},
|
|
12085
13519
|
{
|
|
12086
13520
|
path: ["session", "list"],
|
|
13521
|
+
action: "session.list",
|
|
12087
13522
|
summary: "List native-agent sessions of an environment (no transcripts are read).",
|
|
12088
13523
|
positionals: [],
|
|
12089
13524
|
options: [
|
|
@@ -12112,6 +13547,7 @@ var sessionCommands = [
|
|
|
12112
13547
|
},
|
|
12113
13548
|
{
|
|
12114
13549
|
path: ["session", "get"],
|
|
13550
|
+
action: "session.get",
|
|
12115
13551
|
summary: "Show one session (activity, latest request, pending interactions).",
|
|
12116
13552
|
positionals: [{ name: "session", required: true }],
|
|
12117
13553
|
options: [],
|
|
@@ -12132,6 +13568,7 @@ var sessionCommands = [
|
|
|
12132
13568
|
},
|
|
12133
13569
|
{
|
|
12134
13570
|
path: ["session", "start"],
|
|
13571
|
+
action: "session.start",
|
|
12135
13572
|
summary: "Start an independent conversation in a ready environment and send its first prompt.",
|
|
12136
13573
|
description: "Creates a new agent tab without focusing it. The response is a submission receipt; add --wait (or run `run wait`) to observe the turn. Independent sessions in one environment share its files.",
|
|
12137
13574
|
positionals: [],
|
|
@@ -12151,9 +13588,12 @@ var sessionCommands = [
|
|
|
12151
13588
|
{ name: "title", kind: "string", valueName: "TITLE", description: "Tab title." },
|
|
12152
13589
|
...CONTROL_OPTIONS,
|
|
12153
13590
|
...PROMPT_OPTIONS,
|
|
13591
|
+
LITERAL_OPTION,
|
|
13592
|
+
ATTACH_OPTION,
|
|
12154
13593
|
...WAIT_OPTIONS,
|
|
12155
13594
|
REQUEST_ID_OPTION
|
|
12156
13595
|
],
|
|
13596
|
+
inputFields: { attach: "attachments" },
|
|
12157
13597
|
idOutput: "session ID",
|
|
12158
13598
|
async run(context, parsed) {
|
|
12159
13599
|
const options2 = parsed.options;
|
|
@@ -12164,12 +13604,15 @@ var sessionCommands = [
|
|
|
12164
13604
|
throw new CliError("invalid-input", "--environment and --agent are required");
|
|
12165
13605
|
}
|
|
12166
13606
|
const prompt = await readPrompt(context.io, options2);
|
|
13607
|
+
const attachments = await readAttachments(context, options2);
|
|
12167
13608
|
const { session, result, receipt, warnings, recoveryDeadline } = await submitRun(context, "session.start", {
|
|
12168
13609
|
environmentId,
|
|
12169
13610
|
agent,
|
|
12170
13611
|
prompt,
|
|
12171
13612
|
...options2.title !== undefined ? { title: options2.title } : {},
|
|
12172
|
-
...controlsInput(options2)
|
|
13613
|
+
...controlsInput(options2),
|
|
13614
|
+
...options2.literal === true ? { literal: true } : {},
|
|
13615
|
+
...attachments ? { attachments } : {}
|
|
12173
13616
|
}, options2);
|
|
12174
13617
|
const fromReceipt = (final) => ({
|
|
12175
13618
|
result: {
|
|
@@ -12194,8 +13637,9 @@ var sessionCommands = [
|
|
|
12194
13637
|
},
|
|
12195
13638
|
{
|
|
12196
13639
|
path: ["session", "prompt"],
|
|
12197
|
-
|
|
12198
|
-
|
|
13640
|
+
action: "session.prompt",
|
|
13641
|
+
summary: "Send a follow-up prompt to an idle session, or queue it behind a busy one.",
|
|
13642
|
+
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.",
|
|
12199
13643
|
positionals: [{ name: "session", required: true }],
|
|
12200
13644
|
options: [
|
|
12201
13645
|
{
|
|
@@ -12205,18 +13649,26 @@ var sessionCommands = [
|
|
|
12205
13649
|
description: "Conversation mode for this turn."
|
|
12206
13650
|
},
|
|
12207
13651
|
...PROMPT_OPTIONS,
|
|
13652
|
+
LITERAL_OPTION,
|
|
13653
|
+
ENQUEUE_OPTION,
|
|
13654
|
+
ATTACH_OPTION,
|
|
12208
13655
|
...WAIT_OPTIONS,
|
|
12209
13656
|
REQUEST_ID_OPTION
|
|
12210
13657
|
],
|
|
13658
|
+
inputFields: { attach: "attachments" },
|
|
12211
13659
|
idOutput: "operation (run) ID",
|
|
12212
13660
|
async run(context, parsed) {
|
|
12213
13661
|
const options2 = parsed.options;
|
|
12214
13662
|
validateWaitOptions(options2);
|
|
12215
13663
|
const prompt = await readPrompt(context.io, options2);
|
|
13664
|
+
const attachments = await readAttachments(context, options2);
|
|
12216
13665
|
const { session, result, receipt, warnings, recoveryDeadline } = await submitRun(context, "session.prompt", {
|
|
12217
13666
|
sessionId: parsed.positionals.session,
|
|
12218
13667
|
prompt,
|
|
12219
|
-
...options2.mode !== undefined ? { mode: oneOf(options2.mode, ["plan", "build"], "--mode") } : {}
|
|
13668
|
+
...options2.mode !== undefined ? { mode: oneOf(options2.mode, ["plan", "build"], "--mode") } : {},
|
|
13669
|
+
...options2.literal === true ? { literal: true } : {},
|
|
13670
|
+
...options2.enqueue === true ? { enqueue: true } : {},
|
|
13671
|
+
...attachments ? { attachments } : {}
|
|
12220
13672
|
}, options2);
|
|
12221
13673
|
const runId = result?.runId ?? receipt.operationId;
|
|
12222
13674
|
return finishRun(context, parsed, {
|
|
@@ -12230,8 +13682,101 @@ var sessionCommands = [
|
|
|
12230
13682
|
}, recoveryDeadline);
|
|
12231
13683
|
}
|
|
12232
13684
|
},
|
|
13685
|
+
{
|
|
13686
|
+
path: ["session", "commands"],
|
|
13687
|
+
action: "session.commands",
|
|
13688
|
+
summary: "List the session's slash commands (ID, name, availability, argument policy).",
|
|
13689
|
+
description: "Reads the catalogue the backend already holds; --refresh asks the provider again. Use an ID (or a unique name) with `session command`.",
|
|
13690
|
+
positionals: [{ name: "session", required: true }],
|
|
13691
|
+
options: [
|
|
13692
|
+
{ name: "refresh", kind: "boolean", description: "Ask the provider for its list again." }
|
|
13693
|
+
],
|
|
13694
|
+
idOutput: "command IDs, one per line",
|
|
13695
|
+
async run(context, parsed) {
|
|
13696
|
+
const session = await context.session();
|
|
13697
|
+
const list = await session.read("session.commands", {
|
|
13698
|
+
sessionId: parsed.positionals.session,
|
|
13699
|
+
...parsed.options.refresh === true ? { refresh: true } : {}
|
|
13700
|
+
});
|
|
13701
|
+
return {
|
|
13702
|
+
action: "session.commands",
|
|
13703
|
+
result: list,
|
|
13704
|
+
connection: session.identity,
|
|
13705
|
+
ids: list.commands.map((command) => command.id),
|
|
13706
|
+
human: [
|
|
13707
|
+
`catalogue ${list.catalogue.status}${list.catalogue.error ? ` (${list.catalogue.error})` : ""}`,
|
|
13708
|
+
...table(["NAME", "ID", "ARGS", "AVAILABLE", "DESCRIPTION"], list.commands.map((command) => [
|
|
13709
|
+
command.name,
|
|
13710
|
+
command.id,
|
|
13711
|
+
command.arguments,
|
|
13712
|
+
command.available ? "yes" : `no: ${command.unavailableReason ?? ""}`,
|
|
13713
|
+
(command.description ?? "").slice(0, 60)
|
|
13714
|
+
]))
|
|
13715
|
+
]
|
|
13716
|
+
};
|
|
13717
|
+
}
|
|
13718
|
+
},
|
|
13719
|
+
{
|
|
13720
|
+
path: ["session", "command"],
|
|
13721
|
+
action: "session.command",
|
|
13722
|
+
summary: "Run one selected slash command in a session.",
|
|
13723
|
+
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.",
|
|
13724
|
+
positionals: [{ name: "session", required: true }],
|
|
13725
|
+
options: [
|
|
13726
|
+
{
|
|
13727
|
+
name: "command",
|
|
13728
|
+
kind: "string",
|
|
13729
|
+
valueName: "ID|NAME",
|
|
13730
|
+
description: "Command ID or name from `session commands`."
|
|
13731
|
+
},
|
|
13732
|
+
{
|
|
13733
|
+
name: "expected-binding-revision",
|
|
13734
|
+
kind: "string",
|
|
13735
|
+
valueName: "REV",
|
|
13736
|
+
description: "Refuse if the command changed since `session commands` listed this revision."
|
|
13737
|
+
},
|
|
13738
|
+
ENQUEUE_OPTION,
|
|
13739
|
+
...WAIT_OPTIONS,
|
|
13740
|
+
REQUEST_ID_OPTION
|
|
13741
|
+
],
|
|
13742
|
+
acceptsRest: true,
|
|
13743
|
+
idOutput: "operation (run) ID",
|
|
13744
|
+
examples: [
|
|
13745
|
+
"orkestrator session command ses_\u2026 --command /review -- the open diff",
|
|
13746
|
+
"orkestrator session command ses_\u2026 --command /compact --wait"
|
|
13747
|
+
],
|
|
13748
|
+
async run(context, parsed) {
|
|
13749
|
+
const options2 = parsed.options;
|
|
13750
|
+
validateWaitOptions(options2);
|
|
13751
|
+
const command = stringOption(options2, "command");
|
|
13752
|
+
if (!command)
|
|
13753
|
+
throw new CliError("invalid-input", "--command is required");
|
|
13754
|
+
const args = parsed.rest.join(" ");
|
|
13755
|
+
if (Buffer.byteLength(args) > PUBLIC_API_LIMITS.commandArgumentsMaxBytes) {
|
|
13756
|
+
throw new CliError("input-too-large", "Command arguments are too large");
|
|
13757
|
+
}
|
|
13758
|
+
const { session, result, receipt, warnings, recoveryDeadline } = await submitRun(context, "session.command", {
|
|
13759
|
+
sessionId: parsed.positionals.session,
|
|
13760
|
+
command,
|
|
13761
|
+
...args.trim() ? { arguments: args } : {},
|
|
13762
|
+
...options2["expected-binding-revision"] !== undefined ? { expectedBindingRevision: options2["expected-binding-revision"] } : {},
|
|
13763
|
+
...options2.enqueue === true ? { enqueue: true } : {}
|
|
13764
|
+
}, options2);
|
|
13765
|
+
const runId = result?.runId ?? receipt.operationId;
|
|
13766
|
+
return finishRun(context, parsed, {
|
|
13767
|
+
action: "session.command",
|
|
13768
|
+
result: result ?? { runId },
|
|
13769
|
+
receipt,
|
|
13770
|
+
warnings,
|
|
13771
|
+
connection: session.identity,
|
|
13772
|
+
ids: [runId],
|
|
13773
|
+
human: receipt ? receiptLines(receipt) : [`run ${runId}`]
|
|
13774
|
+
}, recoveryDeadline);
|
|
13775
|
+
}
|
|
13776
|
+
},
|
|
12233
13777
|
{
|
|
12234
13778
|
path: ["session", "stop"],
|
|
13779
|
+
action: "session.stop",
|
|
12235
13780
|
summary: "Stop the session's current turn (not the environment).",
|
|
12236
13781
|
description: "--expect-run binds the stop to that run: if a different turn is now active the stop is refused (exit 8). Success requires the provider to confirm the turn ended; otherwise the result is unknown (exit 7).",
|
|
12237
13782
|
positionals: [{ name: "session", required: true }],
|
|
@@ -12263,6 +13808,7 @@ var sessionCommands = [
|
|
|
12263
13808
|
},
|
|
12264
13809
|
{
|
|
12265
13810
|
path: ["session", "steer"],
|
|
13811
|
+
action: "session.steer",
|
|
12266
13812
|
summary: "Redirect the active turn (providers that support steering only).",
|
|
12267
13813
|
positionals: [{ name: "session", required: true }],
|
|
12268
13814
|
options: [
|
|
@@ -12311,6 +13857,7 @@ var sessionCommands = [
|
|
|
12311
13857
|
},
|
|
12312
13858
|
{
|
|
12313
13859
|
path: ["session", "config", "get"],
|
|
13860
|
+
action: "session.config.get",
|
|
12314
13861
|
summary: "Show the live session's effective controls.",
|
|
12315
13862
|
positionals: [{ name: "session", required: true }],
|
|
12316
13863
|
options: [],
|
|
@@ -12329,6 +13876,7 @@ var sessionCommands = [
|
|
|
12329
13876
|
},
|
|
12330
13877
|
{
|
|
12331
13878
|
path: ["session", "config", "set"],
|
|
13879
|
+
action: "session.config.set",
|
|
12332
13880
|
summary: "Change the live session's controls (not environment defaults).",
|
|
12333
13881
|
description: "Applies to this conversation now, as validated by its provider; the response shows the values actually in force (a provider may clamp them). To change defaults for future sessions use `environment config set`.",
|
|
12334
13882
|
positionals: [{ name: "session", required: true }],
|
|
@@ -12350,6 +13898,7 @@ var sessionCommands = [
|
|
|
12350
13898
|
},
|
|
12351
13899
|
{
|
|
12352
13900
|
path: ["session", "history"],
|
|
13901
|
+
action: "session.history",
|
|
12353
13902
|
summary: "List earlier provider conversations this session can resume.",
|
|
12354
13903
|
positionals: [{ name: "session", required: true }],
|
|
12355
13904
|
options: [],
|
|
@@ -12372,6 +13921,7 @@ var sessionCommands = [
|
|
|
12372
13921
|
},
|
|
12373
13922
|
{
|
|
12374
13923
|
path: ["session", "resume"],
|
|
13924
|
+
action: "session.resume",
|
|
12375
13925
|
summary: "Rebind the session to an earlier conversation from `session history`.",
|
|
12376
13926
|
description: "The current conversation's history is kept; nothing is deleted.",
|
|
12377
13927
|
positionals: [
|
|
@@ -12386,6 +13936,8 @@ var sessionCommands = [
|
|
|
12386
13936
|
},
|
|
12387
13937
|
{
|
|
12388
13938
|
path: ["session", "fork"],
|
|
13939
|
+
action: "session.fork",
|
|
13940
|
+
inputFields: { message: "messageId" },
|
|
12389
13941
|
summary: "Fork the conversation into a new session (optionally at a message).",
|
|
12390
13942
|
positionals: [{ name: "session", required: true }],
|
|
12391
13943
|
options: [
|
|
@@ -12416,6 +13968,7 @@ var sessionCommands = [
|
|
|
12416
13968
|
},
|
|
12417
13969
|
{
|
|
12418
13970
|
path: ["session", "interactions", "list"],
|
|
13971
|
+
action: "session.interactions",
|
|
12419
13972
|
summary: "List pending questions and approvals with their permitted answers.",
|
|
12420
13973
|
positionals: [{ name: "session", required: true }],
|
|
12421
13974
|
options: [],
|
|
@@ -12432,6 +13985,7 @@ var sessionCommands = [
|
|
|
12432
13985
|
ids: interactions.items.map((item) => item.id),
|
|
12433
13986
|
human: interactions.items.length ? interactions.items.flatMap((item) => [
|
|
12434
13987
|
`${item.id} ${item.kind} revision ${item.revision} ${item.title}`,
|
|
13988
|
+
...item.expiresAt !== undefined ? [` expires ${new Date(item.expiresAt).toISOString()}${holdLabel(item.hold)}`] : [],
|
|
12435
13989
|
...item.questions.map((question) => ` ${question.id}: ${question.prompt} [${question.options.map((option) => option.id).join(", ")}]${question.allowFreeText ? " (free text)" : ""}`),
|
|
12436
13990
|
` actions: ${item.actions.join(", ")}`
|
|
12437
13991
|
]) : ["(no pending interactions)"]
|
|
@@ -12440,6 +13994,8 @@ var sessionCommands = [
|
|
|
12440
13994
|
},
|
|
12441
13995
|
{
|
|
12442
13996
|
path: ["session", "interactions", "resolve"],
|
|
13997
|
+
action: "session.interaction.resolve",
|
|
13998
|
+
inputFields: { revision: "expectedRevision", choose: "answers", text: "answers" },
|
|
12443
13999
|
summary: "Answer one pending interaction at its current revision.",
|
|
12444
14000
|
description: "--revision must match the interaction's current revision; a replaced or expired interaction is refused (exit 8), never answered. Nothing is approved implicitly.",
|
|
12445
14001
|
positionals: [
|
|
@@ -12509,8 +14065,44 @@ var sessionCommands = [
|
|
|
12509
14065
|
};
|
|
12510
14066
|
}
|
|
12511
14067
|
},
|
|
14068
|
+
{
|
|
14069
|
+
path: ["session", "interactions", "hold"],
|
|
14070
|
+
action: "session.interaction.hold",
|
|
14071
|
+
inputFields: { resume: "held" },
|
|
14072
|
+
summary: "Pause one pending interaction's countdown; --resume restarts a fresh window.",
|
|
14073
|
+
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.",
|
|
14074
|
+
positionals: [
|
|
14075
|
+
{ name: "session", required: true },
|
|
14076
|
+
{ name: "interaction", required: true }
|
|
14077
|
+
],
|
|
14078
|
+
options: [
|
|
14079
|
+
{
|
|
14080
|
+
name: "resume",
|
|
14081
|
+
kind: "boolean",
|
|
14082
|
+
description: "Resume the countdown instead of pausing it."
|
|
14083
|
+
},
|
|
14084
|
+
REQUEST_ID_OPTION
|
|
14085
|
+
],
|
|
14086
|
+
async run(context, parsed) {
|
|
14087
|
+
const held = parsed.options.resume !== true;
|
|
14088
|
+
const { session, result, receipt, warnings } = await submit(context, "session.interaction.hold", {
|
|
14089
|
+
sessionId: parsed.positionals.session,
|
|
14090
|
+
interactionId: parsed.positionals.interaction,
|
|
14091
|
+
held
|
|
14092
|
+
}, parsed.options);
|
|
14093
|
+
return {
|
|
14094
|
+
action: "session.interaction.hold",
|
|
14095
|
+
result,
|
|
14096
|
+
receipt,
|
|
14097
|
+
warnings,
|
|
14098
|
+
connection: session.identity,
|
|
14099
|
+
human: receipt ? receiptLines(receipt) : [held ? "paused" : "resumed"]
|
|
14100
|
+
};
|
|
14101
|
+
}
|
|
14102
|
+
},
|
|
12512
14103
|
{
|
|
12513
14104
|
path: ["session", "transcript"],
|
|
14105
|
+
action: "session.transcript",
|
|
12514
14106
|
summary: "Read a bounded transcript page; --follow polls for new messages.",
|
|
12515
14107
|
description: "Pages are oldest-first and hold at most --limit messages (max 100). Pass the returned olderCursor as --before for earlier history. --follow --jsonl streams one record per new message by polling snapshots; it stops at --timeout or Ctrl+C and never affects the session.",
|
|
12516
14108
|
positionals: [{ name: "session", required: true }],
|
|
@@ -12612,19 +14204,256 @@ var sessionCommands = [
|
|
|
12612
14204
|
throw error;
|
|
12613
14205
|
}
|
|
12614
14206
|
return {
|
|
12615
|
-
action: "session.transcript",
|
|
12616
|
-
result: { followed: true, emitted },
|
|
14207
|
+
action: "session.transcript",
|
|
14208
|
+
result: { followed: true, emitted },
|
|
14209
|
+
connection: session.identity,
|
|
14210
|
+
human: []
|
|
14211
|
+
};
|
|
14212
|
+
}
|
|
14213
|
+
}
|
|
14214
|
+
];
|
|
14215
|
+
|
|
14216
|
+
// src/client/commands/ticket.ts
|
|
14217
|
+
var STATUSES = ["backlog", "in-progress", "review", "done"];
|
|
14218
|
+
var PROJECT_OPTION = {
|
|
14219
|
+
name: "project",
|
|
14220
|
+
kind: "string",
|
|
14221
|
+
valueName: "PROJECT_ID",
|
|
14222
|
+
description: "Project ID (project list)."
|
|
14223
|
+
};
|
|
14224
|
+
var STATUS_OPTION = {
|
|
14225
|
+
name: "status",
|
|
14226
|
+
kind: "string",
|
|
14227
|
+
valueName: "COLUMN",
|
|
14228
|
+
description: `Column: ${STATUSES.join(", ")}.`
|
|
14229
|
+
};
|
|
14230
|
+
var TEXT_FIELD_OPTIONS = [
|
|
14231
|
+
{ name: "title", kind: "string", valueName: "TEXT", description: "Ticket title." },
|
|
14232
|
+
{
|
|
14233
|
+
name: "description",
|
|
14234
|
+
kind: "string",
|
|
14235
|
+
valueName: "TEXT",
|
|
14236
|
+
description: "Description (or --description-file)."
|
|
14237
|
+
},
|
|
14238
|
+
{
|
|
14239
|
+
name: "description-file",
|
|
14240
|
+
kind: "string",
|
|
14241
|
+
valueName: "PATH",
|
|
14242
|
+
description: "Read the description from a local file."
|
|
14243
|
+
},
|
|
14244
|
+
{
|
|
14245
|
+
name: "criteria",
|
|
14246
|
+
kind: "string",
|
|
14247
|
+
valueName: "TEXT",
|
|
14248
|
+
description: "Acceptance criteria."
|
|
14249
|
+
}
|
|
14250
|
+
];
|
|
14251
|
+
function requireProject(options2) {
|
|
14252
|
+
const projectId = stringOption(options2, "project");
|
|
14253
|
+
if (!projectId)
|
|
14254
|
+
throw new CliError("invalid-input", "--project is required");
|
|
14255
|
+
return projectId;
|
|
14256
|
+
}
|
|
14257
|
+
async function ticketFields(io, options2) {
|
|
14258
|
+
const description = await readTextSource(io, {
|
|
14259
|
+
file: options2["description-file"],
|
|
14260
|
+
inline: options2.description,
|
|
14261
|
+
label: "Description",
|
|
14262
|
+
maxBytes: 400000,
|
|
14263
|
+
names: { file: "--description-file", stdin: "--description-stdin", inline: "--description" },
|
|
14264
|
+
required: false
|
|
14265
|
+
});
|
|
14266
|
+
return {
|
|
14267
|
+
...stringOption(options2, "title") !== undefined ? { title: options2.title } : {},
|
|
14268
|
+
...description !== undefined ? { description } : {},
|
|
14269
|
+
...stringOption(options2, "criteria") !== undefined ? { acceptanceCriteria: options2.criteria } : {},
|
|
14270
|
+
...options2.status !== undefined ? { status: oneOf(options2.status, STATUSES, "--status") } : {}
|
|
14271
|
+
};
|
|
14272
|
+
}
|
|
14273
|
+
function ticketRows(tickets) {
|
|
14274
|
+
return table(["ID", "STATUS", "TITLE", "COMMENTS"], tickets.map((ticket) => [ticket.id, ticket.status, ticket.title, String(ticket.commentCount)]));
|
|
14275
|
+
}
|
|
14276
|
+
function ticketLines(ticket) {
|
|
14277
|
+
return [
|
|
14278
|
+
...keyValues([
|
|
14279
|
+
["id", ticket.id],
|
|
14280
|
+
["project", ticket.projectId],
|
|
14281
|
+
["status", ticket.status],
|
|
14282
|
+
["title", ticket.title],
|
|
14283
|
+
["environment", ticket.environmentId],
|
|
14284
|
+
["pr", ticket.pr.url]
|
|
14285
|
+
]),
|
|
14286
|
+
...ticket.description ? ["", ticket.description] : [],
|
|
14287
|
+
...ticket.acceptanceCriteria ? ["", "Acceptance criteria:", ticket.acceptanceCriteria] : [],
|
|
14288
|
+
...ticket.comments.flatMap((comment) => ["", `[${comment.createdAt}] ${comment.text}`])
|
|
14289
|
+
];
|
|
14290
|
+
}
|
|
14291
|
+
var ticketCommands = [
|
|
14292
|
+
{
|
|
14293
|
+
path: ["ticket", "list"],
|
|
14294
|
+
action: "ticket.list",
|
|
14295
|
+
summary: "List a project's Kanban tickets.",
|
|
14296
|
+
positionals: [],
|
|
14297
|
+
options: [
|
|
14298
|
+
PROJECT_OPTION,
|
|
14299
|
+
STATUS_OPTION,
|
|
14300
|
+
{ name: "limit", kind: "integer", valueName: "N", description: "Items per page (max 200)." },
|
|
14301
|
+
{
|
|
14302
|
+
name: "cursor",
|
|
14303
|
+
kind: "string",
|
|
14304
|
+
valueName: "CURSOR",
|
|
14305
|
+
description: "Continue from a previous page."
|
|
14306
|
+
}
|
|
14307
|
+
],
|
|
14308
|
+
idOutput: "ticket IDs, one per line",
|
|
14309
|
+
async run(context, parsed) {
|
|
14310
|
+
const session = await context.session();
|
|
14311
|
+
const page = await session.read("ticket.list", {
|
|
14312
|
+
projectId: requireProject(parsed.options),
|
|
14313
|
+
...parsed.options.status !== undefined ? { status: oneOf(parsed.options.status, STATUSES, "--status") } : {},
|
|
14314
|
+
...parsed.options.limit !== undefined ? { limit: parsed.options.limit } : {},
|
|
14315
|
+
...parsed.options.cursor !== undefined ? { cursor: parsed.options.cursor } : {}
|
|
14316
|
+
});
|
|
14317
|
+
return {
|
|
14318
|
+
action: "ticket.list",
|
|
14319
|
+
result: page,
|
|
12617
14320
|
connection: session.identity,
|
|
12618
|
-
|
|
14321
|
+
ids: page.items.map((ticket) => ticket.id),
|
|
14322
|
+
human: [
|
|
14323
|
+
...ticketRows(page.items),
|
|
14324
|
+
...page.nextCursor ? [`more: --cursor ${page.nextCursor}`] : []
|
|
14325
|
+
]
|
|
14326
|
+
};
|
|
14327
|
+
}
|
|
14328
|
+
},
|
|
14329
|
+
{
|
|
14330
|
+
path: ["ticket", "get"],
|
|
14331
|
+
action: "ticket.get",
|
|
14332
|
+
summary: "Show one ticket with its description, criteria and latest comments.",
|
|
14333
|
+
positionals: [{ name: "ticket", required: true }],
|
|
14334
|
+
options: [PROJECT_OPTION],
|
|
14335
|
+
inputFields: { ticket: "ticketId" },
|
|
14336
|
+
idOutput: "ticket ID",
|
|
14337
|
+
async run(context, parsed) {
|
|
14338
|
+
const session = await context.session();
|
|
14339
|
+
const ticket = await session.read("ticket.get", {
|
|
14340
|
+
projectId: requireProject(parsed.options),
|
|
14341
|
+
ticketId: parsed.positionals.ticket
|
|
14342
|
+
});
|
|
14343
|
+
return {
|
|
14344
|
+
action: "ticket.get",
|
|
14345
|
+
result: ticket,
|
|
14346
|
+
connection: session.identity,
|
|
14347
|
+
ids: [ticket.id],
|
|
14348
|
+
human: ticketLines(ticket)
|
|
14349
|
+
};
|
|
14350
|
+
}
|
|
14351
|
+
},
|
|
14352
|
+
{
|
|
14353
|
+
path: ["ticket", "create"],
|
|
14354
|
+
action: "ticket.create",
|
|
14355
|
+
summary: "Create a Kanban ticket.",
|
|
14356
|
+
positionals: [],
|
|
14357
|
+
options: [PROJECT_OPTION, ...TEXT_FIELD_OPTIONS, STATUS_OPTION, REQUEST_ID_OPTION],
|
|
14358
|
+
inputFields: {
|
|
14359
|
+
"description-file": "description",
|
|
14360
|
+
criteria: "acceptanceCriteria"
|
|
14361
|
+
},
|
|
14362
|
+
idOutput: "ticket ID",
|
|
14363
|
+
async run(context, parsed) {
|
|
14364
|
+
const fields = await ticketFields(context.io, parsed.options);
|
|
14365
|
+
if (typeof fields.title !== "string")
|
|
14366
|
+
throw new CliError("invalid-input", "--title is required");
|
|
14367
|
+
const { session, result, receipt, warnings } = await submit(context, "ticket.create", { projectId: requireProject(parsed.options), ...fields }, parsed.options);
|
|
14368
|
+
return {
|
|
14369
|
+
action: "ticket.create",
|
|
14370
|
+
result,
|
|
14371
|
+
receipt,
|
|
14372
|
+
warnings,
|
|
14373
|
+
connection: session.identity,
|
|
14374
|
+
ids: [result.ticketId],
|
|
14375
|
+
human: [`created ticket ${result.ticketId}`]
|
|
14376
|
+
};
|
|
14377
|
+
}
|
|
14378
|
+
},
|
|
14379
|
+
{
|
|
14380
|
+
path: ["ticket", "update"],
|
|
14381
|
+
action: "ticket.update",
|
|
14382
|
+
summary: "Change a ticket's title, description, criteria or column.",
|
|
14383
|
+
positionals: [{ name: "ticket", required: true }],
|
|
14384
|
+
options: [PROJECT_OPTION, ...TEXT_FIELD_OPTIONS, STATUS_OPTION, REQUEST_ID_OPTION],
|
|
14385
|
+
inputFields: {
|
|
14386
|
+
ticket: "ticketId",
|
|
14387
|
+
"description-file": "description",
|
|
14388
|
+
criteria: "acceptanceCriteria"
|
|
14389
|
+
},
|
|
14390
|
+
idOutput: "ticket ID",
|
|
14391
|
+
async run(context, parsed) {
|
|
14392
|
+
const fields = await ticketFields(context.io, parsed.options);
|
|
14393
|
+
if (Object.keys(fields).length === 0) {
|
|
14394
|
+
throw new CliError("invalid-input", "Pass at least one field to change");
|
|
14395
|
+
}
|
|
14396
|
+
const { session, result, receipt, warnings } = await submit(context, "ticket.update", {
|
|
14397
|
+
projectId: requireProject(parsed.options),
|
|
14398
|
+
ticketId: parsed.positionals.ticket,
|
|
14399
|
+
...fields
|
|
14400
|
+
}, parsed.options);
|
|
14401
|
+
return {
|
|
14402
|
+
action: "ticket.update",
|
|
14403
|
+
result,
|
|
14404
|
+
receipt,
|
|
14405
|
+
warnings,
|
|
14406
|
+
connection: session.identity,
|
|
14407
|
+
ids: [result.ticketId],
|
|
14408
|
+
human: [`updated ticket ${result.ticketId}`]
|
|
14409
|
+
};
|
|
14410
|
+
}
|
|
14411
|
+
},
|
|
14412
|
+
{
|
|
14413
|
+
path: ["ticket", "comment"],
|
|
14414
|
+
action: "ticket.comment",
|
|
14415
|
+
summary: "Append a comment to a ticket.",
|
|
14416
|
+
positionals: [{ name: "ticket", required: true }],
|
|
14417
|
+
options: [
|
|
14418
|
+
PROJECT_OPTION,
|
|
14419
|
+
{ name: "text", kind: "string", valueName: "TEXT", description: "Comment text." },
|
|
14420
|
+
{
|
|
14421
|
+
name: "text-file",
|
|
14422
|
+
kind: "string",
|
|
14423
|
+
valueName: "PATH",
|
|
14424
|
+
description: "Read the comment from a local file."
|
|
14425
|
+
},
|
|
14426
|
+
REQUEST_ID_OPTION
|
|
14427
|
+
],
|
|
14428
|
+
inputFields: { ticket: "ticketId", "text-file": "text" },
|
|
14429
|
+
idOutput: "ticket ID",
|
|
14430
|
+
async run(context, parsed) {
|
|
14431
|
+
const text = await readTextSource(context.io, {
|
|
14432
|
+
file: parsed.options["text-file"],
|
|
14433
|
+
inline: parsed.options.text,
|
|
14434
|
+
label: "Comment",
|
|
14435
|
+
maxBytes: 80000,
|
|
14436
|
+
names: { file: "--text-file", stdin: "--text-stdin", inline: "--text" },
|
|
14437
|
+
required: true
|
|
14438
|
+
});
|
|
14439
|
+
const { session, result, receipt, warnings } = await submit(context, "ticket.comment", { projectId: requireProject(parsed.options), ticketId: parsed.positionals.ticket, text }, parsed.options);
|
|
14440
|
+
return {
|
|
14441
|
+
action: "ticket.comment",
|
|
14442
|
+
result,
|
|
14443
|
+
receipt,
|
|
14444
|
+
warnings,
|
|
14445
|
+
connection: session.identity,
|
|
14446
|
+
ids: [result.ticketId],
|
|
14447
|
+
human: [`commented on ticket ${result.ticketId}`]
|
|
12619
14448
|
};
|
|
12620
14449
|
}
|
|
12621
14450
|
}
|
|
12622
14451
|
];
|
|
12623
14452
|
|
|
12624
14453
|
// src/client/commands/toolchain.ts
|
|
12625
|
-
import
|
|
14454
|
+
import path19 from "path";
|
|
12626
14455
|
function dataDirectory2(context, parsed) {
|
|
12627
|
-
return
|
|
14456
|
+
return path19.resolve(stringOption(parsed.options, "data-dir") ?? context.io.env.ORKESTRATOR_DATA_DIR ?? defaultDataDir(process.platform, context.io.env));
|
|
12628
14457
|
}
|
|
12629
14458
|
function requestedTools(parsed) {
|
|
12630
14459
|
const raw = parsed.options.tool;
|
|
@@ -12727,7 +14556,7 @@ var toolchainCommands = [
|
|
|
12727
14556
|
const rows = installed.tools.map((tool) => ({
|
|
12728
14557
|
tool,
|
|
12729
14558
|
version: installed.versions[tool] ?? "",
|
|
12730
|
-
executable:
|
|
14559
|
+
executable: path19.join(installed.currentBinDir, path19.basename(installed.executables[tool]))
|
|
12731
14560
|
}));
|
|
12732
14561
|
return {
|
|
12733
14562
|
action: "toolchain.install",
|
|
@@ -12757,6 +14586,337 @@ var toolchainCommands = [
|
|
|
12757
14586
|
}
|
|
12758
14587
|
];
|
|
12759
14588
|
|
|
14589
|
+
// src/client/commands/workflows.ts
|
|
14590
|
+
init_agent_platforms();
|
|
14591
|
+
function parseSelection(raw, flag) {
|
|
14592
|
+
const equals = raw.indexOf("=");
|
|
14593
|
+
const agent = raw.slice(0, equals);
|
|
14594
|
+
if (equals <= 0 || !AGENT_PLATFORMS.includes(agent)) {
|
|
14595
|
+
throw new CliError("invalid-input", `${flag} expects AGENT=MODEL[@EFFORT] with AGENT one of ${AGENT_PLATFORMS.join(", ")}`);
|
|
14596
|
+
}
|
|
14597
|
+
const rest = raw.slice(equals + 1);
|
|
14598
|
+
const at = rest.lastIndexOf("@");
|
|
14599
|
+
const model = at > 0 ? rest.slice(0, at) : rest;
|
|
14600
|
+
const reasoningEffort = at > 0 ? rest.slice(at + 1) : undefined;
|
|
14601
|
+
if (!model)
|
|
14602
|
+
throw new CliError("invalid-input", `${flag} needs a model`);
|
|
14603
|
+
return { agent, model, ...reasoningEffort ? { reasoningEffort } : {} };
|
|
14604
|
+
}
|
|
14605
|
+
var SELECTION_HELP = "AGENT=MODEL[@EFFORT], e.g. codex=gpt-5.5@high.";
|
|
14606
|
+
var AGENT_OPTIONS = [
|
|
14607
|
+
{
|
|
14608
|
+
name: "agent",
|
|
14609
|
+
kind: "string",
|
|
14610
|
+
valueName: "AGENT",
|
|
14611
|
+
description: "Agent platform. Omitted: the action's configured default."
|
|
14612
|
+
},
|
|
14613
|
+
{ name: "model", kind: "string", valueName: "MODEL", description: "Requires --agent." },
|
|
14614
|
+
{
|
|
14615
|
+
name: "reasoning",
|
|
14616
|
+
kind: "string",
|
|
14617
|
+
valueName: "LEVEL",
|
|
14618
|
+
description: "Reasoning level; requires --model."
|
|
14619
|
+
},
|
|
14620
|
+
{
|
|
14621
|
+
name: "fast",
|
|
14622
|
+
kind: "boolean",
|
|
14623
|
+
negatable: true,
|
|
14624
|
+
description: "Fast speed where supported."
|
|
14625
|
+
}
|
|
14626
|
+
];
|
|
14627
|
+
function reviewLines(review) {
|
|
14628
|
+
return keyValues([
|
|
14629
|
+
["review", review.id],
|
|
14630
|
+
["environment", review.environmentId],
|
|
14631
|
+
["phase", review.phase],
|
|
14632
|
+
...review.targetBranch ? [["target", review.targetBranch]] : [],
|
|
14633
|
+
...review.error ? [["error", review.error]] : []
|
|
14634
|
+
]);
|
|
14635
|
+
}
|
|
14636
|
+
function reviewMutation(path, action, summary) {
|
|
14637
|
+
return {
|
|
14638
|
+
path: ["review", path],
|
|
14639
|
+
action,
|
|
14640
|
+
summary,
|
|
14641
|
+
positionals: [{ name: "workflow", required: true, description: "Multi Review ID." }],
|
|
14642
|
+
options: [REQUEST_ID_OPTION],
|
|
14643
|
+
inputFields: { workflow: "workflowId" },
|
|
14644
|
+
idOutput: "workflow ID",
|
|
14645
|
+
async run(context, parsed) {
|
|
14646
|
+
const workflowId = parsed.positionals.workflow;
|
|
14647
|
+
const { session, result, receipt, warnings } = await submit(context, action, { workflowId }, parsed.options);
|
|
14648
|
+
return {
|
|
14649
|
+
action,
|
|
14650
|
+
result,
|
|
14651
|
+
receipt,
|
|
14652
|
+
warnings,
|
|
14653
|
+
connection: session.identity,
|
|
14654
|
+
ids: [workflowId],
|
|
14655
|
+
human: keyValues([
|
|
14656
|
+
["review", workflowId],
|
|
14657
|
+
["phase", result.phase ?? receipt?.state ?? ""]
|
|
14658
|
+
])
|
|
14659
|
+
};
|
|
14660
|
+
}
|
|
14661
|
+
};
|
|
14662
|
+
}
|
|
14663
|
+
function prCommand(verb) {
|
|
14664
|
+
const action = verb === "create" ? "pr.create" : "pr.resolve";
|
|
14665
|
+
return {
|
|
14666
|
+
path: ["pr", verb],
|
|
14667
|
+
action,
|
|
14668
|
+
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.",
|
|
14669
|
+
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.",
|
|
14670
|
+
positionals: [{ name: "environment", required: true, description: "Ready environment." }],
|
|
14671
|
+
options: [
|
|
14672
|
+
{
|
|
14673
|
+
name: "target-branch",
|
|
14674
|
+
kind: "string",
|
|
14675
|
+
valueName: "BRANCH",
|
|
14676
|
+
description: verb === "create" ? "Pull request base. Omitted: the project's PR base branch." : "Branch to merge in. Omitted: the environment's PR base branch."
|
|
14677
|
+
},
|
|
14678
|
+
...AGENT_OPTIONS,
|
|
14679
|
+
...WAIT_OPTIONS,
|
|
14680
|
+
REQUEST_ID_OPTION
|
|
14681
|
+
],
|
|
14682
|
+
idOutput: "session ID",
|
|
14683
|
+
async run(context, parsed) {
|
|
14684
|
+
const options2 = parsed.options;
|
|
14685
|
+
validateWaitOptions(options2);
|
|
14686
|
+
const fast = optionalBoolean(options2, "fast");
|
|
14687
|
+
const input = {
|
|
14688
|
+
environmentId: parsed.positionals.environment,
|
|
14689
|
+
...options2["target-branch"] !== undefined ? { targetBranch: options2["target-branch"] } : {},
|
|
14690
|
+
...options2.agent !== undefined ? { agent: options2.agent } : {},
|
|
14691
|
+
...options2.model !== undefined ? { model: options2.model } : {},
|
|
14692
|
+
...options2.reasoning !== undefined ? { reasoning: options2.reasoning } : {},
|
|
14693
|
+
...fast !== undefined ? { fastMode: fast } : {}
|
|
14694
|
+
};
|
|
14695
|
+
const { session, result, receipt, warnings, recoveryDeadline } = await submitRun(context, action, input, options2);
|
|
14696
|
+
const fromReceipt = (final) => ({
|
|
14697
|
+
result: {
|
|
14698
|
+
sessionId: final.resources.sessionId ?? null,
|
|
14699
|
+
tabId: final.resources.tabId ?? null,
|
|
14700
|
+
runId: final.operationId
|
|
14701
|
+
},
|
|
14702
|
+
ids: final.resources.sessionId ? [final.resources.sessionId] : []
|
|
14703
|
+
});
|
|
14704
|
+
return finishRun(context, parsed, {
|
|
14705
|
+
action,
|
|
14706
|
+
...result ? { result, ids: [result.sessionId] } : fromReceipt(receipt),
|
|
14707
|
+
receipt,
|
|
14708
|
+
warnings,
|
|
14709
|
+
connection: session.identity,
|
|
14710
|
+
human: result ? keyValues([
|
|
14711
|
+
["session", result.sessionId],
|
|
14712
|
+
["run", result.runId]
|
|
14713
|
+
]) : []
|
|
14714
|
+
}, recoveryDeadline, result ? undefined : fromReceipt);
|
|
14715
|
+
}
|
|
14716
|
+
};
|
|
14717
|
+
}
|
|
14718
|
+
var workflowCommands = [
|
|
14719
|
+
{
|
|
14720
|
+
path: ["node", "list"],
|
|
14721
|
+
action: "node.list",
|
|
14722
|
+
summary: "List this node and the leaf nodes it controls; pass a node's ID with --node.",
|
|
14723
|
+
positionals: [],
|
|
14724
|
+
options: [],
|
|
14725
|
+
idOutput: "node IDs",
|
|
14726
|
+
async run(context) {
|
|
14727
|
+
if (context.global.node) {
|
|
14728
|
+
throw new CliError("invalid-input", "node list always lists the connected node; omit --node");
|
|
14729
|
+
}
|
|
14730
|
+
const session = await context.session();
|
|
14731
|
+
const list = await session.read("node.list", {});
|
|
14732
|
+
return {
|
|
14733
|
+
action: "node.list",
|
|
14734
|
+
result: list,
|
|
14735
|
+
connection: session.identity,
|
|
14736
|
+
ids: list.items.map((node) => node.nodeId),
|
|
14737
|
+
human: table(["NODE", "NAME", "STATE", "VERSION", "ACTIONS"], list.items.map((node) => [
|
|
14738
|
+
node.nodeId,
|
|
14739
|
+
node.self ? `${node.name} (this node)` : node.name,
|
|
14740
|
+
node.state,
|
|
14741
|
+
node.version ?? "",
|
|
14742
|
+
node.publicActions ? "yes" : node.reason ?? "no"
|
|
14743
|
+
]))
|
|
14744
|
+
};
|
|
14745
|
+
}
|
|
14746
|
+
},
|
|
14747
|
+
{
|
|
14748
|
+
path: ["review", "launch"],
|
|
14749
|
+
action: "review.launch",
|
|
14750
|
+
summary: "Start an environment's Multi Review, as its button does (an active one is reused).",
|
|
14751
|
+
description: "Omitted reviewers, models and auto-actions use the saved Multi Review settings. The review continues in the backend; follow it with `review get`.",
|
|
14752
|
+
positionals: [{ name: "environment", required: true, description: "Environment to review." }],
|
|
14753
|
+
options: [
|
|
14754
|
+
{
|
|
14755
|
+
name: "reviewer",
|
|
14756
|
+
kind: "list",
|
|
14757
|
+
valueName: "SELECTION",
|
|
14758
|
+
description: `Reviewer row (repeatable): ${SELECTION_HELP}`
|
|
14759
|
+
},
|
|
14760
|
+
{
|
|
14761
|
+
name: "review-model",
|
|
14762
|
+
kind: "string",
|
|
14763
|
+
valueName: "SELECTION",
|
|
14764
|
+
description: `Preparation and consolidation model: ${SELECTION_HELP}`
|
|
14765
|
+
},
|
|
14766
|
+
{
|
|
14767
|
+
name: "fix-model",
|
|
14768
|
+
kind: "string",
|
|
14769
|
+
valueName: "SELECTION",
|
|
14770
|
+
description: `Fix model: ${SELECTION_HELP}`
|
|
14771
|
+
},
|
|
14772
|
+
{
|
|
14773
|
+
name: "auto-fix",
|
|
14774
|
+
kind: "boolean",
|
|
14775
|
+
negatable: true,
|
|
14776
|
+
description: "Start the Fix when consolidation reports findings."
|
|
14777
|
+
},
|
|
14778
|
+
{
|
|
14779
|
+
name: "auto-pr",
|
|
14780
|
+
kind: "boolean",
|
|
14781
|
+
negatable: true,
|
|
14782
|
+
description: "Open the pull request after the Fix (never merges)."
|
|
14783
|
+
},
|
|
14784
|
+
{
|
|
14785
|
+
name: "target-branch",
|
|
14786
|
+
kind: "string",
|
|
14787
|
+
valueName: "BRANCH",
|
|
14788
|
+
description: "Branch to review against. Omitted: the project's PR base branch."
|
|
14789
|
+
},
|
|
14790
|
+
{
|
|
14791
|
+
name: "instruction-file",
|
|
14792
|
+
kind: "string",
|
|
14793
|
+
valueName: "PATH",
|
|
14794
|
+
description: "Read the review instruction from a local file (empty: none)."
|
|
14795
|
+
},
|
|
14796
|
+
REQUEST_ID_OPTION
|
|
14797
|
+
],
|
|
14798
|
+
inputFields: {
|
|
14799
|
+
reviewer: "reviewers",
|
|
14800
|
+
"instruction-file": "reviewInstruction"
|
|
14801
|
+
},
|
|
14802
|
+
idOutput: "workflow ID",
|
|
14803
|
+
async run(context, parsed) {
|
|
14804
|
+
const options2 = parsed.options;
|
|
14805
|
+
const reviewers = (options2.reviewer ?? []).map((raw) => parseSelection(raw, "--reviewer"));
|
|
14806
|
+
const reviewModel = stringOption(options2, "review-model");
|
|
14807
|
+
const fixModel = stringOption(options2, "fix-model");
|
|
14808
|
+
const autoFix = optionalBoolean(options2, "auto-fix");
|
|
14809
|
+
const autoPr = optionalBoolean(options2, "auto-pr");
|
|
14810
|
+
const instructionFile = stringOption(options2, "instruction-file");
|
|
14811
|
+
const reviewInstruction = instructionFile !== undefined ? await readTextSource(context.io, {
|
|
14812
|
+
file: instructionFile,
|
|
14813
|
+
label: "Review instruction",
|
|
14814
|
+
maxBytes: 1e5,
|
|
14815
|
+
names: { file: "--instruction-file", stdin: "--instruction-file -" },
|
|
14816
|
+
required: false
|
|
14817
|
+
}) : undefined;
|
|
14818
|
+
const { session, result, receipt, warnings } = await submit(context, "review.launch", {
|
|
14819
|
+
environmentId: parsed.positionals.environment,
|
|
14820
|
+
...reviewers.length ? { reviewers } : {},
|
|
14821
|
+
...reviewModel ? { reviewModel: parseSelection(reviewModel, "--review-model") } : {},
|
|
14822
|
+
...fixModel ? { fixModel: parseSelection(fixModel, "--fix-model") } : {},
|
|
14823
|
+
...autoFix !== undefined ? { autoFix } : {},
|
|
14824
|
+
...autoPr !== undefined ? { autoPr } : {},
|
|
14825
|
+
...options2["target-branch"] !== undefined ? { targetBranch: options2["target-branch"] } : {},
|
|
14826
|
+
...reviewInstruction !== undefined ? { reviewInstruction } : {}
|
|
14827
|
+
}, options2);
|
|
14828
|
+
return {
|
|
14829
|
+
action: "review.launch",
|
|
14830
|
+
result,
|
|
14831
|
+
receipt,
|
|
14832
|
+
warnings,
|
|
14833
|
+
connection: session.identity,
|
|
14834
|
+
ids: [result.workflowId],
|
|
14835
|
+
human: keyValues([
|
|
14836
|
+
["review", result.workflowId],
|
|
14837
|
+
["phase", result.phase ?? ""],
|
|
14838
|
+
["reused", result.reused ? "yes" : "no"]
|
|
14839
|
+
])
|
|
14840
|
+
};
|
|
14841
|
+
}
|
|
14842
|
+
},
|
|
14843
|
+
{
|
|
14844
|
+
path: ["review", "list"],
|
|
14845
|
+
action: "review.list",
|
|
14846
|
+
summary: "List an environment's Multi Reviews, newest first.",
|
|
14847
|
+
positionals: [{ name: "environment", required: true }],
|
|
14848
|
+
options: [],
|
|
14849
|
+
idOutput: "workflow IDs",
|
|
14850
|
+
async run(context, parsed) {
|
|
14851
|
+
const session = await context.session();
|
|
14852
|
+
const page = await session.read("review.list", {
|
|
14853
|
+
environmentId: parsed.positionals.environment
|
|
14854
|
+
});
|
|
14855
|
+
return {
|
|
14856
|
+
action: "review.list",
|
|
14857
|
+
result: page,
|
|
14858
|
+
connection: session.identity,
|
|
14859
|
+
ids: page.items.map((review) => review.id),
|
|
14860
|
+
human: table(["REVIEW", "PHASE", "TARGET", "CREATED"], page.items.map((review) => [
|
|
14861
|
+
review.id,
|
|
14862
|
+
review.phase,
|
|
14863
|
+
review.targetBranch ?? "",
|
|
14864
|
+
review.createdAt ?? ""
|
|
14865
|
+
]))
|
|
14866
|
+
};
|
|
14867
|
+
}
|
|
14868
|
+
},
|
|
14869
|
+
{
|
|
14870
|
+
path: ["review", "get"],
|
|
14871
|
+
action: "review.get",
|
|
14872
|
+
summary: "Read one Multi Review.",
|
|
14873
|
+
positionals: [{ name: "workflow", required: true, description: "Multi Review ID." }],
|
|
14874
|
+
options: [],
|
|
14875
|
+
inputFields: { workflow: "workflowId" },
|
|
14876
|
+
async run(context, parsed) {
|
|
14877
|
+
const session = await context.session();
|
|
14878
|
+
const review = await session.read("review.get", {
|
|
14879
|
+
workflowId: parsed.positionals.workflow
|
|
14880
|
+
});
|
|
14881
|
+
return {
|
|
14882
|
+
action: "review.get",
|
|
14883
|
+
result: review,
|
|
14884
|
+
connection: session.identity,
|
|
14885
|
+
human: reviewLines(review)
|
|
14886
|
+
};
|
|
14887
|
+
}
|
|
14888
|
+
},
|
|
14889
|
+
{
|
|
14890
|
+
path: ["review", "open"],
|
|
14891
|
+
action: "review.open",
|
|
14892
|
+
summary: "Open or focus a Multi Review's tab (--fix: its Fix tab); starts no work.",
|
|
14893
|
+
positionals: [{ name: "workflow", required: true, description: "Multi Review ID." }],
|
|
14894
|
+
options: [
|
|
14895
|
+
{ name: "fix", kind: "boolean", description: "Open the Fix session tab instead." },
|
|
14896
|
+
REQUEST_ID_OPTION
|
|
14897
|
+
],
|
|
14898
|
+
inputFields: { workflow: "workflowId", fix: "surface" },
|
|
14899
|
+
idOutput: "workflow ID",
|
|
14900
|
+
async run(context, parsed) {
|
|
14901
|
+
const workflowId = parsed.positionals.workflow;
|
|
14902
|
+
const { session, result, receipt, warnings } = await submit(context, "review.open", { workflowId, ...parsed.options.fix === true ? { surface: "fix" } : {} }, parsed.options);
|
|
14903
|
+
return {
|
|
14904
|
+
action: "review.open",
|
|
14905
|
+
result,
|
|
14906
|
+
receipt,
|
|
14907
|
+
warnings,
|
|
14908
|
+
connection: session.identity,
|
|
14909
|
+
ids: [workflowId],
|
|
14910
|
+
human: [`opened review ${workflowId}`]
|
|
14911
|
+
};
|
|
14912
|
+
}
|
|
14913
|
+
},
|
|
14914
|
+
reviewMutation("address", "review.address", "Address a Multi Review's findings: hand them to its Fix session."),
|
|
14915
|
+
reviewMutation("cancel", "review.cancel", "Cancel a running Multi Review; its record is kept."),
|
|
14916
|
+
prCommand("create"),
|
|
14917
|
+
prCommand("resolve")
|
|
14918
|
+
];
|
|
14919
|
+
|
|
12760
14920
|
// src/client/help.ts
|
|
12761
14921
|
var GROUP_SUMMARIES = {
|
|
12762
14922
|
connection: "Select and verify the backend this client talks to.",
|
|
@@ -12891,6 +15051,7 @@ class OutputWriter {
|
|
|
12891
15051
|
ok: true,
|
|
12892
15052
|
...outcome.connection ? { connection: outcome.connection } : {},
|
|
12893
15053
|
result: outcome.result ?? null,
|
|
15054
|
+
...outcome.node ? { node: outcome.node } : {},
|
|
12894
15055
|
...outcome.receipt ? { receipt: outcome.receipt } : {},
|
|
12895
15056
|
...outcome.warnings && outcome.warnings.length > 0 ? { warnings: outcome.warnings } : {}
|
|
12896
15057
|
};
|
|
@@ -12914,7 +15075,7 @@ class OutputWriter {
|
|
|
12914
15075
|
this.io.stderr(`warning: ${warning}
|
|
12915
15076
|
`);
|
|
12916
15077
|
if (outcome.receipt)
|
|
12917
|
-
this.io.stderr(`${receiptHint(outcome.receipt)}
|
|
15078
|
+
this.io.stderr(`${receiptHint(outcome.receipt, outcome.node)}
|
|
12918
15079
|
`);
|
|
12919
15080
|
return outcome.exitCodeOverride ?? 0;
|
|
12920
15081
|
}
|
|
@@ -12922,12 +15083,14 @@ class OutputWriter {
|
|
|
12922
15083
|
const cliError = error instanceof CliError ? error : new CliError("internal-error", error instanceof Error ? boundMessage(error.message) : "Unexpected client failure");
|
|
12923
15084
|
const exitCode = exitCodeForError(cliError.code);
|
|
12924
15085
|
const receipt = cliError.receipt ?? context.receipt;
|
|
15086
|
+
const node = cliError.node ?? context.node;
|
|
12925
15087
|
if (this.mode === "json" || this.mode === "jsonl") {
|
|
12926
15088
|
const envelope = {
|
|
12927
15089
|
schemaVersion: PUBLIC_API_SCHEMA_VERSION,
|
|
12928
15090
|
action: cliError.action ?? context.action ?? "cli",
|
|
12929
15091
|
ok: false,
|
|
12930
15092
|
...context.connection ? { connection: context.connection } : {},
|
|
15093
|
+
...node ? { node } : {},
|
|
12931
15094
|
error: {
|
|
12932
15095
|
code: cliError.code,
|
|
12933
15096
|
message: cliError.message,
|
|
@@ -12942,9 +15105,12 @@ class OutputWriter {
|
|
|
12942
15105
|
return exitCode;
|
|
12943
15106
|
}
|
|
12944
15107
|
this.io.stderr(`error [${cliError.code}]: ${cliError.message}
|
|
15108
|
+
`);
|
|
15109
|
+
if (typeof cliError.details?.recovery === "string")
|
|
15110
|
+
this.io.stderr(`recover with \`${cliError.details.recovery}\`
|
|
12945
15111
|
`);
|
|
12946
15112
|
if (receipt)
|
|
12947
|
-
this.io.stderr(`${receiptHint(receipt)}
|
|
15113
|
+
this.io.stderr(`${receiptHint(receipt, node)}
|
|
12948
15114
|
`);
|
|
12949
15115
|
return exitCode;
|
|
12950
15116
|
}
|
|
@@ -12955,6 +15121,7 @@ class OutputWriter {
|
|
|
12955
15121
|
this.io.stdout(`${JSON.stringify({
|
|
12956
15122
|
schemaVersion: PUBLIC_API_SCHEMA_VERSION,
|
|
12957
15123
|
action: context.action,
|
|
15124
|
+
...context.node ? { node: context.node } : {},
|
|
12958
15125
|
ok: false,
|
|
12959
15126
|
...context.connection ? { connection: context.connection } : {},
|
|
12960
15127
|
error: {
|
|
@@ -12971,13 +15138,13 @@ class OutputWriter {
|
|
|
12971
15138
|
this.io.stderr(`${message}
|
|
12972
15139
|
`);
|
|
12973
15140
|
if (context.receipt)
|
|
12974
|
-
this.io.stderr(`${receiptHint(context.receipt)}
|
|
15141
|
+
this.io.stderr(`${receiptHint(context.receipt, context.node)}
|
|
12975
15142
|
`);
|
|
12976
15143
|
return code;
|
|
12977
15144
|
}
|
|
12978
15145
|
}
|
|
12979
|
-
function receiptHint(receipt) {
|
|
12980
|
-
return `operation ${receipt.operationId} (${receipt.action}) is ${receipt.state}; inspect with \`orkestrator run get ${receipt.operationId}\``;
|
|
15146
|
+
function receiptHint(receipt, node) {
|
|
15147
|
+
return `operation ${receipt.operationId} (${receipt.action}) is ${receipt.state}; inspect with \`orkestrator run get ${receipt.operationId}${node ? ` --node ${node.nodeId}` : ""}\``;
|
|
12981
15148
|
}
|
|
12982
15149
|
function boundMessage(message, max = 500) {
|
|
12983
15150
|
const single = message.replace(/\s+/g, " ").trim();
|
|
@@ -13046,10 +15213,16 @@ var CLIENT_COMMANDS = [
|
|
|
13046
15213
|
...environmentCommands,
|
|
13047
15214
|
...sessionCommands,
|
|
13048
15215
|
...changesCommands,
|
|
15216
|
+
...workflowCommands,
|
|
15217
|
+
...attentionCommands,
|
|
15218
|
+
...ticketCommands,
|
|
15219
|
+
...mailCommands,
|
|
15220
|
+
...scheduleCommands,
|
|
13049
15221
|
...runCommands,
|
|
13050
15222
|
...toolchainCommands,
|
|
13051
15223
|
...hostCommands,
|
|
13052
|
-
...serviceCommands
|
|
15224
|
+
...serviceCommands,
|
|
15225
|
+
...completionCommands(() => CLIENT_COMMANDS)
|
|
13053
15226
|
];
|
|
13054
15227
|
var COMMAND_TREE = { commands: CLIENT_COMMANDS };
|
|
13055
15228
|
function stripHelpWord(argv) {
|
|
@@ -13115,11 +15288,15 @@ async function runClient(argv, options2) {
|
|
|
13115
15288
|
session = new ClientSession(target, new LocalReceiptStore(configStore.receiptsDirectory), {
|
|
13116
15289
|
fetchImpl: options2.fetchImpl,
|
|
13117
15290
|
requestTimeoutMs: parsed.global.requestTimeoutMs,
|
|
13118
|
-
signal
|
|
15291
|
+
signal,
|
|
15292
|
+
...parsed.global.node ? { node: parsed.global.node } : {}
|
|
13119
15293
|
});
|
|
13120
15294
|
return session;
|
|
13121
15295
|
}
|
|
13122
15296
|
};
|
|
15297
|
+
if (command.spec.local && parsed.global.node) {
|
|
15298
|
+
return output.failure(new CliError("invalid-input", `'${command.spec.path.join(" ")}' does not use --node`), { action });
|
|
15299
|
+
}
|
|
13123
15300
|
if (command.spec.local && (parsed.global.connection || parsed.global.profile)) {
|
|
13124
15301
|
if (!(command.spec.path[0] === "connection" && command.spec.path[1] === "check")) {
|
|
13125
15302
|
return output.failure(new CliError("invalid-input", `'${command.spec.path.join(" ")}' does not use --connection or --profile`), { action });
|
|
@@ -13129,12 +15306,14 @@ async function runClient(argv, options2) {
|
|
|
13129
15306
|
try {
|
|
13130
15307
|
outcome = await command.spec.run(context, command);
|
|
13131
15308
|
} catch (error) {
|
|
13132
|
-
const
|
|
15309
|
+
const failedSession = session;
|
|
15310
|
+
const connection = failedSession?.identity;
|
|
13133
15311
|
if (signal.aborted && !(error instanceof CliError && error.code === "observation-interrupted")) {
|
|
13134
15312
|
return output.interrupted(signalName(signal), {
|
|
13135
15313
|
action,
|
|
13136
15314
|
...error instanceof CliError && error.receipt ? { receipt: error.receipt } : {},
|
|
13137
|
-
...connection ? { connection } : {}
|
|
15315
|
+
...connection ? { connection } : {},
|
|
15316
|
+
...failedSession?.nodeTag ? { node: failedSession.nodeTag } : {}
|
|
13138
15317
|
});
|
|
13139
15318
|
}
|
|
13140
15319
|
if (error instanceof CliError && error.code === "observation-interrupted") {
|
|
@@ -13142,12 +15321,22 @@ async function runClient(argv, options2) {
|
|
|
13142
15321
|
action: error.action ?? action,
|
|
13143
15322
|
details: error.details,
|
|
13144
15323
|
...error.receipt ? { receipt: error.receipt } : {},
|
|
13145
|
-
...connection ? { connection } : {}
|
|
15324
|
+
...connection ? { connection } : {},
|
|
15325
|
+
...failedSession?.nodeTag ? { node: failedSession.nodeTag } : {}
|
|
13146
15326
|
});
|
|
13147
15327
|
}
|
|
13148
|
-
return output.failure(error, {
|
|
15328
|
+
return output.failure(error, {
|
|
15329
|
+
action,
|
|
15330
|
+
...connection ? { connection } : {},
|
|
15331
|
+
...failedSession?.nodeTag ? { node: failedSession.nodeTag } : {}
|
|
15332
|
+
});
|
|
13149
15333
|
}
|
|
13150
|
-
|
|
15334
|
+
const activeSession = session;
|
|
15335
|
+
return output.success({
|
|
15336
|
+
...outcome,
|
|
15337
|
+
...activeSession?.nodeTag ? { node: activeSession.nodeTag } : {},
|
|
15338
|
+
warnings: [...new Set([...outcome.warnings ?? [], ...activeSession?.warnings ?? []])]
|
|
15339
|
+
});
|
|
13151
15340
|
}
|
|
13152
15341
|
async function runClientProcess(argv, version) {
|
|
13153
15342
|
const controller = new AbortController;
|