@amaster.ai/employee-runtime-connector 0.1.0-beta.21 → 0.1.0-beta.23
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 +265 -60
- package/dist/amaster-runtime.mjs +31 -4
- 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
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
// src/amaster-runtime-daemon.mjs
|
|
5
5
|
import { createHash as createHash6 } from "node:crypto";
|
|
6
|
-
import { chmodSync as chmodSync3, copyFileSync as copyFileSync2, existsSync as existsSync9, lstatSync as lstatSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync7, readdirSync as readdirSync6, realpathSync as realpathSync3, renameSync as
|
|
6
|
+
import { chmodSync as chmodSync3, copyFileSync as copyFileSync2, existsSync as existsSync9, lstatSync as lstatSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync7, readdirSync as readdirSync6, realpathSync as realpathSync3, renameSync as renameSync3, rmSync as rmSync5, statSync as statSync7, symlinkSync as symlinkSync2, unlinkSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
7
7
|
import { arch as arch3, homedir as homedir3, hostname as hostname2, platform as platform3 } from "node:os";
|
|
8
8
|
import { basename as basename6, delimiter as delimiter2, dirname as dirname6, extname as extname2, isAbsolute as isAbsolute6, join as join10, relative as relative6, resolve as resolve8 } from "node:path";
|
|
9
9
|
import { spawn, spawnSync as spawnSync5 } from "node:child_process";
|
|
@@ -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 {
|
|
@@ -2435,7 +2456,7 @@ function compileCommandPromptWithManifest(input, options = {}) {
|
|
|
2435
2456
|
}
|
|
2436
2457
|
|
|
2437
2458
|
// src/amaster-runtime-daemon/config-state.mjs
|
|
2438
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2459
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2439
2460
|
import { homedir as homedir2, hostname } from "node:os";
|
|
2440
2461
|
import { dirname as dirname3, join as join4 } from "node:path";
|
|
2441
2462
|
|
|
@@ -2559,16 +2580,53 @@ function runtimeHome(env) {
|
|
|
2559
2580
|
function stateFilePath(env) {
|
|
2560
2581
|
return env.AMASTER_DAEMON_STATE_FILE ? expandHomePath(String(env.AMASTER_DAEMON_STATE_FILE)) : join4(runtimeHome(env), "runtime-connector-state.json");
|
|
2561
2582
|
}
|
|
2583
|
+
function corruptStatePath(path) {
|
|
2584
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\D/g, "").slice(0, 14);
|
|
2585
|
+
const base = `${path}.corrupt-${timestamp}Z`;
|
|
2586
|
+
let candidate = base;
|
|
2587
|
+
for (let suffix = 1; existsSync3(candidate); suffix += 1) {
|
|
2588
|
+
candidate = `${base}.${suffix}`;
|
|
2589
|
+
}
|
|
2590
|
+
return candidate;
|
|
2591
|
+
}
|
|
2562
2592
|
function readState(env) {
|
|
2563
2593
|
const path = stateFilePath(env);
|
|
2564
2594
|
if (!existsSync3(path)) return {};
|
|
2565
|
-
|
|
2595
|
+
try {
|
|
2596
|
+
const state = JSON.parse(readFileSync3(path, "utf8"));
|
|
2597
|
+
if (!state || typeof state !== "object" || Array.isArray(state)) {
|
|
2598
|
+
throw new TypeError("runtime connector state must be a JSON object");
|
|
2599
|
+
}
|
|
2600
|
+
return state;
|
|
2601
|
+
} catch {
|
|
2602
|
+
const quarantinePath = corruptStatePath(path);
|
|
2603
|
+
try {
|
|
2604
|
+
renameSync(path, quarantinePath);
|
|
2605
|
+
process.stderr.write(`AMaster runtime state was invalid and quarantined at ${quarantinePath}
|
|
2606
|
+
`);
|
|
2607
|
+
} catch (error) {
|
|
2608
|
+
process.stderr.write(
|
|
2609
|
+
`AMaster runtime state was invalid but could not be quarantined (${error?.code ?? "unknown error"})
|
|
2610
|
+
`
|
|
2611
|
+
);
|
|
2612
|
+
}
|
|
2613
|
+
return {};
|
|
2614
|
+
}
|
|
2566
2615
|
}
|
|
2567
2616
|
function writeState(env, state) {
|
|
2568
2617
|
const path = stateFilePath(env);
|
|
2569
2618
|
mkdirSync3(dirname3(path), { recursive: true });
|
|
2570
|
-
|
|
2571
|
-
|
|
2619
|
+
const temporaryPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
2620
|
+
try {
|
|
2621
|
+
writeFileSync3(temporaryPath, `${JSON.stringify(state, null, 2)}
|
|
2622
|
+
`, {
|
|
2623
|
+
flag: "wx",
|
|
2624
|
+
mode: 384
|
|
2625
|
+
});
|
|
2626
|
+
renameSync(temporaryPath, path);
|
|
2627
|
+
} finally {
|
|
2628
|
+
rmSync3(temporaryPath, { force: true });
|
|
2629
|
+
}
|
|
2572
2630
|
}
|
|
2573
2631
|
function buildConfig(env = process.env, flags = {}) {
|
|
2574
2632
|
const serverUrl = String(flags.serverUrl ?? env.AMASTER_EMPLOYEE_SERVER_URL ?? "http://127.0.0.1:3100").replace(/\/+$/, "");
|
|
@@ -3722,14 +3780,15 @@ function buildRuntimeConnectorAuthHeaders(config) {
|
|
|
3722
3780
|
if (config.token) return { Authorization: `Bearer ${config.token}` };
|
|
3723
3781
|
return {};
|
|
3724
3782
|
}
|
|
3725
|
-
async function postRuntimeConnectorJson(config, path, payload) {
|
|
3783
|
+
async function postRuntimeConnectorJson(config, path, payload, options = {}) {
|
|
3726
3784
|
const res = await fetch(`${config.serverUrl}${path}`, {
|
|
3727
3785
|
method: "POST",
|
|
3728
3786
|
headers: {
|
|
3729
3787
|
"content-type": "application/json",
|
|
3730
3788
|
...buildRuntimeConnectorAuthHeaders(config)
|
|
3731
3789
|
},
|
|
3732
|
-
body: JSON.stringify(payload)
|
|
3790
|
+
body: JSON.stringify(payload),
|
|
3791
|
+
signal: options.signal
|
|
3733
3792
|
});
|
|
3734
3793
|
const text = await res.text();
|
|
3735
3794
|
const body = text ? JSON.parse(text) : null;
|
|
@@ -3760,6 +3819,26 @@ async function postRuntimeConnectorBytes(config, path, body, headers = {}) {
|
|
|
3760
3819
|
}
|
|
3761
3820
|
return response;
|
|
3762
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
|
+
}
|
|
3763
3842
|
async function bestEffortPostRuntimeConnectorJson(config, path, payload, options = {}) {
|
|
3764
3843
|
try {
|
|
3765
3844
|
return await postRuntimeConnectorJson(config, path, payload);
|
|
@@ -3775,6 +3854,7 @@ async function bestEffortPostRuntimeConnectorJson(config, path, payload, options
|
|
|
3775
3854
|
}
|
|
3776
3855
|
}
|
|
3777
3856
|
var postJson = postRuntimeConnectorJson;
|
|
3857
|
+
var postJsonWithRetry = postRuntimeConnectorJsonWithRetry;
|
|
3778
3858
|
var bestEffortPostJson = bestEffortPostRuntimeConnectorJson;
|
|
3779
3859
|
|
|
3780
3860
|
// src/amaster-runtime-daemon/runtime-artifact-upload.mjs
|
|
@@ -3843,7 +3923,7 @@ import { existsSync as existsSync5, mkdirSync as mkdirSync4, realpathSync as rea
|
|
|
3843
3923
|
import { basename as basename4, join as join6, isAbsolute as isAbsolute4, relative as relative4, resolve as resolve4 } from "node:path";
|
|
3844
3924
|
|
|
3845
3925
|
// src/amaster-runtime-daemon/workspace-manifest.mjs
|
|
3846
|
-
import { existsSync as existsSync4, readFileSync as readFileSync5, renameSync, rmSync as
|
|
3926
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, renameSync as renameSync2, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3847
3927
|
import { basename as basename3, dirname as dirname4, join as join5 } from "node:path";
|
|
3848
3928
|
var WORKSPACE_MANIFEST_FILENAME = ".amaster-runtime.json";
|
|
3849
3929
|
function nowIso() {
|
|
@@ -3872,9 +3952,9 @@ function writeWorkspaceManifest(manifestPath, manifest) {
|
|
|
3872
3952
|
try {
|
|
3873
3953
|
writeFileSync4(tempPath, `${JSON.stringify(manifest, null, 2)}
|
|
3874
3954
|
`, { mode: 384 });
|
|
3875
|
-
|
|
3955
|
+
renameSync2(tempPath, manifestPath);
|
|
3876
3956
|
} catch (err) {
|
|
3877
|
-
|
|
3957
|
+
rmSync4(tempPath, { force: true });
|
|
3878
3958
|
throw err;
|
|
3879
3959
|
}
|
|
3880
3960
|
return manifest;
|
|
@@ -3891,6 +3971,7 @@ function createWorkspaceManifest(workspace, input = {}) {
|
|
|
3891
3971
|
executorKind: input.executorKind ?? null,
|
|
3892
3972
|
executorHome: workspace.executorHome ?? null,
|
|
3893
3973
|
commandId: input.commandId ?? null,
|
|
3974
|
+
generation: input.generation ?? null,
|
|
3894
3975
|
runId: input.runId ?? null,
|
|
3895
3976
|
issueId: input.issueId ?? null,
|
|
3896
3977
|
workspaceKey: workspace.workspaceKey ?? null,
|
|
@@ -4004,6 +4085,7 @@ function resolveExecutionWorkspace(config, command, opts = {}) {
|
|
|
4004
4085
|
createWorkspaceManifest(workspace, {
|
|
4005
4086
|
executorKind,
|
|
4006
4087
|
commandId: readString(command.commandId) ?? null,
|
|
4088
|
+
generation: Number.isInteger(command.generation) ? command.generation : null,
|
|
4007
4089
|
runId,
|
|
4008
4090
|
issueId
|
|
4009
4091
|
});
|
|
@@ -4581,7 +4663,7 @@ function readWorkspaceStatus(cwd, opts = {}) {
|
|
|
4581
4663
|
}
|
|
4582
4664
|
|
|
4583
4665
|
// src/amaster-runtime-daemon.mjs
|
|
4584
|
-
var CONNECTOR_VERSION = "0.1.0-beta.
|
|
4666
|
+
var CONNECTOR_VERSION = "0.1.0-beta.23";
|
|
4585
4667
|
var CONNECTOR_CONTRACT_VERSION = "2026-06-04.v1";
|
|
4586
4668
|
var MAX_CHECKPOINT_BYTES = 20 * 1024 * 1024;
|
|
4587
4669
|
var CHECKPOINT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -4662,6 +4744,40 @@ function resultOutboxActiveRunCommands(config) {
|
|
|
4662
4744
|
}
|
|
4663
4745
|
return entries;
|
|
4664
4746
|
}
|
|
4747
|
+
function resultOutboxFailedRunCommands(config) {
|
|
4748
|
+
const dir = resultOutboxInvalidDir(config);
|
|
4749
|
+
if (!existsSync9(dir)) return [];
|
|
4750
|
+
const entries = [];
|
|
4751
|
+
for (const file of readdirSync6(dir).filter((name) => name.endsWith(".json")).sort().slice(-100)) {
|
|
4752
|
+
let entry;
|
|
4753
|
+
try {
|
|
4754
|
+
entry = asRecord(JSON.parse(readFileSync7(join10(dir, file), "utf8")));
|
|
4755
|
+
} catch {
|
|
4756
|
+
continue;
|
|
4757
|
+
}
|
|
4758
|
+
const failureReason = readString(entry.invalidReason);
|
|
4759
|
+
if (!failureReason || !["server_rejected_terminal", "retry_exhausted"].includes(failureReason)) continue;
|
|
4760
|
+
const activeRun = asRecord(entry.activeRun);
|
|
4761
|
+
const commandId = readString(activeRun.commandId) ?? readString(entry.commandId);
|
|
4762
|
+
if (!commandId) continue;
|
|
4763
|
+
entries.push({
|
|
4764
|
+
commandId,
|
|
4765
|
+
...readString(activeRun.runId) ? { runId: readString(activeRun.runId) } : {},
|
|
4766
|
+
...readString(activeRun.issueId) ? { issueId: readString(activeRun.issueId) } : {},
|
|
4767
|
+
...readString(activeRun.executorKind) ? { executorKind: readString(activeRun.executorKind) } : {},
|
|
4768
|
+
...readString(activeRun.workspacePath) ? { workspacePath: readString(activeRun.workspacePath) } : {},
|
|
4769
|
+
...readString(activeRun.startedAt) ? { startedAt: readString(activeRun.startedAt) } : {},
|
|
4770
|
+
...readString(activeRun.executorCompletedAt) ? { executorCompletedAt: readString(activeRun.executorCompletedAt) } : {},
|
|
4771
|
+
phase: "result_outbox_failed",
|
|
4772
|
+
outboxPending: 0,
|
|
4773
|
+
evidenceDeliveryStatus: "failed",
|
|
4774
|
+
evidenceFailureReason: failureReason,
|
|
4775
|
+
...typeof entry.httpStatus === "number" ? { evidenceHttpStatus: entry.httpStatus } : {},
|
|
4776
|
+
...readString(entry.invalidAt) ? { evidenceFailedAt: readString(entry.invalidAt) } : {}
|
|
4777
|
+
});
|
|
4778
|
+
}
|
|
4779
|
+
return entries;
|
|
4780
|
+
}
|
|
4665
4781
|
function clearDeliveredResultOutboxCommand(config, entry) {
|
|
4666
4782
|
const commandId = readString(entry?.commandId);
|
|
4667
4783
|
if (commandId) pendingResultOutboxRunCommands.delete(commandId);
|
|
@@ -4964,8 +5080,10 @@ function buildHeartbeatPayload(config, options = {}) {
|
|
|
4964
5080
|
const orphanReaperSummary = asRecord(options.orphanReaper ?? lastOrphanReaperSummary);
|
|
4965
5081
|
const activeRunCommandById = /* @__PURE__ */ new Map();
|
|
4966
5082
|
for (const entry of [
|
|
5083
|
+
...Array.from(localDispatchCommandIds, (commandId) => ({ commandId, phase: "preparing" })),
|
|
4967
5084
|
...Array.from(activeRunCommands.values()),
|
|
4968
5085
|
...Array.from(pendingResultOutboxRunCommands.values()),
|
|
5086
|
+
...resultOutboxFailedRunCommands(config),
|
|
4969
5087
|
...resultOutboxActiveRunCommands(config)
|
|
4970
5088
|
]) {
|
|
4971
5089
|
const commandId = readString(entry.commandId);
|
|
@@ -5534,7 +5652,7 @@ function writeJsonFileAtomic(filePath, value) {
|
|
|
5534
5652
|
const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
5535
5653
|
writeFileSync5(tmpPath, `${JSON.stringify(value, null, 2)}
|
|
5536
5654
|
`, { mode: 384 });
|
|
5537
|
-
|
|
5655
|
+
renameSync3(tmpPath, filePath);
|
|
5538
5656
|
}
|
|
5539
5657
|
function isPlainRecord(value) {
|
|
5540
5658
|
return value && typeof value === "object" && !Array.isArray(value);
|
|
@@ -5956,7 +6074,7 @@ function buildExecutorInvocation(executor, command = {}, workspace = null) {
|
|
|
5956
6074
|
}
|
|
5957
6075
|
return { command: executor.command, args: [], stdin: "prompt" };
|
|
5958
6076
|
}
|
|
5959
|
-
async function executeModelCallCommand(config, command) {
|
|
6077
|
+
async function executeModelCallCommand(config, command, signal) {
|
|
5960
6078
|
const executor = selectExecutor(config, command);
|
|
5961
6079
|
const payload = asRecord(command.payload);
|
|
5962
6080
|
const prompt = readString(payload.prompt) ?? "";
|
|
@@ -5972,6 +6090,7 @@ async function executeModelCallCommand(config, command) {
|
|
|
5972
6090
|
config.executorMaxOutputBytes,
|
|
5973
6091
|
readNumber(payload.maxOutputBytes, 512 * 1024)
|
|
5974
6092
|
));
|
|
6093
|
+
await ackCommand(config, command, "spawned");
|
|
5975
6094
|
await ingestLog(config, command, "system", "info", `Starting ${executor.kind} runtime model call`, {
|
|
5976
6095
|
executorKind: executor.kind,
|
|
5977
6096
|
args: invocation.args,
|
|
@@ -5984,7 +6103,8 @@ async function executeModelCallCommand(config, command) {
|
|
|
5984
6103
|
stdin: invocation.stdin === "prompt" ? prompt : "",
|
|
5985
6104
|
timeoutSeconds,
|
|
5986
6105
|
maxOutputBytes,
|
|
5987
|
-
executorKind: executor.kind
|
|
6106
|
+
executorKind: executor.kind,
|
|
6107
|
+
signal
|
|
5988
6108
|
});
|
|
5989
6109
|
if (execution.stdout) {
|
|
5990
6110
|
await ingestLog(config, command, "stdout", "info", truncateText(execution.stdout, 4e3));
|
|
@@ -6022,6 +6142,17 @@ async function executeModelCallCommand(config, command) {
|
|
|
6022
6142
|
} : {}
|
|
6023
6143
|
}, error ?? void 0);
|
|
6024
6144
|
}
|
|
6145
|
+
async function executeTrackedModelCallCommand(config, command) {
|
|
6146
|
+
const abortController = new AbortController();
|
|
6147
|
+
const forgetActiveModelCall = rememberActiveRunCommand(command, {});
|
|
6148
|
+
const stopActiveModelCallHeartbeats = startActiveRunHeartbeats(config, command, abortController);
|
|
6149
|
+
try {
|
|
6150
|
+
return await executeModelCallCommand(config, command, abortController.signal);
|
|
6151
|
+
} finally {
|
|
6152
|
+
stopActiveModelCallHeartbeats();
|
|
6153
|
+
forgetActiveModelCall();
|
|
6154
|
+
}
|
|
6155
|
+
}
|
|
6025
6156
|
function redactProtectedText(value, protectedValues = []) {
|
|
6026
6157
|
let text = String(value ?? "");
|
|
6027
6158
|
for (const protectedValue of protectedValues) {
|
|
@@ -6268,6 +6399,7 @@ function realOrResolvedPath(value) {
|
|
|
6268
6399
|
return resolve8(value);
|
|
6269
6400
|
}
|
|
6270
6401
|
}
|
|
6402
|
+
var LSOF_COMMAND = process.platform === "darwin" && existsSync9("/usr/sbin/lsof") ? "/usr/sbin/lsof" : "lsof";
|
|
6271
6403
|
function processCwdForPid(pid) {
|
|
6272
6404
|
if (process.platform === "linux") {
|
|
6273
6405
|
try {
|
|
@@ -6277,7 +6409,7 @@ function processCwdForPid(pid) {
|
|
|
6277
6409
|
}
|
|
6278
6410
|
}
|
|
6279
6411
|
if (process.platform === "darwin") {
|
|
6280
|
-
const result2 = spawnSync5(
|
|
6412
|
+
const result2 = spawnSync5(LSOF_COMMAND, ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], {
|
|
6281
6413
|
encoding: "utf8",
|
|
6282
6414
|
stdio: ["ignore", "pipe", "ignore"]
|
|
6283
6415
|
});
|
|
@@ -6307,7 +6439,7 @@ function allProcessCwdsByPid() {
|
|
|
6307
6439
|
return cwds;
|
|
6308
6440
|
}
|
|
6309
6441
|
if (process.platform === "darwin") {
|
|
6310
|
-
const result2 = spawnSync5(
|
|
6442
|
+
const result2 = spawnSync5(LSOF_COMMAND, ["-nP", "-d", "cwd", "-Fp", "-Fn"], {
|
|
6311
6443
|
encoding: "utf8",
|
|
6312
6444
|
stdio: ["ignore", "pipe", "ignore"]
|
|
6313
6445
|
});
|
|
@@ -6525,8 +6657,19 @@ function runExecutor(command, args, options) {
|
|
|
6525
6657
|
let timer = null;
|
|
6526
6658
|
let rssTimer = null;
|
|
6527
6659
|
let stopKillTimer = null;
|
|
6528
|
-
let
|
|
6660
|
+
let quiescenceTimer = null;
|
|
6661
|
+
let stoppedResidentTimer = null;
|
|
6529
6662
|
let completionOutputDrainTimer = null;
|
|
6663
|
+
let stopReason = null;
|
|
6664
|
+
let childExited = false;
|
|
6665
|
+
let childClosed = false;
|
|
6666
|
+
let closeCode = null;
|
|
6667
|
+
let closeSignal = null;
|
|
6668
|
+
let spawnError = null;
|
|
6669
|
+
let outputDrainForcedClosed = false;
|
|
6670
|
+
let killedWorkspaceResidents = [];
|
|
6671
|
+
let stoppedResidentDrainUntil = 0;
|
|
6672
|
+
const residentSignalledAt = /* @__PURE__ */ new Map();
|
|
6530
6673
|
let piCompletionOutputLineBuffer = "";
|
|
6531
6674
|
const outputBytes = { stdout: 0, stderr: 0 };
|
|
6532
6675
|
const maxOutputBytes = parsePositiveInteger(options.maxOutputBytes, 50 * 1024 * 1024);
|
|
@@ -6545,11 +6688,10 @@ function runExecutor(command, args, options) {
|
|
|
6545
6688
|
if (timer) clearTimeout(timer);
|
|
6546
6689
|
if (rssTimer) clearInterval(rssTimer);
|
|
6547
6690
|
if (stopKillTimer) clearTimeout(stopKillTimer);
|
|
6548
|
-
if (
|
|
6691
|
+
if (quiescenceTimer) clearTimeout(quiescenceTimer);
|
|
6692
|
+
if (stoppedResidentTimer) clearTimeout(stoppedResidentTimer);
|
|
6549
6693
|
if (completionOutputDrainTimer) clearTimeout(completionOutputDrainTimer);
|
|
6550
6694
|
options.signal?.removeEventListener?.("abort", abort);
|
|
6551
|
-
const shouldCleanupWorkspaceResidents = Boolean(aborted || outputFlood || memoryLimit || result2.timedOut || completionOutputType);
|
|
6552
|
-
const killedWorkspaceResidents = shouldCleanupWorkspaceResidents ? killWorkspaceResidentProcesses(options.cwd, processGroupId) : [];
|
|
6553
6695
|
resolveRun({
|
|
6554
6696
|
stdout,
|
|
6555
6697
|
stderr,
|
|
@@ -6558,25 +6700,77 @@ function runExecutor(command, args, options) {
|
|
|
6558
6700
|
memoryLimit,
|
|
6559
6701
|
completionOutputType,
|
|
6560
6702
|
killedWorkspaceResidents,
|
|
6703
|
+
...outputDrainForcedClosed ? { outputDrainForcedClosed: true } : {},
|
|
6561
6704
|
...result2
|
|
6562
6705
|
});
|
|
6563
6706
|
};
|
|
6564
6707
|
const scheduleStopKill = () => {
|
|
6565
|
-
if (stopKillTimer)
|
|
6708
|
+
if (stopKillTimer) return;
|
|
6566
6709
|
stopKillTimer = setTimeout(() => signalExecutorProcess(child, "SIGKILL", processGroupId), 2e3);
|
|
6567
|
-
stopKillTimer.unref?.();
|
|
6568
6710
|
};
|
|
6569
|
-
const
|
|
6570
|
-
|
|
6571
|
-
|
|
6711
|
+
const requestStop = (reason) => {
|
|
6712
|
+
if (settled || stopReason) return;
|
|
6713
|
+
stopReason = reason;
|
|
6714
|
+
signalExecutorProcess(child, "SIGTERM", processGroupId);
|
|
6715
|
+
scheduleStopKill();
|
|
6716
|
+
};
|
|
6717
|
+
const reapStoppedWorkspaceResidents = () => {
|
|
6718
|
+
if (settled || !stopReason || stopReason === "completion_cleanup") return;
|
|
6719
|
+
const residents = listWorkspaceResidentProcesses(options.cwd, processGroupId, {
|
|
6720
|
+
processRows: allProcessRows(),
|
|
6721
|
+
processCwdsByPid: allProcessCwdsByPid()
|
|
6722
|
+
});
|
|
6723
|
+
const seen = new Set(killedWorkspaceResidents.map((resident) => resident.pid));
|
|
6724
|
+
const now = Date.now();
|
|
6725
|
+
for (const resident of residents) {
|
|
6726
|
+
if (!seen.has(resident.pid)) killedWorkspaceResidents.push(resident);
|
|
6727
|
+
if (!residentSignalledAt.has(resident.pid)) {
|
|
6728
|
+
residentSignalledAt.set(resident.pid, now);
|
|
6729
|
+
try {
|
|
6730
|
+
process.kill(resident.pid, "SIGTERM");
|
|
6731
|
+
} catch {
|
|
6732
|
+
}
|
|
6733
|
+
}
|
|
6734
|
+
}
|
|
6735
|
+
for (const resident of killedWorkspaceResidents) {
|
|
6736
|
+
const signalledAt = residentSignalledAt.get(resident.pid) ?? now;
|
|
6737
|
+
if (now - signalledAt < 2e3 || !isProcessIdAlive(resident.pid)) continue;
|
|
6738
|
+
try {
|
|
6739
|
+
process.kill(resident.pid, "SIGKILL");
|
|
6740
|
+
} catch {
|
|
6741
|
+
}
|
|
6742
|
+
}
|
|
6743
|
+
if (isExecutorProcessAlive(child, processGroupId) || now < stoppedResidentDrainUntil || killedWorkspaceResidents.some((resident) => isProcessIdAlive(resident.pid))) {
|
|
6744
|
+
stoppedResidentTimer = setTimeout(reapStoppedWorkspaceResidents, 100);
|
|
6745
|
+
} else {
|
|
6746
|
+
stoppedResidentTimer = null;
|
|
6747
|
+
}
|
|
6748
|
+
maybeFinishQuiescent();
|
|
6572
6749
|
};
|
|
6573
|
-
const
|
|
6574
|
-
if (settled || !
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6750
|
+
const maybeFinishQuiescent = () => {
|
|
6751
|
+
if (settled || !childExited && !childClosed) return;
|
|
6752
|
+
if (isExecutorProcessAlive(child, processGroupId) || !childClosed && Date.now() < stoppedResidentDrainUntil || killedWorkspaceResidents.some((resident) => isProcessIdAlive(resident.pid))) {
|
|
6753
|
+
requestStop("completion_cleanup");
|
|
6754
|
+
if (!quiescenceTimer) {
|
|
6755
|
+
quiescenceTimer = setTimeout(() => {
|
|
6756
|
+
quiescenceTimer = null;
|
|
6757
|
+
maybeFinishQuiescent();
|
|
6758
|
+
}, 25);
|
|
6759
|
+
}
|
|
6760
|
+
return;
|
|
6761
|
+
}
|
|
6762
|
+
if (!childClosed) {
|
|
6763
|
+
outputDrainForcedClosed = true;
|
|
6764
|
+
child.stdout.destroy();
|
|
6765
|
+
child.stderr.destroy();
|
|
6766
|
+
}
|
|
6767
|
+
finish({
|
|
6768
|
+
exitCode: closeCode,
|
|
6769
|
+
signal: closeSignal,
|
|
6770
|
+
timedOut: stopReason === "timeout",
|
|
6771
|
+
spawnError,
|
|
6772
|
+
cancelled: aborted
|
|
6773
|
+
});
|
|
6580
6774
|
};
|
|
6581
6775
|
const maybeSchedulePiCompletionDrain = (text) => {
|
|
6582
6776
|
if (settled || outputFlood || options.executorKind !== "pi" || completionOutputType) return;
|
|
@@ -6601,8 +6795,7 @@ function runExecutor(command, args, options) {
|
|
|
6601
6795
|
completionOutputType = stopType;
|
|
6602
6796
|
completionOutputDrainTimer = setTimeout(() => {
|
|
6603
6797
|
if (settled) return;
|
|
6604
|
-
|
|
6605
|
-
scheduleStopKill();
|
|
6798
|
+
requestStop("completion_output");
|
|
6606
6799
|
}, PI_COMPLETION_OUTPUT_GRACE_MS);
|
|
6607
6800
|
completionOutputDrainTimer.unref?.();
|
|
6608
6801
|
return;
|
|
@@ -6618,8 +6811,7 @@ function runExecutor(command, args, options) {
|
|
|
6618
6811
|
bytes: outputBytes[stream],
|
|
6619
6812
|
limitBytes: maxOutputBytes
|
|
6620
6813
|
};
|
|
6621
|
-
|
|
6622
|
-
scheduleStopKill();
|
|
6814
|
+
requestStop("output_flood");
|
|
6623
6815
|
};
|
|
6624
6816
|
const killForMemoryLimit = (rssBytes) => {
|
|
6625
6817
|
if (settled || memoryLimit) return;
|
|
@@ -6627,23 +6819,17 @@ function runExecutor(command, args, options) {
|
|
|
6627
6819
|
rssBytes,
|
|
6628
6820
|
limitBytes: maxRssBytes
|
|
6629
6821
|
};
|
|
6630
|
-
|
|
6631
|
-
scheduleStopKill();
|
|
6822
|
+
requestStop("memory_limit");
|
|
6632
6823
|
};
|
|
6633
6824
|
const abort = () => {
|
|
6634
6825
|
if (settled) return;
|
|
6635
6826
|
aborted = true;
|
|
6636
|
-
|
|
6637
|
-
scheduleStopKill();
|
|
6827
|
+
requestStop("cancel");
|
|
6638
6828
|
};
|
|
6639
6829
|
options.signal?.addEventListener?.("abort", abort, { once: true });
|
|
6640
6830
|
if (options.signal?.aborted) abort();
|
|
6641
6831
|
timer = setTimeout(() => {
|
|
6642
|
-
|
|
6643
|
-
const timeoutKillTimer = setTimeout(() => signalExecutorProcess(child, "SIGKILL", processGroupId), 2e3);
|
|
6644
|
-
timeoutKillTimer.unref?.();
|
|
6645
|
-
destroyExecutorOutputPipes();
|
|
6646
|
-
finish({ exitCode: null, signal: "SIGTERM", timedOut: true, spawnError: null });
|
|
6832
|
+
requestStop("timeout");
|
|
6647
6833
|
}, options.timeoutSeconds * 1e3);
|
|
6648
6834
|
if (maxRssMb > 0 && processGroupId !== null) {
|
|
6649
6835
|
rssTimer = setInterval(() => {
|
|
@@ -6670,13 +6856,27 @@ function runExecutor(command, args, options) {
|
|
|
6670
6856
|
if (outputBytes.stderr > maxOutputBytes) killForOutputFlood("stderr");
|
|
6671
6857
|
});
|
|
6672
6858
|
child.on("error", (err) => {
|
|
6673
|
-
|
|
6859
|
+
spawnError = err.message;
|
|
6674
6860
|
});
|
|
6675
6861
|
child.on("exit", (code, signal) => {
|
|
6676
|
-
|
|
6862
|
+
childExited = true;
|
|
6863
|
+
closeCode = code;
|
|
6864
|
+
closeSignal = signal;
|
|
6865
|
+
stoppedResidentDrainUntil = Date.now() + 100;
|
|
6866
|
+
if (!stopReason && isExecutorProcessAlive(child, processGroupId)) {
|
|
6867
|
+
requestStop("completion_cleanup");
|
|
6868
|
+
}
|
|
6869
|
+
if (stopReason && stopReason !== "completion_cleanup") {
|
|
6870
|
+
stoppedResidentDrainUntil = Date.now() + 100;
|
|
6871
|
+
reapStoppedWorkspaceResidents();
|
|
6872
|
+
}
|
|
6873
|
+
maybeFinishQuiescent();
|
|
6677
6874
|
});
|
|
6678
6875
|
child.on("close", (code, signal) => {
|
|
6679
|
-
|
|
6876
|
+
childClosed = true;
|
|
6877
|
+
closeCode = code;
|
|
6878
|
+
closeSignal = signal;
|
|
6879
|
+
maybeFinishQuiescent();
|
|
6680
6880
|
});
|
|
6681
6881
|
if (options.stdin) child.stdin.end(options.stdin);
|
|
6682
6882
|
else child.stdin.end();
|
|
@@ -6869,7 +7069,7 @@ function moveResultOutboxEntryToInvalid(config, file, fullPath, reason, detail,
|
|
|
6869
7069
|
const invalidPath = join10(invalidDir, file);
|
|
6870
7070
|
if (original === void 0) {
|
|
6871
7071
|
try {
|
|
6872
|
-
|
|
7072
|
+
renameSync3(fullPath, invalidPath);
|
|
6873
7073
|
} catch {
|
|
6874
7074
|
copyFileSync2(fullPath, invalidPath);
|
|
6875
7075
|
unlinkSync(fullPath);
|
|
@@ -7240,7 +7440,7 @@ async function materializeRequiredArtifactInputs(config, command, workspace) {
|
|
|
7240
7440
|
throw new Error("artifact_input_manifest_invalid: expected version 1 entries array");
|
|
7241
7441
|
}
|
|
7242
7442
|
const targetRoot = join10(workspace.cwd, "input-artifacts");
|
|
7243
|
-
|
|
7443
|
+
rmSync5(targetRoot, { recursive: true, force: true });
|
|
7244
7444
|
mkdirSync5(targetRoot, { recursive: true });
|
|
7245
7445
|
const usedPaths = /* @__PURE__ */ new Set();
|
|
7246
7446
|
const materialized = [];
|
|
@@ -7316,7 +7516,7 @@ function issueCheckpointDir(workspace) {
|
|
|
7316
7516
|
async function clearIssueCheckpoint(config, command, workspace, reason) {
|
|
7317
7517
|
const checkpointDir = issueCheckpointDir(workspace);
|
|
7318
7518
|
if (!existsSync9(checkpointDir)) return false;
|
|
7319
|
-
|
|
7519
|
+
rmSync5(checkpointDir, { recursive: true, force: true });
|
|
7320
7520
|
await ingestLog(config, command, "system", "info", "Cleared issue continuation checkpoint", { reason });
|
|
7321
7521
|
return true;
|
|
7322
7522
|
}
|
|
@@ -7339,12 +7539,12 @@ async function materializeIssueCheckpoint(config, command, workspace) {
|
|
|
7339
7539
|
try {
|
|
7340
7540
|
manifest = asRecord(JSON.parse(readFileSync7(manifestPath, "utf8")));
|
|
7341
7541
|
} catch (err) {
|
|
7342
|
-
|
|
7542
|
+
rmSync5(checkpointDir, { recursive: true, force: true });
|
|
7343
7543
|
throw new Error(`invalid issue checkpoint manifest: ${err instanceof Error ? err.message : String(err)}`);
|
|
7344
7544
|
}
|
|
7345
7545
|
const expiresAt = Date.parse(readString(manifest.expiresAt) ?? "");
|
|
7346
7546
|
if (!Number.isFinite(expiresAt) || expiresAt <= Date.now() || readString(manifest.issueId) !== commandIssueId(command)) {
|
|
7347
|
-
|
|
7547
|
+
rmSync5(checkpointDir, { recursive: true, force: true });
|
|
7348
7548
|
return [];
|
|
7349
7549
|
}
|
|
7350
7550
|
try {
|
|
@@ -7418,7 +7618,7 @@ async function materializeIssueCheckpoint(config, command, workspace) {
|
|
|
7418
7618
|
async function saveIssueCheckpoint(config, command, workspace, candidates) {
|
|
7419
7619
|
const checkpointDir = issueCheckpointDir(workspace);
|
|
7420
7620
|
const filesDir = join10(checkpointDir, "files");
|
|
7421
|
-
|
|
7621
|
+
rmSync5(checkpointDir, { recursive: true, force: true });
|
|
7422
7622
|
mkdirSync5(filesDir, { recursive: true });
|
|
7423
7623
|
const files = [];
|
|
7424
7624
|
let totalBytes = 0;
|
|
@@ -7441,7 +7641,7 @@ async function saveIssueCheckpoint(config, command, workspace, candidates) {
|
|
|
7441
7641
|
files.push({ path: relativePath, byteSize, sha256: hashFileSha256(ownedSource) });
|
|
7442
7642
|
}
|
|
7443
7643
|
if (files.length === 0) {
|
|
7444
|
-
|
|
7644
|
+
rmSync5(checkpointDir, { recursive: true, force: true });
|
|
7445
7645
|
return null;
|
|
7446
7646
|
}
|
|
7447
7647
|
const manifest = {
|
|
@@ -7479,10 +7679,12 @@ function piOutputValidationError(parsed, options = {}) {
|
|
|
7479
7679
|
}
|
|
7480
7680
|
return null;
|
|
7481
7681
|
}
|
|
7482
|
-
async function ackCommand(config, command) {
|
|
7682
|
+
async function ackCommand(config, command, phase = "preparing") {
|
|
7483
7683
|
const connectorId = requireConnectorId(config);
|
|
7484
|
-
return await
|
|
7485
|
-
leaseId: command.leaseId
|
|
7684
|
+
return await postJsonWithRetry(config, `/api/amaster/runtime-connectors/${connectorId}/commands/${command.commandId}/ack`, {
|
|
7685
|
+
leaseId: command.leaseId,
|
|
7686
|
+
generation: command.generation,
|
|
7687
|
+
phase
|
|
7486
7688
|
});
|
|
7487
7689
|
}
|
|
7488
7690
|
async function executeRunCommand(config, command) {
|
|
@@ -7571,6 +7773,7 @@ async function executeRunCommand(config, command) {
|
|
|
7571
7773
|
presentationKind: "context_manifest",
|
|
7572
7774
|
contextManifest
|
|
7573
7775
|
});
|
|
7776
|
+
await ackCommand(config, command, "spawned");
|
|
7574
7777
|
await ingestLog(config, command, "system", "info", `Starting ${executor.kind} executor`, {
|
|
7575
7778
|
executorKind: executor.kind,
|
|
7576
7779
|
cwd,
|
|
@@ -7759,6 +7962,7 @@ async function executeRunCommand(config, command) {
|
|
|
7759
7962
|
});
|
|
7760
7963
|
}
|
|
7761
7964
|
const result2 = {
|
|
7965
|
+
evidenceContract: { version: 1 },
|
|
7762
7966
|
executorKind: executor.kind,
|
|
7763
7967
|
command: invocation.command,
|
|
7764
7968
|
args: invocation.args,
|
|
@@ -7768,6 +7972,7 @@ async function executeRunCommand(config, command) {
|
|
|
7768
7972
|
exitCode: execution.exitCode,
|
|
7769
7973
|
signal: execution.signal,
|
|
7770
7974
|
timedOut: execution.timedOut,
|
|
7975
|
+
...execution.outputDrainForcedClosed ? { outputDrainForcedClosed: true } : {},
|
|
7771
7976
|
...readString(execution.completionOutputType) ? { completionOutputType: readString(execution.completionOutputType) } : {},
|
|
7772
7977
|
...cancelled ? { cancelledByControlPlane: true } : {},
|
|
7773
7978
|
...invocation.nativeSession ? { nativeSession: invocation.nativeSession } : {},
|
|
@@ -7863,7 +8068,7 @@ async function processCommand(config, command) {
|
|
|
7863
8068
|
return;
|
|
7864
8069
|
}
|
|
7865
8070
|
if (command.commandType === "model_call") {
|
|
7866
|
-
await
|
|
8071
|
+
await executeTrackedModelCallCommand(config, command);
|
|
7867
8072
|
return;
|
|
7868
8073
|
}
|
|
7869
8074
|
await completeCommand(config, command, "succeeded", {
|
package/dist/amaster-runtime.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { spawn, spawnSync } from "node:child_process";
|
|
4
4
|
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.23";
|
|
9
9
|
|
|
10
10
|
const CAPABILITIES = [
|
|
11
11
|
"remote_registration",
|
|
@@ -277,8 +277,26 @@ function readState(config) {
|
|
|
277
277
|
const statePath = config.AMASTER_DAEMON_STATE_FILE || join(configDir, "runtime-connector-state.json");
|
|
278
278
|
if (!existsSync(statePath)) return {};
|
|
279
279
|
try {
|
|
280
|
-
|
|
280
|
+
const state = JSON.parse(readFileSync(statePath, "utf8"));
|
|
281
|
+
if (!state || typeof state !== "object" || Array.isArray(state)) {
|
|
282
|
+
throw new TypeError("runtime connector state must be a JSON object");
|
|
283
|
+
}
|
|
284
|
+
return state;
|
|
281
285
|
} catch {
|
|
286
|
+
const timestamp = new Date().toISOString().replace(/\D/g, "").slice(0, 14);
|
|
287
|
+
const base = `${statePath}.corrupt-${timestamp}Z`;
|
|
288
|
+
let quarantinePath = base;
|
|
289
|
+
for (let suffix = 1; existsSync(quarantinePath); suffix += 1) {
|
|
290
|
+
quarantinePath = `${base}.${suffix}`;
|
|
291
|
+
}
|
|
292
|
+
try {
|
|
293
|
+
renameSync(statePath, quarantinePath);
|
|
294
|
+
process.stderr.write(`AMaster runtime state was invalid and quarantined at ${quarantinePath}\n`);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
process.stderr.write(
|
|
297
|
+
`AMaster runtime state was invalid but could not be quarantined (${error?.code ?? "unknown error"})\n`,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
282
300
|
return {};
|
|
283
301
|
}
|
|
284
302
|
}
|
|
@@ -286,7 +304,16 @@ function readState(config) {
|
|
|
286
304
|
function writeState(config, state) {
|
|
287
305
|
const statePath = withDefaults(config).AMASTER_DAEMON_STATE_FILE || join(configDir, "runtime-connector-state.json");
|
|
288
306
|
mkdirSync(dirname(statePath), { recursive: true });
|
|
289
|
-
|
|
307
|
+
const temporaryPath = `${statePath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
308
|
+
try {
|
|
309
|
+
writeFileSync(temporaryPath, `${JSON.stringify(state, null, 2)}\n`, {
|
|
310
|
+
flag: "wx",
|
|
311
|
+
mode: 0o600,
|
|
312
|
+
});
|
|
313
|
+
renameSync(temporaryPath, statePath);
|
|
314
|
+
} finally {
|
|
315
|
+
rmSync(temporaryPath, { force: true });
|
|
316
|
+
}
|
|
290
317
|
}
|
|
291
318
|
|
|
292
319
|
function readPidInfo() {
|