@alfe.ai/gateway 0.10.2 → 0.10.3
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/dist/health.js +65 -3
- package/dist/runtime-upgrade.js +47 -14
- package/package.json +1 -1
package/dist/health.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ErrorLineDetector, c as createLogger, f as parseOpenClawVersion, h as __require, i as CaptureThrottle, m as __commonJSMin, o as STRICT_ERROR_CAPTURE_PROFILE, r as RuntimeProcess, s as toFingerprintKey, t as DRAIN_GRACE_MS } from "./runtime-process.js";
|
|
1
|
+
import { a as ErrorLineDetector, c as createLogger, f as parseOpenClawVersion, h as __require, i as CaptureThrottle, m as __commonJSMin, n as OPENCLAW_EXTERNAL_SUPERVISOR_ENV, o as STRICT_ERROR_CAPTURE_PROFILE, r as RuntimeProcess, s as toFingerprintKey, t as DRAIN_GRACE_MS } from "./runtime-process.js";
|
|
2
2
|
import { a as captureFatal, d as flushSentry, f as initAgentSentry, h as logger, o as captureIntegrationFailure, p as setAgentContext, s as captureMcpFailure } from "./sentry.js";
|
|
3
3
|
import { chmod, mkdir, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
@@ -5858,16 +5858,57 @@ const log$5 = logger.child({ component: "RuntimeGate" });
|
|
|
5858
5858
|
var RuntimeGate = class {
|
|
5859
5859
|
depth = 0;
|
|
5860
5860
|
wasRunning = false;
|
|
5861
|
+
exclusive = false;
|
|
5862
|
+
/** Brackets parked behind an exclusive hold (or an exclusive request parked behind open brackets). */
|
|
5863
|
+
waiters = [];
|
|
5861
5864
|
constructor(getRuntime) {
|
|
5862
5865
|
this.getRuntime = getRuntime;
|
|
5863
5866
|
}
|
|
5867
|
+
/** True while any bracket (shared or exclusive) is open. */
|
|
5868
|
+
get isSuspended() {
|
|
5869
|
+
return this.depth > 0;
|
|
5870
|
+
}
|
|
5871
|
+
/** True while an exclusive bracket is open. */
|
|
5872
|
+
get isExclusive() {
|
|
5873
|
+
return this.exclusive;
|
|
5874
|
+
}
|
|
5875
|
+
waitFor(ready) {
|
|
5876
|
+
if (ready()) return Promise.resolve();
|
|
5877
|
+
return new Promise((resolve) => {
|
|
5878
|
+
const check = () => {
|
|
5879
|
+
if (ready()) resolve();
|
|
5880
|
+
else this.waiters.push(check);
|
|
5881
|
+
};
|
|
5882
|
+
this.waiters.push(check);
|
|
5883
|
+
});
|
|
5884
|
+
}
|
|
5885
|
+
wakeWaiters() {
|
|
5886
|
+
const pending = this.waiters;
|
|
5887
|
+
this.waiters = [];
|
|
5888
|
+
for (const wake of pending) wake();
|
|
5889
|
+
}
|
|
5864
5890
|
/**
|
|
5865
5891
|
* Suspend the runtime child. Only the outermost call (depth 0 → 1) actually
|
|
5866
5892
|
* stops it; nested calls just bump the refcount. Remembers whether the child
|
|
5867
5893
|
* was running so `resume()` doesn't spuriously start a runtime that was never
|
|
5868
|
-
* up (e.g. autoStartRuntime=false).
|
|
5894
|
+
* up (e.g. autoStartRuntime=false). Waits while an exclusive bracket is open.
|
|
5869
5895
|
*/
|
|
5870
5896
|
async suspend() {
|
|
5897
|
+
if (this.exclusive) log$5.info("Mutating reconcile parked behind an exclusive runtime hold (upgrade in flight)");
|
|
5898
|
+
while (this.exclusive) await this.waitFor(() => !this.exclusive);
|
|
5899
|
+
await this.enter();
|
|
5900
|
+
}
|
|
5901
|
+
async suspendExclusive() {
|
|
5902
|
+
if (this.exclusive || this.depth !== 0) log$5.info({
|
|
5903
|
+
depth: this.depth,
|
|
5904
|
+
exclusive: this.exclusive
|
|
5905
|
+
}, "Exclusive runtime hold parked behind open brackets (reconcile in flight)");
|
|
5906
|
+
while (this.exclusive || this.depth !== 0) await this.waitFor(() => !this.exclusive && this.depth === 0);
|
|
5907
|
+
log$5.info("Exclusive runtime hold acquired");
|
|
5908
|
+
this.exclusive = true;
|
|
5909
|
+
await this.enter();
|
|
5910
|
+
}
|
|
5911
|
+
async enter() {
|
|
5871
5912
|
if (this.depth++ > 0) return;
|
|
5872
5913
|
const rp = this.getRuntime();
|
|
5873
5914
|
this.wasRunning = rp?.isRunning ?? false;
|
|
@@ -5891,6 +5932,8 @@ var RuntimeGate = class {
|
|
|
5891
5932
|
rp.resume();
|
|
5892
5933
|
}
|
|
5893
5934
|
this.wasRunning = false;
|
|
5935
|
+
if (this.exclusive) this.exclusive = false;
|
|
5936
|
+
this.wakeWaiters();
|
|
5894
5937
|
return Promise.resolve();
|
|
5895
5938
|
}
|
|
5896
5939
|
/**
|
|
@@ -5920,6 +5963,9 @@ var NoopRuntimeGate = class {
|
|
|
5920
5963
|
suspend() {
|
|
5921
5964
|
return Promise.resolve();
|
|
5922
5965
|
}
|
|
5966
|
+
suspendExclusive() {
|
|
5967
|
+
return Promise.resolve();
|
|
5968
|
+
}
|
|
5923
5969
|
resume() {
|
|
5924
5970
|
return Promise.resolve();
|
|
5925
5971
|
}
|
|
@@ -7982,6 +8028,7 @@ var IPCServer = class {
|
|
|
7982
8028
|
* standalone pieces of `startDaemon()`: version detection, the one-shot
|
|
7983
8029
|
* persona-file migration, the AI proxy listener, and the IPC server start.
|
|
7984
8030
|
*/
|
|
8031
|
+
const RUNTIME_VERSION_TIMEOUT_MS = 1e4;
|
|
7985
8032
|
const execFileAsync$2 = promisify(execFile);
|
|
7986
8033
|
/**
|
|
7987
8034
|
* Resolve the installed @alfe.ai/cli version.
|
|
@@ -8066,7 +8113,13 @@ async function getRuntimeVersion(runtime) {
|
|
|
8066
8113
|
return;
|
|
8067
8114
|
}
|
|
8068
8115
|
try {
|
|
8069
|
-
const { stdout } = await execFileAsync$2(cmd.command, cmd.args
|
|
8116
|
+
const { stdout } = await execFileAsync$2(cmd.command, cmd.args, {
|
|
8117
|
+
timeout: RUNTIME_VERSION_TIMEOUT_MS,
|
|
8118
|
+
env: {
|
|
8119
|
+
...process.env,
|
|
8120
|
+
...runtime === "openclaw" ? OPENCLAW_EXTERNAL_SUPERVISOR_ENV : {}
|
|
8121
|
+
}
|
|
8122
|
+
});
|
|
8070
8123
|
return normalizeRuntimeVersion(runtime, stdout);
|
|
8071
8124
|
} catch {
|
|
8072
8125
|
logger.debug({ runtime }, "Could not resolve runtime version");
|
|
@@ -9944,6 +9997,15 @@ async function drainRuntimeBeforeExit(reason) {
|
|
|
9944
9997
|
}
|
|
9945
9998
|
async function executeCloudCommand(command) {
|
|
9946
9999
|
if (isWorkCommand(command.command)) return executeWorkCommand(command, config.agentId, config.runtime, ipcServer);
|
|
10000
|
+
if ((command.command === "daemon.update" || command.command === "daemon.restart") && upgradingRuntime) return {
|
|
10001
|
+
type: "COMMAND_ACK",
|
|
10002
|
+
commandId: command.commandId,
|
|
10003
|
+
status: "error",
|
|
10004
|
+
result: {
|
|
10005
|
+
code: "RUNTIME_UPGRADE_IN_PROGRESS",
|
|
10006
|
+
message: `${command.command} refused: a runtime upgrade is in progress; retry when it has completed`
|
|
10007
|
+
}
|
|
10008
|
+
};
|
|
9947
10009
|
if (command.command === "daemon.update") {
|
|
9948
10010
|
const version = command.payload?.version ?? "latest";
|
|
9949
10011
|
setTimeout(() => {
|
package/dist/runtime-upgrade.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { f as parseOpenClawVersion, n as OPENCLAW_EXTERNAL_SUPERVISOR_ENV, p as supportsOpenClawNode, u as OPENCLAW_NODE_REQUIREMENT } from "./runtime-process.js";
|
|
2
|
-
import { c as captureNodeRemediation, h as logger } from "./sentry.js";
|
|
2
|
+
import { c as captureNodeRemediation, h as logger, i as captureCliFailure } from "./sentry.js";
|
|
3
3
|
import { n as remediateManagedNode, t as NodeRemediationError } from "./node-remediation.js";
|
|
4
4
|
import { mkdir } from "node:fs/promises";
|
|
5
5
|
import { join } from "node:path";
|
|
@@ -27,6 +27,7 @@ import { promisify } from "node:util";
|
|
|
27
27
|
const execFileAsync = promisify(execFile);
|
|
28
28
|
const OPENCLAW_UPGRADE_TIMEOUT_MS = 3e5;
|
|
29
29
|
const OPENCLAW_DOCTOR_TIMEOUT_MS = 9e5;
|
|
30
|
+
const OPENCLAW_DOCTOR_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
30
31
|
/**
|
|
31
32
|
* A non-diagnostic probe run right after the new package lands. 2026.9.x
|
|
32
33
|
* classifies `--version`/`--help` as diagnostic and lets them succeed even when
|
|
@@ -41,6 +42,31 @@ const OPENCLAW_DOCTOR_TIMEOUT_MS = 9e5;
|
|
|
41
42
|
* runtime regardless of config contents.
|
|
42
43
|
*/
|
|
43
44
|
const OPENCLAW_ADMISSION_PROBE_ARGS = ["config", "file"];
|
|
45
|
+
/**
|
|
46
|
+
* `doctor --fix` runs the state/config migration. On 2026.9.x `--json` puts
|
|
47
|
+
* doctor into READ-ONLY lint mode and it refuses the combination outright
|
|
48
|
+
* (exit 2: "doctor --json runs read-only lint checks and cannot be combined
|
|
49
|
+
* with --repair, --fix, or --force"), so the migration must run without it.
|
|
50
|
+
* The post-upgrade audit is read-only and keeps `--json`.
|
|
51
|
+
*/
|
|
52
|
+
const OPENCLAW_DOCTOR_FIX_ARGS = [
|
|
53
|
+
"doctor",
|
|
54
|
+
"--fix",
|
|
55
|
+
"--non-interactive",
|
|
56
|
+
"--yes"
|
|
57
|
+
];
|
|
58
|
+
const OPENCLAW_DOCTOR_AUDIT_ARGS = [
|
|
59
|
+
"doctor",
|
|
60
|
+
"--post-upgrade",
|
|
61
|
+
"--json"
|
|
62
|
+
];
|
|
63
|
+
/** Keep the tail of a command's output in an error so a failed migration is diagnosable from the daemon log. */
|
|
64
|
+
const OUTPUT_TAIL_CHARS = 1500;
|
|
65
|
+
const ANSI_ESCAPE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[A-Za-z]`, "g");
|
|
66
|
+
function outputTail(value) {
|
|
67
|
+
const text = typeof value === "string" ? value.replace(ANSI_ESCAPE, "").trim() : "";
|
|
68
|
+
return text.length > OUTPUT_TAIL_CHARS ? `…${text.slice(-OUTPUT_TAIL_CHARS)}` : text;
|
|
69
|
+
}
|
|
44
70
|
function resolveUpgradeCommand(runtime, version) {
|
|
45
71
|
switch (runtime) {
|
|
46
72
|
case "openclaw":
|
|
@@ -206,18 +232,25 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
|
|
|
206
232
|
runtimeChanged = installedVersion !== previousOpenClawVersion;
|
|
207
233
|
await runCommand("openclaw", [...OPENCLAW_ADMISSION_PROBE_ARGS], commandOptions(6e4));
|
|
208
234
|
doctorStarted = true;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
"
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
235
|
+
try {
|
|
236
|
+
const doctor = await runCommand("openclaw", [...OPENCLAW_DOCTOR_FIX_ARGS], {
|
|
237
|
+
...commandOptions(OPENCLAW_DOCTOR_TIMEOUT_MS),
|
|
238
|
+
maxBuffer: OPENCLAW_DOCTOR_MAX_BUFFER_BYTES
|
|
239
|
+
});
|
|
240
|
+
logger.info({ doctor: outputTail(doctor.stdout) }, "OpenClaw doctor migration completed");
|
|
241
|
+
} catch (err) {
|
|
242
|
+
const failure = err;
|
|
243
|
+
const stdout = outputTail(failure.stdout);
|
|
244
|
+
const stderr = outputTail(failure.stderr);
|
|
245
|
+
logger.error({
|
|
246
|
+
stdout,
|
|
247
|
+
stderr
|
|
248
|
+
}, "OpenClaw doctor migration failed");
|
|
249
|
+
const wrapped = new Error(`openclaw doctor --fix failed: ${failure.message ?? String(err)}` + (stderr ? `\nstderr: ${stderr}` : "") + (stdout ? `\nstdout: ${stdout}` : ""), { cause: err });
|
|
250
|
+
captureCliFailure("openclaw-doctor-migration", wrapped);
|
|
251
|
+
throw wrapped;
|
|
252
|
+
}
|
|
253
|
+
const audit = await runCommand("openclaw", [...OPENCLAW_DOCTOR_AUDIT_ARGS], commandOptions(OPENCLAW_DOCTOR_TIMEOUT_MS));
|
|
221
254
|
if (audit.stdout) logger.info({ audit: audit.stdout.trim() }, "OpenClaw post-upgrade audit completed");
|
|
222
255
|
}
|
|
223
256
|
} catch (err) {
|
|
@@ -239,7 +272,7 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
|
|
|
239
272
|
try {
|
|
240
273
|
if (options.gate) {
|
|
241
274
|
gateSuspended = true;
|
|
242
|
-
await options.gate.
|
|
275
|
+
await options.gate.suspendExclusive();
|
|
243
276
|
} else if (runtimeProcess) await runtimeProcess.stopWhenIdle();
|
|
244
277
|
if (runtime === "openclaw" && options.cliLock) await options.cliLock.run(performUpgrade);
|
|
245
278
|
else await performUpgrade();
|