@alfe.ai/gateway 0.10.0 → 0.10.2
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/bin/gateway.js +1 -1
- package/dist/health.js +315 -19
- package/dist/node-remediation.js +163 -0
- package/dist/runtime-process.js +53 -16
- package/dist/runtime-upgrade.js +77 -24
- package/dist/sentry.js +29 -1
- package/dist/src/index.d.ts +82 -8
- package/dist/src/index.js +4 -3
- package/dist/upgrade.js +1 -1
- package/package.json +2 -2
package/dist/bin/gateway.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { a as installService, d as uninstallService, h as SOCKET_PATH, i as checkExistingDaemon, l as stopExistingDaemon, n as queryDaemonHealth, r as startDaemon, t as formatHealthReport } from "../health.js";
|
|
3
|
-
import {
|
|
3
|
+
import { m as LOG_FILE } from "../sentry.js";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
//#region bin/gateway.ts
|
|
6
6
|
/**
|
package/dist/health.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as ErrorLineDetector, c as createLogger, f as
|
|
2
|
-
import { a as captureFatal, d as
|
|
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";
|
|
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";
|
|
5
5
|
import { homedir } from "node:os";
|
|
@@ -7689,9 +7689,7 @@ var IPCServer = class {
|
|
|
7689
7689
|
reject,
|
|
7690
7690
|
timer
|
|
7691
7691
|
});
|
|
7692
|
-
|
|
7693
|
-
conn.socket.write(JSON.stringify(request) + "\n");
|
|
7694
|
-
} catch (err) {
|
|
7692
|
+
if (!this.writeFrame(conn, request, "request")) {
|
|
7695
7693
|
clearTimeout(timer);
|
|
7696
7694
|
conn.pending.delete(id);
|
|
7697
7695
|
resolve({
|
|
@@ -7699,7 +7697,7 @@ var IPCServer = class {
|
|
|
7699
7697
|
ok: false,
|
|
7700
7698
|
error: {
|
|
7701
7699
|
code: "SEND_FAILED",
|
|
7702
|
-
message:
|
|
7700
|
+
message: "Failed to send to plugin (write failed or frame over the transport limit)"
|
|
7703
7701
|
}
|
|
7704
7702
|
});
|
|
7705
7703
|
}
|
|
@@ -7919,21 +7917,57 @@ var IPCServer = class {
|
|
|
7919
7917
|
}));
|
|
7920
7918
|
}
|
|
7921
7919
|
sendResponse(conn, response) {
|
|
7920
|
+
if (!this.writeFrame(conn, response, "response")) logger.error({
|
|
7921
|
+
connId: conn.id,
|
|
7922
|
+
requestId: response.id
|
|
7923
|
+
}, "IPC: failed to send response");
|
|
7924
|
+
}
|
|
7925
|
+
sendEvent(conn, event, payload) {
|
|
7926
|
+
return this.writeFrame(conn, createIPCEvent(event, payload), "event");
|
|
7927
|
+
}
|
|
7928
|
+
/**
|
|
7929
|
+
* Every outbound frame goes through here. The plugin destroys its socket on
|
|
7930
|
+
* a frame over MAX_IPC_MESSAGE_BYTES (the mirror of our inbound cap), so a
|
|
7931
|
+
* frame we know it will reject must never be written: responses degrade to a
|
|
7932
|
+
* typed per-request error the plugin surfaces to the model, while requests
|
|
7933
|
+
* and events are logged and dropped (a dropped request times out at the
|
|
7934
|
+
* peer instead of killing the connection). MCP tool results are additionally
|
|
7935
|
+
* bounded semantically in mcp-handlers.ts so this is the backstop, not the
|
|
7936
|
+
* normal path.
|
|
7937
|
+
*/
|
|
7938
|
+
writeFrame(conn, message, kind) {
|
|
7939
|
+
let frame;
|
|
7922
7940
|
try {
|
|
7923
|
-
|
|
7941
|
+
frame = JSON.stringify(message) + "\n";
|
|
7924
7942
|
} catch (err) {
|
|
7925
7943
|
logger.error({
|
|
7926
7944
|
connId: conn.id,
|
|
7945
|
+
kind,
|
|
7927
7946
|
err
|
|
7928
|
-
}, "IPC: failed to
|
|
7947
|
+
}, "IPC: failed to serialize outbound frame");
|
|
7948
|
+
if (kind !== "response") return false;
|
|
7949
|
+
frame = JSON.stringify(createIPCError(message.id, "INTERNAL", "Response could not be serialized")) + "\n";
|
|
7950
|
+
}
|
|
7951
|
+
const bytes = Buffer.byteLength(frame, "utf8");
|
|
7952
|
+
if (bytes > 1048576) {
|
|
7953
|
+
logger.warn({
|
|
7954
|
+
connId: conn.id,
|
|
7955
|
+
kind,
|
|
7956
|
+
bytes,
|
|
7957
|
+
maxBytes: MAX_IPC_MESSAGE_BYTES
|
|
7958
|
+
}, "IPC: outbound frame exceeded size limit");
|
|
7959
|
+
if (kind !== "response") return false;
|
|
7960
|
+
frame = JSON.stringify(createIPCError(message.id, "RESPONSE_TOO_LARGE", `Response exceeded the ${String(MAX_IPC_MESSAGE_BYTES)} byte transport limit and was dropped. Narrow the request (filters, smaller page size) and retry.`)) + "\n";
|
|
7929
7961
|
}
|
|
7930
|
-
}
|
|
7931
|
-
sendEvent(conn, event, payload) {
|
|
7932
7962
|
try {
|
|
7933
|
-
|
|
7934
|
-
conn.socket.write(JSON.stringify(msg) + "\n");
|
|
7963
|
+
conn.socket.write(frame);
|
|
7935
7964
|
return true;
|
|
7936
|
-
} catch {
|
|
7965
|
+
} catch (err) {
|
|
7966
|
+
logger.error({
|
|
7967
|
+
connId: conn.id,
|
|
7968
|
+
kind,
|
|
7969
|
+
err
|
|
7970
|
+
}, "IPC: failed to write frame");
|
|
7937
7971
|
return false;
|
|
7938
7972
|
}
|
|
7939
7973
|
}
|
|
@@ -8006,9 +8040,24 @@ function resolveRuntimeVersionCommand(runtime) {
|
|
|
8006
8040
|
return RUNTIME_VERSION_COMMANDS[runtime];
|
|
8007
8041
|
}
|
|
8008
8042
|
/**
|
|
8043
|
+
* Reduce a runtime's `--version` output to the bare version the control plane
|
|
8044
|
+
* compares against catalog versions. Every OpenClaw release prints a branded
|
|
8045
|
+
* line (`OpenClaw 2026.9.5 (ec9c1a1)`); reporting that verbatim hid every
|
|
8046
|
+
* dashboard upgrade affordance for the agents that most needed one. The agents
|
|
8047
|
+
* service also normalises at ingest (it reaches hosts on older daemons); this
|
|
8048
|
+
* is the producer-side half. Falls back to the trimmed output when no version
|
|
8049
|
+
* token is found so an unexpected format is still visible rather than blank.
|
|
8050
|
+
*/
|
|
8051
|
+
function normalizeRuntimeVersion(runtime, stdout) {
|
|
8052
|
+
const trimmed = stdout.trim();
|
|
8053
|
+
if (!trimmed) return void 0;
|
|
8054
|
+
if (runtime === "openclaw") return parseOpenClawVersion(trimmed) ?? trimmed;
|
|
8055
|
+
return trimmed;
|
|
8056
|
+
}
|
|
8057
|
+
/**
|
|
8009
8058
|
* Resolve the installed runtime version by running the per-runtime version
|
|
8010
|
-
* command and
|
|
8011
|
-
*
|
|
8059
|
+
* command and normalising its stdout. An unknown runtime (no command in the
|
|
8060
|
+
* map) returns `undefined` without spawning — it must never throw.
|
|
8012
8061
|
*/
|
|
8013
8062
|
async function getRuntimeVersion(runtime) {
|
|
8014
8063
|
const cmd = resolveRuntimeVersionCommand(runtime);
|
|
@@ -8018,7 +8067,7 @@ async function getRuntimeVersion(runtime) {
|
|
|
8018
8067
|
}
|
|
8019
8068
|
try {
|
|
8020
8069
|
const { stdout } = await execFileAsync$2(cmd.command, cmd.args);
|
|
8021
|
-
return stdout
|
|
8070
|
+
return normalizeRuntimeVersion(runtime, stdout);
|
|
8022
8071
|
} catch {
|
|
8023
8072
|
logger.debug({ runtime }, "Could not resolve runtime version");
|
|
8024
8073
|
return;
|
|
@@ -8162,6 +8211,250 @@ async function startIpcServer(socketPath, requestHandler, daemonVersion) {
|
|
|
8162
8211
|
//#endregion
|
|
8163
8212
|
//#region src/mcp-handlers.ts
|
|
8164
8213
|
/**
|
|
8214
|
+
* Aggregate text budget for one MCP tool call. An order of magnitude under
|
|
8215
|
+
* MAX_IPC_MESSAGE_BYTES because JSON escaping inflates text: quotes and
|
|
8216
|
+
* newlines cost 2 bytes, control characters 6. The serialized fence below
|
|
8217
|
+
* makes the guarantee absolute; this is the budget the model sees.
|
|
8218
|
+
*/
|
|
8219
|
+
const MAX_MCP_RESULT_TEXT_BYTES = 256 * 1024;
|
|
8220
|
+
/**
|
|
8221
|
+
* Per-text-item cap. The consuming plugin (`@alfe.ai/openclaw-mcp-bundler`,
|
|
8222
|
+
* `plugin.ts` `MAX_JSON_STRING_LENGTH = 65_536`) rejects the WHOLE result as
|
|
8223
|
+
* "malformed" if any single string exceeds 65,536 UTF-16 code units, so a
|
|
8224
|
+
* kept item must never exceed that. UTF-8 bytes ≥ UTF-16 units, so capping
|
|
8225
|
+
* bytes at 64 KiB is safe for every already-deployed bundler.
|
|
8226
|
+
*/
|
|
8227
|
+
const MAX_MCP_RESULT_ITEM_TEXT_BYTES = 64 * 1024;
|
|
8228
|
+
/** Worst-case JSON wrapper per content item (`{"type":"text","text":""},`) for the fast-path estimate. */
|
|
8229
|
+
const ITEM_ENVELOPE_BYTES = 64;
|
|
8230
|
+
function isTextItem(item) {
|
|
8231
|
+
return typeof item === "object" && item !== null && item.type === "text" && typeof item.text === "string";
|
|
8232
|
+
}
|
|
8233
|
+
function serializedBytes(value) {
|
|
8234
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
8235
|
+
}
|
|
8236
|
+
/**
|
|
8237
|
+
* Longest string value anywhere inside `value`, in UTF-8 bytes. The bundler
|
|
8238
|
+
* plugin's parser rejects the WHOLE result when any string at any depth is
|
|
8239
|
+
* over its cap (counted in UTF-16 units; UTF-8 bytes are never fewer), so
|
|
8240
|
+
* this is what a non-text item (image data, resource text) is measured by.
|
|
8241
|
+
*/
|
|
8242
|
+
/**
|
|
8243
|
+
* The bundler plugin's `sanitizeJsonRecord` limits, mirrored so the daemon
|
|
8244
|
+
* never hands it an item it will reject (which nulls the WHOLE result).
|
|
8245
|
+
* Keep in sync with `packages/openclaw-mcp-bundler/src/plugin.ts`.
|
|
8246
|
+
*/
|
|
8247
|
+
const PLUGIN_MAX_JSON_NODES = 1e4;
|
|
8248
|
+
const PLUGIN_MAX_JSON_DEPTH = 32;
|
|
8249
|
+
const PLUGIN_MAX_JSON_COLLECTION_ENTRIES = 2048;
|
|
8250
|
+
const PLUGIN_MAX_KEY_LENGTH = 512;
|
|
8251
|
+
/**
|
|
8252
|
+
* True when a content item stays inside the plugin parser's structural limits
|
|
8253
|
+
* (node budget per item, nesting depth, entries per collection, key length).
|
|
8254
|
+
* String length is bounded separately because `text` can be cut to fit; a
|
|
8255
|
+
* structural violation cannot, so the item is dropped.
|
|
8256
|
+
*/
|
|
8257
|
+
function withinPluginStructure(item) {
|
|
8258
|
+
const budget = { nodes: 0 };
|
|
8259
|
+
const walk = (value, depth) => {
|
|
8260
|
+
budget.nodes += 1;
|
|
8261
|
+
if (budget.nodes > PLUGIN_MAX_JSON_NODES || depth > PLUGIN_MAX_JSON_DEPTH) return false;
|
|
8262
|
+
if (Array.isArray(value)) {
|
|
8263
|
+
if (value.length > PLUGIN_MAX_JSON_COLLECTION_ENTRIES) return false;
|
|
8264
|
+
for (const entry of value) if (!walk(entry, depth + 1)) return false;
|
|
8265
|
+
return true;
|
|
8266
|
+
}
|
|
8267
|
+
if (typeof value === "object" && value !== null) {
|
|
8268
|
+
const entries = Object.entries(value);
|
|
8269
|
+
if (entries.length > PLUGIN_MAX_JSON_COLLECTION_ENTRIES) return false;
|
|
8270
|
+
return entries.every(([key, entry]) => key.length <= PLUGIN_MAX_KEY_LENGTH && walk(entry, depth + 1));
|
|
8271
|
+
}
|
|
8272
|
+
return true;
|
|
8273
|
+
};
|
|
8274
|
+
return walk(item, 0);
|
|
8275
|
+
}
|
|
8276
|
+
function longestStringBytes(value) {
|
|
8277
|
+
if (typeof value === "string") return Buffer.byteLength(value, "utf8");
|
|
8278
|
+
if (Array.isArray(value)) {
|
|
8279
|
+
let max = 0;
|
|
8280
|
+
for (const entry of value) max = Math.max(max, longestStringBytes(entry));
|
|
8281
|
+
return max;
|
|
8282
|
+
}
|
|
8283
|
+
if (typeof value === "object" && value !== null) {
|
|
8284
|
+
let max = 0;
|
|
8285
|
+
for (const entry of Object.values(value)) max = Math.max(max, longestStringBytes(entry));
|
|
8286
|
+
return max;
|
|
8287
|
+
}
|
|
8288
|
+
return 0;
|
|
8289
|
+
}
|
|
8290
|
+
/** Cut at a UTF-8 boundary; drop a replacement char introduced by the cut. */
|
|
8291
|
+
function truncateUtf8(text, maxBytes) {
|
|
8292
|
+
const buf = Buffer.from(text, "utf8");
|
|
8293
|
+
if (buf.byteLength <= maxBytes) return text;
|
|
8294
|
+
const out = buf.subarray(0, maxBytes).toString("utf8");
|
|
8295
|
+
return out.endsWith("�") && !text.endsWith("�") ? out.slice(0, -1) : out;
|
|
8296
|
+
}
|
|
8297
|
+
/**
|
|
8298
|
+
* Model-visible notice. Deliberately names no bare tool: MCP hosts prefix tool
|
|
8299
|
+
* names, so a bare name is a dead end (packages/DEVELOPING.md, "Model-visible
|
|
8300
|
+
* errors carry the fix").
|
|
8301
|
+
*/
|
|
8302
|
+
function truncationNotice(args) {
|
|
8303
|
+
const dropped = [];
|
|
8304
|
+
if (args.droppedItems > 0) dropped.push(`${String(args.droppedItems)} further content item(s)`);
|
|
8305
|
+
if (args.droppedStructured) dropped.push("the structured-content block");
|
|
8306
|
+
return [
|
|
8307
|
+
"[alfe] This tool result was truncated to fit the agent transport limit.",
|
|
8308
|
+
`Source result was ${String(args.sourceBytes)} bytes; returned ${String(args.keptBytes)} bytes of text` + (args.truncatedItems > 0 ? ` (${String(args.truncatedItems)} item(s) cut short).` : ".") + (dropped.length > 0 ? ` Dropped ${dropped.join(" and ")}.` : ""),
|
|
8309
|
+
"Do NOT retry this call unchanged; the same request will truncate again.",
|
|
8310
|
+
"Narrow it instead: add a date range or status filter, lower the page size",
|
|
8311
|
+
"(top / $top / limit), and page with skip / $skip. If this data source offers a",
|
|
8312
|
+
"summary or report form of the same question, prefer that over listing raw records."
|
|
8313
|
+
].join("\n");
|
|
8314
|
+
}
|
|
8315
|
+
/**
|
|
8316
|
+
* Bound a bundler tool result so the daemon never writes an IPC frame the
|
|
8317
|
+
* plugin will reject. In-budget results are returned by reference (no clone).
|
|
8318
|
+
* Over budget: items are kept front-to-back until the budget (text bytes plus
|
|
8319
|
+
* a per-item envelope allowance) is spent. Text items are cut at the per-item
|
|
8320
|
+
* cap on a UTF-8 boundary; a non-text item carrying any string over that cap
|
|
8321
|
+
* (image data, resource text) is dropped outright because the plugin would
|
|
8322
|
+
* reject the whole result on its account and it cannot be cut meaningfully.
|
|
8323
|
+
* `structuredContent` is dropped, `isError` is preserved verbatim, and a
|
|
8324
|
+
* model-visible notice is appended. If the serialized result is still over
|
|
8325
|
+
* the fence, only the notice is returned.
|
|
8326
|
+
*/
|
|
8327
|
+
function boundMcpToolResult(result) {
|
|
8328
|
+
if (!Array.isArray(result.content)) throw new Error("MCP tool result content must be an array");
|
|
8329
|
+
const content = result.content;
|
|
8330
|
+
let textBytes = 0;
|
|
8331
|
+
let maxItemBytes = 0;
|
|
8332
|
+
let onlyBareText = true;
|
|
8333
|
+
let structurallyValid = true;
|
|
8334
|
+
for (const item of content) {
|
|
8335
|
+
if (isTextItem(item)) {
|
|
8336
|
+
textBytes += Buffer.byteLength(item.text, "utf8");
|
|
8337
|
+
if (onlyBareText && Object.keys(item).some((key) => key !== "type" && key !== "text")) onlyBareText = false;
|
|
8338
|
+
} else onlyBareText = false;
|
|
8339
|
+
maxItemBytes = Math.max(maxItemBytes, longestStringBytes(item));
|
|
8340
|
+
if (structurallyValid && !withinPluginStructure(item)) structurallyValid = false;
|
|
8341
|
+
}
|
|
8342
|
+
const structured = result.structuredContent;
|
|
8343
|
+
const hasExtraKeys = Object.keys(result).some((key) => key !== "content" && key !== "isError" && key !== "structuredContent");
|
|
8344
|
+
if (structurallyValid && textBytes <= 262144 && maxItemBytes <= 65536) {
|
|
8345
|
+
const worstCase = textBytes * 6 + content.length * ITEM_ENVELOPE_BYTES + 256;
|
|
8346
|
+
if (onlyBareText && structured === void 0 && !hasExtraKeys && worstCase <= 524288) return result;
|
|
8347
|
+
if (serializedBytes(result) <= 524288) return result;
|
|
8348
|
+
}
|
|
8349
|
+
const sourceBytes = serializedBytes(result);
|
|
8350
|
+
const { structuredContent: _dropped, ...withExtras } = result;
|
|
8351
|
+
const rest = hasExtraKeys && serializedBytes(withExtras) - serializedBytes(content) > 524288 / 2 ? {
|
|
8352
|
+
content,
|
|
8353
|
+
...result.isError === void 0 ? {} : { isError: result.isError }
|
|
8354
|
+
} : withExtras;
|
|
8355
|
+
for (const textBudget of [
|
|
8356
|
+
MAX_MCP_RESULT_TEXT_BYTES,
|
|
8357
|
+
MAX_MCP_RESULT_TEXT_BYTES / 2,
|
|
8358
|
+
MAX_MCP_RESULT_TEXT_BYTES / 4,
|
|
8359
|
+
MAX_MCP_RESULT_TEXT_BYTES / 8
|
|
8360
|
+
]) {
|
|
8361
|
+
const candidate = boundWithBudget(content, rest, textBudget, sourceBytes, structured !== void 0);
|
|
8362
|
+
if (serializedBytes(candidate) <= 524288) return candidate;
|
|
8363
|
+
}
|
|
8364
|
+
logger.warn({ sourceBytes }, "MCP tool result still over the serialized fence after text truncation; returning notice only");
|
|
8365
|
+
const notice = truncationNotice({
|
|
8366
|
+
sourceBytes,
|
|
8367
|
+
keptBytes: 0,
|
|
8368
|
+
truncatedItems: 0,
|
|
8369
|
+
droppedItems: content.length,
|
|
8370
|
+
droppedStructured: structured !== void 0
|
|
8371
|
+
});
|
|
8372
|
+
return {
|
|
8373
|
+
...rest,
|
|
8374
|
+
content: [{
|
|
8375
|
+
type: "text",
|
|
8376
|
+
text: notice
|
|
8377
|
+
}]
|
|
8378
|
+
};
|
|
8379
|
+
}
|
|
8380
|
+
function boundWithBudget(content, rest, textBudget, sourceBytes, droppedStructured) {
|
|
8381
|
+
let remaining = textBudget;
|
|
8382
|
+
let kept = 0;
|
|
8383
|
+
let truncatedItems = 0;
|
|
8384
|
+
let droppedItems = 0;
|
|
8385
|
+
let exhausted = false;
|
|
8386
|
+
const bounded = [];
|
|
8387
|
+
for (const item of content) {
|
|
8388
|
+
if (exhausted) {
|
|
8389
|
+
droppedItems += 1;
|
|
8390
|
+
continue;
|
|
8391
|
+
}
|
|
8392
|
+
if (!withinPluginStructure(item)) {
|
|
8393
|
+
droppedItems += 1;
|
|
8394
|
+
continue;
|
|
8395
|
+
}
|
|
8396
|
+
if (!isTextItem(item)) {
|
|
8397
|
+
if (longestStringBytes(item) > 65536) {
|
|
8398
|
+
droppedItems += 1;
|
|
8399
|
+
continue;
|
|
8400
|
+
}
|
|
8401
|
+
const size = serializedBytes(item) + ITEM_ENVELOPE_BYTES;
|
|
8402
|
+
if (size > remaining) {
|
|
8403
|
+
droppedItems += 1;
|
|
8404
|
+
exhausted = true;
|
|
8405
|
+
continue;
|
|
8406
|
+
}
|
|
8407
|
+
remaining -= size;
|
|
8408
|
+
bounded.push(item);
|
|
8409
|
+
continue;
|
|
8410
|
+
}
|
|
8411
|
+
if (longestStringBytes({
|
|
8412
|
+
...item,
|
|
8413
|
+
text: ""
|
|
8414
|
+
}) > 65536) {
|
|
8415
|
+
droppedItems += 1;
|
|
8416
|
+
continue;
|
|
8417
|
+
}
|
|
8418
|
+
const size = Buffer.byteLength(item.text, "utf8");
|
|
8419
|
+
const allow = Math.min(remaining - ITEM_ENVELOPE_BYTES, MAX_MCP_RESULT_ITEM_TEXT_BYTES);
|
|
8420
|
+
if (allow <= 0) {
|
|
8421
|
+
droppedItems += 1;
|
|
8422
|
+
exhausted = true;
|
|
8423
|
+
continue;
|
|
8424
|
+
}
|
|
8425
|
+
if (size <= allow) {
|
|
8426
|
+
remaining -= size + ITEM_ENVELOPE_BYTES;
|
|
8427
|
+
kept += size;
|
|
8428
|
+
bounded.push(item);
|
|
8429
|
+
continue;
|
|
8430
|
+
}
|
|
8431
|
+
const cut = truncateUtf8(item.text, allow);
|
|
8432
|
+
const cutBytes = Buffer.byteLength(cut, "utf8");
|
|
8433
|
+
kept += cutBytes;
|
|
8434
|
+
remaining -= cutBytes + ITEM_ENVELOPE_BYTES;
|
|
8435
|
+
truncatedItems += 1;
|
|
8436
|
+
bounded.push({
|
|
8437
|
+
...item,
|
|
8438
|
+
text: cut
|
|
8439
|
+
});
|
|
8440
|
+
if (remaining <= 0) exhausted = true;
|
|
8441
|
+
}
|
|
8442
|
+
const notice = {
|
|
8443
|
+
type: "text",
|
|
8444
|
+
text: truncationNotice({
|
|
8445
|
+
sourceBytes,
|
|
8446
|
+
keptBytes: kept,
|
|
8447
|
+
truncatedItems,
|
|
8448
|
+
droppedItems,
|
|
8449
|
+
droppedStructured
|
|
8450
|
+
})
|
|
8451
|
+
};
|
|
8452
|
+
return {
|
|
8453
|
+
...rest,
|
|
8454
|
+
content: [...bounded, notice]
|
|
8455
|
+
};
|
|
8456
|
+
}
|
|
8457
|
+
/**
|
|
8165
8458
|
* Return the daemon-hosted MCP bundler's current namespaced tool catalog.
|
|
8166
8459
|
* Called by the openclaw-mcp-bundler plugin on every tool-factory invocation;
|
|
8167
8460
|
* cheap (in-memory snapshot, no I/O).
|
|
@@ -8429,7 +8722,7 @@ async function handleMcpCallTool(bundler, params) {
|
|
|
8429
8722
|
try {
|
|
8430
8723
|
return {
|
|
8431
8724
|
ok: true,
|
|
8432
|
-
payload: await bundler.callTool(name, args)
|
|
8725
|
+
payload: boundMcpToolResult(await bundler.callTool(name, args))
|
|
8433
8726
|
};
|
|
8434
8727
|
} catch (err) {
|
|
8435
8728
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -9695,7 +9988,7 @@ async function executeCloudCommand(command) {
|
|
|
9695
9988
|
};
|
|
9696
9989
|
const payload = command.payload;
|
|
9697
9990
|
const runtime = config.runtime;
|
|
9698
|
-
const version = payload?.version ?? (runtime === "openclaw" ? "2026.
|
|
9991
|
+
const version = payload?.version ?? (runtime === "openclaw" ? "2026.9.5" : runtime === "claude-code" ? "0.1.6" : void 0);
|
|
9699
9992
|
upgradingRuntime = true;
|
|
9700
9993
|
setTimeout(() => {
|
|
9701
9994
|
(async () => {
|
|
@@ -9703,6 +9996,7 @@ async function executeCloudCommand(command) {
|
|
|
9703
9996
|
const { upgradeRuntime } = await import("./runtime-upgrade.js");
|
|
9704
9997
|
const result = await upgradeRuntime(runtime, version, runtimeProcess, {
|
|
9705
9998
|
...runtime === "openclaw" && openclawCliLockRef ? { cliLock: openclawCliLockRef } : {},
|
|
9999
|
+
...runtime === "openclaw" && isManagedMode() && process.env.ALFE_PRIVATE_DATA_PLANE !== "true" ? { nodeRemediation: {} } : {},
|
|
9706
10000
|
...runtime === "openclaw" && runtimeGateRef ? { gate: runtimeGateRef } : {}
|
|
9707
10001
|
});
|
|
9708
10002
|
if (result.success || result.runtimeChanged) {
|
|
@@ -9714,7 +10008,8 @@ async function executeCloudCommand(command) {
|
|
|
9714
10008
|
else {
|
|
9715
10009
|
logger.info({
|
|
9716
10010
|
runtime,
|
|
9717
|
-
runtimeVersion: resolvedRuntimeVersion
|
|
10011
|
+
runtimeVersion: resolvedRuntimeVersion,
|
|
10012
|
+
nodeUpgraded: result.nodeUpgraded ?? false
|
|
9718
10013
|
}, "Runtime version updated after upgrade");
|
|
9719
10014
|
cloudClient.updateRuntimeVersionAndReRegister(resolvedRuntimeVersion);
|
|
9720
10015
|
}
|
|
@@ -9722,6 +10017,7 @@ async function executeCloudCommand(command) {
|
|
|
9722
10017
|
if (!result.success) logger.error({
|
|
9723
10018
|
runtime,
|
|
9724
10019
|
version,
|
|
10020
|
+
nodeUpgraded: result.nodeUpgraded ?? false,
|
|
9725
10021
|
error: result.error
|
|
9726
10022
|
}, "Runtime upgrade did not complete cleanly");
|
|
9727
10023
|
} catch (err) {
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { p as supportsOpenClawNode, u as OPENCLAW_NODE_REQUIREMENT } from "./runtime-process.js";
|
|
2
|
+
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
//#region src/node-remediation.ts
|
|
6
|
+
/**
|
|
7
|
+
* Managed-host Node.js remediation.
|
|
8
|
+
*
|
|
9
|
+
* OpenClaw 2026.9.x dropped Node 22 (and 25) from its engine range and its
|
|
10
|
+
* preinstall refuses to install on them, while every managed Hetzner VM was
|
|
11
|
+
* provisioned on Node 22. Rather than rebuild the fleet, the daemon's
|
|
12
|
+
* `runtime.update` preflight and `alfe setup --managed` install the NodeSource
|
|
13
|
+
* channel for `OPENCLAW_MANAGED_NODE_MAJOR` in place, re-verify, and continue.
|
|
14
|
+
*
|
|
15
|
+
* Self-hosted hosts never reach this module: the operator owns their Node and
|
|
16
|
+
* gets the explicit engine error instead.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* NodeSource setup + apt on a 2-vCPU box can take a few minutes, and
|
|
20
|
+
* `unattended-upgrades` may hold the dpkg lock for a while first.
|
|
21
|
+
*/
|
|
22
|
+
const NODE_REMEDIATION_TIMEOUT_MS = 6e5;
|
|
23
|
+
/**
|
|
24
|
+
* After a failed attempt, refuse to re-run `curl | bash` + apt for this long.
|
|
25
|
+
* `runtime.update` has no server-side dedupe or backoff (the dashboard and the
|
|
26
|
+
* fleet bulk path can re-issue it immediately and indefinitely), so a host
|
|
27
|
+
* whose remediation keeps failing must degrade to a cheap, loud no-op rather
|
|
28
|
+
* than a NodeSource fetch storm.
|
|
29
|
+
*/
|
|
30
|
+
const NODE_REMEDIATION_FAILURE_COOLDOWN_MS = 30 * 6e4;
|
|
31
|
+
const NODESOURCE_SETUP_URL = `https://deb.nodesource.com/setup_${String(24)}.x`;
|
|
32
|
+
const FAILURE_MARKER_FILE = "node-remediation-failure.json";
|
|
33
|
+
/**
|
|
34
|
+
* Thrown when remediation fails. `nodeReplaced` is the load-bearing field: a
|
|
35
|
+
* failure AFTER apt has swapped the host Node (verification or the installed
|
|
36
|
+
* runtime's boot check failed) leaves the host on a new, unproven Node, which
|
|
37
|
+
* is the state that needs an operator — very different from "never touched".
|
|
38
|
+
*/
|
|
39
|
+
var NodeRemediationError = class extends Error {
|
|
40
|
+
nodeReplaced;
|
|
41
|
+
resolvedNode;
|
|
42
|
+
cooldownActive;
|
|
43
|
+
constructor(message, details) {
|
|
44
|
+
super(message, details.cause === void 0 ? void 0 : { cause: details.cause });
|
|
45
|
+
this.name = "NodeRemediationError";
|
|
46
|
+
this.nodeReplaced = details.nodeReplaced;
|
|
47
|
+
this.resolvedNode = details.resolvedNode;
|
|
48
|
+
this.cooldownActive = details.cooldownActive ?? false;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
async function readFailureMarker(path) {
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
54
|
+
if (typeof parsed === "object" && parsed !== null && typeof parsed.failedAt === "number") return parsed;
|
|
55
|
+
} catch {}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
async function writeFailureMarker(path, marker) {
|
|
59
|
+
try {
|
|
60
|
+
await mkdir(dirname(path), {
|
|
61
|
+
recursive: true,
|
|
62
|
+
mode: 448
|
|
63
|
+
});
|
|
64
|
+
await writeFile(path, JSON.stringify(marker), { mode: 384 });
|
|
65
|
+
} catch {}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Install the managed Node.js channel on a Debian/Ubuntu managed host and
|
|
69
|
+
* return the resolved `node --version`.
|
|
70
|
+
*
|
|
71
|
+
* Runs BEFORE any runtime gate suspend or backup: the live OpenClaw child keeps
|
|
72
|
+
* its already-open binary, and later `openclaw`/`npm` spawns resolve the new
|
|
73
|
+
* binary through PATH. Fails closed (with `nodeReplaced` set accurately) if the
|
|
74
|
+
* resulting Node still misses the range or if the currently installed OpenClaw
|
|
75
|
+
* no longer boots under it — in that case the caller must not proceed to a
|
|
76
|
+
* backup it could not produce.
|
|
77
|
+
*/
|
|
78
|
+
async function remediateManagedNode(runCommand, baseEnv, options = {}) {
|
|
79
|
+
const now = options.now ?? Date.now;
|
|
80
|
+
const stateDirectory = options.stateDirectory === void 0 ? join(homedir(), ".alfe") : options.stateDirectory;
|
|
81
|
+
const markerPath = stateDirectory ? join(stateDirectory, FAILURE_MARKER_FILE) : null;
|
|
82
|
+
if (markerPath) {
|
|
83
|
+
const marker = await readFailureMarker(markerPath);
|
|
84
|
+
if (marker && now() - marker.failedAt < 18e5) {
|
|
85
|
+
const remaining = Math.ceil((NODE_REMEDIATION_FAILURE_COOLDOWN_MS - (now() - marker.failedAt)) / 6e4);
|
|
86
|
+
throw new NodeRemediationError(`Node.js remediation failed ${String(Math.round((now() - marker.failedAt) / 6e4))} min ago (${marker.error}); not retrying for another ${String(remaining)} min. Fix the host (network egress to deb.nodesource.com, apt state) or remove ${markerPath} to retry now.`, {
|
|
87
|
+
nodeReplaced: false,
|
|
88
|
+
cooldownActive: true
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const env = {
|
|
93
|
+
...baseEnv,
|
|
94
|
+
DEBIAN_FRONTEND: "noninteractive"
|
|
95
|
+
};
|
|
96
|
+
const longRunning = {
|
|
97
|
+
timeout: NODE_REMEDIATION_TIMEOUT_MS,
|
|
98
|
+
env
|
|
99
|
+
};
|
|
100
|
+
const quick = {
|
|
101
|
+
timeout: 1e4,
|
|
102
|
+
env
|
|
103
|
+
};
|
|
104
|
+
const { stdout: before } = await runCommand("node", ["--version"], quick).catch(() => ({ stdout: "" }));
|
|
105
|
+
const fromNode = before.trim();
|
|
106
|
+
const fail = async (message, details) => {
|
|
107
|
+
if (markerPath) await writeFailureMarker(markerPath, {
|
|
108
|
+
failedAt: now(),
|
|
109
|
+
fromNode,
|
|
110
|
+
error: message
|
|
111
|
+
});
|
|
112
|
+
throw new NodeRemediationError(message, details);
|
|
113
|
+
};
|
|
114
|
+
try {
|
|
115
|
+
await runCommand("bash", ["-c", `set -o pipefail; curl -fsSL --retry 3 --retry-connrefused --max-time 180 ${NODESOURCE_SETUP_URL} | bash -`], longRunning);
|
|
116
|
+
await runCommand("apt-get", [
|
|
117
|
+
"-o",
|
|
118
|
+
`DPkg::Lock::Timeout=${String(300)}`,
|
|
119
|
+
"install",
|
|
120
|
+
"-y",
|
|
121
|
+
"-qq",
|
|
122
|
+
"nodejs"
|
|
123
|
+
], longRunning);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
126
|
+
const observed = await runCommand("node", ["--version"], quick).then((r) => r.stdout.trim()).catch(() => void 0);
|
|
127
|
+
const nodeReplaced = observed !== void 0 && observed !== fromNode;
|
|
128
|
+
return fail(`Node.js channel install failed: ${message}`, {
|
|
129
|
+
nodeReplaced,
|
|
130
|
+
...nodeReplaced ? { resolvedNode: observed } : {},
|
|
131
|
+
cause: err
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
let nodeVersion;
|
|
135
|
+
try {
|
|
136
|
+
const { stdout } = await runCommand("node", ["--version"], quick);
|
|
137
|
+
nodeVersion = stdout.trim();
|
|
138
|
+
} catch (err) {
|
|
139
|
+
return fail(`Node.js was replaced but \`node --version\` failed afterwards: ${err instanceof Error ? err.message : String(err)}`, {
|
|
140
|
+
nodeReplaced: true,
|
|
141
|
+
cause: err
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
const nodeReplaced = nodeVersion !== fromNode;
|
|
145
|
+
if (!supportsOpenClawNode(nodeVersion)) return fail(`Node.js remediation resolved ${nodeVersion}, which does not satisfy ${OPENCLAW_NODE_REQUIREMENT}`, {
|
|
146
|
+
nodeReplaced,
|
|
147
|
+
resolvedNode: nodeVersion
|
|
148
|
+
});
|
|
149
|
+
if (options.verifyInstalledRuntime ?? true) try {
|
|
150
|
+
await runCommand("openclaw", ["--version"], quick);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
153
|
+
return fail(`Host Node.js was replaced with ${nodeVersion} but the installed OpenClaw no longer boots under it: ${message}`, {
|
|
154
|
+
nodeReplaced,
|
|
155
|
+
resolvedNode: nodeVersion,
|
|
156
|
+
cause: err
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
if (markerPath) await unlink(markerPath).catch(() => void 0);
|
|
160
|
+
return nodeVersion;
|
|
161
|
+
}
|
|
162
|
+
//#endregion
|
|
163
|
+
export { remediateManagedNode as n, NodeRemediationError as t };
|
package/dist/runtime-process.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { l as captureRuntimeCrash, u as captureRuntimeErrorOutput } from "./sentry.js";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import pino from "pino";
|
|
4
4
|
import crypto from "crypto";
|
|
@@ -50,32 +50,55 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
50
50
|
* truth for every install path:
|
|
51
51
|
* - `@alfe.ai/cli` `installRuntime` (fresh `alfe setup`)
|
|
52
52
|
* - the daemon `runtime.update` default (this package)
|
|
53
|
-
* - managed provisioning (`services/compute/src/lib/provisioner.ts` installs
|
|
54
|
-
*
|
|
53
|
+
* - managed provisioning (`services/compute/src/lib/provisioner.ts` installs the
|
|
54
|
+
* Node.js 24 channel before the CLI consumes this pin)
|
|
55
55
|
*
|
|
56
56
|
* Pinning (rather than floating `npm install -g openclaw` → `latest`) is deliberate:
|
|
57
57
|
* OpenClaw runs on every managed agent, so a `latest` that changes runtime semantics —
|
|
58
58
|
* e.g. 2026.6.8 made a non-empty `plugins.allow` an exclusive allowlist — becomes a
|
|
59
59
|
* silent fleet outage with no Alfe deploy. Bump deliberately after validating a new
|
|
60
60
|
* OpenClaw, then cut a CLI release so the pin travels with the CLI version.
|
|
61
|
+
*
|
|
62
|
+
* Raising the engine range below obligates every consumer of it: the daemon upgrade
|
|
63
|
+
* preflight (`runtime-upgrade.ts`), the CLI `installRuntime`, and the compute
|
|
64
|
+
* cloud-init literals (tied to these constants by `provisioner.test.ts`).
|
|
65
|
+
*/
|
|
66
|
+
const PINNED_OPENCLAW_VERSION = "2026.9.5";
|
|
67
|
+
/**
|
|
68
|
+
* OpenClaw 2026.9.5's published Node.js engine floor. Node 22 and Node 25 are no
|
|
69
|
+
* longer supported upstream (2026.9.x dropped both branches; the preinstall script
|
|
70
|
+
* refuses to install on them).
|
|
71
|
+
*/
|
|
72
|
+
const OPENCLAW_NODE_REQUIREMENT = ">=24.16.0 <25 || >=26.1.0";
|
|
73
|
+
/**
|
|
74
|
+
* The Node.js major the managed image installs (NodeSource `setup_24.x`) and that
|
|
75
|
+
* `upgradeRuntime` remediates existing managed hosts onto. 24 is the current LTS
|
|
76
|
+
* line; 26 is not LTS yet.
|
|
61
77
|
*/
|
|
62
|
-
const
|
|
63
|
-
/** OpenClaw 2026.8.1's published Node.js engine floor. */
|
|
64
|
-
const OPENCLAW_NODE_REQUIREMENT = ">=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0";
|
|
78
|
+
const OPENCLAW_MANAGED_NODE_MAJOR = 24;
|
|
65
79
|
/**
|
|
66
|
-
* Whether a concrete Node.js version satisfies OpenClaw
|
|
67
|
-
* Accepts `process.versions.node` (`
|
|
80
|
+
* Whether a concrete Node.js version satisfies OpenClaw's engine range.
|
|
81
|
+
* Accepts `process.versions.node` (`24.21.0`) or CLI output (`v24.21.0`).
|
|
82
|
+
* Pre-release tags are rejected; build metadata (`+…`) is accepted.
|
|
68
83
|
*/
|
|
69
84
|
function supportsOpenClawNode(nodeVersion) {
|
|
70
85
|
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:\+[0-9A-Za-z.-]+)?$/u.exec(nodeVersion.trim());
|
|
71
86
|
if (!match) return false;
|
|
72
87
|
const major = Number(match[1]);
|
|
73
88
|
const minor = Number(match[2]);
|
|
74
|
-
|
|
75
|
-
if (major ===
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
89
|
+
if (major === 24) return minor >= 16;
|
|
90
|
+
if (major === 26) return minor >= 1;
|
|
91
|
+
return major > 26;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Extract the semver-like version token printed by `openclaw --version`.
|
|
95
|
+
* Every OpenClaw release prints a branded line (`OpenClaw 2026.6.11 (e085fa1)`,
|
|
96
|
+
* `OpenClaw 2026.9.5 (ec9c1a1)`); this reduces it to the bare token. Requires
|
|
97
|
+
* whitespace delimiters and a four-digit year major, so unexpected formats
|
|
98
|
+
* return `undefined` and callers fall back to the raw line.
|
|
99
|
+
*/
|
|
100
|
+
function parseOpenClawVersion(output) {
|
|
101
|
+
return /(?:^|\s)(\d{4}\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)(?:\s|$)/u.exec(output.trim())?.[1];
|
|
79
102
|
}
|
|
80
103
|
//#endregion
|
|
81
104
|
//#region ../../node_modules/.pnpm/dotenv@17.2.2/node_modules/dotenv/package.json
|
|
@@ -15389,11 +15412,25 @@ const DETECTOR_FLUSH_DEBOUNCE_MS = 1500;
|
|
|
15389
15412
|
const BACKOFF_INITIAL_MS = 1e3;
|
|
15390
15413
|
const BACKOFF_MAX_MS = 3e4;
|
|
15391
15414
|
const STABLE_UPTIME_MS = 6e4;
|
|
15392
|
-
/**
|
|
15415
|
+
/**
|
|
15416
|
+
* Alfe owns supervision and version rollout; OpenClaw must not self-manage.
|
|
15417
|
+
*
|
|
15418
|
+
* `OPENCLAW_NODE_UPDATE_RESPAWNED=1` also disables 2026.9.x's node-runtime
|
|
15419
|
+
* recovery, which would otherwise re-exec the CLI under whatever other Node it
|
|
15420
|
+
* finds on PATH when the current one fails engine admission. Alfe chooses the
|
|
15421
|
+
* Node binary (managed remediation in `node-remediation.ts`); an admission
|
|
15422
|
+
* failure must surface as a clean exit the daemon can report, not a silent
|
|
15423
|
+
* respawn under an unmanaged interpreter.
|
|
15424
|
+
*
|
|
15425
|
+
* `OPENCLAW_CONFIG_READONLY` must never be set on the child or on upgrade
|
|
15426
|
+
* commands: since 2026.9.x it makes openclaw.json immutable and blocks setup,
|
|
15427
|
+
* doctor repairs and plugin installs.
|
|
15428
|
+
*/
|
|
15393
15429
|
const OPENCLAW_EXTERNAL_SUPERVISOR_ENV = {
|
|
15394
15430
|
OPENCLAW_SUPERVISOR_MODE: "external",
|
|
15395
15431
|
OPENCLAW_SERVICE_REPAIR_POLICY: "external",
|
|
15396
|
-
OPENCLAW_NO_AUTO_UPDATE: "1"
|
|
15432
|
+
OPENCLAW_NO_AUTO_UPDATE: "1",
|
|
15433
|
+
OPENCLAW_NODE_UPDATE_RESPAWNED: "1"
|
|
15397
15434
|
};
|
|
15398
15435
|
/**
|
|
15399
15436
|
* Max time a NON-crash restart waits for an in-flight chat turn to finish
|
|
@@ -15739,4 +15776,4 @@ var RuntimeProcess = class {
|
|
|
15739
15776
|
}
|
|
15740
15777
|
};
|
|
15741
15778
|
//#endregion
|
|
15742
|
-
export { ErrorLineDetector as a, createLogger as c,
|
|
15779
|
+
export { ErrorLineDetector as a, createLogger as c, PINNED_OPENCLAW_VERSION as d, parseOpenClawVersion as f, __require as h, CaptureThrottle as i, OPENCLAW_MANAGED_NODE_MAJOR as l, __commonJSMin as m, OPENCLAW_EXTERNAL_SUPERVISOR_ENV as n, STRICT_ERROR_CAPTURE_PROFILE as o, supportsOpenClawNode as p, RuntimeProcess as r, toFingerprintKey as s, DRAIN_GRACE_MS as t, OPENCLAW_NODE_REQUIREMENT as u };
|
package/dist/runtime-upgrade.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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";
|
|
3
|
+
import { n as remediateManagedNode, t as NodeRemediationError } from "./node-remediation.js";
|
|
3
4
|
import { mkdir } from "node:fs/promises";
|
|
4
5
|
import { join } from "node:path";
|
|
5
6
|
import { homedir } from "node:os";
|
|
@@ -25,7 +26,21 @@ import { promisify } from "node:util";
|
|
|
25
26
|
*/
|
|
26
27
|
const execFileAsync = promisify(execFile);
|
|
27
28
|
const OPENCLAW_UPGRADE_TIMEOUT_MS = 3e5;
|
|
28
|
-
const OPENCLAW_DOCTOR_TIMEOUT_MS =
|
|
29
|
+
const OPENCLAW_DOCTOR_TIMEOUT_MS = 9e5;
|
|
30
|
+
/**
|
|
31
|
+
* A non-diagnostic probe run right after the new package lands. 2026.9.x
|
|
32
|
+
* classifies `--version`/`--help` as diagnostic and lets them succeed even when
|
|
33
|
+
* Node/SQLite runtime admission fails; `config` does not get that pass, so an
|
|
34
|
+
* admission failure surfaces here — while `doctorStarted` is still false and
|
|
35
|
+
* the pre-doctor package rollback is still safe. `config file` (prints the
|
|
36
|
+
* resolved config path) is used rather than `config get <key>` because `get`
|
|
37
|
+
* exits 1 for a valid-but-unset key, and not every agent has every key
|
|
38
|
+
* authored (self-hosted agents set up by older CLIs, the ENOENT branch of
|
|
39
|
+
* `initWorkspace`); a state-dependent probe would roll a good package back.
|
|
40
|
+
* `config file` exists on 2026.8.1 and 2026.9.5 and exits 0 on an admitted
|
|
41
|
+
* runtime regardless of config contents.
|
|
42
|
+
*/
|
|
43
|
+
const OPENCLAW_ADMISSION_PROBE_ARGS = ["config", "file"];
|
|
29
44
|
function resolveUpgradeCommand(runtime, version) {
|
|
30
45
|
switch (runtime) {
|
|
31
46
|
case "openclaw":
|
|
@@ -55,10 +70,6 @@ function resolveUpgradeCommand(runtime, version) {
|
|
|
55
70
|
default: return;
|
|
56
71
|
}
|
|
57
72
|
}
|
|
58
|
-
/** Extract the semver-like version token printed by `openclaw --version`. */
|
|
59
|
-
function parseOpenClawVersion(output) {
|
|
60
|
-
return /(?:^|\s)(\d{4}\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)(?:\s|$)/u.exec(output.trim())?.[1];
|
|
61
|
-
}
|
|
62
73
|
async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
|
|
63
74
|
const runCommand = options.runCommand ?? (async (command, args, commandOptions) => {
|
|
64
75
|
const result = await execFileAsync(command, args, commandOptions);
|
|
@@ -67,12 +78,13 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
|
|
|
67
78
|
stderr: result.stderr
|
|
68
79
|
};
|
|
69
80
|
});
|
|
81
|
+
const openclawEnv = {
|
|
82
|
+
...process.env,
|
|
83
|
+
...OPENCLAW_EXTERNAL_SUPERVISOR_ENV
|
|
84
|
+
};
|
|
70
85
|
const commandOptions = (timeout) => runtime === "openclaw" ? {
|
|
71
86
|
timeout,
|
|
72
|
-
env:
|
|
73
|
-
...process.env,
|
|
74
|
-
...OPENCLAW_EXTERNAL_SUPERVISOR_ENV
|
|
75
|
-
}
|
|
87
|
+
env: openclawEnv
|
|
76
88
|
} : { timeout };
|
|
77
89
|
const cmd = resolveUpgradeCommand(runtime, version);
|
|
78
90
|
if (!cmd) {
|
|
@@ -89,24 +101,60 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
|
|
|
89
101
|
};
|
|
90
102
|
}
|
|
91
103
|
let previousOpenClawVersion;
|
|
104
|
+
let nodeUpgraded = false;
|
|
92
105
|
if (runtime === "openclaw") try {
|
|
93
106
|
const [{ stdout: nodeOutput }, { stdout: openClawOutput }] = await Promise.all([runCommand("node", ["--version"], commandOptions(1e4)), runCommand("openclaw", ["--version"], commandOptions(1e4))]);
|
|
94
|
-
|
|
107
|
+
previousOpenClawVersion = parseOpenClawVersion(openClawOutput);
|
|
108
|
+
let nodeVersion = nodeOutput.trim();
|
|
95
109
|
if (!supportsOpenClawNode(nodeVersion)) {
|
|
96
|
-
|
|
97
|
-
|
|
110
|
+
if (!options.nodeRemediation) {
|
|
111
|
+
const message = `OpenClaw ${version ?? "2.x"} requires Node.js ${OPENCLAW_NODE_REQUIREMENT}; this host resolves ${nodeVersion}. Upgrade Node.js or rebuild the agent before retrying.`;
|
|
112
|
+
logger.error({
|
|
113
|
+
runtime,
|
|
114
|
+
version,
|
|
115
|
+
nodeVersion
|
|
116
|
+
}, message);
|
|
117
|
+
return {
|
|
118
|
+
success: false,
|
|
119
|
+
runtime,
|
|
120
|
+
version,
|
|
121
|
+
error: message
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const fromNode = nodeVersion;
|
|
125
|
+
logger.warn({
|
|
98
126
|
runtime,
|
|
99
127
|
version,
|
|
128
|
+
nodeVersion,
|
|
129
|
+
requirement: OPENCLAW_NODE_REQUIREMENT
|
|
130
|
+
}, "Host Node.js does not satisfy OpenClaw; installing the managed Node channel first");
|
|
131
|
+
try {
|
|
132
|
+
nodeVersion = await remediateManagedNode(runCommand, openclawEnv, options.nodeRemediation);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
const replaced = err instanceof NodeRemediationError && err.nodeReplaced;
|
|
135
|
+
const resolvedNode = err instanceof NodeRemediationError ? err.resolvedNode : void 0;
|
|
136
|
+
nodeUpgraded = replaced;
|
|
137
|
+
if (!(err instanceof NodeRemediationError && err.cooldownActive)) captureNodeRemediation({
|
|
138
|
+
outcome: "failure",
|
|
139
|
+
fromNode,
|
|
140
|
+
...replaced && resolvedNode ? { toNode: resolvedNode } : {},
|
|
141
|
+
runtimeVersion: previousOpenClawVersion,
|
|
142
|
+
cause: err
|
|
143
|
+
});
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
nodeUpgraded = true;
|
|
147
|
+
captureNodeRemediation({
|
|
148
|
+
outcome: "success",
|
|
149
|
+
fromNode,
|
|
150
|
+
toNode: nodeVersion,
|
|
151
|
+
runtimeVersion: previousOpenClawVersion
|
|
152
|
+
});
|
|
153
|
+
logger.info({
|
|
154
|
+
fromNode,
|
|
100
155
|
nodeVersion
|
|
101
|
-
},
|
|
102
|
-
return {
|
|
103
|
-
success: false,
|
|
104
|
-
runtime,
|
|
105
|
-
version,
|
|
106
|
-
error: message
|
|
107
|
-
};
|
|
156
|
+
}, "Managed Node.js remediation complete");
|
|
108
157
|
}
|
|
109
|
-
previousOpenClawVersion = parseOpenClawVersion(openClawOutput);
|
|
110
158
|
} catch (err) {
|
|
111
159
|
const message = `OpenClaw upgrade preflight failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
112
160
|
logger.error({
|
|
@@ -117,6 +165,7 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
|
|
|
117
165
|
success: false,
|
|
118
166
|
runtime,
|
|
119
167
|
version,
|
|
168
|
+
nodeUpgraded,
|
|
120
169
|
error: message
|
|
121
170
|
};
|
|
122
171
|
}
|
|
@@ -155,6 +204,7 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
|
|
|
155
204
|
const installedVersion = parseOpenClawVersion((await runCommand("openclaw", ["--version"], commandOptions(1e4))).stdout);
|
|
156
205
|
if (installedVersion !== version) throw new Error(`OpenClaw package verification failed: requested ${version}, resolved ${installedVersion ?? "unknown"}`);
|
|
157
206
|
runtimeChanged = installedVersion !== previousOpenClawVersion;
|
|
207
|
+
await runCommand("openclaw", [...OPENCLAW_ADMISSION_PROBE_ARGS], commandOptions(6e4));
|
|
158
208
|
doctorStarted = true;
|
|
159
209
|
await runCommand("openclaw", [
|
|
160
210
|
"doctor",
|
|
@@ -195,13 +245,15 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
|
|
|
195
245
|
else await performUpgrade();
|
|
196
246
|
logger.info({
|
|
197
247
|
runtime,
|
|
198
|
-
version
|
|
248
|
+
version,
|
|
249
|
+
nodeUpgraded
|
|
199
250
|
}, "Runtime upgraded successfully");
|
|
200
251
|
return {
|
|
201
252
|
success: true,
|
|
202
253
|
runtime,
|
|
203
254
|
version,
|
|
204
|
-
runtimeChanged
|
|
255
|
+
runtimeChanged,
|
|
256
|
+
nodeUpgraded
|
|
205
257
|
};
|
|
206
258
|
} catch (err) {
|
|
207
259
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -215,6 +267,7 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
|
|
|
215
267
|
runtime,
|
|
216
268
|
version,
|
|
217
269
|
runtimeChanged,
|
|
270
|
+
nodeUpgraded,
|
|
218
271
|
error: message
|
|
219
272
|
};
|
|
220
273
|
} finally {
|
package/dist/sentry.js
CHANGED
|
@@ -313,6 +313,34 @@ function captureCliFailure(context, cause) {
|
|
|
313
313
|
} catch {}
|
|
314
314
|
}
|
|
315
315
|
/**
|
|
316
|
+
* Fleet-visible record of a managed Node.js remediation attempt (the in-place
|
|
317
|
+
* Node 22 → 24 move `runtime-upgrade.ts` performs before installing an OpenClaw
|
|
318
|
+
* that dropped Node 22). One event per host per attempt so the campaign can
|
|
319
|
+
* count remediated vs failed agents; the daemon's local log alone cannot answer
|
|
320
|
+
* that. No-op when Sentry is not initialised. Never throws.
|
|
321
|
+
*/
|
|
322
|
+
function captureNodeRemediation(opts) {
|
|
323
|
+
if (!sentry) return;
|
|
324
|
+
try {
|
|
325
|
+
const tags = {
|
|
326
|
+
context: "node-remediation",
|
|
327
|
+
outcome: opts.outcome,
|
|
328
|
+
fromNode: opts.fromNode,
|
|
329
|
+
...opts.toNode ? { toNode: opts.toNode } : {},
|
|
330
|
+
...opts.runtimeVersion ? { runtimeVersion: opts.runtimeVersion } : {}
|
|
331
|
+
};
|
|
332
|
+
if (opts.outcome === "failure" && opts.cause instanceof Error) {
|
|
333
|
+
sentry.captureException(opts.cause, { tags });
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const detail = opts.outcome === "failure" && opts.cause !== void 0 ? `: ${String(opts.cause)}` : "";
|
|
337
|
+
sentry.captureMessage(`node remediation ${opts.outcome}${detail}`, {
|
|
338
|
+
level: opts.outcome === "failure" ? "error" : "info",
|
|
339
|
+
tags
|
|
340
|
+
});
|
|
341
|
+
} catch {}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
316
344
|
* Capture a runtime child-process crash (non-graceful exit) or spawn failure
|
|
317
345
|
* into the dedicated `agent-runtime` project. Recent output travels as
|
|
318
346
|
* breadcrumbs — NEVER `extra`, which `scrubEvent` (the runtime client's
|
|
@@ -428,4 +456,4 @@ function captureFatal(cause) {
|
|
|
428
456
|
} catch {}
|
|
429
457
|
}
|
|
430
458
|
//#endregion
|
|
431
|
-
export { captureFatal as a,
|
|
459
|
+
export { captureFatal as a, captureNodeRemediation as c, flushSentry as d, initAgentSentry as f, logger as h, captureCliFailure as i, captureRuntimeCrash as l, LOG_FILE as m, AGENT_MCP_SENTRY_DSN as n, captureIntegrationFailure as o, setAgentContext as p, AGENT_RUNTIME_SENTRY_DSN as r, captureMcpFailure as s, AGENT_DAEMON_SENTRY_DSN as t, captureRuntimeErrorOutput as u };
|
package/dist/src/index.d.ts
CHANGED
|
@@ -44,23 +44,89 @@ declare function startDaemon(): Promise<void>;
|
|
|
44
44
|
* truth for every install path:
|
|
45
45
|
* - `@alfe.ai/cli` `installRuntime` (fresh `alfe setup`)
|
|
46
46
|
* - the daemon `runtime.update` default (this package)
|
|
47
|
-
* - managed provisioning (`services/compute/src/lib/provisioner.ts` installs
|
|
48
|
-
*
|
|
47
|
+
* - managed provisioning (`services/compute/src/lib/provisioner.ts` installs the
|
|
48
|
+
* Node.js 24 channel before the CLI consumes this pin)
|
|
49
49
|
*
|
|
50
50
|
* Pinning (rather than floating `npm install -g openclaw` → `latest`) is deliberate:
|
|
51
51
|
* OpenClaw runs on every managed agent, so a `latest` that changes runtime semantics —
|
|
52
52
|
* e.g. 2026.6.8 made a non-empty `plugins.allow` an exclusive allowlist — becomes a
|
|
53
53
|
* silent fleet outage with no Alfe deploy. Bump deliberately after validating a new
|
|
54
54
|
* OpenClaw, then cut a CLI release so the pin travels with the CLI version.
|
|
55
|
+
*
|
|
56
|
+
* Raising the engine range below obligates every consumer of it: the daemon upgrade
|
|
57
|
+
* preflight (`runtime-upgrade.ts`), the CLI `installRuntime`, and the compute
|
|
58
|
+
* cloud-init literals (tied to these constants by `provisioner.test.ts`).
|
|
59
|
+
*/
|
|
60
|
+
declare const PINNED_OPENCLAW_VERSION = "2026.9.5";
|
|
61
|
+
/**
|
|
62
|
+
* OpenClaw 2026.9.5's published Node.js engine floor. Node 22 and Node 25 are no
|
|
63
|
+
* longer supported upstream (2026.9.x dropped both branches; the preinstall script
|
|
64
|
+
* refuses to install on them).
|
|
55
65
|
*/
|
|
56
|
-
declare const
|
|
57
|
-
/** OpenClaw 2026.8.1's published Node.js engine floor. */
|
|
58
|
-
declare const OPENCLAW_NODE_REQUIREMENT = ">=22.22.3 <23 || >=24.15.0 <25 || >=25.9.0";
|
|
66
|
+
declare const OPENCLAW_NODE_REQUIREMENT = ">=24.16.0 <25 || >=26.1.0";
|
|
59
67
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
68
|
+
* The Node.js major the managed image installs (NodeSource `setup_24.x`) and that
|
|
69
|
+
* `upgradeRuntime` remediates existing managed hosts onto. 24 is the current LTS
|
|
70
|
+
* line; 26 is not LTS yet.
|
|
71
|
+
*/
|
|
72
|
+
declare const OPENCLAW_MANAGED_NODE_MAJOR = 24;
|
|
73
|
+
/**
|
|
74
|
+
* Whether a concrete Node.js version satisfies OpenClaw's engine range.
|
|
75
|
+
* Accepts `process.versions.node` (`24.21.0`) or CLI output (`v24.21.0`).
|
|
76
|
+
* Pre-release tags are rejected; build metadata (`+…`) is accepted.
|
|
62
77
|
*/
|
|
63
78
|
declare function supportsOpenClawNode(nodeVersion: string): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Extract the semver-like version token printed by `openclaw --version`.
|
|
81
|
+
* Every OpenClaw release prints a branded line (`OpenClaw 2026.6.11 (e085fa1)`,
|
|
82
|
+
* `OpenClaw 2026.9.5 (ec9c1a1)`); this reduces it to the bare token. Requires
|
|
83
|
+
* whitespace delimiters and a four-digit year major, so unexpected formats
|
|
84
|
+
* return `undefined` and callers fall back to the raw line.
|
|
85
|
+
*/
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/node-remediation.d.ts
|
|
88
|
+
|
|
89
|
+
type RemediationCommandRunner = (command: string, args: string[], options: {
|
|
90
|
+
timeout: number;
|
|
91
|
+
env?: NodeJS.ProcessEnv;
|
|
92
|
+
}) => Promise<{
|
|
93
|
+
stdout: string;
|
|
94
|
+
stderr: string;
|
|
95
|
+
}>;
|
|
96
|
+
interface RemediateManagedNodeOptions {
|
|
97
|
+
/**
|
|
98
|
+
* Prove the currently installed `openclaw` still boots under the new Node
|
|
99
|
+
* (default true — the daemon needs the old runtime for the pre-upgrade
|
|
100
|
+
* backup). `alfe setup` on a fresh VM passes false: nothing is installed yet.
|
|
101
|
+
*/
|
|
102
|
+
verifyInstalledRuntime?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Where the failure cooldown marker lives (default `~/.alfe`). Test seam;
|
|
105
|
+
* `null` disables the marker entirely.
|
|
106
|
+
*/
|
|
107
|
+
stateDirectory?: string | null;
|
|
108
|
+
/** Test seam for the cooldown clock. */
|
|
109
|
+
now?: () => number;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Thrown when remediation fails. `nodeReplaced` is the load-bearing field: a
|
|
113
|
+
* failure AFTER apt has swapped the host Node (verification or the installed
|
|
114
|
+
* runtime's boot check failed) leaves the host on a new, unproven Node, which
|
|
115
|
+
* is the state that needs an operator — very different from "never touched".
|
|
116
|
+
*/
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Install the managed Node.js channel on a Debian/Ubuntu managed host and
|
|
120
|
+
* return the resolved `node --version`.
|
|
121
|
+
*
|
|
122
|
+
* Runs BEFORE any runtime gate suspend or backup: the live OpenClaw child keeps
|
|
123
|
+
* its already-open binary, and later `openclaw`/`npm` spawns resolve the new
|
|
124
|
+
* binary through PATH. Fails closed (with `nodeReplaced` set accurately) if the
|
|
125
|
+
* resulting Node still misses the range or if the currently installed OpenClaw
|
|
126
|
+
* no longer boots under it — in that case the caller must not proceed to a
|
|
127
|
+
* backup it could not produce.
|
|
128
|
+
*/
|
|
129
|
+
declare function remediateManagedNode(runCommand: RemediationCommandRunner, baseEnv: NodeJS.ProcessEnv, options?: RemediateManagedNodeOptions): Promise<string>;
|
|
64
130
|
//#endregion
|
|
65
131
|
//#region src/claude-code-host-version.d.ts
|
|
66
132
|
/**
|
|
@@ -164,6 +230,14 @@ declare function captureIntegrationFailure(integration: string, phase: string, c
|
|
|
164
230
|
* Sentry is not initialised. Never throws.
|
|
165
231
|
*/
|
|
166
232
|
declare function captureCliFailure(context: string, cause?: unknown): void;
|
|
233
|
+
/**
|
|
234
|
+
* Fleet-visible record of a managed Node.js remediation attempt (the in-place
|
|
235
|
+
* Node 22 → 24 move `runtime-upgrade.ts` performs before installing an OpenClaw
|
|
236
|
+
* that dropped Node 22). One event per host per attempt so the campaign can
|
|
237
|
+
* count remediated vs failed agents; the daemon's local log alone cannot answer
|
|
238
|
+
* that. No-op when Sentry is not initialised. Never throws.
|
|
239
|
+
*/
|
|
240
|
+
|
|
167
241
|
/** A line of runtime child-process output attached to a crash event. */
|
|
168
242
|
interface RuntimeOutputLine {
|
|
169
243
|
stream: "stdout" | "stderr";
|
|
@@ -414,4 +488,4 @@ declare function checkExistingDaemon(): Promise<number | null>;
|
|
|
414
488
|
*/
|
|
415
489
|
declare function stopExistingDaemon(): Promise<boolean>;
|
|
416
490
|
//#endregion
|
|
417
|
-
export { AGENT_DAEMON_SENTRY_DSN, AGENT_MCP_SENTRY_DSN, AGENT_RUNTIME_SENTRY_DSN, ALFE_DIR, type AgentIdentity, type AgentWorkspaceConfig, type DaemonConfig, type DaemonHealth, type IPCEvent, type IPCRequest, type IPCResponse, type InitAgentSentryOptions, OPENCLAW_NODE_REQUIREMENT, PID_PATH, PINNED_CLAUDE_CODE_HOST_VERSION, PINNED_OPENCLAW_VERSION, PROTOCOL_VERSION, type RuntimeOutputLine, SOCKET_PATH, type SentrySurface, captureCliFailure, captureFatal, captureIntegrationFailure, captureMcpFailure, captureRuntimeCrash, captureRuntimeErrorOutput, checkExistingDaemon, fetchAgentConfig, flushSentry, formatHealthReport, initAgentSentry, installService, isServiceInstalled, loadDaemonConfig, logger, queryDaemonHealth, resolveAgentIdentity, restartService, setAgentContext, startDaemon, startService, stopExistingDaemon, stopService, supportsOpenClawNode, uninstallService };
|
|
491
|
+
export { AGENT_DAEMON_SENTRY_DSN, AGENT_MCP_SENTRY_DSN, AGENT_RUNTIME_SENTRY_DSN, ALFE_DIR, type AgentIdentity, type AgentWorkspaceConfig, type DaemonConfig, type DaemonHealth, type IPCEvent, type IPCRequest, type IPCResponse, type InitAgentSentryOptions, OPENCLAW_MANAGED_NODE_MAJOR, OPENCLAW_NODE_REQUIREMENT, PID_PATH, PINNED_CLAUDE_CODE_HOST_VERSION, PINNED_OPENCLAW_VERSION, PROTOCOL_VERSION, type RemediateManagedNodeOptions, type RemediationCommandRunner, type RuntimeOutputLine, SOCKET_PATH, type SentrySurface, captureCliFailure, captureFatal, captureIntegrationFailure, captureMcpFailure, captureRuntimeCrash, captureRuntimeErrorOutput, checkExistingDaemon, fetchAgentConfig, flushSentry, formatHealthReport, initAgentSentry, installService, isServiceInstalled, loadDaemonConfig, logger, queryDaemonHealth, remediateManagedNode, resolveAgentIdentity, restartService, setAgentContext, startDaemon, startService, stopExistingDaemon, stopService, supportsOpenClawNode, uninstallService };
|
package/dist/src/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { d as
|
|
1
|
+
import { d as PINNED_OPENCLAW_VERSION, l as OPENCLAW_MANAGED_NODE_MAJOR, p as supportsOpenClawNode, u as OPENCLAW_NODE_REQUIREMENT } from "../runtime-process.js";
|
|
2
2
|
import { _ as loadDaemonConfig, a as installService, c as startService, d as uninstallService, f as PROTOCOL_VERSION, g as fetchAgentConfig, h as SOCKET_PATH, i as checkExistingDaemon, l as stopExistingDaemon, m as PID_PATH, n as queryDaemonHealth, o as isServiceInstalled, p as ALFE_DIR, r as startDaemon, s as restartService, t as formatHealthReport, u as stopService, v as resolveAgentIdentity, y as PINNED_CLAUDE_CODE_HOST_VERSION } from "../health.js";
|
|
3
|
-
import { a as captureFatal,
|
|
4
|
-
|
|
3
|
+
import { a as captureFatal, d as flushSentry, f as initAgentSentry, h as logger, i as captureCliFailure, l as captureRuntimeCrash, n as AGENT_MCP_SENTRY_DSN, o as captureIntegrationFailure, p as setAgentContext, r as AGENT_RUNTIME_SENTRY_DSN, s as captureMcpFailure, t as AGENT_DAEMON_SENTRY_DSN, u as captureRuntimeErrorOutput } from "../sentry.js";
|
|
4
|
+
import { n as remediateManagedNode } from "../node-remediation.js";
|
|
5
|
+
export { AGENT_DAEMON_SENTRY_DSN, AGENT_MCP_SENTRY_DSN, AGENT_RUNTIME_SENTRY_DSN, ALFE_DIR, OPENCLAW_MANAGED_NODE_MAJOR, OPENCLAW_NODE_REQUIREMENT, PID_PATH, PINNED_CLAUDE_CODE_HOST_VERSION, PINNED_OPENCLAW_VERSION, PROTOCOL_VERSION, SOCKET_PATH, captureCliFailure, captureFatal, captureIntegrationFailure, captureMcpFailure, captureRuntimeCrash, captureRuntimeErrorOutput, checkExistingDaemon, fetchAgentConfig, flushSentry, formatHealthReport, initAgentSentry, installService, isServiceInstalled, loadDaemonConfig, logger, queryDaemonHealth, remediateManagedNode, resolveAgentIdentity, restartService, setAgentContext, startDaemon, startService, stopExistingDaemon, stopService, supportsOpenClawNode, uninstallService };
|
package/dist/upgrade.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/gateway",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Alfe local gateway daemon — persistent control plane for agent integrations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"@alfe.ai/ai-proxy-local": "^0.0.17",
|
|
28
28
|
"@alfe.ai/config": "^0.4.1",
|
|
29
29
|
"@alfe.ai/integration-manifest": "^0.4.2",
|
|
30
|
-
"@alfe.ai/integrations": "^0.6.
|
|
30
|
+
"@alfe.ai/integrations": "^0.6.8",
|
|
31
31
|
"@alfe.ai/mcp-bundler": "^0.4.2"
|
|
32
32
|
},
|
|
33
33
|
"license": "UNLICENSED",
|