@alfe.ai/gateway 0.9.19 → 0.10.1
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 +347 -21
- package/dist/node-remediation.js +163 -0
- package/dist/runtime-process.js +43 -16
- package/dist/runtime-upgrade.js +77 -20
- package/dist/sentry.js +29 -1
- package/dist/src/index.d.ts +75 -8
- package/dist/src/index.js +4 -3
- package/dist/upgrade.js +1 -1
- package/package.json +3 -3
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,
|
|
2
|
-
import { a as captureFatal, d as
|
|
1
|
+
import { a as ErrorLineDetector, c as createLogger, i as CaptureThrottle, m as __require, o as STRICT_ERROR_CAPTURE_PROFILE, p as __commonJSMin, 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";
|
|
@@ -5662,16 +5662,20 @@ const MAX_IPC_MESSAGE_BYTES = 1024 * 1024;
|
|
|
5662
5662
|
* Map a cloud command name to an IPC method name.
|
|
5663
5663
|
* Cloud commands use dot-notation matching IPC methods.
|
|
5664
5664
|
*
|
|
5665
|
-
*
|
|
5666
|
-
*
|
|
5665
|
+
* Work reads are dispatched by executeWorkCommand so they select a capable
|
|
5666
|
+
* OpenClaw plugin and fail immediately instead of joining an offline queue.
|
|
5667
5667
|
*/
|
|
5668
|
-
const CLOUD_COMMAND_TO_IPC = {
|
|
5668
|
+
const CLOUD_COMMAND_TO_IPC = {
|
|
5669
|
+
"work.snapshot": "work.snapshot",
|
|
5670
|
+
"work.job_runs": "work.job_runs",
|
|
5671
|
+
"work.actions": "work.actions"
|
|
5672
|
+
};
|
|
5669
5673
|
/**
|
|
5670
5674
|
* Translate a cloud COMMAND into a local IPC request.
|
|
5671
5675
|
* Returns null if the command isn't recognized.
|
|
5672
5676
|
*/
|
|
5673
5677
|
function cloudCommandToIPCRequest(command) {
|
|
5674
|
-
const method = CLOUD_COMMAND_TO_IPC[command.command];
|
|
5678
|
+
const method = Object.hasOwn(CLOUD_COMMAND_TO_IPC, command.command) ? CLOUD_COMMAND_TO_IPC[command.command] : void 0;
|
|
5675
5679
|
if (!method) return null;
|
|
5676
5680
|
return {
|
|
5677
5681
|
type: "req",
|
|
@@ -6997,6 +7001,42 @@ var CommandQueue = class {
|
|
|
6997
7001
|
}
|
|
6998
7002
|
};
|
|
6999
7003
|
//#endregion
|
|
7004
|
+
//#region src/work-commands.ts
|
|
7005
|
+
const WORK_COMMANDS = [
|
|
7006
|
+
"work.snapshot",
|
|
7007
|
+
"work.job_runs",
|
|
7008
|
+
"work.actions"
|
|
7009
|
+
];
|
|
7010
|
+
function isWorkCommand(command) {
|
|
7011
|
+
return WORK_COMMANDS.includes(command);
|
|
7012
|
+
}
|
|
7013
|
+
const MAX_WORK_RESPONSE_BYTES = 768 * 1024;
|
|
7014
|
+
/** Read requests never enter an offline queue or choose an arbitrary connected plugin. */
|
|
7015
|
+
async function executeWorkCommand(command, agentId, runtime, ipc) {
|
|
7016
|
+
const error = (code, message) => ({
|
|
7017
|
+
type: "COMMAND_ACK",
|
|
7018
|
+
commandId: command.commandId,
|
|
7019
|
+
status: "error",
|
|
7020
|
+
result: {
|
|
7021
|
+
code,
|
|
7022
|
+
message
|
|
7023
|
+
}
|
|
7024
|
+
});
|
|
7025
|
+
if (!isWorkCommand(command.command)) return error("UNKNOWN_COMMAND", "Unsupported work command");
|
|
7026
|
+
if (command.agentId !== agentId) return error("AGENT_MISMATCH", "Work request targets a different agent");
|
|
7027
|
+
if (runtime !== "openclaw") return error("UNSUPPORTED_RUNTIME", "Work visibility is available for OpenClaw agents");
|
|
7028
|
+
if (!command.payload || typeof command.payload !== "object" || Array.isArray(command.payload)) return error("INVALID_PARAMS", "Work parameters must be an object");
|
|
7029
|
+
const plugin = ipc?.getRegisteredPlugins().find(([, info]) => info.name === "@alfe.ai/openclaw" && info.capabilities.includes("work.read"));
|
|
7030
|
+
if (!ipc || !plugin) return error("WORK_UNAVAILABLE", "The runtime work bridge is unavailable; update the agent if needed");
|
|
7031
|
+
try {
|
|
7032
|
+
const response = await ipc.sendRequest(plugin[0], command.command, command.payload, 6e3);
|
|
7033
|
+
if (Buffer.byteLength(JSON.stringify(response), "utf8") > MAX_WORK_RESPONSE_BYTES) return error("RESPONSE_TOO_LARGE", "Runtime work data exceeds the response limit");
|
|
7034
|
+
return ipcResponseToCloudAck(command.commandId, response);
|
|
7035
|
+
} catch {
|
|
7036
|
+
return error("WORK_UNAVAILABLE", "The runtime work bridge did not respond");
|
|
7037
|
+
}
|
|
7038
|
+
}
|
|
7039
|
+
//#endregion
|
|
7000
7040
|
//#region src/process-manager.ts
|
|
7001
7041
|
/**
|
|
7002
7042
|
* Process management — launchd/systemd service installation.
|
|
@@ -7649,9 +7689,7 @@ var IPCServer = class {
|
|
|
7649
7689
|
reject,
|
|
7650
7690
|
timer
|
|
7651
7691
|
});
|
|
7652
|
-
|
|
7653
|
-
conn.socket.write(JSON.stringify(request) + "\n");
|
|
7654
|
-
} catch (err) {
|
|
7692
|
+
if (!this.writeFrame(conn, request, "request")) {
|
|
7655
7693
|
clearTimeout(timer);
|
|
7656
7694
|
conn.pending.delete(id);
|
|
7657
7695
|
resolve({
|
|
@@ -7659,7 +7697,7 @@ var IPCServer = class {
|
|
|
7659
7697
|
ok: false,
|
|
7660
7698
|
error: {
|
|
7661
7699
|
code: "SEND_FAILED",
|
|
7662
|
-
message:
|
|
7700
|
+
message: "Failed to send to plugin (write failed or frame over the transport limit)"
|
|
7663
7701
|
}
|
|
7664
7702
|
});
|
|
7665
7703
|
}
|
|
@@ -7879,21 +7917,57 @@ var IPCServer = class {
|
|
|
7879
7917
|
}));
|
|
7880
7918
|
}
|
|
7881
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;
|
|
7882
7940
|
try {
|
|
7883
|
-
|
|
7941
|
+
frame = JSON.stringify(message) + "\n";
|
|
7884
7942
|
} catch (err) {
|
|
7885
7943
|
logger.error({
|
|
7886
7944
|
connId: conn.id,
|
|
7945
|
+
kind,
|
|
7887
7946
|
err
|
|
7888
|
-
}, "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";
|
|
7889
7961
|
}
|
|
7890
|
-
}
|
|
7891
|
-
sendEvent(conn, event, payload) {
|
|
7892
7962
|
try {
|
|
7893
|
-
|
|
7894
|
-
conn.socket.write(JSON.stringify(msg) + "\n");
|
|
7963
|
+
conn.socket.write(frame);
|
|
7895
7964
|
return true;
|
|
7896
|
-
} catch {
|
|
7965
|
+
} catch (err) {
|
|
7966
|
+
logger.error({
|
|
7967
|
+
connId: conn.id,
|
|
7968
|
+
kind,
|
|
7969
|
+
err
|
|
7970
|
+
}, "IPC: failed to write frame");
|
|
7897
7971
|
return false;
|
|
7898
7972
|
}
|
|
7899
7973
|
}
|
|
@@ -8122,6 +8196,250 @@ async function startIpcServer(socketPath, requestHandler, daemonVersion) {
|
|
|
8122
8196
|
//#endregion
|
|
8123
8197
|
//#region src/mcp-handlers.ts
|
|
8124
8198
|
/**
|
|
8199
|
+
* Aggregate text budget for one MCP tool call. An order of magnitude under
|
|
8200
|
+
* MAX_IPC_MESSAGE_BYTES because JSON escaping inflates text: quotes and
|
|
8201
|
+
* newlines cost 2 bytes, control characters 6. The serialized fence below
|
|
8202
|
+
* makes the guarantee absolute; this is the budget the model sees.
|
|
8203
|
+
*/
|
|
8204
|
+
const MAX_MCP_RESULT_TEXT_BYTES = 256 * 1024;
|
|
8205
|
+
/**
|
|
8206
|
+
* Per-text-item cap. The consuming plugin (`@alfe.ai/openclaw-mcp-bundler`,
|
|
8207
|
+
* `plugin.ts` `MAX_JSON_STRING_LENGTH = 65_536`) rejects the WHOLE result as
|
|
8208
|
+
* "malformed" if any single string exceeds 65,536 UTF-16 code units, so a
|
|
8209
|
+
* kept item must never exceed that. UTF-8 bytes ≥ UTF-16 units, so capping
|
|
8210
|
+
* bytes at 64 KiB is safe for every already-deployed bundler.
|
|
8211
|
+
*/
|
|
8212
|
+
const MAX_MCP_RESULT_ITEM_TEXT_BYTES = 64 * 1024;
|
|
8213
|
+
/** Worst-case JSON wrapper per content item (`{"type":"text","text":""},`) for the fast-path estimate. */
|
|
8214
|
+
const ITEM_ENVELOPE_BYTES = 64;
|
|
8215
|
+
function isTextItem(item) {
|
|
8216
|
+
return typeof item === "object" && item !== null && item.type === "text" && typeof item.text === "string";
|
|
8217
|
+
}
|
|
8218
|
+
function serializedBytes(value) {
|
|
8219
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
8220
|
+
}
|
|
8221
|
+
/**
|
|
8222
|
+
* Longest string value anywhere inside `value`, in UTF-8 bytes. The bundler
|
|
8223
|
+
* plugin's parser rejects the WHOLE result when any string at any depth is
|
|
8224
|
+
* over its cap (counted in UTF-16 units; UTF-8 bytes are never fewer), so
|
|
8225
|
+
* this is what a non-text item (image data, resource text) is measured by.
|
|
8226
|
+
*/
|
|
8227
|
+
/**
|
|
8228
|
+
* The bundler plugin's `sanitizeJsonRecord` limits, mirrored so the daemon
|
|
8229
|
+
* never hands it an item it will reject (which nulls the WHOLE result).
|
|
8230
|
+
* Keep in sync with `packages/openclaw-mcp-bundler/src/plugin.ts`.
|
|
8231
|
+
*/
|
|
8232
|
+
const PLUGIN_MAX_JSON_NODES = 1e4;
|
|
8233
|
+
const PLUGIN_MAX_JSON_DEPTH = 32;
|
|
8234
|
+
const PLUGIN_MAX_JSON_COLLECTION_ENTRIES = 2048;
|
|
8235
|
+
const PLUGIN_MAX_KEY_LENGTH = 512;
|
|
8236
|
+
/**
|
|
8237
|
+
* True when a content item stays inside the plugin parser's structural limits
|
|
8238
|
+
* (node budget per item, nesting depth, entries per collection, key length).
|
|
8239
|
+
* String length is bounded separately because `text` can be cut to fit; a
|
|
8240
|
+
* structural violation cannot, so the item is dropped.
|
|
8241
|
+
*/
|
|
8242
|
+
function withinPluginStructure(item) {
|
|
8243
|
+
const budget = { nodes: 0 };
|
|
8244
|
+
const walk = (value, depth) => {
|
|
8245
|
+
budget.nodes += 1;
|
|
8246
|
+
if (budget.nodes > PLUGIN_MAX_JSON_NODES || depth > PLUGIN_MAX_JSON_DEPTH) return false;
|
|
8247
|
+
if (Array.isArray(value)) {
|
|
8248
|
+
if (value.length > PLUGIN_MAX_JSON_COLLECTION_ENTRIES) return false;
|
|
8249
|
+
for (const entry of value) if (!walk(entry, depth + 1)) return false;
|
|
8250
|
+
return true;
|
|
8251
|
+
}
|
|
8252
|
+
if (typeof value === "object" && value !== null) {
|
|
8253
|
+
const entries = Object.entries(value);
|
|
8254
|
+
if (entries.length > PLUGIN_MAX_JSON_COLLECTION_ENTRIES) return false;
|
|
8255
|
+
return entries.every(([key, entry]) => key.length <= PLUGIN_MAX_KEY_LENGTH && walk(entry, depth + 1));
|
|
8256
|
+
}
|
|
8257
|
+
return true;
|
|
8258
|
+
};
|
|
8259
|
+
return walk(item, 0);
|
|
8260
|
+
}
|
|
8261
|
+
function longestStringBytes(value) {
|
|
8262
|
+
if (typeof value === "string") return Buffer.byteLength(value, "utf8");
|
|
8263
|
+
if (Array.isArray(value)) {
|
|
8264
|
+
let max = 0;
|
|
8265
|
+
for (const entry of value) max = Math.max(max, longestStringBytes(entry));
|
|
8266
|
+
return max;
|
|
8267
|
+
}
|
|
8268
|
+
if (typeof value === "object" && value !== null) {
|
|
8269
|
+
let max = 0;
|
|
8270
|
+
for (const entry of Object.values(value)) max = Math.max(max, longestStringBytes(entry));
|
|
8271
|
+
return max;
|
|
8272
|
+
}
|
|
8273
|
+
return 0;
|
|
8274
|
+
}
|
|
8275
|
+
/** Cut at a UTF-8 boundary; drop a replacement char introduced by the cut. */
|
|
8276
|
+
function truncateUtf8(text, maxBytes) {
|
|
8277
|
+
const buf = Buffer.from(text, "utf8");
|
|
8278
|
+
if (buf.byteLength <= maxBytes) return text;
|
|
8279
|
+
const out = buf.subarray(0, maxBytes).toString("utf8");
|
|
8280
|
+
return out.endsWith("�") && !text.endsWith("�") ? out.slice(0, -1) : out;
|
|
8281
|
+
}
|
|
8282
|
+
/**
|
|
8283
|
+
* Model-visible notice. Deliberately names no bare tool: MCP hosts prefix tool
|
|
8284
|
+
* names, so a bare name is a dead end (packages/DEVELOPING.md, "Model-visible
|
|
8285
|
+
* errors carry the fix").
|
|
8286
|
+
*/
|
|
8287
|
+
function truncationNotice(args) {
|
|
8288
|
+
const dropped = [];
|
|
8289
|
+
if (args.droppedItems > 0) dropped.push(`${String(args.droppedItems)} further content item(s)`);
|
|
8290
|
+
if (args.droppedStructured) dropped.push("the structured-content block");
|
|
8291
|
+
return [
|
|
8292
|
+
"[alfe] This tool result was truncated to fit the agent transport limit.",
|
|
8293
|
+
`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 ")}.` : ""),
|
|
8294
|
+
"Do NOT retry this call unchanged; the same request will truncate again.",
|
|
8295
|
+
"Narrow it instead: add a date range or status filter, lower the page size",
|
|
8296
|
+
"(top / $top / limit), and page with skip / $skip. If this data source offers a",
|
|
8297
|
+
"summary or report form of the same question, prefer that over listing raw records."
|
|
8298
|
+
].join("\n");
|
|
8299
|
+
}
|
|
8300
|
+
/**
|
|
8301
|
+
* Bound a bundler tool result so the daemon never writes an IPC frame the
|
|
8302
|
+
* plugin will reject. In-budget results are returned by reference (no clone).
|
|
8303
|
+
* Over budget: items are kept front-to-back until the budget (text bytes plus
|
|
8304
|
+
* a per-item envelope allowance) is spent. Text items are cut at the per-item
|
|
8305
|
+
* cap on a UTF-8 boundary; a non-text item carrying any string over that cap
|
|
8306
|
+
* (image data, resource text) is dropped outright because the plugin would
|
|
8307
|
+
* reject the whole result on its account and it cannot be cut meaningfully.
|
|
8308
|
+
* `structuredContent` is dropped, `isError` is preserved verbatim, and a
|
|
8309
|
+
* model-visible notice is appended. If the serialized result is still over
|
|
8310
|
+
* the fence, only the notice is returned.
|
|
8311
|
+
*/
|
|
8312
|
+
function boundMcpToolResult(result) {
|
|
8313
|
+
if (!Array.isArray(result.content)) throw new Error("MCP tool result content must be an array");
|
|
8314
|
+
const content = result.content;
|
|
8315
|
+
let textBytes = 0;
|
|
8316
|
+
let maxItemBytes = 0;
|
|
8317
|
+
let onlyBareText = true;
|
|
8318
|
+
let structurallyValid = true;
|
|
8319
|
+
for (const item of content) {
|
|
8320
|
+
if (isTextItem(item)) {
|
|
8321
|
+
textBytes += Buffer.byteLength(item.text, "utf8");
|
|
8322
|
+
if (onlyBareText && Object.keys(item).some((key) => key !== "type" && key !== "text")) onlyBareText = false;
|
|
8323
|
+
} else onlyBareText = false;
|
|
8324
|
+
maxItemBytes = Math.max(maxItemBytes, longestStringBytes(item));
|
|
8325
|
+
if (structurallyValid && !withinPluginStructure(item)) structurallyValid = false;
|
|
8326
|
+
}
|
|
8327
|
+
const structured = result.structuredContent;
|
|
8328
|
+
const hasExtraKeys = Object.keys(result).some((key) => key !== "content" && key !== "isError" && key !== "structuredContent");
|
|
8329
|
+
if (structurallyValid && textBytes <= 262144 && maxItemBytes <= 65536) {
|
|
8330
|
+
const worstCase = textBytes * 6 + content.length * ITEM_ENVELOPE_BYTES + 256;
|
|
8331
|
+
if (onlyBareText && structured === void 0 && !hasExtraKeys && worstCase <= 524288) return result;
|
|
8332
|
+
if (serializedBytes(result) <= 524288) return result;
|
|
8333
|
+
}
|
|
8334
|
+
const sourceBytes = serializedBytes(result);
|
|
8335
|
+
const { structuredContent: _dropped, ...withExtras } = result;
|
|
8336
|
+
const rest = hasExtraKeys && serializedBytes(withExtras) - serializedBytes(content) > 524288 / 2 ? {
|
|
8337
|
+
content,
|
|
8338
|
+
...result.isError === void 0 ? {} : { isError: result.isError }
|
|
8339
|
+
} : withExtras;
|
|
8340
|
+
for (const textBudget of [
|
|
8341
|
+
MAX_MCP_RESULT_TEXT_BYTES,
|
|
8342
|
+
MAX_MCP_RESULT_TEXT_BYTES / 2,
|
|
8343
|
+
MAX_MCP_RESULT_TEXT_BYTES / 4,
|
|
8344
|
+
MAX_MCP_RESULT_TEXT_BYTES / 8
|
|
8345
|
+
]) {
|
|
8346
|
+
const candidate = boundWithBudget(content, rest, textBudget, sourceBytes, structured !== void 0);
|
|
8347
|
+
if (serializedBytes(candidate) <= 524288) return candidate;
|
|
8348
|
+
}
|
|
8349
|
+
logger.warn({ sourceBytes }, "MCP tool result still over the serialized fence after text truncation; returning notice only");
|
|
8350
|
+
const notice = truncationNotice({
|
|
8351
|
+
sourceBytes,
|
|
8352
|
+
keptBytes: 0,
|
|
8353
|
+
truncatedItems: 0,
|
|
8354
|
+
droppedItems: content.length,
|
|
8355
|
+
droppedStructured: structured !== void 0
|
|
8356
|
+
});
|
|
8357
|
+
return {
|
|
8358
|
+
...rest,
|
|
8359
|
+
content: [{
|
|
8360
|
+
type: "text",
|
|
8361
|
+
text: notice
|
|
8362
|
+
}]
|
|
8363
|
+
};
|
|
8364
|
+
}
|
|
8365
|
+
function boundWithBudget(content, rest, textBudget, sourceBytes, droppedStructured) {
|
|
8366
|
+
let remaining = textBudget;
|
|
8367
|
+
let kept = 0;
|
|
8368
|
+
let truncatedItems = 0;
|
|
8369
|
+
let droppedItems = 0;
|
|
8370
|
+
let exhausted = false;
|
|
8371
|
+
const bounded = [];
|
|
8372
|
+
for (const item of content) {
|
|
8373
|
+
if (exhausted) {
|
|
8374
|
+
droppedItems += 1;
|
|
8375
|
+
continue;
|
|
8376
|
+
}
|
|
8377
|
+
if (!withinPluginStructure(item)) {
|
|
8378
|
+
droppedItems += 1;
|
|
8379
|
+
continue;
|
|
8380
|
+
}
|
|
8381
|
+
if (!isTextItem(item)) {
|
|
8382
|
+
if (longestStringBytes(item) > 65536) {
|
|
8383
|
+
droppedItems += 1;
|
|
8384
|
+
continue;
|
|
8385
|
+
}
|
|
8386
|
+
const size = serializedBytes(item) + ITEM_ENVELOPE_BYTES;
|
|
8387
|
+
if (size > remaining) {
|
|
8388
|
+
droppedItems += 1;
|
|
8389
|
+
exhausted = true;
|
|
8390
|
+
continue;
|
|
8391
|
+
}
|
|
8392
|
+
remaining -= size;
|
|
8393
|
+
bounded.push(item);
|
|
8394
|
+
continue;
|
|
8395
|
+
}
|
|
8396
|
+
if (longestStringBytes({
|
|
8397
|
+
...item,
|
|
8398
|
+
text: ""
|
|
8399
|
+
}) > 65536) {
|
|
8400
|
+
droppedItems += 1;
|
|
8401
|
+
continue;
|
|
8402
|
+
}
|
|
8403
|
+
const size = Buffer.byteLength(item.text, "utf8");
|
|
8404
|
+
const allow = Math.min(remaining - ITEM_ENVELOPE_BYTES, MAX_MCP_RESULT_ITEM_TEXT_BYTES);
|
|
8405
|
+
if (allow <= 0) {
|
|
8406
|
+
droppedItems += 1;
|
|
8407
|
+
exhausted = true;
|
|
8408
|
+
continue;
|
|
8409
|
+
}
|
|
8410
|
+
if (size <= allow) {
|
|
8411
|
+
remaining -= size + ITEM_ENVELOPE_BYTES;
|
|
8412
|
+
kept += size;
|
|
8413
|
+
bounded.push(item);
|
|
8414
|
+
continue;
|
|
8415
|
+
}
|
|
8416
|
+
const cut = truncateUtf8(item.text, allow);
|
|
8417
|
+
const cutBytes = Buffer.byteLength(cut, "utf8");
|
|
8418
|
+
kept += cutBytes;
|
|
8419
|
+
remaining -= cutBytes + ITEM_ENVELOPE_BYTES;
|
|
8420
|
+
truncatedItems += 1;
|
|
8421
|
+
bounded.push({
|
|
8422
|
+
...item,
|
|
8423
|
+
text: cut
|
|
8424
|
+
});
|
|
8425
|
+
if (remaining <= 0) exhausted = true;
|
|
8426
|
+
}
|
|
8427
|
+
const notice = {
|
|
8428
|
+
type: "text",
|
|
8429
|
+
text: truncationNotice({
|
|
8430
|
+
sourceBytes,
|
|
8431
|
+
keptBytes: kept,
|
|
8432
|
+
truncatedItems,
|
|
8433
|
+
droppedItems,
|
|
8434
|
+
droppedStructured
|
|
8435
|
+
})
|
|
8436
|
+
};
|
|
8437
|
+
return {
|
|
8438
|
+
...rest,
|
|
8439
|
+
content: [...bounded, notice]
|
|
8440
|
+
};
|
|
8441
|
+
}
|
|
8442
|
+
/**
|
|
8125
8443
|
* Return the daemon-hosted MCP bundler's current namespaced tool catalog.
|
|
8126
8444
|
* Called by the openclaw-mcp-bundler plugin on every tool-factory invocation;
|
|
8127
8445
|
* cheap (in-memory snapshot, no I/O).
|
|
@@ -8389,7 +8707,7 @@ async function handleMcpCallTool(bundler, params) {
|
|
|
8389
8707
|
try {
|
|
8390
8708
|
return {
|
|
8391
8709
|
ok: true,
|
|
8392
|
-
payload: await bundler.callTool(name, args)
|
|
8710
|
+
payload: boundMcpToolResult(await bundler.callTool(name, args))
|
|
8393
8711
|
};
|
|
8394
8712
|
} catch (err) {
|
|
8395
8713
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -9211,6 +9529,10 @@ let cloudConnected = false;
|
|
|
9211
9529
|
* so a duplicate is ACKed with the prior result without re-executing.
|
|
9212
9530
|
*/
|
|
9213
9531
|
const commandDedupe = new CommandDedupe();
|
|
9532
|
+
const workCommandDedupe = new CommandDedupe({
|
|
9533
|
+
maxEntries: 4,
|
|
9534
|
+
ttlMs: 5e3
|
|
9535
|
+
});
|
|
9214
9536
|
let shuttingDown = false;
|
|
9215
9537
|
let commandRegistry;
|
|
9216
9538
|
let resolvedCliVersion;
|
|
@@ -9553,7 +9875,7 @@ async function startDaemon() {
|
|
|
9553
9875
|
* bounce the box a second time.
|
|
9554
9876
|
*/
|
|
9555
9877
|
async function handleCloudCommand(command) {
|
|
9556
|
-
const { ack, duplicate } = await commandDedupe.run(command.commandId, () => executeCloudCommand(command));
|
|
9878
|
+
const { ack, duplicate } = await (isWorkCommand(command.command) ? workCommandDedupe : commandDedupe).run(command.commandId, () => executeCloudCommand(command));
|
|
9557
9879
|
if (duplicate) {
|
|
9558
9880
|
logger.info({
|
|
9559
9881
|
commandId: command.commandId,
|
|
@@ -9606,6 +9928,7 @@ async function drainRuntimeBeforeExit(reason) {
|
|
|
9606
9928
|
}, "Runtime still mid-turn at drain grace deadline — proceeding with daemon exit");
|
|
9607
9929
|
}
|
|
9608
9930
|
async function executeCloudCommand(command) {
|
|
9931
|
+
if (isWorkCommand(command.command)) return executeWorkCommand(command, config.agentId, config.runtime, ipcServer);
|
|
9609
9932
|
if (command.command === "daemon.update") {
|
|
9610
9933
|
const version = command.payload?.version ?? "latest";
|
|
9611
9934
|
setTimeout(() => {
|
|
@@ -9650,7 +9973,7 @@ async function executeCloudCommand(command) {
|
|
|
9650
9973
|
};
|
|
9651
9974
|
const payload = command.payload;
|
|
9652
9975
|
const runtime = config.runtime;
|
|
9653
|
-
const version = payload?.version ?? (runtime === "openclaw" ? "2026.
|
|
9976
|
+
const version = payload?.version ?? (runtime === "openclaw" ? "2026.9.5" : runtime === "claude-code" ? "0.1.6" : void 0);
|
|
9654
9977
|
upgradingRuntime = true;
|
|
9655
9978
|
setTimeout(() => {
|
|
9656
9979
|
(async () => {
|
|
@@ -9658,6 +9981,7 @@ async function executeCloudCommand(command) {
|
|
|
9658
9981
|
const { upgradeRuntime } = await import("./runtime-upgrade.js");
|
|
9659
9982
|
const result = await upgradeRuntime(runtime, version, runtimeProcess, {
|
|
9660
9983
|
...runtime === "openclaw" && openclawCliLockRef ? { cliLock: openclawCliLockRef } : {},
|
|
9984
|
+
...runtime === "openclaw" && isManagedMode() && process.env.ALFE_PRIVATE_DATA_PLANE !== "true" ? { nodeRemediation: {} } : {},
|
|
9661
9985
|
...runtime === "openclaw" && runtimeGateRef ? { gate: runtimeGateRef } : {}
|
|
9662
9986
|
});
|
|
9663
9987
|
if (result.success || result.runtimeChanged) {
|
|
@@ -9669,7 +9993,8 @@ async function executeCloudCommand(command) {
|
|
|
9669
9993
|
else {
|
|
9670
9994
|
logger.info({
|
|
9671
9995
|
runtime,
|
|
9672
|
-
runtimeVersion: resolvedRuntimeVersion
|
|
9996
|
+
runtimeVersion: resolvedRuntimeVersion,
|
|
9997
|
+
nodeUpgraded: result.nodeUpgraded ?? false
|
|
9673
9998
|
}, "Runtime version updated after upgrade");
|
|
9674
9999
|
cloudClient.updateRuntimeVersionAndReRegister(resolvedRuntimeVersion);
|
|
9675
10000
|
}
|
|
@@ -9677,6 +10002,7 @@ async function executeCloudCommand(command) {
|
|
|
9677
10002
|
if (!result.success) logger.error({
|
|
9678
10003
|
runtime,
|
|
9679
10004
|
version,
|
|
10005
|
+
nodeUpgraded: result.nodeUpgraded ?? false,
|
|
9680
10006
|
error: result.error
|
|
9681
10007
|
}, "Runtime upgrade did not complete cleanly");
|
|
9682
10008
|
} catch (err) {
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { f 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,45 @@ 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).
|
|
61
71
|
*/
|
|
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";
|
|
72
|
+
const OPENCLAW_NODE_REQUIREMENT = ">=24.16.0 <25 || >=26.1.0";
|
|
65
73
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
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.
|
|
77
|
+
*/
|
|
78
|
+
const OPENCLAW_MANAGED_NODE_MAJOR = 24;
|
|
79
|
+
/**
|
|
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
|
-
if (major === 25) return minor > 9 || minor === 9 && patch >= 0;
|
|
78
|
-
return major > 25;
|
|
89
|
+
if (major === 24) return minor >= 16;
|
|
90
|
+
if (major === 26) return minor >= 1;
|
|
91
|
+
return major > 26;
|
|
79
92
|
}
|
|
80
93
|
//#endregion
|
|
81
94
|
//#region ../../node_modules/.pnpm/dotenv@17.2.2/node_modules/dotenv/package.json
|
|
@@ -15389,11 +15402,25 @@ const DETECTOR_FLUSH_DEBOUNCE_MS = 1500;
|
|
|
15389
15402
|
const BACKOFF_INITIAL_MS = 1e3;
|
|
15390
15403
|
const BACKOFF_MAX_MS = 3e4;
|
|
15391
15404
|
const STABLE_UPTIME_MS = 6e4;
|
|
15392
|
-
/**
|
|
15405
|
+
/**
|
|
15406
|
+
* Alfe owns supervision and version rollout; OpenClaw must not self-manage.
|
|
15407
|
+
*
|
|
15408
|
+
* `OPENCLAW_NODE_UPDATE_RESPAWNED=1` also disables 2026.9.x's node-runtime
|
|
15409
|
+
* recovery, which would otherwise re-exec the CLI under whatever other Node it
|
|
15410
|
+
* finds on PATH when the current one fails engine admission. Alfe chooses the
|
|
15411
|
+
* Node binary (managed remediation in `node-remediation.ts`); an admission
|
|
15412
|
+
* failure must surface as a clean exit the daemon can report, not a silent
|
|
15413
|
+
* respawn under an unmanaged interpreter.
|
|
15414
|
+
*
|
|
15415
|
+
* `OPENCLAW_CONFIG_READONLY` must never be set on the child or on upgrade
|
|
15416
|
+
* commands: since 2026.9.x it makes openclaw.json immutable and blocks setup,
|
|
15417
|
+
* doctor repairs and plugin installs.
|
|
15418
|
+
*/
|
|
15393
15419
|
const OPENCLAW_EXTERNAL_SUPERVISOR_ENV = {
|
|
15394
15420
|
OPENCLAW_SUPERVISOR_MODE: "external",
|
|
15395
15421
|
OPENCLAW_SERVICE_REPAIR_POLICY: "external",
|
|
15396
|
-
OPENCLAW_NO_AUTO_UPDATE: "1"
|
|
15422
|
+
OPENCLAW_NO_AUTO_UPDATE: "1",
|
|
15423
|
+
OPENCLAW_NODE_UPDATE_RESPAWNED: "1"
|
|
15397
15424
|
};
|
|
15398
15425
|
/**
|
|
15399
15426
|
* Max time a NON-crash restart waits for an in-flight chat turn to finish
|
|
@@ -15739,4 +15766,4 @@ var RuntimeProcess = class {
|
|
|
15739
15766
|
}
|
|
15740
15767
|
};
|
|
15741
15768
|
//#endregion
|
|
15742
|
-
export { ErrorLineDetector as a, createLogger as c,
|
|
15769
|
+
export { ErrorLineDetector as a, createLogger as c, PINNED_OPENCLAW_VERSION as d, supportsOpenClawNode as f, CaptureThrottle as i, OPENCLAW_MANAGED_NODE_MAJOR as l, __require as m, OPENCLAW_EXTERNAL_SUPERVISOR_ENV as n, STRICT_ERROR_CAPTURE_PROFILE as o, __commonJSMin 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 supportsOpenClawNode, n as OPENCLAW_EXTERNAL_SUPERVISOR_ENV, 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":
|
|
@@ -67,12 +82,13 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
|
|
|
67
82
|
stderr: result.stderr
|
|
68
83
|
};
|
|
69
84
|
});
|
|
85
|
+
const openclawEnv = {
|
|
86
|
+
...process.env,
|
|
87
|
+
...OPENCLAW_EXTERNAL_SUPERVISOR_ENV
|
|
88
|
+
};
|
|
70
89
|
const commandOptions = (timeout) => runtime === "openclaw" ? {
|
|
71
90
|
timeout,
|
|
72
|
-
env:
|
|
73
|
-
...process.env,
|
|
74
|
-
...OPENCLAW_EXTERNAL_SUPERVISOR_ENV
|
|
75
|
-
}
|
|
91
|
+
env: openclawEnv
|
|
76
92
|
} : { timeout };
|
|
77
93
|
const cmd = resolveUpgradeCommand(runtime, version);
|
|
78
94
|
if (!cmd) {
|
|
@@ -89,24 +105,60 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
|
|
|
89
105
|
};
|
|
90
106
|
}
|
|
91
107
|
let previousOpenClawVersion;
|
|
108
|
+
let nodeUpgraded = false;
|
|
92
109
|
if (runtime === "openclaw") try {
|
|
93
110
|
const [{ stdout: nodeOutput }, { stdout: openClawOutput }] = await Promise.all([runCommand("node", ["--version"], commandOptions(1e4)), runCommand("openclaw", ["--version"], commandOptions(1e4))]);
|
|
94
|
-
|
|
111
|
+
previousOpenClawVersion = parseOpenClawVersion(openClawOutput);
|
|
112
|
+
let nodeVersion = nodeOutput.trim();
|
|
95
113
|
if (!supportsOpenClawNode(nodeVersion)) {
|
|
96
|
-
|
|
97
|
-
|
|
114
|
+
if (!options.nodeRemediation) {
|
|
115
|
+
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.`;
|
|
116
|
+
logger.error({
|
|
117
|
+
runtime,
|
|
118
|
+
version,
|
|
119
|
+
nodeVersion
|
|
120
|
+
}, message);
|
|
121
|
+
return {
|
|
122
|
+
success: false,
|
|
123
|
+
runtime,
|
|
124
|
+
version,
|
|
125
|
+
error: message
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const fromNode = nodeVersion;
|
|
129
|
+
logger.warn({
|
|
98
130
|
runtime,
|
|
99
131
|
version,
|
|
132
|
+
nodeVersion,
|
|
133
|
+
requirement: OPENCLAW_NODE_REQUIREMENT
|
|
134
|
+
}, "Host Node.js does not satisfy OpenClaw; installing the managed Node channel first");
|
|
135
|
+
try {
|
|
136
|
+
nodeVersion = await remediateManagedNode(runCommand, openclawEnv, options.nodeRemediation);
|
|
137
|
+
} catch (err) {
|
|
138
|
+
const replaced = err instanceof NodeRemediationError && err.nodeReplaced;
|
|
139
|
+
const resolvedNode = err instanceof NodeRemediationError ? err.resolvedNode : void 0;
|
|
140
|
+
nodeUpgraded = replaced;
|
|
141
|
+
if (!(err instanceof NodeRemediationError && err.cooldownActive)) captureNodeRemediation({
|
|
142
|
+
outcome: "failure",
|
|
143
|
+
fromNode,
|
|
144
|
+
...replaced && resolvedNode ? { toNode: resolvedNode } : {},
|
|
145
|
+
runtimeVersion: previousOpenClawVersion,
|
|
146
|
+
cause: err
|
|
147
|
+
});
|
|
148
|
+
throw err;
|
|
149
|
+
}
|
|
150
|
+
nodeUpgraded = true;
|
|
151
|
+
captureNodeRemediation({
|
|
152
|
+
outcome: "success",
|
|
153
|
+
fromNode,
|
|
154
|
+
toNode: nodeVersion,
|
|
155
|
+
runtimeVersion: previousOpenClawVersion
|
|
156
|
+
});
|
|
157
|
+
logger.info({
|
|
158
|
+
fromNode,
|
|
100
159
|
nodeVersion
|
|
101
|
-
},
|
|
102
|
-
return {
|
|
103
|
-
success: false,
|
|
104
|
-
runtime,
|
|
105
|
-
version,
|
|
106
|
-
error: message
|
|
107
|
-
};
|
|
160
|
+
}, "Managed Node.js remediation complete");
|
|
108
161
|
}
|
|
109
|
-
previousOpenClawVersion = parseOpenClawVersion(openClawOutput);
|
|
110
162
|
} catch (err) {
|
|
111
163
|
const message = `OpenClaw upgrade preflight failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
112
164
|
logger.error({
|
|
@@ -117,6 +169,7 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
|
|
|
117
169
|
success: false,
|
|
118
170
|
runtime,
|
|
119
171
|
version,
|
|
172
|
+
nodeUpgraded,
|
|
120
173
|
error: message
|
|
121
174
|
};
|
|
122
175
|
}
|
|
@@ -155,6 +208,7 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
|
|
|
155
208
|
const installedVersion = parseOpenClawVersion((await runCommand("openclaw", ["--version"], commandOptions(1e4))).stdout);
|
|
156
209
|
if (installedVersion !== version) throw new Error(`OpenClaw package verification failed: requested ${version}, resolved ${installedVersion ?? "unknown"}`);
|
|
157
210
|
runtimeChanged = installedVersion !== previousOpenClawVersion;
|
|
211
|
+
await runCommand("openclaw", [...OPENCLAW_ADMISSION_PROBE_ARGS], commandOptions(6e4));
|
|
158
212
|
doctorStarted = true;
|
|
159
213
|
await runCommand("openclaw", [
|
|
160
214
|
"doctor",
|
|
@@ -195,13 +249,15 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
|
|
|
195
249
|
else await performUpgrade();
|
|
196
250
|
logger.info({
|
|
197
251
|
runtime,
|
|
198
|
-
version
|
|
252
|
+
version,
|
|
253
|
+
nodeUpgraded
|
|
199
254
|
}, "Runtime upgraded successfully");
|
|
200
255
|
return {
|
|
201
256
|
success: true,
|
|
202
257
|
runtime,
|
|
203
258
|
version,
|
|
204
|
-
runtimeChanged
|
|
259
|
+
runtimeChanged,
|
|
260
|
+
nodeUpgraded
|
|
205
261
|
};
|
|
206
262
|
} catch (err) {
|
|
207
263
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -215,6 +271,7 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
|
|
|
215
271
|
runtime,
|
|
216
272
|
version,
|
|
217
273
|
runtimeChanged,
|
|
274
|
+
nodeUpgraded,
|
|
218
275
|
error: message
|
|
219
276
|
};
|
|
220
277
|
} 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,24 +44,83 @@ 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).
|
|
65
|
+
*/
|
|
66
|
+
declare const OPENCLAW_NODE_REQUIREMENT = ">=24.16.0 <25 || >=26.1.0";
|
|
67
|
+
/**
|
|
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.
|
|
55
71
|
*/
|
|
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";
|
|
72
|
+
declare const OPENCLAW_MANAGED_NODE_MAJOR = 24;
|
|
59
73
|
/**
|
|
60
|
-
* Whether a concrete Node.js version satisfies OpenClaw
|
|
61
|
-
* Accepts `process.versions.node` (`
|
|
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;
|
|
64
79
|
//#endregion
|
|
80
|
+
//#region src/node-remediation.d.ts
|
|
81
|
+
|
|
82
|
+
type RemediationCommandRunner = (command: string, args: string[], options: {
|
|
83
|
+
timeout: number;
|
|
84
|
+
env?: NodeJS.ProcessEnv;
|
|
85
|
+
}) => Promise<{
|
|
86
|
+
stdout: string;
|
|
87
|
+
stderr: string;
|
|
88
|
+
}>;
|
|
89
|
+
interface RemediateManagedNodeOptions {
|
|
90
|
+
/**
|
|
91
|
+
* Prove the currently installed `openclaw` still boots under the new Node
|
|
92
|
+
* (default true — the daemon needs the old runtime for the pre-upgrade
|
|
93
|
+
* backup). `alfe setup` on a fresh VM passes false: nothing is installed yet.
|
|
94
|
+
*/
|
|
95
|
+
verifyInstalledRuntime?: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Where the failure cooldown marker lives (default `~/.alfe`). Test seam;
|
|
98
|
+
* `null` disables the marker entirely.
|
|
99
|
+
*/
|
|
100
|
+
stateDirectory?: string | null;
|
|
101
|
+
/** Test seam for the cooldown clock. */
|
|
102
|
+
now?: () => number;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Thrown when remediation fails. `nodeReplaced` is the load-bearing field: a
|
|
106
|
+
* failure AFTER apt has swapped the host Node (verification or the installed
|
|
107
|
+
* runtime's boot check failed) leaves the host on a new, unproven Node, which
|
|
108
|
+
* is the state that needs an operator — very different from "never touched".
|
|
109
|
+
*/
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Install the managed Node.js channel on a Debian/Ubuntu managed host and
|
|
113
|
+
* return the resolved `node --version`.
|
|
114
|
+
*
|
|
115
|
+
* Runs BEFORE any runtime gate suspend or backup: the live OpenClaw child keeps
|
|
116
|
+
* its already-open binary, and later `openclaw`/`npm` spawns resolve the new
|
|
117
|
+
* binary through PATH. Fails closed (with `nodeReplaced` set accurately) if the
|
|
118
|
+
* resulting Node still misses the range or if the currently installed OpenClaw
|
|
119
|
+
* no longer boots under it — in that case the caller must not proceed to a
|
|
120
|
+
* backup it could not produce.
|
|
121
|
+
*/
|
|
122
|
+
declare function remediateManagedNode(runCommand: RemediationCommandRunner, baseEnv: NodeJS.ProcessEnv, options?: RemediateManagedNodeOptions): Promise<string>;
|
|
123
|
+
//#endregion
|
|
65
124
|
//#region src/claude-code-host-version.d.ts
|
|
66
125
|
/**
|
|
67
126
|
* The `@alfe.ai/claude-code-host` version Alfe pins the claude-code runtime to —
|
|
@@ -164,6 +223,14 @@ declare function captureIntegrationFailure(integration: string, phase: string, c
|
|
|
164
223
|
* Sentry is not initialised. Never throws.
|
|
165
224
|
*/
|
|
166
225
|
declare function captureCliFailure(context: string, cause?: unknown): void;
|
|
226
|
+
/**
|
|
227
|
+
* Fleet-visible record of a managed Node.js remediation attempt (the in-place
|
|
228
|
+
* Node 22 → 24 move `runtime-upgrade.ts` performs before installing an OpenClaw
|
|
229
|
+
* that dropped Node 22). One event per host per attempt so the campaign can
|
|
230
|
+
* count remediated vs failed agents; the daemon's local log alone cannot answer
|
|
231
|
+
* that. No-op when Sentry is not initialised. Never throws.
|
|
232
|
+
*/
|
|
233
|
+
|
|
167
234
|
/** A line of runtime child-process output attached to a crash event. */
|
|
168
235
|
interface RuntimeOutputLine {
|
|
169
236
|
stream: "stdout" | "stderr";
|
|
@@ -414,4 +481,4 @@ declare function checkExistingDaemon(): Promise<number | null>;
|
|
|
414
481
|
*/
|
|
415
482
|
declare function stopExistingDaemon(): Promise<boolean>;
|
|
416
483
|
//#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 };
|
|
484
|
+
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 supportsOpenClawNode, l as
|
|
1
|
+
import { d as PINNED_OPENCLAW_VERSION, f as supportsOpenClawNode, l as OPENCLAW_MANAGED_NODE_MAJOR, 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.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "Alfe local gateway daemon — persistent control plane for agent integrations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
"pino-roll": "^1.2.0",
|
|
24
24
|
"smol-toml": ">=1.6.1",
|
|
25
25
|
"ws": "^8.18.0",
|
|
26
|
-
"@alfe.ai/agent-api-client": "^0.19.
|
|
26
|
+
"@alfe.ai/agent-api-client": "^0.19.2",
|
|
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.7",
|
|
31
31
|
"@alfe.ai/mcp-bundler": "^0.4.2"
|
|
32
32
|
},
|
|
33
33
|
"license": "UNLICENSED",
|