@alfe.ai/gateway 0.10.6 → 0.10.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/health.js CHANGED
@@ -1,242 +1,52 @@
1
- import { a as ErrorLineDetector, c as createLogger, f as parseOpenClawVersion, h as __require, i as CaptureThrottle, m as __commonJSMin, n as OPENCLAW_EXTERNAL_SUPERVISOR_ENV, o as STRICT_ERROR_CAPTURE_PROFILE, r as RuntimeProcess, s as toFingerprintKey, t as DRAIN_GRACE_MS } from "./runtime-process.js";
1
+ import { C as resolveOpenClawUpgradeTarget, E as __require, S as parseOpenClawVersion, T as __commonJSMin, _ as createLogger, a as recoverInterruptedRuntimeUpgrade, d as OPENCLAW_EXTERNAL_SUPERVISOR_ENV, f as RuntimeProcess, g as toFingerprintKey, h as STRICT_ERROR_CAPTURE_PROFILE, i as detectUpgradeIsolation, m as ErrorLineDetector, o as startBootUpgradeRecovery, p as CaptureThrottle, r as defaultRuntimeUpgradeMarkerPath, u as DRAIN_GRACE_MS } from "./runtime-upgrade-recovery.js";
2
2
  import { a as captureFatal, d as flushSentry, f as initAgentSentry, h as logger, o as captureIntegrationFailure, p as setAgentContext, s as captureMcpFailure } from "./sentry.js";
3
3
  import { chmod, mkdir, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
4
4
  import { dirname, join } from "node:path";
5
5
  import { homedir } from "node:os";
6
6
  import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
7
+ import { ClaudeCodeApplier, ClaudeCodeMcpSync, HermesApplier, HermesMcpSync, IntegrationManager, IntegrationManagerAdapter, LockManager, McpApplier, NoopOpenClawCliLock, OpenClawApplier, SerialOpenClawCliLock } from "@alfe.ai/integrations";
8
+ import { execFile, execSync } from "node:child_process";
9
+ import { promisify } from "node:util";
7
10
  import { getEndpointFromToken, readConfig } from "@alfe.ai/config";
8
11
  import { AgentApiClient } from "@alfe.ai/agent-api-client";
9
12
  import { parse } from "smol-toml";
10
13
  import WebSocket from "ws";
11
- import { execFile, execSync } from "node:child_process";
12
- import { promisify } from "node:util";
13
- import { ClaudeCodeApplier, ClaudeCodeMcpSync, HermesApplier, HermesMcpSync, IntegrationManager, IntegrationManagerAdapter, McpApplier, NoopOpenClawCliLock, OpenClawApplier, SerialOpenClawCliLock } from "@alfe.ai/integrations";
14
+ import { createHash, randomUUID } from "node:crypto";
14
15
  import { Manager, McpBundler, defaultConnect, serverLaunchFingerprint } from "@alfe.ai/mcp-bundler";
15
16
  import { createConnection, createServer } from "node:net";
16
17
  import { pathToFileURL } from "node:url";
17
- import { randomUUID } from "node:crypto";
18
- //#region src/claude-code-host-version.ts
18
+ //#region src/openclaw-plugin-ownership.ts
19
19
  /**
20
- * The `@alfe.ai/claude-code-host` version Alfe pins the claude-code runtime to —
21
- * the single source of truth for its install paths:
22
- * - `@alfe.ai/cli` `installClaudeCodeHost` (fresh `alfe setup`)
23
- * - the daemon `runtime.update` default (this package)
24
- *
25
- * Pinning (rather than floating `npm install -g @alfe.ai/claude-code-host` →
26
- * `latest`) is deliberate: the host is the claude-code runtime child on every
27
- * self-hosted claude-code agent, so a bad `latest` publish would auto-reach
28
- * every new `alfe setup` with no CLI gate. Bump deliberately after validating a
29
- * new host, then cut a CLI release so the pin travels with the CLI version. The
30
- * dashboard still surfaces npm-`latest` as an explicit opt-in upgrade.
20
+ * Runtime-lock plugin ownership for the OpenClaw upgrade preflight.
31
21
  *
32
- * NOTE: claude-code is self-hosted-only (no managed Docker image), so — unlike
33
- * `PINNED_OPENCLAW_VERSION` — there is no `services/compute/Dockerfile` mirror to
34
- * keep in sync.
22
+ * `~/.alfe/runtime-lock.json` (the integrations package's `LockManager`) records
23
+ * which integration contributed each OpenClaw plugin. A plugin that is not in
24
+ * it is owned by nothing the daemon manages. The upgrade only ever acts on such
25
+ * a plugin when it is ALSO an `@alfe.ai/*` package and definitively
26
+ * activate-only (see openclaw-plugin-compat.ts). A missing lock file or one
27
+ * with no OpenClaw rows is "ownership unknown" (block, never sweep). The
28
+ * residual risk is a lock that lists SOME plugins but dropped a row for one
29
+ * still in use; that plugin is activate-only, so it would fail to load on the
30
+ * target runtime anyway (`alfe remove` scope rule: only Alfe artifacts).
35
31
  */
36
- const PINNED_CLAUDE_CODE_HOST_VERSION = "0.1.6";
37
- //#endregion
38
- //#region ../../packages-internal/ids/dist/prefixes.js
39
- const ID_PREFIXES = {
40
- agent: "agt",
41
- organization: "org",
42
- person: "per",
43
- auditEvent: "evt",
44
- token: "tok",
45
- transaction: "txn",
46
- subscription: "sub",
47
- conversation: "conv",
48
- referral: "ref",
49
- promoCode: "prc",
50
- promoRedemption: "prr",
51
- pendingPromo: "pdp",
52
- remoteSession: "rms",
53
- webhook: "whk",
54
- webhookDelivery: "wdl",
55
- onboardingSession: "obs",
56
- inviteToken: "inv",
57
- claimToken: "clm",
58
- usageLimit: "lim",
59
- team: "team",
60
- project: "proj",
61
- goal: "goal",
62
- memory: "mem",
63
- knowledgeTriple: "kgt",
64
- accessCode: "acc",
65
- pendingInject: "pij",
66
- identity: "idn",
67
- identityNote: "ino",
68
- identityTag: "itg",
69
- identityAlias: "ial",
70
- identityVerification: "ivf",
71
- role: "role",
72
- directGrant: "dgr",
73
- revision: "rev",
74
- fact: "kfc",
75
- changeRequest: "chr",
76
- oauthConnection: "con",
77
- channel: "chn",
78
- oauthConnectionRequest: "crq",
79
- deviceCode: "dvc",
80
- attachment: "att",
81
- run: "run",
82
- request: "req",
83
- connection: "conn",
84
- correlation: "cor",
85
- command: "cmd",
86
- message: "msg",
87
- ipcRequest: "ipc",
88
- pluginConnection: "plg"
89
- };
90
- //#endregion
91
- //#region ../../packages-internal/ids/dist/create.js
92
- var import_index_umd = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
93
- (function(global, factory) {
94
- typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.ULID = {}));
95
- })(exports, (function(exports$1) {
96
- "use strict";
97
- function createError(message) {
98
- const err = new Error(message);
99
- err.source = "ulid";
100
- return err;
101
- }
102
- const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
103
- const ENCODING_LEN = 32;
104
- const TIME_MAX = Math.pow(2, 48) - 1;
105
- const TIME_LEN = 10;
106
- const RANDOM_LEN = 16;
107
- function replaceCharAt(str, index, char) {
108
- if (index > str.length - 1) return str;
109
- return str.substr(0, index) + char + str.substr(index + 1);
110
- }
111
- function incrementBase32(str) {
112
- let done = void 0;
113
- let index = str.length;
114
- let char;
115
- let charIndex;
116
- const maxCharIndex = ENCODING_LEN - 1;
117
- while (!done && index-- >= 0) {
118
- char = str[index];
119
- charIndex = ENCODING.indexOf(char);
120
- if (charIndex === -1) throw createError("incorrectly encoded string");
121
- if (charIndex === maxCharIndex) {
122
- str = replaceCharAt(str, index, ENCODING[0]);
123
- continue;
124
- }
125
- done = replaceCharAt(str, index, ENCODING[charIndex + 1]);
126
- }
127
- if (typeof done === "string") return done;
128
- throw createError("cannot increment this string");
129
- }
130
- function randomChar(prng) {
131
- let rand = Math.floor(prng() * ENCODING_LEN);
132
- if (rand === ENCODING_LEN) rand = ENCODING_LEN - 1;
133
- return ENCODING.charAt(rand);
134
- }
135
- function encodeTime(now, len) {
136
- if (isNaN(now)) throw new Error(now + " must be a number");
137
- if (now > TIME_MAX) throw createError("cannot encode time greater than " + TIME_MAX);
138
- if (now < 0) throw createError("time must be positive");
139
- if (Number.isInteger(Number(now)) === false) throw createError("time must be an integer");
140
- let mod;
141
- let str = "";
142
- for (; len > 0; len--) {
143
- mod = now % ENCODING_LEN;
144
- str = ENCODING.charAt(mod) + str;
145
- now = (now - mod) / ENCODING_LEN;
146
- }
147
- return str;
148
- }
149
- function encodeRandom(len, prng) {
150
- let str = "";
151
- for (; len > 0; len--) str = randomChar(prng) + str;
152
- return str;
153
- }
154
- function decodeTime(id) {
155
- if (id.length !== TIME_LEN + RANDOM_LEN) throw createError("malformed ulid");
156
- var time = id.substr(0, TIME_LEN).split("").reverse().reduce((carry, char, index) => {
157
- const encodingIndex = ENCODING.indexOf(char);
158
- if (encodingIndex === -1) throw createError("invalid character found: " + char);
159
- return carry += encodingIndex * Math.pow(ENCODING_LEN, index);
160
- }, 0);
161
- if (time > TIME_MAX) throw createError("malformed ulid, timestamp too large");
162
- return time;
163
- }
164
- function detectPrng(allowInsecure = false, root) {
165
- if (!root) root = typeof window !== "undefined" ? window : null;
166
- const browserCrypto = root && (root.crypto || root.msCrypto);
167
- if (browserCrypto) return () => {
168
- const buffer = new Uint8Array(1);
169
- browserCrypto.getRandomValues(buffer);
170
- return buffer[0] / 255;
171
- };
172
- else try {
173
- const nodeCrypto = __require("crypto");
174
- return () => nodeCrypto.randomBytes(1).readUInt8() / 255;
175
- } catch (e) {}
176
- if (allowInsecure) {
177
- try {
178
- console.error("secure crypto unusable, falling back to insecure Math.random()!");
179
- } catch (e) {}
180
- return () => Math.random();
181
- }
182
- throw createError("secure crypto unusable, insecure Math.random not allowed");
183
- }
184
- function factory(currPrng) {
185
- if (!currPrng) currPrng = detectPrng();
186
- return function ulid(seedTime) {
187
- if (isNaN(seedTime)) seedTime = Date.now();
188
- return encodeTime(seedTime, TIME_LEN) + encodeRandom(RANDOM_LEN, currPrng);
189
- };
190
- }
191
- function monotonicFactory(currPrng) {
192
- if (!currPrng) currPrng = detectPrng();
193
- let lastTime = 0;
194
- let lastRandom;
195
- return function ulid(seedTime) {
196
- if (isNaN(seedTime)) seedTime = Date.now();
197
- if (seedTime <= lastTime) {
198
- const incrementedRandom = lastRandom = incrementBase32(lastRandom);
199
- return encodeTime(lastTime, TIME_LEN) + incrementedRandom;
200
- }
201
- lastTime = seedTime;
202
- const newRandom = lastRandom = encodeRandom(RANDOM_LEN, currPrng);
203
- return encodeTime(seedTime, TIME_LEN) + newRandom;
204
- };
205
- }
206
- const ulid = factory();
207
- exports$1.decodeTime = decodeTime;
208
- exports$1.detectPrng = detectPrng;
209
- exports$1.encodeRandom = encodeRandom;
210
- exports$1.encodeTime = encodeTime;
211
- exports$1.factory = factory;
212
- exports$1.incrementBase32 = incrementBase32;
213
- exports$1.monotonicFactory = monotonicFactory;
214
- exports$1.randomChar = randomChar;
215
- exports$1.replaceCharAt = replaceCharAt;
216
- exports$1.ulid = ulid;
217
- }));
218
- })))();
219
- function createId(prefix) {
220
- return `${prefix}_${(0, import_index_umd.ulid)()}`;
32
+ /** Bare npm package name from a manifest plugin spec (`@scope/name@1.2.3` → `@scope/name`). */
33
+ function barePluginPackage(spec) {
34
+ const at = spec.lastIndexOf("@");
35
+ return at <= 0 ? spec : spec.slice(0, at);
221
36
  }
222
- function correlationId() {
223
- return createId(ID_PREFIXES.correlation);
224
- }
225
- function pluginConnectionId() {
226
- return createId(ID_PREFIXES.pluginConnection);
227
- }
228
- //#endregion
229
- //#region ../../packages-internal/types/dist/lib/enum-values.js
230
- /**
231
- * Converts a const enum object into a non-empty readonly tuple.
232
- *
233
- * Satisfies both `z.enum()` and ElectroDB `type` field requirements
234
- * (both need `readonly [string, ...string[]]`).
235
- */
236
- function enumValues(obj) {
237
- const values = Object.values(obj);
238
- if (values.length === 0) throw new TypeError("enumValues requires at least one value");
239
- return values;
37
+ const DEFAULT_LOCK_PATH = join(homedir(), ".alfe", "runtime-lock.json");
38
+ function buildOpenClawPluginOwnership(applier, lockPath = DEFAULT_LOCK_PATH, lock = new LockManager(lockPath)) {
39
+ return {
40
+ ownedPlugins() {
41
+ if (!existsSync(lockPath)) throw new Error(`runtime lock ${lockPath} does not exist`);
42
+ const plugins = lock.read().runtimes.openclaw?.plugins ?? [];
43
+ if (plugins.length === 0) throw new Error("runtime lock records no OpenClaw plugin owners");
44
+ const owned = /* @__PURE__ */ new Map();
45
+ for (const entry of plugins) owned.set(barePluginPackage(entry.package), entry.sourceIntegration);
46
+ return owned;
47
+ },
48
+ removePlugin: (pkg) => applier.removePlugin(pkg)
49
+ };
240
50
  }
241
51
  Object.freeze({ status: "aborted" });
242
52
  function $constructor(name, initializer, params) {
@@ -758,7 +568,7 @@ const _safeDecodeAsync = (_Err) => async (schema, value, _ctx) => {
758
568
  //#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/regexes.js
759
569
  const cuid = /^[cC][^\s-]{8,}$/;
760
570
  const cuid2 = /^[0-9a-z]+$/;
761
- const ulid = /^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/;
571
+ const ulid$1 = /^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/;
762
572
  const xid = /^[0-9a-vA-V]{20}$/;
763
573
  const ksuid = /^[A-Za-z0-9]{27}$/;
764
574
  const nanoid = /^[a-zA-Z0-9_-]{21}$/;
@@ -1405,7 +1215,7 @@ const $ZodCUID2 = /* @__PURE__ */ $constructor("$ZodCUID2", (inst, def) => {
1405
1215
  $ZodStringFormat.init(inst, def);
1406
1216
  });
1407
1217
  const $ZodULID = /* @__PURE__ */ $constructor("$ZodULID", (inst, def) => {
1408
- def.pattern ?? (def.pattern = ulid);
1218
+ def.pattern ?? (def.pattern = ulid$1);
1409
1219
  $ZodStringFormat.init(inst, def);
1410
1220
  });
1411
1221
  const $ZodXID = /* @__PURE__ */ $constructor("$ZodXID", (inst, def) => {
@@ -3920,6 +3730,230 @@ function superRefine(fn) {
3920
3730
  return /* @__PURE__ */ _superRefine(fn);
3921
3731
  }
3922
3732
  //#endregion
3733
+ //#region src/claude-code-host-version.ts
3734
+ /**
3735
+ * The `@alfe.ai/claude-code-host` version Alfe pins the claude-code runtime to —
3736
+ * the single source of truth for its install paths:
3737
+ * - `@alfe.ai/cli` `installClaudeCodeHost` (fresh `alfe setup`)
3738
+ * - the daemon `runtime.update` default (this package)
3739
+ *
3740
+ * Pinning (rather than floating `npm install -g @alfe.ai/claude-code-host` →
3741
+ * `latest`) is deliberate: the host is the claude-code runtime child on every
3742
+ * self-hosted claude-code agent, so a bad `latest` publish would auto-reach
3743
+ * every new `alfe setup` with no CLI gate. Bump deliberately after validating a
3744
+ * new host, then cut a CLI release so the pin travels with the CLI version. The
3745
+ * dashboard still surfaces npm-`latest` as an explicit opt-in upgrade.
3746
+ *
3747
+ * NOTE: claude-code is self-hosted-only (no managed Docker image), so — unlike
3748
+ * `PINNED_OPENCLAW_VERSION` — there is no `services/compute/Dockerfile` mirror to
3749
+ * keep in sync.
3750
+ */
3751
+ const PINNED_CLAUDE_CODE_HOST_VERSION = "0.1.6";
3752
+ //#endregion
3753
+ //#region ../../packages-internal/ids/dist/prefixes.js
3754
+ const ID_PREFIXES = {
3755
+ agent: "agt",
3756
+ organization: "org",
3757
+ person: "per",
3758
+ auditEvent: "evt",
3759
+ token: "tok",
3760
+ transaction: "txn",
3761
+ subscription: "sub",
3762
+ conversation: "conv",
3763
+ referral: "ref",
3764
+ promoCode: "prc",
3765
+ promoRedemption: "prr",
3766
+ pendingPromo: "pdp",
3767
+ remoteSession: "rms",
3768
+ webhook: "whk",
3769
+ webhookDelivery: "wdl",
3770
+ onboardingSession: "obs",
3771
+ inviteToken: "inv",
3772
+ claimToken: "clm",
3773
+ usageLimit: "lim",
3774
+ team: "team",
3775
+ project: "proj",
3776
+ goal: "goal",
3777
+ memory: "mem",
3778
+ knowledgeTriple: "kgt",
3779
+ accessCode: "acc",
3780
+ pendingInject: "pij",
3781
+ identity: "idn",
3782
+ identityNote: "ino",
3783
+ identityTag: "itg",
3784
+ identityAlias: "ial",
3785
+ identityVerification: "ivf",
3786
+ role: "role",
3787
+ directGrant: "dgr",
3788
+ revision: "rev",
3789
+ fact: "kfc",
3790
+ changeRequest: "chr",
3791
+ oauthConnection: "con",
3792
+ channel: "chn",
3793
+ oauthConnectionRequest: "crq",
3794
+ deviceCode: "dvc",
3795
+ attachment: "att",
3796
+ run: "run",
3797
+ request: "req",
3798
+ connection: "conn",
3799
+ correlation: "cor",
3800
+ command: "cmd",
3801
+ message: "msg",
3802
+ ipcRequest: "ipc",
3803
+ pluginConnection: "plg"
3804
+ };
3805
+ //#endregion
3806
+ //#region ../../packages-internal/ids/dist/create.js
3807
+ var import_index_umd = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
3808
+ (function(global, factory) {
3809
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.ULID = {}));
3810
+ })(exports, (function(exports$1) {
3811
+ "use strict";
3812
+ function createError(message) {
3813
+ const err = new Error(message);
3814
+ err.source = "ulid";
3815
+ return err;
3816
+ }
3817
+ const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
3818
+ const ENCODING_LEN = 32;
3819
+ const TIME_MAX = Math.pow(2, 48) - 1;
3820
+ const TIME_LEN = 10;
3821
+ const RANDOM_LEN = 16;
3822
+ function replaceCharAt(str, index, char) {
3823
+ if (index > str.length - 1) return str;
3824
+ return str.substr(0, index) + char + str.substr(index + 1);
3825
+ }
3826
+ function incrementBase32(str) {
3827
+ let done = void 0;
3828
+ let index = str.length;
3829
+ let char;
3830
+ let charIndex;
3831
+ const maxCharIndex = ENCODING_LEN - 1;
3832
+ while (!done && index-- >= 0) {
3833
+ char = str[index];
3834
+ charIndex = ENCODING.indexOf(char);
3835
+ if (charIndex === -1) throw createError("incorrectly encoded string");
3836
+ if (charIndex === maxCharIndex) {
3837
+ str = replaceCharAt(str, index, ENCODING[0]);
3838
+ continue;
3839
+ }
3840
+ done = replaceCharAt(str, index, ENCODING[charIndex + 1]);
3841
+ }
3842
+ if (typeof done === "string") return done;
3843
+ throw createError("cannot increment this string");
3844
+ }
3845
+ function randomChar(prng) {
3846
+ let rand = Math.floor(prng() * ENCODING_LEN);
3847
+ if (rand === ENCODING_LEN) rand = ENCODING_LEN - 1;
3848
+ return ENCODING.charAt(rand);
3849
+ }
3850
+ function encodeTime(now, len) {
3851
+ if (isNaN(now)) throw new Error(now + " must be a number");
3852
+ if (now > TIME_MAX) throw createError("cannot encode time greater than " + TIME_MAX);
3853
+ if (now < 0) throw createError("time must be positive");
3854
+ if (Number.isInteger(Number(now)) === false) throw createError("time must be an integer");
3855
+ let mod;
3856
+ let str = "";
3857
+ for (; len > 0; len--) {
3858
+ mod = now % ENCODING_LEN;
3859
+ str = ENCODING.charAt(mod) + str;
3860
+ now = (now - mod) / ENCODING_LEN;
3861
+ }
3862
+ return str;
3863
+ }
3864
+ function encodeRandom(len, prng) {
3865
+ let str = "";
3866
+ for (; len > 0; len--) str = randomChar(prng) + str;
3867
+ return str;
3868
+ }
3869
+ function decodeTime(id) {
3870
+ if (id.length !== TIME_LEN + RANDOM_LEN) throw createError("malformed ulid");
3871
+ var time = id.substr(0, TIME_LEN).split("").reverse().reduce((carry, char, index) => {
3872
+ const encodingIndex = ENCODING.indexOf(char);
3873
+ if (encodingIndex === -1) throw createError("invalid character found: " + char);
3874
+ return carry += encodingIndex * Math.pow(ENCODING_LEN, index);
3875
+ }, 0);
3876
+ if (time > TIME_MAX) throw createError("malformed ulid, timestamp too large");
3877
+ return time;
3878
+ }
3879
+ function detectPrng(allowInsecure = false, root) {
3880
+ if (!root) root = typeof window !== "undefined" ? window : null;
3881
+ const browserCrypto = root && (root.crypto || root.msCrypto);
3882
+ if (browserCrypto) return () => {
3883
+ const buffer = new Uint8Array(1);
3884
+ browserCrypto.getRandomValues(buffer);
3885
+ return buffer[0] / 255;
3886
+ };
3887
+ else try {
3888
+ const nodeCrypto = __require("crypto");
3889
+ return () => nodeCrypto.randomBytes(1).readUInt8() / 255;
3890
+ } catch (e) {}
3891
+ if (allowInsecure) {
3892
+ try {
3893
+ console.error("secure crypto unusable, falling back to insecure Math.random()!");
3894
+ } catch (e) {}
3895
+ return () => Math.random();
3896
+ }
3897
+ throw createError("secure crypto unusable, insecure Math.random not allowed");
3898
+ }
3899
+ function factory(currPrng) {
3900
+ if (!currPrng) currPrng = detectPrng();
3901
+ return function ulid(seedTime) {
3902
+ if (isNaN(seedTime)) seedTime = Date.now();
3903
+ return encodeTime(seedTime, TIME_LEN) + encodeRandom(RANDOM_LEN, currPrng);
3904
+ };
3905
+ }
3906
+ function monotonicFactory(currPrng) {
3907
+ if (!currPrng) currPrng = detectPrng();
3908
+ let lastTime = 0;
3909
+ let lastRandom;
3910
+ return function ulid(seedTime) {
3911
+ if (isNaN(seedTime)) seedTime = Date.now();
3912
+ if (seedTime <= lastTime) {
3913
+ const incrementedRandom = lastRandom = incrementBase32(lastRandom);
3914
+ return encodeTime(lastTime, TIME_LEN) + incrementedRandom;
3915
+ }
3916
+ lastTime = seedTime;
3917
+ const newRandom = lastRandom = encodeRandom(RANDOM_LEN, currPrng);
3918
+ return encodeTime(seedTime, TIME_LEN) + newRandom;
3919
+ };
3920
+ }
3921
+ const ulid = factory();
3922
+ exports$1.decodeTime = decodeTime;
3923
+ exports$1.detectPrng = detectPrng;
3924
+ exports$1.encodeRandom = encodeRandom;
3925
+ exports$1.encodeTime = encodeTime;
3926
+ exports$1.factory = factory;
3927
+ exports$1.incrementBase32 = incrementBase32;
3928
+ exports$1.monotonicFactory = monotonicFactory;
3929
+ exports$1.randomChar = randomChar;
3930
+ exports$1.replaceCharAt = replaceCharAt;
3931
+ exports$1.ulid = ulid;
3932
+ }));
3933
+ })))();
3934
+ function createId(prefix) {
3935
+ return `${prefix}_${(0, import_index_umd.ulid)()}`;
3936
+ }
3937
+ function correlationId() {
3938
+ return createId(ID_PREFIXES.correlation);
3939
+ }
3940
+ function pluginConnectionId() {
3941
+ return createId(ID_PREFIXES.pluginConnection);
3942
+ }
3943
+ //#endregion
3944
+ //#region ../../packages-internal/types/dist/lib/enum-values.js
3945
+ /**
3946
+ * Converts a const enum object into a non-empty readonly tuple.
3947
+ *
3948
+ * Satisfies both `z.enum()` and ElectroDB `type` field requirements
3949
+ * (both need `readonly [string, ...string[]]`).
3950
+ */
3951
+ function enumValues(obj) {
3952
+ const values = Object.values(obj);
3953
+ if (values.length === 0) throw new TypeError("enumValues requires at least one value");
3954
+ return values;
3955
+ }
3956
+ //#endregion
3923
3957
  //#region ../../packages-internal/types/dist/lib/usd.js
3924
3958
  function isWholeUsdCentAmount(amountUsd) {
3925
3959
  const roundedCents = Math.round(amountUsd * 100);
@@ -4885,7 +4919,33 @@ var AlfeApiClient = class {
4885
4919
  }
4886
4920
  };
4887
4921
  //#endregion
4922
+ //#region ../../packages-internal/api-client/dist/services/billing.js
4923
+ /** Sizes every deployed billing API returns. */
4924
+ const BASE_MANAGED_AGENT_SIZES = [
4925
+ "small",
4926
+ "medium",
4927
+ "large"
4928
+ ];
4929
+ /**
4930
+ * Fill `agentAddonSizesCovered` for an API that predates it. Such an API
4931
+ * predates xl too, so the all-sizes flag meant the sizes that existed then;
4932
+ * xl stays uncovered (an undiscounted price is the safe display).
4933
+ */
4934
+ function normalizeSubscriptionDiscount(wire) {
4935
+ return {
4936
+ ...wire,
4937
+ agentAddonSizesCovered: wire.agentAddonSizesCovered ?? (wire.appliesToAgentAddons ? [...BASE_MANAGED_AGENT_SIZES] : [])
4938
+ };
4939
+ }
4940
+ //#endregion
4888
4941
  //#region ../../packages-internal/api-client/dist/services/auth.js
4942
+ function normalizePlatformSubscriptionStatus(wire) {
4943
+ const { discount, ...rest } = wire;
4944
+ return discount ? {
4945
+ ...rest,
4946
+ discount: normalizeSubscriptionDiscount(discount)
4947
+ } : rest;
4948
+ }
4889
4949
  var AuthService = class {
4890
4950
  client;
4891
4951
  constructor(client) {
@@ -5040,7 +5100,10 @@ var AuthService = class {
5040
5100
  });
5041
5101
  }
5042
5102
  getSubscriptionStatus() {
5043
- return this.client.request(`${this.prefix}/subscription`);
5103
+ return this.client.request(`${this.prefix}/subscription`).then((r) => r.ok ? {
5104
+ ...r,
5105
+ data: normalizePlatformSubscriptionStatus(r.data)
5106
+ } : r);
5044
5107
  }
5045
5108
  /**
5046
5109
  * Mint a short-lived, single-use Clerk sign-in token for the calling user so
@@ -5868,6 +5931,13 @@ const log$6 = logger.child({ component: "RuntimeGate" });
5868
5931
  var RuntimeGate = class {
5869
5932
  depth = 0;
5870
5933
  wasRunning = false;
5934
+ /**
5935
+ * True once the OUTERMOST `enter()` has finished stopping the runtime child
5936
+ * (its `stopWhenIdle()` resolved, i.e. the process exited), until the gate
5937
+ * fully reopens. `depth > 0` alone is not enough: depth is bumped before the
5938
+ * stop is awaited, so it is already set while the old child is draining.
5939
+ */
5940
+ stopSettled = false;
5871
5941
  exclusive = false;
5872
5942
  /** Brackets parked behind an exclusive hold (or an exclusive request parked behind open brackets). */
5873
5943
  waiters = [];
@@ -5882,6 +5952,14 @@ var RuntimeGate = class {
5882
5952
  get isSuspended() {
5883
5953
  return this.depth > 0;
5884
5954
  }
5955
+ /**
5956
+ * Whether the runtime child is PROVABLY down right now: a bracket is open and
5957
+ * its stop has completed. The OpenClaw applier's one direct openclaw.json
5958
+ * write (the dangling channel-ref repair) is allow-listed on this.
5959
+ */
5960
+ get isRuntimeStopped() {
5961
+ return this.depth > 0 && this.stopSettled;
5962
+ }
5885
5963
  /** True while an exclusive bracket is open. */
5886
5964
  get isExclusive() {
5887
5965
  return this.exclusive;
@@ -5934,12 +6012,14 @@ var RuntimeGate = class {
5934
6012
  }
5935
6013
  async enter() {
5936
6014
  if (this.depth++ > 0) return;
6015
+ this.stopSettled = false;
5937
6016
  const rp = this.getRuntime();
5938
6017
  this.wasRunning = rp?.isActive ?? false;
5939
6018
  if (rp && this.wasRunning) {
5940
6019
  log$6.info("Suspending runtime for mutating reconcile (avoid concurrent SQLite writers)");
5941
6020
  await rp.stopWhenIdle();
5942
6021
  }
6022
+ if (this.depth > 0) this.stopSettled = true;
5943
6023
  }
5944
6024
  /**
5945
6025
  * Resume the runtime child. Only the outermost call (depth 1 → 0) actually
@@ -5956,6 +6036,7 @@ var RuntimeGate = class {
5956
6036
  rp.resume();
5957
6037
  }
5958
6038
  this.wasRunning = false;
6039
+ this.stopSettled = false;
5959
6040
  if (this.exclusive) this.exclusive = false;
5960
6041
  this.wakeWaiters();
5961
6042
  return Promise.resolve();
@@ -6027,6 +6108,22 @@ var NoopRuntimeGate = class {
6027
6108
  };
6028
6109
  //#endregion
6029
6110
  //#region src/reconciliation.ts
6111
+ /**
6112
+ * ReconciliationEngine — converges local integration state toward cloud desired state.
6113
+ *
6114
+ * Takes: desired integrations from cloud + reference to the local integration manager.
6115
+ * Produces: a ReconciliationReport of actions taken and resulting actual statuses.
6116
+ *
6117
+ * For each desired integration with desiredStatus="active":
6118
+ * 1. Check if installed at correct version (from ~/.alfe/integrations.json)
6119
+ * 2. If not installed or wrong version: call integrationManager.install()
6120
+ * 3. If installed but not active: call integrationManager.activate()
6121
+ *
6122
+ * For each locally installed integration NOT in desired list (or desiredStatus="removed"):
6123
+ * 1. Call integrationManager.deactivate() then integrationManager.uninstall()
6124
+ *
6125
+ * Returns ReconciliationReport with results for each integration processed.
6126
+ */
6030
6127
  const log$5 = logger.child({ component: "Reconciliation" });
6031
6128
  /**
6032
6129
  * How many times reconcile will re-attempt activation of an intact integration
@@ -6055,6 +6152,25 @@ var ReconciliationEngine = class {
6055
6152
  */
6056
6153
  heldSuspension = false;
6057
6154
  /**
6155
+ * Most recent per-attempt re-activation error per integration. Becomes the
6156
+ * error signature of the "re-activation attempts exhausted" report, so the
6157
+ * terminal event says WHY the integration is stuck.
6158
+ */
6159
+ lastReactivateError = /* @__PURE__ */ new Map();
6160
+ /**
6161
+ * Sentry dedupe for reconcile outcomes that RECUR on every DESIRED_STATE
6162
+ * push once a budget is spent (re-activation exhausted, auto-reinstall
6163
+ * blocked) or that repeat identically across attempts. Keyed on
6164
+ * integration + phase + error signature, for one stuck EPISODE: the keys
6165
+ * are cleared by {@link resetSelfHealBudget} whenever the retry budget
6166
+ * resets (reinstall, upgrade, successful activation, healthy pass), so a
6167
+ * manual reinstall that fails with the same error reports again. The engine
6168
+ * lives as long as the daemon process (owned by `CloudClient`). Without it
6169
+ * a single stuck integration produced one event per push (51 for one agent).
6170
+ * Logs and the cloud-facing report are NOT deduped, only the Sentry capture.
6171
+ */
6172
+ reportedFailures = /* @__PURE__ */ new Set();
6173
+ /**
6058
6174
  * @param manager The integration manager to converge.
6059
6175
  * @param gate Runtime gate used to suspend the runtime child around
6060
6176
  * mutating branches so `openclaw` CLI subprocesses don't write
@@ -6067,6 +6183,29 @@ var ReconciliationEngine = class {
6067
6183
  this.gate = gate;
6068
6184
  }
6069
6185
  /**
6186
+ * Capture an integration failure to Sentry at most once per
6187
+ * `(integration, phase, signature)` within the current stuck episode. A new
6188
+ * signature (a different underlying error) reports again.
6189
+ */
6190
+ captureFailureOnce(integrationId, phase, signature, cause, fingerprint) {
6191
+ const key = `${integrationId}\u0000${phase}\u0000${createHash("sha256").update(signature).digest("hex")}`;
6192
+ if (this.reportedFailures.has(key)) return;
6193
+ this.reportedFailures.add(key);
6194
+ if (fingerprint) captureIntegrationFailure(integrationId, phase, cause, { fingerprint });
6195
+ else captureIntegrationFailure(integrationId, phase, cause);
6196
+ }
6197
+ /**
6198
+ * Start a fresh self-heal episode for an integration: a new re-activation
6199
+ * budget, and a cleared Sentry dedupe so a repeat of the same failure in the
6200
+ * new episode is reported again.
6201
+ */
6202
+ resetSelfHealBudget(integrationId) {
6203
+ this.activateAttempts.delete(integrationId);
6204
+ this.lastReactivateError.delete(integrationId);
6205
+ const prefix = `${integrationId}\u0000`;
6206
+ for (const key of this.reportedFailures) if (key.startsWith(prefix)) this.reportedFailures.delete(key);
6207
+ }
6208
+ /**
6070
6209
  * Suspend the runtime on the first mutating branch of a reconcile pass.
6071
6210
  * Idempotent within a pass — subsequent mutating branches are covered by the
6072
6211
  * single suspend held until the `finally` in {@link reconcile}.
@@ -6182,6 +6321,7 @@ var ReconciliationEngine = class {
6182
6321
  try {
6183
6322
  if (!local) {
6184
6323
  await this.ensureSuspended();
6324
+ this.resetSelfHealBudget(id);
6185
6325
  log$5.info(`Installing ${id}@${desired.version}`);
6186
6326
  await this.manager.install(id, desired.version, desired.config, desired.customSource);
6187
6327
  report.installed.push(id);
@@ -6200,7 +6340,7 @@ var ReconciliationEngine = class {
6200
6340
  await this.ensureSuspended();
6201
6341
  log$5.info(`Reinstalling ${id} on version change (reinstall requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt}): ${local.version} → ${desired.version}`);
6202
6342
  this.manager.resetReinstallAttempts(id);
6203
- this.activateAttempts.delete(id);
6343
+ this.resetSelfHealBudget(id);
6204
6344
  if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource, { reinstallRequestedAt: desired.reinstallRequestedAt })).configApplied) report.configApplied = true;
6205
6345
  report.installed.push(id);
6206
6346
  report.activated.push(id);
@@ -6215,7 +6355,7 @@ var ReconciliationEngine = class {
6215
6355
  try {
6216
6356
  if ((await this.manager.upgrade(id, desired.version, desired.config, desired.customSource, { onBeforeRuntimeMutation: () => this.ensureSuspended() })).configApplied) report.configApplied = true;
6217
6357
  this.manager.resetReinstallAttempts(id);
6218
- this.activateAttempts.delete(id);
6358
+ this.resetSelfHealBudget(id);
6219
6359
  report.installed.push(id);
6220
6360
  report.activated.push(id);
6221
6361
  report.results.push({
@@ -6245,7 +6385,7 @@ var ReconciliationEngine = class {
6245
6385
  await this.ensureSuspended();
6246
6386
  log$5.info(`Reinstalling ${id} from error state (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
6247
6387
  this.manager.resetReinstallAttempts(id);
6248
- this.activateAttempts.delete(id);
6388
+ this.resetSelfHealBudget(id);
6249
6389
  if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource, { reinstallRequestedAt: desired.reinstallRequestedAt })).configApplied) report.configApplied = true;
6250
6390
  report.installed.push(id);
6251
6391
  report.activated.push(id);
@@ -6260,7 +6400,8 @@ var ReconciliationEngine = class {
6260
6400
  const attempts = this.manager.getReinstallAttempts(id);
6261
6401
  if (attempts >= 3) {
6262
6402
  log$5.error(`Auto-reinstall blocked for ${id} — ${String(attempts)} consecutive failures. Manual reinstall required.`);
6263
- captureIntegrationFailure(id, "reinstall", `Max auto-reinstall attempts (${String(attempts)}) reached — manual reinstall required`);
6403
+ const blockedMessage = `Max auto-reinstall attempts (${String(attempts)}) reached — manual reinstall required`;
6404
+ this.captureFailureOnce(id, "reinstall", `blocked:${blockedMessage}`, blockedMessage);
6264
6405
  report.errors.push({
6265
6406
  integrationId: id,
6266
6407
  error: `Max auto-reinstall attempts (${String(attempts)}) reached`
@@ -6279,7 +6420,7 @@ var ReconciliationEngine = class {
6279
6420
  try {
6280
6421
  if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
6281
6422
  this.manager.resetReinstallAttempts(id);
6282
- this.activateAttempts.delete(id);
6423
+ this.resetSelfHealBudget(id);
6283
6424
  report.installed.push(id);
6284
6425
  report.activated.push(id);
6285
6426
  report.results.push({
@@ -6311,7 +6452,7 @@ var ReconciliationEngine = class {
6311
6452
  log$5.info(`Re-activating ${id} from error state (attempt ${String(activateAttempts + 1)}/${String(MAX_ERROR_ACTIVATE_ATTEMPTS)})`);
6312
6453
  try {
6313
6454
  if ((await this.manager.activate(id, { forcePlugins: true })).configApplied) report.configApplied = true;
6314
- this.activateAttempts.delete(id);
6455
+ this.resetSelfHealBudget(id);
6315
6456
  report.activated.push(id);
6316
6457
  report.results.push({
6317
6458
  integrationId: id,
@@ -6322,7 +6463,8 @@ var ReconciliationEngine = class {
6322
6463
  } catch (reactivateErr) {
6323
6464
  const reactivateMsg = reactivateErr instanceof Error ? reactivateErr.message : String(reactivateErr);
6324
6465
  log$5.warn(`Re-activation of ${id} from error state failed (attempt ${String(activateAttempts + 1)}/${String(MAX_ERROR_ACTIVATE_ATTEMPTS)}): ${reactivateMsg}`);
6325
- captureIntegrationFailure(id, "reactivate", reactivateErr);
6466
+ this.lastReactivateError.set(id, reactivateMsg);
6467
+ this.captureFailureOnce(id, "reactivate", `attempt:${reactivateMsg}`, reactivateErr);
6326
6468
  report.errors.push({
6327
6469
  integrationId: id,
6328
6470
  error: reactivateMsg
@@ -6337,7 +6479,8 @@ var ReconciliationEngine = class {
6337
6479
  }
6338
6480
  }
6339
6481
  log$5.warn(`Integration ${id} is in error state — re-activation exhausted, waiting for reinstall request`);
6340
- captureIntegrationFailure(id, "reactivate", `Integration ${id} stuck in error state — re-activation attempts exhausted`);
6482
+ const lastReactivateError = this.lastReactivateError.get(id);
6483
+ this.captureFailureOnce(id, "reactivate", `exhausted:${lastReactivateError ?? ""}`, `Integration ${id} stuck in error state — re-activation attempts exhausted${lastReactivateError ? ` (last error: ${lastReactivateError})` : ""}`, ["integration-reactivate-exhausted", id]);
6341
6484
  report.errors.push({
6342
6485
  integrationId: id,
6343
6486
  error: "Integration is in error state"
@@ -6355,7 +6498,7 @@ var ReconciliationEngine = class {
6355
6498
  await this.ensureSuspended();
6356
6499
  log$5.info(`Reinstalling ${id} from stale installing state (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
6357
6500
  this.manager.resetReinstallAttempts(id);
6358
- this.activateAttempts.delete(id);
6501
+ this.resetSelfHealBudget(id);
6359
6502
  if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource, { reinstallRequestedAt: desired.reinstallRequestedAt })).configApplied) report.configApplied = true;
6360
6503
  report.installed.push(id);
6361
6504
  report.activated.push(id);
@@ -6370,7 +6513,7 @@ var ReconciliationEngine = class {
6370
6513
  if (attempts >= 3) {
6371
6514
  const errorMessage = `Stale installing state — max auto-reinstall attempts (${String(attempts)}) reached — manual reinstall required`;
6372
6515
  log$5.error(`Auto-reinstall blocked for ${id} — stuck in "installing", ${String(attempts)} consecutive failures. Manual reinstall required.`);
6373
- captureIntegrationFailure(id, "reinstall", errorMessage);
6516
+ this.captureFailureOnce(id, "reinstall", `blocked:${errorMessage}`, errorMessage);
6374
6517
  report.errors.push({
6375
6518
  integrationId: id,
6376
6519
  error: `Max auto-reinstall attempts (${String(attempts)}) reached`
@@ -6389,7 +6532,7 @@ var ReconciliationEngine = class {
6389
6532
  try {
6390
6533
  if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
6391
6534
  this.manager.resetReinstallAttempts(id);
6392
- this.activateAttempts.delete(id);
6535
+ this.resetSelfHealBudget(id);
6393
6536
  report.installed.push(id);
6394
6537
  report.activated.push(id);
6395
6538
  report.results.push({
@@ -6418,6 +6561,7 @@ var ReconciliationEngine = class {
6418
6561
  await this.ensureSuspended();
6419
6562
  log$5.info(`Activating ${id}`);
6420
6563
  if ((await this.manager.activate(id, { forcePlugins: true })).configApplied) report.configApplied = true;
6564
+ this.resetSelfHealBudget(id);
6421
6565
  report.activated.push(id);
6422
6566
  report.results.push({
6423
6567
  integrationId: id,
@@ -6430,7 +6574,7 @@ var ReconciliationEngine = class {
6430
6574
  await this.ensureSuspended();
6431
6575
  log$5.info(`Reinstall requested for ${id} (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
6432
6576
  this.manager.resetReinstallAttempts(id);
6433
- this.activateAttempts.delete(id);
6577
+ this.resetSelfHealBudget(id);
6434
6578
  if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource, { reinstallRequestedAt: desired.reinstallRequestedAt })).configApplied) report.configApplied = true;
6435
6579
  report.installed.push(id);
6436
6580
  report.activated.push(id);
@@ -6441,7 +6585,7 @@ var ReconciliationEngine = class {
6441
6585
  });
6442
6586
  return;
6443
6587
  }
6444
- this.activateAttempts.delete(id);
6588
+ this.resetSelfHealBudget(id);
6445
6589
  report.results.push({
6446
6590
  integrationId: id,
6447
6591
  action: "up_to_date",
@@ -6480,6 +6624,7 @@ var ReconciliationEngine = class {
6480
6624
  report.deactivated.push(id);
6481
6625
  }
6482
6626
  await this.manager.uninstall(id);
6627
+ this.resetSelfHealBudget(id);
6483
6628
  report.uninstalled.push(id);
6484
6629
  report.results.push({
6485
6630
  integrationId: id,
@@ -9885,6 +10030,45 @@ let runtimeGateRef = null;
9885
10030
  let firstRuntimeStart = null;
9886
10031
  const LEGACY_CONFIG_SET_KEYS = new Set(["agents.defaults.model"]);
9887
10032
  const MAX_LEGACY_CONFIG_VALUE_LENGTH = 16 * 1024;
10033
+ /** Daemon-only `upgradeRuntime` options for OpenClaw (see runtime-upgrade.ts). */
10034
+ function openClawUpgradeExtras() {
10035
+ const applier = runtimeAppliersRef?.get("openclaw");
10036
+ const isolation = detectUpgradeIsolation();
10037
+ return {
10038
+ markerPath: defaultRuntimeUpgradeMarkerPath(),
10039
+ ...applier ? { pluginOwnership: buildOpenClawPluginOwnership(applier) } : {},
10040
+ ...isolation ? { isolation } : {}
10041
+ };
10042
+ }
10043
+ /**
10044
+ * Boot-time repair of an interrupted OpenClaw upgrade (see
10045
+ * `startBootUpgradeRecovery`). Must be called BEFORE the runtime's first
10046
+ * `requestStart()`.
10047
+ */
10048
+ function recoverInterruptedOpenClawUpgrade(gate, cliLock) {
10049
+ const recovery = startBootUpgradeRecovery({
10050
+ gate,
10051
+ cliLock,
10052
+ markerPath: defaultRuntimeUpgradeMarkerPath(),
10053
+ recover: (markerPath) => {
10054
+ const isolation = detectUpgradeIsolation();
10055
+ return recoverInterruptedRuntimeUpgrade({
10056
+ markerPath,
10057
+ ...isolation ? { isolation } : {}
10058
+ });
10059
+ },
10060
+ onRecovered: (version) => {
10061
+ if (version === resolvedRuntimeVersion) return;
10062
+ resolvedRuntimeVersion = version;
10063
+ cloudClient.updateRuntimeVersionAndReRegister(version);
10064
+ }
10065
+ });
10066
+ if (!recovery) return;
10067
+ upgradingRuntime = true;
10068
+ recovery.finally(() => {
10069
+ upgradingRuntime = false;
10070
+ });
10071
+ }
9888
10072
  async function startDaemon() {
9889
10073
  startedAt = Date.now();
9890
10074
  const managed = isManagedMode();
@@ -9981,7 +10165,8 @@ async function startDaemon() {
9981
10165
  runtimeAppliers.set(name, new OpenClawApplier({
9982
10166
  home: runtimeCfg.workspace,
9983
10167
  agentWorkspace: runtimeCfg.agentWorkspace,
9984
- cliLock: openclawCliLock
10168
+ cliLock: openclawCliLock,
10169
+ isRuntimeStopped: () => name === config.runtime && runtimeGateRef instanceof RuntimeGate && runtimeGateRef.isRuntimeStopped && runtimeProcess !== null && !runtimeProcess.isActive
9985
10170
  }));
9986
10171
  logger.info({
9987
10172
  runtime: name,
@@ -10123,6 +10308,7 @@ async function startDaemon() {
10123
10308
  });
10124
10309
  }
10125
10310
  }
10311
+ if (config.autoStartRuntime && config.runtime === "openclaw") recoverInterruptedOpenClawUpgrade(runtimeGate, openclawCliLock);
10126
10312
  cloudClient.start();
10127
10313
  logger.debug("Cloud client started");
10128
10314
  if (!config.autoStartRuntime && config.runtime) logger.warn({ runtime: config.runtime }, "Runtime configured but auto_start_runtime=false — runtime will not be spawned; in-runtime plugins (chat/console/sync) will be offline until you start it manually");
@@ -10348,7 +10534,15 @@ async function executeCloudCommand(command) {
10348
10534
  };
10349
10535
  const payload = command.payload;
10350
10536
  const runtime = config.runtime;
10351
- const version = payload?.version ?? (runtime === "openclaw" ? "2026.9.5" : runtime === "claude-code" ? "0.1.6" : void 0);
10537
+ let version;
10538
+ if (runtime === "openclaw") {
10539
+ const target = resolveOpenClawUpgradeTarget(payload?.version);
10540
+ if (target.clamped) logger.warn({
10541
+ requestedVersion: payload?.version,
10542
+ pinnedVersion: target.version
10543
+ }, "runtime.update requested an OpenClaw version above the pin — upgrading to the pin instead");
10544
+ version = target.version;
10545
+ } else version = payload?.version ?? (runtime === "claude-code" ? "0.1.6" : void 0);
10352
10546
  upgradingRuntime = true;
10353
10547
  setTimeout(() => {
10354
10548
  (async () => {
@@ -10357,7 +10551,8 @@ async function executeCloudCommand(command) {
10357
10551
  const result = await upgradeRuntime(runtime, version, runtimeProcess, {
10358
10552
  ...runtime === "openclaw" && openclawCliLockRef ? { cliLock: openclawCliLockRef } : {},
10359
10553
  ...runtime === "openclaw" && isManagedMode() && process.env.ALFE_PRIVATE_DATA_PLANE !== "true" ? { nodeRemediation: {} } : {},
10360
- ...runtime === "openclaw" && runtimeGateRef ? { gate: runtimeGateRef } : {}
10554
+ ...runtime === "openclaw" && runtimeGateRef ? { gate: runtimeGateRef } : {},
10555
+ ...runtime === "openclaw" ? openClawUpgradeExtras() : {}
10361
10556
  });
10362
10557
  if (result.success || result.runtimeChanged) {
10363
10558
  resolvedRuntimeVersion = await getRuntimeVersion(runtime);