@amaster.ai/employee-runtime-connector 0.1.0-beta.22 → 0.1.0-beta.24
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 +1 -1
- package/dist/amaster-runtime-daemon.mjs +276 -50
- package/dist/amaster-runtime.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ The daemon reports its connector contract, exact package/build, platform/archite
|
|
|
38
38
|
|
|
39
39
|
`AMASTER_RUNTIME_ATTESTATION_MODE` defaults to `shadow`. Set it to `enforce` only after the expected version and build marker are configured and shadow diagnostics are clean; enforce mode refuses to create or lease Runtime V2 mutation commands without a current exact attestation. `AMASTER_RUNTIME_ATTESTATION_TTL_SECONDS` defaults to `300` and accepts `60`–`3600`. Invalid mode or TTL values fail server startup visibly.
|
|
40
40
|
|
|
41
|
-
Threat model: these facts are self-reported by a connector authenticated with its existing connector credential. There is no hardware-backed signature, remote attestation, or independent trust root. The proof detects accidental version/schema/executor drift by an honest connector and correlates commands to the observed facts; it does not prove that a compromised or malicious connector is running the claimed code. Keep the default `shadow` mode observational
|
|
41
|
+
Threat model: these facts are self-reported by a connector authenticated with its existing connector credential. There is no hardware-backed signature, remote attestation, or independent trust root. The proof detects accidental version/schema/executor drift by an honest connector and correlates commands to the observed facts; it does not prove that a compromised or malicious connector is running the claimed code. Proof identity is stable for the exact connector credential and fact set, create/lease revalidate it under the connector transaction lock, stale commands are isolated within a poll batch, and an active connector can recover after restoring exact facts. Keep the default `shadow` mode observational, and do not treat this mechanism as a standalone production security boundary or enable `enforce` before expected markers are configured and shadow diagnostics are clean.
|
|
42
42
|
|
|
43
43
|
The AMaster remote stack derives its runtime image from the Pi base image with
|
|
44
44
|
`docker/Dockerfile.amaster-employee-pi-cli-runtime`. The build installs one exact
|
|
@@ -2144,6 +2144,27 @@ function selectExecutor(config, command) {
|
|
|
2144
2144
|
function processGroupIdForChild(child, processPlatform = process.platform) {
|
|
2145
2145
|
return processPlatform === "win32" || typeof child.pid !== "number" || child.pid <= 0 ? null : child.pid;
|
|
2146
2146
|
}
|
|
2147
|
+
function isExecutorProcessAlive(child, processGroupId = processGroupIdForChild(child), processApi = process) {
|
|
2148
|
+
if (processGroupId !== null) {
|
|
2149
|
+
try {
|
|
2150
|
+
processApi.kill(-processGroupId, 0);
|
|
2151
|
+
return true;
|
|
2152
|
+
} catch (error) {
|
|
2153
|
+
if (error?.code === "ESRCH") return false;
|
|
2154
|
+
return true;
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
return child.exitCode === null && child.signalCode === null;
|
|
2158
|
+
}
|
|
2159
|
+
function isProcessIdAlive(pid, processApi = process) {
|
|
2160
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
2161
|
+
try {
|
|
2162
|
+
processApi.kill(pid, 0);
|
|
2163
|
+
return true;
|
|
2164
|
+
} catch (error) {
|
|
2165
|
+
return error?.code !== "ESRCH";
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2147
2168
|
function signalExecutorProcess(child, signal, processGroupId = processGroupIdForChild(child), processApi = process) {
|
|
2148
2169
|
if (processGroupId !== null) {
|
|
2149
2170
|
try {
|
|
@@ -2156,7 +2177,7 @@ function signalExecutorProcess(child, signal, processGroupId = processGroupIdFor
|
|
|
2156
2177
|
}
|
|
2157
2178
|
}
|
|
2158
2179
|
}
|
|
2159
|
-
if (
|
|
2180
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
2160
2181
|
try {
|
|
2161
2182
|
return child.kill(signal);
|
|
2162
2183
|
} catch {
|
|
@@ -3759,14 +3780,15 @@ function buildRuntimeConnectorAuthHeaders(config) {
|
|
|
3759
3780
|
if (config.token) return { Authorization: `Bearer ${config.token}` };
|
|
3760
3781
|
return {};
|
|
3761
3782
|
}
|
|
3762
|
-
async function postRuntimeConnectorJson(config, path, payload) {
|
|
3783
|
+
async function postRuntimeConnectorJson(config, path, payload, options = {}) {
|
|
3763
3784
|
const res = await fetch(`${config.serverUrl}${path}`, {
|
|
3764
3785
|
method: "POST",
|
|
3765
3786
|
headers: {
|
|
3766
3787
|
"content-type": "application/json",
|
|
3767
3788
|
...buildRuntimeConnectorAuthHeaders(config)
|
|
3768
3789
|
},
|
|
3769
|
-
body: JSON.stringify(payload)
|
|
3790
|
+
body: JSON.stringify(payload),
|
|
3791
|
+
signal: options.signal
|
|
3770
3792
|
});
|
|
3771
3793
|
const text = await res.text();
|
|
3772
3794
|
const body = text ? JSON.parse(text) : null;
|
|
@@ -3797,6 +3819,26 @@ async function postRuntimeConnectorBytes(config, path, body, headers = {}) {
|
|
|
3797
3819
|
}
|
|
3798
3820
|
return response;
|
|
3799
3821
|
}
|
|
3822
|
+
function retryableRuntimeConnectorPost(error) {
|
|
3823
|
+
const status = Number(error?.httpStatus);
|
|
3824
|
+
return !Number.isInteger(status) || status === 408 || status === 429 || status >= 500;
|
|
3825
|
+
}
|
|
3826
|
+
async function postRuntimeConnectorJsonWithRetry(config, path, payload, options = {}) {
|
|
3827
|
+
const maxAttempts = Math.max(1, Math.min(5, Number(options.maxAttempts ?? 3)));
|
|
3828
|
+
const timeoutMs = Math.max(1, Number(options.timeoutMs ?? 5e3));
|
|
3829
|
+
const delayMs = Math.max(0, Number(options.delayMs ?? 100));
|
|
3830
|
+
let lastError;
|
|
3831
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
3832
|
+
try {
|
|
3833
|
+
return await postRuntimeConnectorJson(config, path, payload, { signal: AbortSignal.timeout(timeoutMs) });
|
|
3834
|
+
} catch (error) {
|
|
3835
|
+
lastError = error;
|
|
3836
|
+
if (attempt >= maxAttempts || !retryableRuntimeConnectorPost(error)) throw error;
|
|
3837
|
+
if (delayMs > 0) await new Promise((resolve9) => setTimeout(resolve9, delayMs));
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3840
|
+
throw lastError;
|
|
3841
|
+
}
|
|
3800
3842
|
async function bestEffortPostRuntimeConnectorJson(config, path, payload, options = {}) {
|
|
3801
3843
|
try {
|
|
3802
3844
|
return await postRuntimeConnectorJson(config, path, payload);
|
|
@@ -3812,6 +3854,7 @@ async function bestEffortPostRuntimeConnectorJson(config, path, payload, options
|
|
|
3812
3854
|
}
|
|
3813
3855
|
}
|
|
3814
3856
|
var postJson = postRuntimeConnectorJson;
|
|
3857
|
+
var postJsonWithRetry = postRuntimeConnectorJsonWithRetry;
|
|
3815
3858
|
var bestEffortPostJson = bestEffortPostRuntimeConnectorJson;
|
|
3816
3859
|
|
|
3817
3860
|
// src/amaster-runtime-daemon/runtime-artifact-upload.mjs
|
|
@@ -3874,6 +3917,38 @@ function prepareRuntimeArtifactUploads(cwd, mcpToolResults) {
|
|
|
3874
3917
|
return [...uploads.values()];
|
|
3875
3918
|
}
|
|
3876
3919
|
|
|
3920
|
+
// src/amaster-runtime-daemon/runtime-artifact-ingest-queue.mjs
|
|
3921
|
+
function createRuntimeArtifactIngestQueue({ ingest, onError }) {
|
|
3922
|
+
const handledIntentIds = /* @__PURE__ */ new Set();
|
|
3923
|
+
const artifacts = [];
|
|
3924
|
+
let error = null;
|
|
3925
|
+
let queue = Promise.resolve();
|
|
3926
|
+
return {
|
|
3927
|
+
enqueue(results) {
|
|
3928
|
+
if (error) return;
|
|
3929
|
+
const pending = results.filter((result2) => {
|
|
3930
|
+
const intentId = readString(asRecord(result2).artifactIntent?.intentId);
|
|
3931
|
+
if (!intentId || handledIntentIds.has(intentId)) return false;
|
|
3932
|
+
handledIntentIds.add(intentId);
|
|
3933
|
+
return true;
|
|
3934
|
+
});
|
|
3935
|
+
if (pending.length === 0) return;
|
|
3936
|
+
queue = queue.then(async () => artifacts.push(...await ingest(pending))).catch((caught) => {
|
|
3937
|
+
error ??= caught;
|
|
3938
|
+
onError?.(caught);
|
|
3939
|
+
});
|
|
3940
|
+
},
|
|
3941
|
+
hasHandled(intentId) {
|
|
3942
|
+
return handledIntentIds.has(intentId);
|
|
3943
|
+
},
|
|
3944
|
+
async flush() {
|
|
3945
|
+
await queue;
|
|
3946
|
+
if (error) throw error;
|
|
3947
|
+
return [...artifacts];
|
|
3948
|
+
}
|
|
3949
|
+
};
|
|
3950
|
+
}
|
|
3951
|
+
|
|
3877
3952
|
// src/amaster-runtime-daemon/workspace-guard.mjs
|
|
3878
3953
|
import { createHash as createHash4 } from "node:crypto";
|
|
3879
3954
|
import { existsSync as existsSync5, mkdirSync as mkdirSync4, realpathSync as realpathSync2, statSync as statSync3 } from "node:fs";
|
|
@@ -3928,6 +4003,7 @@ function createWorkspaceManifest(workspace, input = {}) {
|
|
|
3928
4003
|
executorKind: input.executorKind ?? null,
|
|
3929
4004
|
executorHome: workspace.executorHome ?? null,
|
|
3930
4005
|
commandId: input.commandId ?? null,
|
|
4006
|
+
generation: input.generation ?? null,
|
|
3931
4007
|
runId: input.runId ?? null,
|
|
3932
4008
|
issueId: input.issueId ?? null,
|
|
3933
4009
|
workspaceKey: workspace.workspaceKey ?? null,
|
|
@@ -4041,6 +4117,7 @@ function resolveExecutionWorkspace(config, command, opts = {}) {
|
|
|
4041
4117
|
createWorkspaceManifest(workspace, {
|
|
4042
4118
|
executorKind,
|
|
4043
4119
|
commandId: readString(command.commandId) ?? null,
|
|
4120
|
+
generation: Number.isInteger(command.generation) ? command.generation : null,
|
|
4044
4121
|
runId,
|
|
4045
4122
|
issueId
|
|
4046
4123
|
});
|
|
@@ -4618,7 +4695,7 @@ function readWorkspaceStatus(cwd, opts = {}) {
|
|
|
4618
4695
|
}
|
|
4619
4696
|
|
|
4620
4697
|
// src/amaster-runtime-daemon.mjs
|
|
4621
|
-
var CONNECTOR_VERSION = "0.1.0-beta.
|
|
4698
|
+
var CONNECTOR_VERSION = "0.1.0-beta.24";
|
|
4622
4699
|
var CONNECTOR_CONTRACT_VERSION = "2026-06-04.v1";
|
|
4623
4700
|
var MAX_CHECKPOINT_BYTES = 20 * 1024 * 1024;
|
|
4624
4701
|
var CHECKPOINT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -4699,6 +4776,40 @@ function resultOutboxActiveRunCommands(config) {
|
|
|
4699
4776
|
}
|
|
4700
4777
|
return entries;
|
|
4701
4778
|
}
|
|
4779
|
+
function resultOutboxFailedRunCommands(config) {
|
|
4780
|
+
const dir = resultOutboxInvalidDir(config);
|
|
4781
|
+
if (!existsSync9(dir)) return [];
|
|
4782
|
+
const entries = [];
|
|
4783
|
+
for (const file of readdirSync6(dir).filter((name) => name.endsWith(".json")).sort().slice(-100)) {
|
|
4784
|
+
let entry;
|
|
4785
|
+
try {
|
|
4786
|
+
entry = asRecord(JSON.parse(readFileSync7(join10(dir, file), "utf8")));
|
|
4787
|
+
} catch {
|
|
4788
|
+
continue;
|
|
4789
|
+
}
|
|
4790
|
+
const failureReason = readString(entry.invalidReason);
|
|
4791
|
+
if (!failureReason || !["server_rejected_terminal", "retry_exhausted"].includes(failureReason)) continue;
|
|
4792
|
+
const activeRun = asRecord(entry.activeRun);
|
|
4793
|
+
const commandId = readString(activeRun.commandId) ?? readString(entry.commandId);
|
|
4794
|
+
if (!commandId) continue;
|
|
4795
|
+
entries.push({
|
|
4796
|
+
commandId,
|
|
4797
|
+
...readString(activeRun.runId) ? { runId: readString(activeRun.runId) } : {},
|
|
4798
|
+
...readString(activeRun.issueId) ? { issueId: readString(activeRun.issueId) } : {},
|
|
4799
|
+
...readString(activeRun.executorKind) ? { executorKind: readString(activeRun.executorKind) } : {},
|
|
4800
|
+
...readString(activeRun.workspacePath) ? { workspacePath: readString(activeRun.workspacePath) } : {},
|
|
4801
|
+
...readString(activeRun.startedAt) ? { startedAt: readString(activeRun.startedAt) } : {},
|
|
4802
|
+
...readString(activeRun.executorCompletedAt) ? { executorCompletedAt: readString(activeRun.executorCompletedAt) } : {},
|
|
4803
|
+
phase: "result_outbox_failed",
|
|
4804
|
+
outboxPending: 0,
|
|
4805
|
+
evidenceDeliveryStatus: "failed",
|
|
4806
|
+
evidenceFailureReason: failureReason,
|
|
4807
|
+
...typeof entry.httpStatus === "number" ? { evidenceHttpStatus: entry.httpStatus } : {},
|
|
4808
|
+
...readString(entry.invalidAt) ? { evidenceFailedAt: readString(entry.invalidAt) } : {}
|
|
4809
|
+
});
|
|
4810
|
+
}
|
|
4811
|
+
return entries;
|
|
4812
|
+
}
|
|
4702
4813
|
function clearDeliveredResultOutboxCommand(config, entry) {
|
|
4703
4814
|
const commandId = readString(entry?.commandId);
|
|
4704
4815
|
if (commandId) pendingResultOutboxRunCommands.delete(commandId);
|
|
@@ -5001,8 +5112,10 @@ function buildHeartbeatPayload(config, options = {}) {
|
|
|
5001
5112
|
const orphanReaperSummary = asRecord(options.orphanReaper ?? lastOrphanReaperSummary);
|
|
5002
5113
|
const activeRunCommandById = /* @__PURE__ */ new Map();
|
|
5003
5114
|
for (const entry of [
|
|
5115
|
+
...Array.from(localDispatchCommandIds, (commandId) => ({ commandId, phase: "preparing" })),
|
|
5004
5116
|
...Array.from(activeRunCommands.values()),
|
|
5005
5117
|
...Array.from(pendingResultOutboxRunCommands.values()),
|
|
5118
|
+
...resultOutboxFailedRunCommands(config),
|
|
5006
5119
|
...resultOutboxActiveRunCommands(config)
|
|
5007
5120
|
]) {
|
|
5008
5121
|
const commandId = readString(entry.commandId);
|
|
@@ -5993,7 +6106,7 @@ function buildExecutorInvocation(executor, command = {}, workspace = null) {
|
|
|
5993
6106
|
}
|
|
5994
6107
|
return { command: executor.command, args: [], stdin: "prompt" };
|
|
5995
6108
|
}
|
|
5996
|
-
async function executeModelCallCommand(config, command) {
|
|
6109
|
+
async function executeModelCallCommand(config, command, signal) {
|
|
5997
6110
|
const executor = selectExecutor(config, command);
|
|
5998
6111
|
const payload = asRecord(command.payload);
|
|
5999
6112
|
const prompt = readString(payload.prompt) ?? "";
|
|
@@ -6009,6 +6122,7 @@ async function executeModelCallCommand(config, command) {
|
|
|
6009
6122
|
config.executorMaxOutputBytes,
|
|
6010
6123
|
readNumber(payload.maxOutputBytes, 512 * 1024)
|
|
6011
6124
|
));
|
|
6125
|
+
await ackCommand(config, command, "spawned");
|
|
6012
6126
|
await ingestLog(config, command, "system", "info", `Starting ${executor.kind} runtime model call`, {
|
|
6013
6127
|
executorKind: executor.kind,
|
|
6014
6128
|
args: invocation.args,
|
|
@@ -6021,7 +6135,8 @@ async function executeModelCallCommand(config, command) {
|
|
|
6021
6135
|
stdin: invocation.stdin === "prompt" ? prompt : "",
|
|
6022
6136
|
timeoutSeconds,
|
|
6023
6137
|
maxOutputBytes,
|
|
6024
|
-
executorKind: executor.kind
|
|
6138
|
+
executorKind: executor.kind,
|
|
6139
|
+
signal
|
|
6025
6140
|
});
|
|
6026
6141
|
if (execution.stdout) {
|
|
6027
6142
|
await ingestLog(config, command, "stdout", "info", truncateText(execution.stdout, 4e3));
|
|
@@ -6059,6 +6174,17 @@ async function executeModelCallCommand(config, command) {
|
|
|
6059
6174
|
} : {}
|
|
6060
6175
|
}, error ?? void 0);
|
|
6061
6176
|
}
|
|
6177
|
+
async function executeTrackedModelCallCommand(config, command) {
|
|
6178
|
+
const abortController = new AbortController();
|
|
6179
|
+
const forgetActiveModelCall = rememberActiveRunCommand(command, {});
|
|
6180
|
+
const stopActiveModelCallHeartbeats = startActiveRunHeartbeats(config, command, abortController);
|
|
6181
|
+
try {
|
|
6182
|
+
return await executeModelCallCommand(config, command, abortController.signal);
|
|
6183
|
+
} finally {
|
|
6184
|
+
stopActiveModelCallHeartbeats();
|
|
6185
|
+
forgetActiveModelCall();
|
|
6186
|
+
}
|
|
6187
|
+
}
|
|
6062
6188
|
function redactProtectedText(value, protectedValues = []) {
|
|
6063
6189
|
let text = String(value ?? "");
|
|
6064
6190
|
for (const protectedValue of protectedValues) {
|
|
@@ -6066,7 +6192,7 @@ function redactProtectedText(value, protectedValues = []) {
|
|
|
6066
6192
|
}
|
|
6067
6193
|
return text;
|
|
6068
6194
|
}
|
|
6069
|
-
function createLiveOutputLogger(config, command, executorKind, protectedValues = []) {
|
|
6195
|
+
function createLiveOutputLogger(config, command, executorKind, protectedValues = [], onMcpToolResults = null) {
|
|
6070
6196
|
const buffers = { stdout: "", stderr: "" };
|
|
6071
6197
|
const displayCounts = { stdout: 0, stderr: 0, system: 0 };
|
|
6072
6198
|
const sourceCounts = { stdout: 0, stderr: 0 };
|
|
@@ -6164,7 +6290,11 @@ function createLiveOutputLogger(config, command, executorKind, protectedValues =
|
|
|
6164
6290
|
if (executorKind === "pi" && readString(asRecord(event).type) === "session") {
|
|
6165
6291
|
executorSessionId = readString(asRecord(event).sessionId ?? asRecord(event).id) ?? executorSessionId;
|
|
6166
6292
|
}
|
|
6167
|
-
if (executorKind === "pi" && event)
|
|
6293
|
+
if (executorKind === "pi" && event) {
|
|
6294
|
+
const results = piMcpToolResults(event);
|
|
6295
|
+
liveMcpToolResults.push(...results);
|
|
6296
|
+
if (results.length > 0) onMcpToolResults?.(results);
|
|
6297
|
+
}
|
|
6168
6298
|
let entry = null;
|
|
6169
6299
|
if (event && shouldPreserveExecutorJsonlForTranscript(executorKind, event)) {
|
|
6170
6300
|
entry = {
|
|
@@ -6305,6 +6435,7 @@ function realOrResolvedPath(value) {
|
|
|
6305
6435
|
return resolve8(value);
|
|
6306
6436
|
}
|
|
6307
6437
|
}
|
|
6438
|
+
var LSOF_COMMAND = process.platform === "darwin" && existsSync9("/usr/sbin/lsof") ? "/usr/sbin/lsof" : "lsof";
|
|
6308
6439
|
function processCwdForPid(pid) {
|
|
6309
6440
|
if (process.platform === "linux") {
|
|
6310
6441
|
try {
|
|
@@ -6314,7 +6445,7 @@ function processCwdForPid(pid) {
|
|
|
6314
6445
|
}
|
|
6315
6446
|
}
|
|
6316
6447
|
if (process.platform === "darwin") {
|
|
6317
|
-
const result2 = spawnSync5(
|
|
6448
|
+
const result2 = spawnSync5(LSOF_COMMAND, ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], {
|
|
6318
6449
|
encoding: "utf8",
|
|
6319
6450
|
stdio: ["ignore", "pipe", "ignore"]
|
|
6320
6451
|
});
|
|
@@ -6344,7 +6475,7 @@ function allProcessCwdsByPid() {
|
|
|
6344
6475
|
return cwds;
|
|
6345
6476
|
}
|
|
6346
6477
|
if (process.platform === "darwin") {
|
|
6347
|
-
const result2 = spawnSync5(
|
|
6478
|
+
const result2 = spawnSync5(LSOF_COMMAND, ["-nP", "-d", "cwd", "-Fp", "-Fn"], {
|
|
6348
6479
|
encoding: "utf8",
|
|
6349
6480
|
stdio: ["ignore", "pipe", "ignore"]
|
|
6350
6481
|
});
|
|
@@ -6514,8 +6645,8 @@ function runOrphanReaper(config, options = {}) {
|
|
|
6514
6645
|
return finishSummary();
|
|
6515
6646
|
}
|
|
6516
6647
|
const refs = activeRuntimeExecutionRefs();
|
|
6517
|
-
|
|
6518
|
-
|
|
6648
|
+
let processRows = null;
|
|
6649
|
+
let processCwdsByPid = null;
|
|
6519
6650
|
for (const workdir of walkManagedWorkdirs(config.runtimeWorkspacesRoot)) {
|
|
6520
6651
|
const manifest = readWorkspaceManifest(workspaceManifestPath(workdir));
|
|
6521
6652
|
if (!manifest || manifest.managed !== true) continue;
|
|
@@ -6524,6 +6655,8 @@ function runOrphanReaper(config, options = {}) {
|
|
|
6524
6655
|
summary.protectedWorkdirCount += 1;
|
|
6525
6656
|
continue;
|
|
6526
6657
|
}
|
|
6658
|
+
processRows ??= allProcessRows();
|
|
6659
|
+
processCwdsByPid ??= allProcessCwdsByPid();
|
|
6527
6660
|
const residents = listWorkspaceResidentProcesses(workdir, null, { processRows, processCwdsByPid });
|
|
6528
6661
|
if (residents.length === 0) continue;
|
|
6529
6662
|
summary.orphanWorkdirCount += 1;
|
|
@@ -6562,8 +6695,19 @@ function runExecutor(command, args, options) {
|
|
|
6562
6695
|
let timer = null;
|
|
6563
6696
|
let rssTimer = null;
|
|
6564
6697
|
let stopKillTimer = null;
|
|
6565
|
-
let
|
|
6698
|
+
let quiescenceTimer = null;
|
|
6699
|
+
let stoppedResidentTimer = null;
|
|
6566
6700
|
let completionOutputDrainTimer = null;
|
|
6701
|
+
let stopReason = null;
|
|
6702
|
+
let childExited = false;
|
|
6703
|
+
let childClosed = false;
|
|
6704
|
+
let closeCode = null;
|
|
6705
|
+
let closeSignal = null;
|
|
6706
|
+
let spawnError = null;
|
|
6707
|
+
let outputDrainForcedClosed = false;
|
|
6708
|
+
let killedWorkspaceResidents = [];
|
|
6709
|
+
let stoppedResidentDrainUntil = 0;
|
|
6710
|
+
const residentSignalledAt = /* @__PURE__ */ new Map();
|
|
6567
6711
|
let piCompletionOutputLineBuffer = "";
|
|
6568
6712
|
const outputBytes = { stdout: 0, stderr: 0 };
|
|
6569
6713
|
const maxOutputBytes = parsePositiveInteger(options.maxOutputBytes, 50 * 1024 * 1024);
|
|
@@ -6582,11 +6726,10 @@ function runExecutor(command, args, options) {
|
|
|
6582
6726
|
if (timer) clearTimeout(timer);
|
|
6583
6727
|
if (rssTimer) clearInterval(rssTimer);
|
|
6584
6728
|
if (stopKillTimer) clearTimeout(stopKillTimer);
|
|
6585
|
-
if (
|
|
6729
|
+
if (quiescenceTimer) clearTimeout(quiescenceTimer);
|
|
6730
|
+
if (stoppedResidentTimer) clearTimeout(stoppedResidentTimer);
|
|
6586
6731
|
if (completionOutputDrainTimer) clearTimeout(completionOutputDrainTimer);
|
|
6587
6732
|
options.signal?.removeEventListener?.("abort", abort);
|
|
6588
|
-
const shouldCleanupWorkspaceResidents = Boolean(aborted || outputFlood || memoryLimit || result2.timedOut || completionOutputType);
|
|
6589
|
-
const killedWorkspaceResidents = shouldCleanupWorkspaceResidents ? killWorkspaceResidentProcesses(options.cwd, processGroupId) : [];
|
|
6590
6733
|
resolveRun({
|
|
6591
6734
|
stdout,
|
|
6592
6735
|
stderr,
|
|
@@ -6595,25 +6738,77 @@ function runExecutor(command, args, options) {
|
|
|
6595
6738
|
memoryLimit,
|
|
6596
6739
|
completionOutputType,
|
|
6597
6740
|
killedWorkspaceResidents,
|
|
6741
|
+
...outputDrainForcedClosed ? { outputDrainForcedClosed: true } : {},
|
|
6598
6742
|
...result2
|
|
6599
6743
|
});
|
|
6600
6744
|
};
|
|
6601
6745
|
const scheduleStopKill = () => {
|
|
6602
|
-
if (stopKillTimer)
|
|
6746
|
+
if (stopKillTimer) return;
|
|
6603
6747
|
stopKillTimer = setTimeout(() => signalExecutorProcess(child, "SIGKILL", processGroupId), 2e3);
|
|
6604
|
-
stopKillTimer.unref?.();
|
|
6605
6748
|
};
|
|
6606
|
-
const
|
|
6607
|
-
|
|
6608
|
-
|
|
6749
|
+
const requestStop = (reason) => {
|
|
6750
|
+
if (settled || stopReason) return;
|
|
6751
|
+
stopReason = reason;
|
|
6752
|
+
signalExecutorProcess(child, "SIGTERM", processGroupId);
|
|
6753
|
+
scheduleStopKill();
|
|
6609
6754
|
};
|
|
6610
|
-
const
|
|
6611
|
-
if (settled || !
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
}
|
|
6616
|
-
|
|
6755
|
+
const reapStoppedWorkspaceResidents = () => {
|
|
6756
|
+
if (settled || !stopReason || stopReason === "completion_cleanup") return;
|
|
6757
|
+
const residents = listWorkspaceResidentProcesses(options.cwd, processGroupId, {
|
|
6758
|
+
processRows: allProcessRows(),
|
|
6759
|
+
processCwdsByPid: allProcessCwdsByPid()
|
|
6760
|
+
});
|
|
6761
|
+
const seen = new Set(killedWorkspaceResidents.map((resident) => resident.pid));
|
|
6762
|
+
const now = Date.now();
|
|
6763
|
+
for (const resident of residents) {
|
|
6764
|
+
if (!seen.has(resident.pid)) killedWorkspaceResidents.push(resident);
|
|
6765
|
+
if (!residentSignalledAt.has(resident.pid)) {
|
|
6766
|
+
residentSignalledAt.set(resident.pid, now);
|
|
6767
|
+
try {
|
|
6768
|
+
process.kill(resident.pid, "SIGTERM");
|
|
6769
|
+
} catch {
|
|
6770
|
+
}
|
|
6771
|
+
}
|
|
6772
|
+
}
|
|
6773
|
+
for (const resident of killedWorkspaceResidents) {
|
|
6774
|
+
const signalledAt = residentSignalledAt.get(resident.pid) ?? now;
|
|
6775
|
+
if (now - signalledAt < 2e3 || !isProcessIdAlive(resident.pid)) continue;
|
|
6776
|
+
try {
|
|
6777
|
+
process.kill(resident.pid, "SIGKILL");
|
|
6778
|
+
} catch {
|
|
6779
|
+
}
|
|
6780
|
+
}
|
|
6781
|
+
if (isExecutorProcessAlive(child, processGroupId) || now < stoppedResidentDrainUntil || killedWorkspaceResidents.some((resident) => isProcessIdAlive(resident.pid))) {
|
|
6782
|
+
stoppedResidentTimer = setTimeout(reapStoppedWorkspaceResidents, 100);
|
|
6783
|
+
} else {
|
|
6784
|
+
stoppedResidentTimer = null;
|
|
6785
|
+
}
|
|
6786
|
+
maybeFinishQuiescent();
|
|
6787
|
+
};
|
|
6788
|
+
const maybeFinishQuiescent = () => {
|
|
6789
|
+
if (settled || !childExited && !childClosed) return;
|
|
6790
|
+
if (isExecutorProcessAlive(child, processGroupId) || !childClosed && Date.now() < stoppedResidentDrainUntil || killedWorkspaceResidents.some((resident) => isProcessIdAlive(resident.pid))) {
|
|
6791
|
+
requestStop("completion_cleanup");
|
|
6792
|
+
if (!quiescenceTimer) {
|
|
6793
|
+
quiescenceTimer = setTimeout(() => {
|
|
6794
|
+
quiescenceTimer = null;
|
|
6795
|
+
maybeFinishQuiescent();
|
|
6796
|
+
}, 25);
|
|
6797
|
+
}
|
|
6798
|
+
return;
|
|
6799
|
+
}
|
|
6800
|
+
if (!childClosed) {
|
|
6801
|
+
outputDrainForcedClosed = true;
|
|
6802
|
+
child.stdout.destroy();
|
|
6803
|
+
child.stderr.destroy();
|
|
6804
|
+
}
|
|
6805
|
+
finish({
|
|
6806
|
+
exitCode: closeCode,
|
|
6807
|
+
signal: closeSignal,
|
|
6808
|
+
timedOut: stopReason === "timeout",
|
|
6809
|
+
spawnError,
|
|
6810
|
+
cancelled: aborted
|
|
6811
|
+
});
|
|
6617
6812
|
};
|
|
6618
6813
|
const maybeSchedulePiCompletionDrain = (text) => {
|
|
6619
6814
|
if (settled || outputFlood || options.executorKind !== "pi" || completionOutputType) return;
|
|
@@ -6638,8 +6833,7 @@ function runExecutor(command, args, options) {
|
|
|
6638
6833
|
completionOutputType = stopType;
|
|
6639
6834
|
completionOutputDrainTimer = setTimeout(() => {
|
|
6640
6835
|
if (settled) return;
|
|
6641
|
-
|
|
6642
|
-
scheduleStopKill();
|
|
6836
|
+
requestStop("completion_output");
|
|
6643
6837
|
}, PI_COMPLETION_OUTPUT_GRACE_MS);
|
|
6644
6838
|
completionOutputDrainTimer.unref?.();
|
|
6645
6839
|
return;
|
|
@@ -6655,8 +6849,7 @@ function runExecutor(command, args, options) {
|
|
|
6655
6849
|
bytes: outputBytes[stream],
|
|
6656
6850
|
limitBytes: maxOutputBytes
|
|
6657
6851
|
};
|
|
6658
|
-
|
|
6659
|
-
scheduleStopKill();
|
|
6852
|
+
requestStop("output_flood");
|
|
6660
6853
|
};
|
|
6661
6854
|
const killForMemoryLimit = (rssBytes) => {
|
|
6662
6855
|
if (settled || memoryLimit) return;
|
|
@@ -6664,23 +6857,17 @@ function runExecutor(command, args, options) {
|
|
|
6664
6857
|
rssBytes,
|
|
6665
6858
|
limitBytes: maxRssBytes
|
|
6666
6859
|
};
|
|
6667
|
-
|
|
6668
|
-
scheduleStopKill();
|
|
6860
|
+
requestStop("memory_limit");
|
|
6669
6861
|
};
|
|
6670
6862
|
const abort = () => {
|
|
6671
6863
|
if (settled) return;
|
|
6672
6864
|
aborted = true;
|
|
6673
|
-
|
|
6674
|
-
scheduleStopKill();
|
|
6865
|
+
requestStop("cancel");
|
|
6675
6866
|
};
|
|
6676
6867
|
options.signal?.addEventListener?.("abort", abort, { once: true });
|
|
6677
6868
|
if (options.signal?.aborted) abort();
|
|
6678
6869
|
timer = setTimeout(() => {
|
|
6679
|
-
|
|
6680
|
-
const timeoutKillTimer = setTimeout(() => signalExecutorProcess(child, "SIGKILL", processGroupId), 2e3);
|
|
6681
|
-
timeoutKillTimer.unref?.();
|
|
6682
|
-
destroyExecutorOutputPipes();
|
|
6683
|
-
finish({ exitCode: null, signal: "SIGTERM", timedOut: true, spawnError: null });
|
|
6870
|
+
requestStop("timeout");
|
|
6684
6871
|
}, options.timeoutSeconds * 1e3);
|
|
6685
6872
|
if (maxRssMb > 0 && processGroupId !== null) {
|
|
6686
6873
|
rssTimer = setInterval(() => {
|
|
@@ -6707,13 +6894,27 @@ function runExecutor(command, args, options) {
|
|
|
6707
6894
|
if (outputBytes.stderr > maxOutputBytes) killForOutputFlood("stderr");
|
|
6708
6895
|
});
|
|
6709
6896
|
child.on("error", (err) => {
|
|
6710
|
-
|
|
6897
|
+
spawnError = err.message;
|
|
6711
6898
|
});
|
|
6712
6899
|
child.on("exit", (code, signal) => {
|
|
6713
|
-
|
|
6900
|
+
childExited = true;
|
|
6901
|
+
closeCode = code;
|
|
6902
|
+
closeSignal = signal;
|
|
6903
|
+
stoppedResidentDrainUntil = Date.now() + 100;
|
|
6904
|
+
if (!stopReason && isExecutorProcessAlive(child, processGroupId)) {
|
|
6905
|
+
requestStop("completion_cleanup");
|
|
6906
|
+
}
|
|
6907
|
+
if (stopReason && stopReason !== "completion_cleanup") {
|
|
6908
|
+
stoppedResidentDrainUntil = Date.now() + 100;
|
|
6909
|
+
reapStoppedWorkspaceResidents();
|
|
6910
|
+
}
|
|
6911
|
+
maybeFinishQuiescent();
|
|
6714
6912
|
});
|
|
6715
6913
|
child.on("close", (code, signal) => {
|
|
6716
|
-
|
|
6914
|
+
childClosed = true;
|
|
6915
|
+
closeCode = code;
|
|
6916
|
+
closeSignal = signal;
|
|
6917
|
+
maybeFinishQuiescent();
|
|
6717
6918
|
});
|
|
6718
6919
|
if (options.stdin) child.stdin.end(options.stdin);
|
|
6719
6920
|
else child.stdin.end();
|
|
@@ -7516,10 +7717,12 @@ function piOutputValidationError(parsed, options = {}) {
|
|
|
7516
7717
|
}
|
|
7517
7718
|
return null;
|
|
7518
7719
|
}
|
|
7519
|
-
async function ackCommand(config, command) {
|
|
7720
|
+
async function ackCommand(config, command, phase = "preparing") {
|
|
7520
7721
|
const connectorId = requireConnectorId(config);
|
|
7521
|
-
return await
|
|
7522
|
-
leaseId: command.leaseId
|
|
7722
|
+
return await postJsonWithRetry(config, `/api/amaster/runtime-connectors/${connectorId}/commands/${command.commandId}/ack`, {
|
|
7723
|
+
leaseId: command.leaseId,
|
|
7724
|
+
generation: command.generation,
|
|
7725
|
+
phase
|
|
7523
7726
|
});
|
|
7524
7727
|
}
|
|
7525
7728
|
async function executeRunCommand(config, command) {
|
|
@@ -7608,6 +7811,7 @@ async function executeRunCommand(config, command) {
|
|
|
7608
7811
|
presentationKind: "context_manifest",
|
|
7609
7812
|
contextManifest
|
|
7610
7813
|
});
|
|
7814
|
+
await ackCommand(config, command, "spawned");
|
|
7611
7815
|
await ingestLog(config, command, "system", "info", `Starting ${executor.kind} executor`, {
|
|
7612
7816
|
executorKind: executor.kind,
|
|
7613
7817
|
cwd,
|
|
@@ -7625,7 +7829,18 @@ async function executeRunCommand(config, command) {
|
|
|
7625
7829
|
const abortController = new AbortController();
|
|
7626
7830
|
const stopActiveRunHeartbeats = startActiveRunHeartbeats(config, command, abortController);
|
|
7627
7831
|
const protectedExecutorValues = managedMcpProfile?.protectedValues ?? (managedMcpProfile ? [governedMcp.sessionToken] : []);
|
|
7628
|
-
const
|
|
7832
|
+
const runtimeArtifacts = [];
|
|
7833
|
+
const runtimeArtifactIngest = createRuntimeArtifactIngestQueue({
|
|
7834
|
+
ingest: (results) => ingestRuntimeArtifacts(config, command, cwd, results),
|
|
7835
|
+
onError: () => abortController.abort()
|
|
7836
|
+
});
|
|
7837
|
+
const liveOutputLogger = createLiveOutputLogger(
|
|
7838
|
+
config,
|
|
7839
|
+
command,
|
|
7840
|
+
executor.kind,
|
|
7841
|
+
protectedExecutorValues,
|
|
7842
|
+
(results) => runtimeArtifactIngest.enqueue(results)
|
|
7843
|
+
);
|
|
7629
7844
|
let execution;
|
|
7630
7845
|
try {
|
|
7631
7846
|
try {
|
|
@@ -7646,6 +7861,7 @@ async function executeRunCommand(config, command) {
|
|
|
7646
7861
|
} finally {
|
|
7647
7862
|
await liveOutputLogger.flush();
|
|
7648
7863
|
}
|
|
7864
|
+
runtimeArtifacts.push(...await runtimeArtifactIngest.flush());
|
|
7649
7865
|
execution.stdout = redactProtectedText(execution.stdout, protectedExecutorValues);
|
|
7650
7866
|
execution.stderr = redactProtectedText(execution.stderr, protectedExecutorValues);
|
|
7651
7867
|
patchActiveRunCommand(command, {
|
|
@@ -7702,7 +7918,15 @@ async function executeRunCommand(config, command) {
|
|
|
7702
7918
|
...Array.isArray(parsed.mcpToolResults) ? parsed.mcpToolResults.map(asRecord) : [],
|
|
7703
7919
|
...liveOutputLogger.mcpToolResults().map(asRecord)
|
|
7704
7920
|
];
|
|
7705
|
-
|
|
7921
|
+
runtimeArtifacts.push(...await ingestRuntimeArtifacts(
|
|
7922
|
+
config,
|
|
7923
|
+
command,
|
|
7924
|
+
cwd,
|
|
7925
|
+
mcpToolResults.filter((result3) => {
|
|
7926
|
+
const intentId = readString(asRecord(result3).artifactIntent?.intentId);
|
|
7927
|
+
return !intentId || !runtimeArtifactIngest.hasHandled(intentId);
|
|
7928
|
+
})
|
|
7929
|
+
));
|
|
7706
7930
|
const shouldPreserveNativeSession = managedMcpProfile && parsed.sessionId && (execution.exitCode === 0 || execution.completionOutputType === "approval_required") && !execution.timedOut && execution.cancelled !== true && !execution.spawnError && ["codex", "pi"].includes(executor.kind) && mcpToolResults.some((result3) => readString(result3.status) === "approval_required");
|
|
7707
7931
|
if (shouldPreserveNativeSession) {
|
|
7708
7932
|
try {
|
|
@@ -7796,6 +8020,7 @@ async function executeRunCommand(config, command) {
|
|
|
7796
8020
|
});
|
|
7797
8021
|
}
|
|
7798
8022
|
const result2 = {
|
|
8023
|
+
evidenceContract: { version: 1 },
|
|
7799
8024
|
executorKind: executor.kind,
|
|
7800
8025
|
command: invocation.command,
|
|
7801
8026
|
args: invocation.args,
|
|
@@ -7805,6 +8030,7 @@ async function executeRunCommand(config, command) {
|
|
|
7805
8030
|
exitCode: execution.exitCode,
|
|
7806
8031
|
signal: execution.signal,
|
|
7807
8032
|
timedOut: execution.timedOut,
|
|
8033
|
+
...execution.outputDrainForcedClosed ? { outputDrainForcedClosed: true } : {},
|
|
7808
8034
|
...readString(execution.completionOutputType) ? { completionOutputType: readString(execution.completionOutputType) } : {},
|
|
7809
8035
|
...cancelled ? { cancelledByControlPlane: true } : {},
|
|
7810
8036
|
...invocation.nativeSession ? { nativeSession: invocation.nativeSession } : {},
|
|
@@ -7900,7 +8126,7 @@ async function processCommand(config, command) {
|
|
|
7900
8126
|
return;
|
|
7901
8127
|
}
|
|
7902
8128
|
if (command.commandType === "model_call") {
|
|
7903
|
-
await
|
|
8129
|
+
await executeTrackedModelCallCommand(config, command);
|
|
7904
8130
|
return;
|
|
7905
8131
|
}
|
|
7906
8132
|
await completeCommand(config, command, "succeeded", {
|
package/dist/amaster-runtime.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { dirname, join, resolve } from "node:path";
|
|
|
5
5
|
import { homedir, hostname } from "node:os";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
|
|
8
|
-
const CONNECTOR_VERSION = "0.1.0-beta.
|
|
8
|
+
const CONNECTOR_VERSION = "0.1.0-beta.24";
|
|
9
9
|
|
|
10
10
|
const CAPABILITIES = [
|
|
11
11
|
"remote_registration",
|