@amistio/cli 0.1.7 → 0.1.8
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 +6 -0
- package/dist/index.js +275 -72
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,8 @@ amistio --help
|
|
|
11
11
|
|
|
12
12
|
The package install only installs the `amistio` command. Repository cloning, project pairing, credential storage, and runner execution happen only when the user explicitly runs commands such as `amistio bootstrap`, `amistio pair`, or `amistio run --watch`.
|
|
13
13
|
|
|
14
|
+
Runner lifecycle controls in the web app, such as update, restart, and remove, apply only to the runner paired by that user unless the active organization role is an admin role. The runner API binds command polling, command status, logs, activity, and tool sessions to the local runner credential that produced them.
|
|
15
|
+
|
|
14
16
|
After pairing, confirm that at least one local AI tool is available:
|
|
15
17
|
|
|
16
18
|
```sh
|
|
@@ -25,8 +27,12 @@ amistio run --watch --background --tool opencode
|
|
|
25
27
|
amistio runner status
|
|
26
28
|
```
|
|
27
29
|
|
|
30
|
+
When `--tool copilot` uses the GitHub Copilot SDK, Amistio approves read-only permission requests by default and denies mutating, network, MCP, hook, memory, and shell requests. Set `AMISTIO_COPILOT_APPROVE_ALL=1` only on a local machine where broad Copilot SDK approval is intentional.
|
|
31
|
+
|
|
28
32
|
`amistio runner status` reports local background runner state, latest heartbeat, and bounded resource usage when available. Resource usage is latest-sample runner process memory/CPU plus safe aggregate system memory/load signals; it does not include source files, environment variables, command lines, process lists, credentials, or arbitrary local paths.
|
|
29
33
|
|
|
34
|
+
Runner setup and local-tool execution use bounded failure controls. `amistio run --watch` retries Git worktree preflight failures by releasing the claim for another attempt, then fails the work item after `--max-preflight-attempts` attempts, defaulting to 3. Active local-tool runs renew the work lease, and `--tool-timeout-seconds` caps tool execution, defaulting to 1800 seconds.
|
|
35
|
+
|
|
30
36
|
For headless startup after login on supported user-level service managers:
|
|
31
37
|
|
|
32
38
|
```sh
|
package/dist/index.js
CHANGED
|
@@ -410,6 +410,7 @@ var runnerCredentialItemSchema = baseItemSchema.extend({
|
|
|
410
410
|
projectId: z.string().min(1),
|
|
411
411
|
runnerCredentialId: z.string().min(1),
|
|
412
412
|
repositoryLinkId: z.string().min(1),
|
|
413
|
+
runnerId: z.string().min(1).optional(),
|
|
413
414
|
pairedByUserId: z.string().min(1).optional(),
|
|
414
415
|
machineId: z.string().min(1).optional(),
|
|
415
416
|
tokenHash: z.string().min(32),
|
|
@@ -719,6 +720,8 @@ var pairingSessionItemSchema = baseItemSchema.extend({
|
|
|
719
720
|
projectId: z.string().min(1),
|
|
720
721
|
createdByUserId: z.string().min(1),
|
|
721
722
|
expiresAt: isoDateTimeSchema,
|
|
723
|
+
failedAttemptCount: z.number().int().nonnegative().optional(),
|
|
724
|
+
lastFailedAttemptAt: isoDateTimeSchema.optional(),
|
|
722
725
|
status: z.enum(["pending", "confirmed", "expired", "revoked"])
|
|
723
726
|
});
|
|
724
727
|
var projectItemUnionSchema = z.discriminatedUnion("type", [
|
|
@@ -1865,7 +1868,7 @@ async function runLocalTool(options) {
|
|
|
1865
1868
|
promptFilePath,
|
|
1866
1869
|
streamOutput: Boolean(options.streamOutput),
|
|
1867
1870
|
...options.session ? { session: options.session } : {}
|
|
1868
|
-
});
|
|
1871
|
+
}, options.timeoutMs);
|
|
1869
1872
|
return {
|
|
1870
1873
|
toolName: runner2.toolName,
|
|
1871
1874
|
displayCommand: runner2.kind === "sdk" ? runner2.displayCommand : runner2.invocation.displayCommand,
|
|
@@ -1950,16 +1953,16 @@ async function createToolRunner(options) {
|
|
|
1950
1953
|
}
|
|
1951
1954
|
throw new Error(`The ${adapter.name} SDK or executable was not found. Install the SDK/runtime or pass --tool-command.`);
|
|
1952
1955
|
}
|
|
1953
|
-
async function executeToolRunner(runner2, input) {
|
|
1956
|
+
async function executeToolRunner(runner2, input, timeoutMs) {
|
|
1954
1957
|
if (runner2.kind === "command") {
|
|
1955
|
-
return executeToolInvocation(runner2.invocation, input.rootDir, input.streamOutput);
|
|
1958
|
+
return executeToolInvocation(runner2.invocation, input.rootDir, input.streamOutput, timeoutMs);
|
|
1956
1959
|
}
|
|
1957
1960
|
try {
|
|
1958
|
-
return await runner2.adapter.runWithSdk(input);
|
|
1961
|
+
return await withTimeout(runner2.adapter.runWithSdk(input), timeoutMs, runner2.displayCommand);
|
|
1959
1962
|
} catch (error) {
|
|
1960
1963
|
if (runner2.allowCommandFallback && runner2.adapter.buildInvocation && runner2.adapter.executable && await commandExists(runner2.adapter.executable)) {
|
|
1961
1964
|
const fallback = runner2.adapter.buildInvocation(input);
|
|
1962
|
-
const result = await executeToolInvocation(fallback, input.rootDir, input.streamOutput);
|
|
1965
|
+
const result = await executeToolInvocation(fallback, input.rootDir, input.streamOutput, timeoutMs);
|
|
1963
1966
|
const sdkFailure = `SDK execution for ${runner2.adapter.name} failed, fell back to ${fallback.displayCommand}: ${errorMessage(error)}`;
|
|
1964
1967
|
return {
|
|
1965
1968
|
...result,
|
|
@@ -2048,7 +2051,7 @@ async function commandExists(command) {
|
|
|
2048
2051
|
lookup.on("close", (exitCode) => resolve(exitCode === 0));
|
|
2049
2052
|
});
|
|
2050
2053
|
}
|
|
2051
|
-
async function executeToolInvocation(invocation, rootDir, streamOutput) {
|
|
2054
|
+
async function executeToolInvocation(invocation, rootDir, streamOutput, timeoutMs) {
|
|
2052
2055
|
return new Promise((resolve, reject) => {
|
|
2053
2056
|
const child = spawn(invocation.command, invocation.args, {
|
|
2054
2057
|
cwd: rootDir,
|
|
@@ -2058,7 +2061,33 @@ async function executeToolInvocation(invocation, rootDir, streamOutput) {
|
|
|
2058
2061
|
});
|
|
2059
2062
|
let stdout = "";
|
|
2060
2063
|
let stderr = "";
|
|
2061
|
-
|
|
2064
|
+
let settled = false;
|
|
2065
|
+
let forceKillTimer;
|
|
2066
|
+
const timeout = timeoutMs && timeoutMs > 0 ? setTimeout(() => {
|
|
2067
|
+
if (settled) return;
|
|
2068
|
+
stderr += `${toolTimeoutMessage(invocation.displayCommand, timeoutMs)}
|
|
2069
|
+
`;
|
|
2070
|
+
child.kill("SIGTERM");
|
|
2071
|
+
forceKillTimer = setTimeout(() => child.kill("SIGKILL"), 5e3);
|
|
2072
|
+
forceKillTimer.unref?.();
|
|
2073
|
+
rejectOnce(new Error(toolTimeoutMessage(invocation.displayCommand, timeoutMs)));
|
|
2074
|
+
}, timeoutMs) : void 0;
|
|
2075
|
+
timeout?.unref?.();
|
|
2076
|
+
const resolveOnce = (value) => {
|
|
2077
|
+
if (settled) return;
|
|
2078
|
+
settled = true;
|
|
2079
|
+
if (timeout) clearTimeout(timeout);
|
|
2080
|
+
if (forceKillTimer) clearTimeout(forceKillTimer);
|
|
2081
|
+
resolve(value);
|
|
2082
|
+
};
|
|
2083
|
+
const rejectOnce = (error) => {
|
|
2084
|
+
if (settled) return;
|
|
2085
|
+
settled = true;
|
|
2086
|
+
if (timeout) clearTimeout(timeout);
|
|
2087
|
+
if (forceKillTimer) clearTimeout(forceKillTimer);
|
|
2088
|
+
reject(error);
|
|
2089
|
+
};
|
|
2090
|
+
child.on("error", rejectOnce);
|
|
2062
2091
|
child.stdout.setEncoding("utf8");
|
|
2063
2092
|
child.stderr.setEncoding("utf8");
|
|
2064
2093
|
child.stdout.on("data", (chunk) => {
|
|
@@ -2079,10 +2108,40 @@ async function executeToolInvocation(invocation, rootDir, streamOutput) {
|
|
|
2079
2108
|
}
|
|
2080
2109
|
child.stdin.end();
|
|
2081
2110
|
child.on("close", (exitCode) => {
|
|
2082
|
-
|
|
2111
|
+
if (forceKillTimer) clearTimeout(forceKillTimer);
|
|
2112
|
+
resolveOnce({ exitCode: exitCode ?? 1, stdout, stderr });
|
|
2083
2113
|
});
|
|
2084
2114
|
});
|
|
2085
2115
|
}
|
|
2116
|
+
async function withTimeout(promise, timeoutMs, displayCommand) {
|
|
2117
|
+
if (!timeoutMs || timeoutMs <= 0) {
|
|
2118
|
+
return promise;
|
|
2119
|
+
}
|
|
2120
|
+
return new Promise((resolve, reject) => {
|
|
2121
|
+
const timeout = setTimeout(() => reject(new Error(toolTimeoutMessage(displayCommand, timeoutMs))), timeoutMs);
|
|
2122
|
+
timeout.unref?.();
|
|
2123
|
+
promise.then(
|
|
2124
|
+
(value) => {
|
|
2125
|
+
clearTimeout(timeout);
|
|
2126
|
+
resolve(value);
|
|
2127
|
+
},
|
|
2128
|
+
(error) => {
|
|
2129
|
+
clearTimeout(timeout);
|
|
2130
|
+
reject(error);
|
|
2131
|
+
}
|
|
2132
|
+
);
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
2135
|
+
function toolTimeoutMessage(displayCommand, timeoutMs) {
|
|
2136
|
+
return `Local tool timed out after ${formatTimeoutDuration(timeoutMs)}: ${displayCommand}`;
|
|
2137
|
+
}
|
|
2138
|
+
function formatTimeoutDuration(timeoutMs) {
|
|
2139
|
+
if (timeoutMs < 1e3) {
|
|
2140
|
+
return `${timeoutMs}ms`;
|
|
2141
|
+
}
|
|
2142
|
+
const seconds = timeoutMs / 1e3;
|
|
2143
|
+
return Number.isInteger(seconds) ? `${seconds}s` : `${seconds.toFixed(1)}s`;
|
|
2144
|
+
}
|
|
2086
2145
|
async function runOpencodeSdk(input) {
|
|
2087
2146
|
const { createOpencode } = await import("@opencode-ai/sdk");
|
|
2088
2147
|
const previousDirectory = process.cwd();
|
|
@@ -2170,7 +2229,7 @@ async function runCodexSdk(input) {
|
|
|
2170
2229
|
return { exitCode: 0, stdout: result.finalResponse, stderr: "" };
|
|
2171
2230
|
}
|
|
2172
2231
|
async function runCopilotSdk(input) {
|
|
2173
|
-
const { CopilotClient
|
|
2232
|
+
const { CopilotClient } = await import("@github/copilot-sdk");
|
|
2174
2233
|
const client = new CopilotClient({
|
|
2175
2234
|
cwd: input.rootDir,
|
|
2176
2235
|
logLevel: "error"
|
|
@@ -2183,7 +2242,7 @@ async function runCopilotSdk(input) {
|
|
|
2183
2242
|
workingDirectory: input.rootDir,
|
|
2184
2243
|
enableConfigDiscovery: true,
|
|
2185
2244
|
streaming: input.streamOutput,
|
|
2186
|
-
onPermissionRequest:
|
|
2245
|
+
onPermissionRequest: createCopilotPermissionHandler()
|
|
2187
2246
|
});
|
|
2188
2247
|
try {
|
|
2189
2248
|
let streamedOutput = "";
|
|
@@ -2205,6 +2264,18 @@ async function runCopilotSdk(input) {
|
|
|
2205
2264
|
await client.stop();
|
|
2206
2265
|
}
|
|
2207
2266
|
}
|
|
2267
|
+
function createCopilotPermissionHandler(env = process.env) {
|
|
2268
|
+
const allowAllPermissions = isCopilotApproveAllEnabled(env);
|
|
2269
|
+
return (request) => {
|
|
2270
|
+
if (allowAllPermissions || request.kind === "read") {
|
|
2271
|
+
return { kind: "approve-once" };
|
|
2272
|
+
}
|
|
2273
|
+
return { kind: "reject" };
|
|
2274
|
+
};
|
|
2275
|
+
}
|
|
2276
|
+
function isCopilotApproveAllEnabled(env = process.env) {
|
|
2277
|
+
return /^(1|true|yes)$/i.test(env.AMISTIO_COPILOT_APPROVE_ALL ?? "");
|
|
2278
|
+
}
|
|
2208
2279
|
function extractTextParts(parts) {
|
|
2209
2280
|
if (!Array.isArray(parts)) {
|
|
2210
2281
|
return "";
|
|
@@ -3813,6 +3884,8 @@ function buildBackgroundRunnerArgs(options) {
|
|
|
3813
3884
|
if (options.maxIterations !== void 0) {
|
|
3814
3885
|
args.push("--max-iterations", String(options.maxIterations));
|
|
3815
3886
|
}
|
|
3887
|
+
args.push("--max-preflight-attempts", String(options.maxPreflightAttempts));
|
|
3888
|
+
args.push("--tool-timeout-seconds", String(options.toolTimeoutSeconds));
|
|
3816
3889
|
if (!options.stream) {
|
|
3817
3890
|
args.push("--no-stream");
|
|
3818
3891
|
}
|
|
@@ -3960,6 +4033,10 @@ var CLI_VERSION = readCliPackageVersion();
|
|
|
3960
4033
|
var program = new Command();
|
|
3961
4034
|
var defaultRoot = process.env.INIT_CWD ?? process.cwd();
|
|
3962
4035
|
var apiUrlOptionDescription = `Amistio API URL override (or ${AMISTIO_API_URL_ENV})`;
|
|
4036
|
+
var DEFAULT_MAX_PREFLIGHT_ATTEMPTS = 3;
|
|
4037
|
+
var DEFAULT_TOOL_TIMEOUT_SECONDS = 30 * 60;
|
|
4038
|
+
var RUNNER_WORK_LEASE_SECONDS = 300;
|
|
4039
|
+
var RUNNER_WORK_LEASE_RENEWAL_MS = 12e4;
|
|
3963
4040
|
program.name("amistio").description("Amistio project brain CLI").version(CLI_VERSION);
|
|
3964
4041
|
program.command("init").description("Create Amistio control-plane folders for a new project").option("--root <path>", "Repository root", defaultRoot).action(async (options) => {
|
|
3965
4042
|
const created = await initControlPlane(options.root);
|
|
@@ -3986,7 +4063,8 @@ program.command("bootstrap").description("Clone a linked repository locally, pre
|
|
|
3986
4063
|
repositoryLinkId: options.repositoryLink,
|
|
3987
4064
|
repoName: parsedRepoUrl.repoName,
|
|
3988
4065
|
repoFingerprint: createRepoFingerprint(options.account, options.project, options.repositoryLink),
|
|
3989
|
-
defaultBranch: options.defaultBranch
|
|
4066
|
+
defaultBranch: options.defaultBranch,
|
|
4067
|
+
machineId: runnerMachineId()
|
|
3990
4068
|
});
|
|
3991
4069
|
const filePath = await writeProjectLink(checkout.targetDir, {
|
|
3992
4070
|
amistioAccountId: options.account,
|
|
@@ -4038,6 +4116,7 @@ program.command("import").description("Pair an existing checkout and import lega
|
|
|
4038
4116
|
repoName: repository.repoName,
|
|
4039
4117
|
repoFingerprint: repository.repoFingerprint,
|
|
4040
4118
|
defaultBranch: repository.defaultBranch,
|
|
4119
|
+
machineId: runnerMachineId(),
|
|
4041
4120
|
...parsedCloneUrl ? { cloneUrl: parsedCloneUrl.cloneUrl } : {},
|
|
4042
4121
|
...parsedCloneUrl?.provider ? { provider: parsedCloneUrl.provider } : {},
|
|
4043
4122
|
...parsedCloneUrl?.repoOwner ? { repoOwner: parsedCloneUrl.repoOwner } : {},
|
|
@@ -4084,7 +4163,8 @@ program.command("pair").description("Pair this repository with an Amistio web pr
|
|
|
4084
4163
|
repositoryLinkId,
|
|
4085
4164
|
repoName: inferRepoName(pairingRoot),
|
|
4086
4165
|
repoFingerprint: createRepoFingerprint(options.account, options.project, repositoryLinkId),
|
|
4087
|
-
defaultBranch: options.defaultBranch
|
|
4166
|
+
defaultBranch: options.defaultBranch,
|
|
4167
|
+
machineId: runnerMachineId()
|
|
4088
4168
|
});
|
|
4089
4169
|
repositoryLinkId = pairing.repositoryLink.repositoryLinkId;
|
|
4090
4170
|
credential = credential ?? pairing.token;
|
|
@@ -4265,7 +4345,7 @@ program.command("orchestrate").description("Update the Amistio control plane thr
|
|
|
4265
4345
|
process.exitCode = result.exitCode;
|
|
4266
4346
|
}
|
|
4267
4347
|
});
|
|
4268
|
-
program.command("run").description("Claim and run approved Amistio work locally").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--runner-id <runnerId>", "Stable runner ID").option("--root <path>", "Repository root", defaultRoot).option("--tool <name>", "Local tool to use: auto, none, opencode, claude, codex, copilot, gemini, aider, cursor-agent").option("--invocation-channel <channel>", "Local invocation channel: auto, sdk, or command", parseInvocationChannel).option("--model <model>", "Model to request when the selected local tool supports model selection").option("--tool-command <command>", "Custom local command. Use {promptFile} and {root} placeholders when supported").option("--session <policy>", "Tool session policy: auto, new, continue:<toolSessionId>, or none", "auto").option("--dry-run", "Claim work and print the generated execution prompt without running a tool").option("--watch", "Keep polling for approved work until stopped").option("--background", "Start a detached background runner that watches for approved work").option("--interval-seconds <seconds>", "Polling interval for --watch", parsePositiveInteger, 10).option("--max-iterations <count>", "Stop watch mode after this many polling attempts", parsePositiveInteger).option("--no-stream", "Capture local tool output instead of streaming it").option("--verbose", "Print detailed runner errors while watching").action(async (options, command) => {
|
|
4348
|
+
program.command("run").description("Claim and run approved Amistio work locally").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--runner-id <runnerId>", "Stable runner ID").option("--root <path>", "Repository root", defaultRoot).option("--tool <name>", "Local tool to use: auto, none, opencode, claude, codex, copilot, gemini, aider, cursor-agent").option("--invocation-channel <channel>", "Local invocation channel: auto, sdk, or command", parseInvocationChannel).option("--model <model>", "Model to request when the selected local tool supports model selection").option("--tool-command <command>", "Custom local command. Use {promptFile} and {root} placeholders when supported").option("--session <policy>", "Tool session policy: auto, new, continue:<toolSessionId>, or none", "auto").option("--dry-run", "Claim work and print the generated execution prompt without running a tool").option("--watch", "Keep polling for approved work until stopped").option("--background", "Start a detached background runner that watches for approved work").option("--interval-seconds <seconds>", "Polling interval for --watch", parsePositiveInteger, 10).option("--max-iterations <count>", "Stop watch mode after this many polling attempts", parsePositiveInteger).option("--max-preflight-attempts <count>", "Fail setup/preflight failures after this many claimed attempts", parsePositiveInteger, DEFAULT_MAX_PREFLIGHT_ATTEMPTS).option("--tool-timeout-seconds <seconds>", "Fail local tool execution after this many seconds", parsePositiveInteger, DEFAULT_TOOL_TIMEOUT_SECONDS).option("--no-stream", "Capture local tool output instead of streaming it").option("--verbose", "Print detailed runner errors while watching").action(async (options, command) => {
|
|
4269
4349
|
const context = await loadPairedApiContext(options.root, options.apiUrl);
|
|
4270
4350
|
if (!context) {
|
|
4271
4351
|
console.log("Repository is not paired. Run `amistio pair` first.");
|
|
@@ -4437,7 +4517,7 @@ runner.command("stop").description("Stop a background runner for the paired repo
|
|
|
4437
4517
|
console.log(stopResult === "stopped" ? `Stopped background runner ${record.runnerId}.` : `Marked background runner ${record.runnerId} stopped; process was not running.`);
|
|
4438
4518
|
});
|
|
4439
4519
|
var runnerService = runner.command("service").description("Manage a user-level startup service for the paired runner");
|
|
4440
|
-
runnerService.command("install").description("Install a user-level startup service for this paired repository runner").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--root <path>", "Repository root", defaultRoot).option("--runner-id <runnerId>", "Stable runner ID").option("--tool <name>", "Local tool to use: auto, opencode, claude, codex, copilot, gemini, aider, cursor-agent").option("--invocation-channel <channel>", "Local invocation channel: auto, sdk, or command", parseInvocationChannel).option("--model <model>", "Model to request when the selected local tool supports model selection").option("--session <policy>", "Tool session policy: auto, new, continue:<toolSessionId>, or none", "auto").option("--interval-seconds <seconds>", "Polling interval for the service runner", parsePositiveInteger, 10).option("--no-stream", "Capture local tool output instead of streaming it").option("--verbose", "Print detailed runner errors").option("--dry-run", "Print the startup service descriptor without installing it").action(async (options) => {
|
|
4520
|
+
runnerService.command("install").description("Install a user-level startup service for this paired repository runner").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--root <path>", "Repository root", defaultRoot).option("--runner-id <runnerId>", "Stable runner ID").option("--tool <name>", "Local tool to use: auto, opencode, claude, codex, copilot, gemini, aider, cursor-agent").option("--invocation-channel <channel>", "Local invocation channel: auto, sdk, or command", parseInvocationChannel).option("--model <model>", "Model to request when the selected local tool supports model selection").option("--session <policy>", "Tool session policy: auto, new, continue:<toolSessionId>, or none", "auto").option("--interval-seconds <seconds>", "Polling interval for the service runner", parsePositiveInteger, 10).option("--max-preflight-attempts <count>", "Fail setup/preflight failures after this many claimed attempts", parsePositiveInteger, DEFAULT_MAX_PREFLIGHT_ATTEMPTS).option("--tool-timeout-seconds <seconds>", "Fail local tool execution after this many seconds", parsePositiveInteger, DEFAULT_TOOL_TIMEOUT_SECONDS).option("--no-stream", "Capture local tool output instead of streaming it").option("--verbose", "Print detailed runner errors").option("--dry-run", "Print the startup service descriptor without installing it").action(async (options) => {
|
|
4441
4521
|
const context = await loadPairedApiContext(options.root, options.apiUrl);
|
|
4442
4522
|
if (!context) {
|
|
4443
4523
|
console.log("Repository is not paired. Run `amistio pair` first.");
|
|
@@ -4550,6 +4630,8 @@ async function runWatchIteration({ command, context, options, runnerId }) {
|
|
|
4550
4630
|
runnerId
|
|
4551
4631
|
},
|
|
4552
4632
|
suppressIdleOutput: Boolean(options.watch),
|
|
4633
|
+
maxPreflightAttempts: options.maxPreflightAttempts,
|
|
4634
|
+
toolTimeoutMs: options.toolTimeoutSeconds * 1e3,
|
|
4553
4635
|
verbose: Boolean(options.verbose)
|
|
4554
4636
|
});
|
|
4555
4637
|
} catch (error) {
|
|
@@ -4564,7 +4646,7 @@ ${detail}`);
|
|
|
4564
4646
|
} else {
|
|
4565
4647
|
console.error(`${message} Run with --verbose for details.`);
|
|
4566
4648
|
}
|
|
4567
|
-
await Promise.allSettled([
|
|
4649
|
+
const settlements = await Promise.allSettled([
|
|
4568
4650
|
context.client.sendRunnerHeartbeat(context.metadata.amistioProjectId, runnerId, context.metadata.repositoryLinkId, "blocked", { ...runnerHeartbeatMetadata(), preferenceMessage: message }),
|
|
4569
4651
|
context.client.recordRunnerLog(context.metadata.amistioProjectId, {
|
|
4570
4652
|
runnerId,
|
|
@@ -4575,6 +4657,7 @@ ${detail}`);
|
|
|
4575
4657
|
machineId: runnerMachineId()
|
|
4576
4658
|
})
|
|
4577
4659
|
]);
|
|
4660
|
+
logRejectedSettlements("record watch error", settlements);
|
|
4578
4661
|
return { status: "failed", exitCode: 1, message };
|
|
4579
4662
|
}
|
|
4580
4663
|
}
|
|
@@ -4590,9 +4673,11 @@ async function runNextWorkItem({
|
|
|
4590
4673
|
explicitModel,
|
|
4591
4674
|
explicitInvocationChannel,
|
|
4592
4675
|
explicitTool,
|
|
4676
|
+
maxPreflightAttempts,
|
|
4593
4677
|
toolCommand,
|
|
4594
4678
|
commandContext,
|
|
4595
4679
|
suppressIdleOutput,
|
|
4680
|
+
toolTimeoutMs,
|
|
4596
4681
|
verbose
|
|
4597
4682
|
}) {
|
|
4598
4683
|
const toolConfig = await resolveRunnerToolConfig({
|
|
@@ -4615,7 +4700,7 @@ async function runNextWorkItem({
|
|
|
4615
4700
|
console.log(toolConfig.message);
|
|
4616
4701
|
return { status: "blocked", exitCode: 1 };
|
|
4617
4702
|
}
|
|
4618
|
-
const result = await apiClient.claimWork(projectId, runnerId, repositoryLinkId,
|
|
4703
|
+
const result = await apiClient.claimWork(projectId, runnerId, repositoryLinkId, RUNNER_WORK_LEASE_SECONDS, runnerIsolationCapabilityMetadata());
|
|
4619
4704
|
if (!result.workItem) {
|
|
4620
4705
|
const nextAction = await loadProjectNextAction(apiClient, projectId, repositoryLinkId, root);
|
|
4621
4706
|
const message = formatProjectNextAction(nextAction);
|
|
@@ -4625,14 +4710,20 @@ async function runNextWorkItem({
|
|
|
4625
4710
|
return { status: "idle", exitCode: 0, nextAction, message };
|
|
4626
4711
|
}
|
|
4627
4712
|
const prompt = await createRunnerWorkPrompt(apiClient, projectId, result.workItem);
|
|
4713
|
+
await recordRunnerMilestone(apiClient, projectId, result.workItem, runnerId, repositoryLinkId, {
|
|
4714
|
+
status: "running",
|
|
4715
|
+
summary: "Prepared local runner execution prompt.",
|
|
4716
|
+
idempotencyKey: `runner_milestone_prompt_${result.workItem.workItemId}_${result.workItem.attempt}`,
|
|
4717
|
+
metadata: { workKind: result.workItem.workKind ?? "implementation", attempt: result.workItem.attempt }
|
|
4718
|
+
});
|
|
4628
4719
|
if (dryRun || toolConfig.tool === "none") {
|
|
4629
4720
|
console.log(prompt);
|
|
4630
4721
|
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
|
|
4631
4722
|
return { status: "preview", exitCode: 0 };
|
|
4632
4723
|
}
|
|
4633
|
-
const worktreeIsolation = await prepareWorktreeForClaimedItem({ apiClient, projectId, repositoryLinkId, root, runnerId, toolConfig, workItem: result.workItem });
|
|
4634
|
-
if (worktreeIsolation.status
|
|
4635
|
-
return { status: "blocked", exitCode: 1, message: worktreeIsolation.message };
|
|
4724
|
+
const worktreeIsolation = await prepareWorktreeForClaimedItem({ apiClient, maxPreflightAttempts, projectId, repositoryLinkId, root, runnerId, toolConfig, workItem: result.workItem });
|
|
4725
|
+
if (worktreeIsolation.status !== "ready") {
|
|
4726
|
+
return { status: worktreeIsolation.status === "failed" ? "failed" : "blocked", exitCode: 1, message: worktreeIsolation.message };
|
|
4636
4727
|
}
|
|
4637
4728
|
const executionRoot = worktreeIsolation.isolation?.worktreePath ?? root;
|
|
4638
4729
|
const isolationTelemetry = workItemIsolationTelemetry(result.workItem, worktreeIsolation.isolation);
|
|
@@ -4669,6 +4760,7 @@ async function runNextWorkItem({
|
|
|
4669
4760
|
const providerSessionStore = new LocalToolSessionStore();
|
|
4670
4761
|
const providerSessionId = sessionContext.toolSession ? await providerSessionStore.getProviderSessionId(sessionContext.toolSession.toolSessionId, preview.toolName) : void 0;
|
|
4671
4762
|
let toolResult;
|
|
4763
|
+
const stopLeaseRenewal = startWorkLeaseRenewal({ apiClient, projectId, repositoryLinkId, runnerId, toolConfig, workItem: result.workItem, telemetry: isolationTelemetry });
|
|
4672
4764
|
try {
|
|
4673
4765
|
toolResult = await runLocalTool({
|
|
4674
4766
|
rootDir: executionRoot,
|
|
@@ -4678,6 +4770,7 @@ async function runNextWorkItem({
|
|
|
4678
4770
|
...toolCommand ? { toolCommand } : {},
|
|
4679
4771
|
...toolConfig.model ? { model: toolConfig.model } : {},
|
|
4680
4772
|
streamOutput: stream,
|
|
4773
|
+
timeoutMs: toolTimeoutMs,
|
|
4681
4774
|
...sessionContext.toolSession ? {
|
|
4682
4775
|
session: {
|
|
4683
4776
|
toolSessionId: sessionContext.toolSession.toolSessionId,
|
|
@@ -4688,10 +4781,11 @@ async function runNextWorkItem({
|
|
|
4688
4781
|
} : {}
|
|
4689
4782
|
});
|
|
4690
4783
|
} catch (error) {
|
|
4784
|
+
stopLeaseRenewal();
|
|
4691
4785
|
const detail = truncateLogExcerpt(errorDetail(error));
|
|
4692
4786
|
const durationMs2 = Date.now() - startedAt;
|
|
4693
4787
|
const message = `${preview.toolName} failed before returning a result.`;
|
|
4694
|
-
await Promise.allSettled([
|
|
4788
|
+
const settlements = await Promise.allSettled([
|
|
4695
4789
|
apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig)),
|
|
4696
4790
|
markToolSessionBlocked(apiClient, projectId, sessionContext.toolSession, errorMessage3(error)),
|
|
4697
4791
|
apiClient.updateWorkStatus(projectId, result.workItem.workItemId, "failed", `run_failed_${result.workItem.workItemId}_${result.workItem.attempt}_${runnerId}`, runnerId, {
|
|
@@ -4713,11 +4807,13 @@ async function runNextWorkItem({
|
|
|
4713
4807
|
metadata: { tool: preview.toolName, error: detail }
|
|
4714
4808
|
})
|
|
4715
4809
|
]);
|
|
4810
|
+
logRejectedSettlements("record local tool failure", settlements);
|
|
4716
4811
|
if (verbose || !stream) {
|
|
4717
4812
|
console.error(detail);
|
|
4718
4813
|
}
|
|
4719
4814
|
return { status: "failed", exitCode: 1, message };
|
|
4720
4815
|
}
|
|
4816
|
+
stopLeaseRenewal();
|
|
4721
4817
|
if (sessionContext.toolSession && toolResult.providerSessionId) {
|
|
4722
4818
|
await providerSessionStore.setProviderSessionId(sessionContext.toolSession.toolSessionId, preview.toolName, toolResult.providerSessionId);
|
|
4723
4819
|
}
|
|
@@ -4728,47 +4824,59 @@ async function runNextWorkItem({
|
|
|
4728
4824
|
console.error(toolResult.stderr.trim());
|
|
4729
4825
|
}
|
|
4730
4826
|
if (result.workItem.workKind === "brainGeneration" || result.workItem.workKind === "planRevision") {
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4827
|
+
try {
|
|
4828
|
+
return await finalizeBrainGenerationWork({
|
|
4829
|
+
apiClient,
|
|
4830
|
+
durationMs: Date.now() - startedAt,
|
|
4831
|
+
projectId,
|
|
4832
|
+
repositoryLinkId,
|
|
4833
|
+
runnerId,
|
|
4834
|
+
sessionContext,
|
|
4835
|
+
toolConfig,
|
|
4836
|
+
toolName: preview.toolName,
|
|
4837
|
+
toolResult,
|
|
4838
|
+
workItem: result.workItem
|
|
4839
|
+
});
|
|
4840
|
+
} catch (error) {
|
|
4841
|
+
return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
|
|
4842
|
+
}
|
|
4743
4843
|
}
|
|
4744
4844
|
if (result.workItem.workKind === "assistantQuestion") {
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4845
|
+
try {
|
|
4846
|
+
return await finalizeAssistantQuestionWork({
|
|
4847
|
+
apiClient,
|
|
4848
|
+
durationMs: Date.now() - startedAt,
|
|
4849
|
+
projectId,
|
|
4850
|
+
repositoryLinkId,
|
|
4851
|
+
runnerId,
|
|
4852
|
+
sessionContext,
|
|
4853
|
+
toolConfig,
|
|
4854
|
+
toolName: preview.toolName,
|
|
4855
|
+
toolResult,
|
|
4856
|
+
workItem: result.workItem
|
|
4857
|
+
});
|
|
4858
|
+
} catch (error) {
|
|
4859
|
+
return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
|
|
4860
|
+
}
|
|
4757
4861
|
}
|
|
4758
4862
|
if (result.workItem.workKind === "impactPreview") {
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4863
|
+
try {
|
|
4864
|
+
return await finalizeImpactPreviewWork({
|
|
4865
|
+
apiClient,
|
|
4866
|
+
durationMs: Date.now() - startedAt,
|
|
4867
|
+
projectId,
|
|
4868
|
+
repositoryLinkId,
|
|
4869
|
+
root,
|
|
4870
|
+
runnerId,
|
|
4871
|
+
sessionContext,
|
|
4872
|
+
toolConfig,
|
|
4873
|
+
toolName: preview.toolName,
|
|
4874
|
+
toolResult,
|
|
4875
|
+
workItem: result.workItem
|
|
4876
|
+
});
|
|
4877
|
+
} catch (error) {
|
|
4878
|
+
return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
|
|
4879
|
+
}
|
|
4772
4880
|
}
|
|
4773
4881
|
const finalStatus = toolResult.exitCode === 0 ? "completed" : "failed";
|
|
4774
4882
|
const durationMs = Date.now() - startedAt;
|
|
@@ -4820,11 +4928,17 @@ async function runNextWorkItem({
|
|
|
4820
4928
|
console.log(`Marked ${statusResult.workItem.workItemId} ${statusResult.workItem.status} after ${durationSeconds}s.`);
|
|
4821
4929
|
return { status: finalStatus, exitCode: toolResult.exitCode };
|
|
4822
4930
|
}
|
|
4823
|
-
async function prepareWorktreeForClaimedItem({ apiClient, projectId, repositoryLinkId, root, runnerId, toolConfig, workItem }) {
|
|
4931
|
+
async function prepareWorktreeForClaimedItem({ apiClient, maxPreflightAttempts, projectId, repositoryLinkId, root, runnerId, toolConfig, workItem }) {
|
|
4824
4932
|
if (!needsGitWorktreeIsolation(workItem)) {
|
|
4825
4933
|
return { status: "ready" };
|
|
4826
4934
|
}
|
|
4827
4935
|
const identity = resolveWorktreeIdentity(workItem);
|
|
4936
|
+
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
4937
|
+
status: "running",
|
|
4938
|
+
summary: `Checking Git worktree isolation for attempt ${workItem.attempt}/${maxPreflightAttempts}.`,
|
|
4939
|
+
idempotencyKey: `runner_milestone_worktree_preflight_${workItem.workItemId}_${workItem.attempt}`,
|
|
4940
|
+
metadata: { executionWorktreeKey: identity.worktreeKey, executionBranch: identity.branch, implementationScopeId: identity.implementationScopeId, attempt: workItem.attempt, maxAttempts: maxPreflightAttempts }
|
|
4941
|
+
});
|
|
4828
4942
|
try {
|
|
4829
4943
|
const isolation = await prepareGitWorktreeIsolation(root, workItem);
|
|
4830
4944
|
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
@@ -4837,29 +4951,111 @@ async function prepareWorktreeForClaimedItem({ apiClient, projectId, repositoryL
|
|
|
4837
4951
|
} catch (error) {
|
|
4838
4952
|
const message = errorMessage3(error);
|
|
4839
4953
|
const telemetry = workItemIsolationTelemetry(workItem, { ...identity, baseRevision: workItem.baseRevision ?? "unknown", worktreePath: "" });
|
|
4840
|
-
const
|
|
4954
|
+
const finalAttempt = workItem.attempt >= maxPreflightAttempts;
|
|
4955
|
+
const statusMessage = finalAttempt ? `Git worktree preflight failed after ${workItem.attempt}/${maxPreflightAttempts} attempts. ${message}` : `Git worktree preflight attempt ${workItem.attempt}/${maxPreflightAttempts} failed. Requeueing for retry. ${message}`;
|
|
4956
|
+
const statusResult = await apiClient.updateWorkStatus(projectId, workItem.workItemId, finalAttempt ? "failed" : "approved", `worktree_${finalAttempt ? "failed" : "retry"}_${workItem.workItemId}_${workItem.attempt}_${randomUUID()}`, runnerId, {
|
|
4841
4957
|
...telemetry,
|
|
4842
|
-
message,
|
|
4843
|
-
blockerReason: message,
|
|
4958
|
+
message: statusMessage,
|
|
4959
|
+
...finalAttempt ? { blockerReason: message } : { releaseClaim: true },
|
|
4844
4960
|
error: message
|
|
4845
4961
|
});
|
|
4846
4962
|
await recordRunnerMilestone(apiClient, projectId, statusResult.workItem, runnerId, repositoryLinkId, {
|
|
4847
|
-
status: "
|
|
4848
|
-
summary:
|
|
4849
|
-
idempotencyKey: `
|
|
4850
|
-
metadata: { executionWorktreeKey: telemetry.executionWorktreeKey ?? "", executionBranch: telemetry.executionBranch ?? "", implementationScopeId: telemetry.implementationScopeId ?? "" }
|
|
4963
|
+
status: finalAttempt ? "failed" : "warning",
|
|
4964
|
+
summary: statusMessage,
|
|
4965
|
+
idempotencyKey: `runner_milestone_worktree_${finalAttempt ? "failed" : "retry"}_${workItem.workItemId}_${statusResult.workItem.idempotencyKey}`,
|
|
4966
|
+
metadata: { executionWorktreeKey: telemetry.executionWorktreeKey ?? "", executionBranch: telemetry.executionBranch ?? "", implementationScopeId: telemetry.implementationScopeId ?? "", attempt: workItem.attempt, maxAttempts: maxPreflightAttempts, error: message }
|
|
4851
4967
|
});
|
|
4852
|
-
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "
|
|
4968
|
+
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", {
|
|
4853
4969
|
...runnerHeartbeatMetadata(toolConfig),
|
|
4854
|
-
|
|
4855
|
-
...telemetry.implementationScopeId ? { currentImplementationScopeId: telemetry.implementationScopeId } : {},
|
|
4856
|
-
...telemetry.executionWorktreeKey ? { currentWorktreeKey: telemetry.executionWorktreeKey } : {},
|
|
4857
|
-
...telemetry.executionBranch ? { currentBranch: telemetry.executionBranch } : {}
|
|
4970
|
+
preferenceMessage: statusMessage
|
|
4858
4971
|
});
|
|
4859
|
-
console.error(
|
|
4860
|
-
return { status: "
|
|
4972
|
+
console.error(statusMessage);
|
|
4973
|
+
return { status: finalAttempt ? "failed" : "retrying", message: statusMessage };
|
|
4861
4974
|
}
|
|
4862
4975
|
}
|
|
4976
|
+
function startWorkLeaseRenewal({ apiClient, projectId, repositoryLinkId, runnerId, toolConfig, workItem, telemetry }) {
|
|
4977
|
+
let stopped = false;
|
|
4978
|
+
const renew = async () => {
|
|
4979
|
+
if (stopped) return;
|
|
4980
|
+
const leaseExpiresAt = new Date(Date.now() + RUNNER_WORK_LEASE_SECONDS * 1e3).toISOString();
|
|
4981
|
+
try {
|
|
4982
|
+
await apiClient.updateWorkStatus(projectId, workItem.workItemId, "running", `lease_renewal_${workItem.workItemId}_${workItem.attempt}_${Date.now()}`, runnerId, {
|
|
4983
|
+
...telemetry,
|
|
4984
|
+
leaseExpiresAt
|
|
4985
|
+
});
|
|
4986
|
+
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "running", {
|
|
4987
|
+
...runnerHeartbeatMetadata(toolConfig),
|
|
4988
|
+
currentWorkItemId: workItem.workItemId,
|
|
4989
|
+
...telemetry.implementationScopeId ? { currentImplementationScopeId: telemetry.implementationScopeId } : {},
|
|
4990
|
+
...telemetry.executionWorktreeKey ? { currentWorktreeKey: telemetry.executionWorktreeKey } : {},
|
|
4991
|
+
...telemetry.executionBranch ? { currentBranch: telemetry.executionBranch } : {}
|
|
4992
|
+
});
|
|
4993
|
+
} catch (error) {
|
|
4994
|
+
const detail = truncateLogExcerpt(errorDetail(error));
|
|
4995
|
+
console.error(`Could not renew Amistio work lease for ${workItem.workItemId}: ${detail}`);
|
|
4996
|
+
await apiClient.recordRunnerLog(projectId, {
|
|
4997
|
+
runnerId,
|
|
4998
|
+
repositoryLinkId,
|
|
4999
|
+
status: "failed",
|
|
5000
|
+
workItemId: workItem.workItemId,
|
|
5001
|
+
workTitle: workItem.title,
|
|
5002
|
+
...workItem.workKind ? { workKind: workItem.workKind } : {},
|
|
5003
|
+
message: "Runner could not renew the active work lease.",
|
|
5004
|
+
error: detail,
|
|
5005
|
+
machineId: runnerMachineId()
|
|
5006
|
+
}).catch(() => void 0);
|
|
5007
|
+
}
|
|
5008
|
+
};
|
|
5009
|
+
const timer = setInterval(() => {
|
|
5010
|
+
void renew();
|
|
5011
|
+
}, RUNNER_WORK_LEASE_RENEWAL_MS);
|
|
5012
|
+
timer.unref?.();
|
|
5013
|
+
return () => {
|
|
5014
|
+
stopped = true;
|
|
5015
|
+
clearInterval(timer);
|
|
5016
|
+
};
|
|
5017
|
+
}
|
|
5018
|
+
async function recordFinalizationFailure({ apiClient, durationMs, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName, workItem }) {
|
|
5019
|
+
const detail = truncateLogExcerpt(errorDetail(error));
|
|
5020
|
+
const message = `${toolName} completed, but Amistio could not finalize the result.`;
|
|
5021
|
+
const settlements = await Promise.allSettled([
|
|
5022
|
+
apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig)),
|
|
5023
|
+
markToolSessionBlocked(apiClient, projectId, sessionContext.toolSession, errorMessage3(error)),
|
|
5024
|
+
apiClient.updateWorkStatus(projectId, workItem.workItemId, "failed", `finalize_failed_${workItem.workItemId}_${workItem.attempt}_${randomUUID()}`, runnerId, {
|
|
5025
|
+
...isolationTelemetry,
|
|
5026
|
+
tool: toolName,
|
|
5027
|
+
durationMs,
|
|
5028
|
+
message,
|
|
5029
|
+
error: detail,
|
|
5030
|
+
...sessionContext.toolSession ? { toolSessionId: sessionContext.toolSession.toolSessionId } : {},
|
|
5031
|
+
sessionPolicy: sessionContext.policy,
|
|
5032
|
+
sessionDecision: sessionContext.decision,
|
|
5033
|
+
sessionDecisionReason: sessionContext.reason
|
|
5034
|
+
}),
|
|
5035
|
+
apiClient.recordRunnerLog(projectId, {
|
|
5036
|
+
runnerId,
|
|
5037
|
+
repositoryLinkId,
|
|
5038
|
+
status: "failed",
|
|
5039
|
+
workItemId: workItem.workItemId,
|
|
5040
|
+
workTitle: workItem.title,
|
|
5041
|
+
...workItem.workKind ? { workKind: workItem.workKind } : {},
|
|
5042
|
+
tool: toolName,
|
|
5043
|
+
durationMs,
|
|
5044
|
+
message,
|
|
5045
|
+
error: detail,
|
|
5046
|
+
machineId: runnerMachineId()
|
|
5047
|
+
}),
|
|
5048
|
+
recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
5049
|
+
status: "failed",
|
|
5050
|
+
summary: message,
|
|
5051
|
+
idempotencyKey: `runner_milestone_finalization_failed_${workItem.workItemId}_${workItem.attempt}`,
|
|
5052
|
+
metadata: { tool: toolName, durationMs, error: detail }
|
|
5053
|
+
})
|
|
5054
|
+
]);
|
|
5055
|
+
logRejectedSettlements("record finalization failure", settlements);
|
|
5056
|
+
console.error(detail);
|
|
5057
|
+
return { status: "failed", exitCode: 1, message };
|
|
5058
|
+
}
|
|
4863
5059
|
function workItemIsolationTelemetry(workItem, isolation) {
|
|
4864
5060
|
const implementationScopeId = isolation?.implementationScopeId ?? workItem.implementationScopeId;
|
|
4865
5061
|
const executionBranch = isolation?.branch ?? workItem.executionBranch;
|
|
@@ -4888,6 +5084,13 @@ async function recordRunnerMilestone(apiClient, projectId, workItem, runnerId, r
|
|
|
4888
5084
|
...input
|
|
4889
5085
|
}).catch(() => void 0);
|
|
4890
5086
|
}
|
|
5087
|
+
function logRejectedSettlements(action, settlements) {
|
|
5088
|
+
for (const settlement of settlements) {
|
|
5089
|
+
if (settlement.status === "rejected") {
|
|
5090
|
+
console.error(`${action} failed: ${errorMessage3(settlement.reason)}`);
|
|
5091
|
+
}
|
|
5092
|
+
}
|
|
5093
|
+
}
|
|
4891
5094
|
async function runPendingRunnerCommand(apiClient, context, heartbeatMetadata) {
|
|
4892
5095
|
const { commands } = await apiClient.listRunnerCommands(context.projectId, context.runnerId, context.repositoryLinkId).catch(() => ({ commands: [] }));
|
|
4893
5096
|
const command = commands.filter((item) => item.status === "pending" || item.status === "acknowledged" || item.status === "running").sort((first, second) => Date.parse(first.createdAt) - Date.parse(second.createdAt))[0];
|