@alfe.ai/gateway 0.10.6 → 0.10.7

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
@@ -6027,6 +6090,22 @@ var NoopRuntimeGate = class {
6027
6090
  };
6028
6091
  //#endregion
6029
6092
  //#region src/reconciliation.ts
6093
+ /**
6094
+ * ReconciliationEngine — converges local integration state toward cloud desired state.
6095
+ *
6096
+ * Takes: desired integrations from cloud + reference to the local integration manager.
6097
+ * Produces: a ReconciliationReport of actions taken and resulting actual statuses.
6098
+ *
6099
+ * For each desired integration with desiredStatus="active":
6100
+ * 1. Check if installed at correct version (from ~/.alfe/integrations.json)
6101
+ * 2. If not installed or wrong version: call integrationManager.install()
6102
+ * 3. If installed but not active: call integrationManager.activate()
6103
+ *
6104
+ * For each locally installed integration NOT in desired list (or desiredStatus="removed"):
6105
+ * 1. Call integrationManager.deactivate() then integrationManager.uninstall()
6106
+ *
6107
+ * Returns ReconciliationReport with results for each integration processed.
6108
+ */
6030
6109
  const log$5 = logger.child({ component: "Reconciliation" });
6031
6110
  /**
6032
6111
  * How many times reconcile will re-attempt activation of an intact integration
@@ -6055,6 +6134,25 @@ var ReconciliationEngine = class {
6055
6134
  */
6056
6135
  heldSuspension = false;
6057
6136
  /**
6137
+ * Most recent per-attempt re-activation error per integration. Becomes the
6138
+ * error signature of the "re-activation attempts exhausted" report, so the
6139
+ * terminal event says WHY the integration is stuck.
6140
+ */
6141
+ lastReactivateError = /* @__PURE__ */ new Map();
6142
+ /**
6143
+ * Sentry dedupe for reconcile outcomes that RECUR on every DESIRED_STATE
6144
+ * push once a budget is spent (re-activation exhausted, auto-reinstall
6145
+ * blocked) or that repeat identically across attempts. Keyed on
6146
+ * integration + phase + error signature, for one stuck EPISODE: the keys
6147
+ * are cleared by {@link resetSelfHealBudget} whenever the retry budget
6148
+ * resets (reinstall, upgrade, successful activation, healthy pass), so a
6149
+ * manual reinstall that fails with the same error reports again. The engine
6150
+ * lives as long as the daemon process (owned by `CloudClient`). Without it
6151
+ * a single stuck integration produced one event per push (51 for one agent).
6152
+ * Logs and the cloud-facing report are NOT deduped, only the Sentry capture.
6153
+ */
6154
+ reportedFailures = /* @__PURE__ */ new Set();
6155
+ /**
6058
6156
  * @param manager The integration manager to converge.
6059
6157
  * @param gate Runtime gate used to suspend the runtime child around
6060
6158
  * mutating branches so `openclaw` CLI subprocesses don't write
@@ -6067,6 +6165,29 @@ var ReconciliationEngine = class {
6067
6165
  this.gate = gate;
6068
6166
  }
6069
6167
  /**
6168
+ * Capture an integration failure to Sentry at most once per
6169
+ * `(integration, phase, signature)` within the current stuck episode. A new
6170
+ * signature (a different underlying error) reports again.
6171
+ */
6172
+ captureFailureOnce(integrationId, phase, signature, cause, fingerprint) {
6173
+ const key = `${integrationId}\u0000${phase}\u0000${createHash("sha256").update(signature).digest("hex")}`;
6174
+ if (this.reportedFailures.has(key)) return;
6175
+ this.reportedFailures.add(key);
6176
+ if (fingerprint) captureIntegrationFailure(integrationId, phase, cause, { fingerprint });
6177
+ else captureIntegrationFailure(integrationId, phase, cause);
6178
+ }
6179
+ /**
6180
+ * Start a fresh self-heal episode for an integration: a new re-activation
6181
+ * budget, and a cleared Sentry dedupe so a repeat of the same failure in the
6182
+ * new episode is reported again.
6183
+ */
6184
+ resetSelfHealBudget(integrationId) {
6185
+ this.activateAttempts.delete(integrationId);
6186
+ this.lastReactivateError.delete(integrationId);
6187
+ const prefix = `${integrationId}\u0000`;
6188
+ for (const key of this.reportedFailures) if (key.startsWith(prefix)) this.reportedFailures.delete(key);
6189
+ }
6190
+ /**
6070
6191
  * Suspend the runtime on the first mutating branch of a reconcile pass.
6071
6192
  * Idempotent within a pass — subsequent mutating branches are covered by the
6072
6193
  * single suspend held until the `finally` in {@link reconcile}.
@@ -6182,6 +6303,7 @@ var ReconciliationEngine = class {
6182
6303
  try {
6183
6304
  if (!local) {
6184
6305
  await this.ensureSuspended();
6306
+ this.resetSelfHealBudget(id);
6185
6307
  log$5.info(`Installing ${id}@${desired.version}`);
6186
6308
  await this.manager.install(id, desired.version, desired.config, desired.customSource);
6187
6309
  report.installed.push(id);
@@ -6200,7 +6322,7 @@ var ReconciliationEngine = class {
6200
6322
  await this.ensureSuspended();
6201
6323
  log$5.info(`Reinstalling ${id} on version change (reinstall requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt}): ${local.version} → ${desired.version}`);
6202
6324
  this.manager.resetReinstallAttempts(id);
6203
- this.activateAttempts.delete(id);
6325
+ this.resetSelfHealBudget(id);
6204
6326
  if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource, { reinstallRequestedAt: desired.reinstallRequestedAt })).configApplied) report.configApplied = true;
6205
6327
  report.installed.push(id);
6206
6328
  report.activated.push(id);
@@ -6215,7 +6337,7 @@ var ReconciliationEngine = class {
6215
6337
  try {
6216
6338
  if ((await this.manager.upgrade(id, desired.version, desired.config, desired.customSource, { onBeforeRuntimeMutation: () => this.ensureSuspended() })).configApplied) report.configApplied = true;
6217
6339
  this.manager.resetReinstallAttempts(id);
6218
- this.activateAttempts.delete(id);
6340
+ this.resetSelfHealBudget(id);
6219
6341
  report.installed.push(id);
6220
6342
  report.activated.push(id);
6221
6343
  report.results.push({
@@ -6245,7 +6367,7 @@ var ReconciliationEngine = class {
6245
6367
  await this.ensureSuspended();
6246
6368
  log$5.info(`Reinstalling ${id} from error state (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
6247
6369
  this.manager.resetReinstallAttempts(id);
6248
- this.activateAttempts.delete(id);
6370
+ this.resetSelfHealBudget(id);
6249
6371
  if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource, { reinstallRequestedAt: desired.reinstallRequestedAt })).configApplied) report.configApplied = true;
6250
6372
  report.installed.push(id);
6251
6373
  report.activated.push(id);
@@ -6260,7 +6382,8 @@ var ReconciliationEngine = class {
6260
6382
  const attempts = this.manager.getReinstallAttempts(id);
6261
6383
  if (attempts >= 3) {
6262
6384
  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`);
6385
+ const blockedMessage = `Max auto-reinstall attempts (${String(attempts)}) reached — manual reinstall required`;
6386
+ this.captureFailureOnce(id, "reinstall", `blocked:${blockedMessage}`, blockedMessage);
6264
6387
  report.errors.push({
6265
6388
  integrationId: id,
6266
6389
  error: `Max auto-reinstall attempts (${String(attempts)}) reached`
@@ -6279,7 +6402,7 @@ var ReconciliationEngine = class {
6279
6402
  try {
6280
6403
  if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
6281
6404
  this.manager.resetReinstallAttempts(id);
6282
- this.activateAttempts.delete(id);
6405
+ this.resetSelfHealBudget(id);
6283
6406
  report.installed.push(id);
6284
6407
  report.activated.push(id);
6285
6408
  report.results.push({
@@ -6311,7 +6434,7 @@ var ReconciliationEngine = class {
6311
6434
  log$5.info(`Re-activating ${id} from error state (attempt ${String(activateAttempts + 1)}/${String(MAX_ERROR_ACTIVATE_ATTEMPTS)})`);
6312
6435
  try {
6313
6436
  if ((await this.manager.activate(id, { forcePlugins: true })).configApplied) report.configApplied = true;
6314
- this.activateAttempts.delete(id);
6437
+ this.resetSelfHealBudget(id);
6315
6438
  report.activated.push(id);
6316
6439
  report.results.push({
6317
6440
  integrationId: id,
@@ -6322,7 +6445,8 @@ var ReconciliationEngine = class {
6322
6445
  } catch (reactivateErr) {
6323
6446
  const reactivateMsg = reactivateErr instanceof Error ? reactivateErr.message : String(reactivateErr);
6324
6447
  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);
6448
+ this.lastReactivateError.set(id, reactivateMsg);
6449
+ this.captureFailureOnce(id, "reactivate", `attempt:${reactivateMsg}`, reactivateErr);
6326
6450
  report.errors.push({
6327
6451
  integrationId: id,
6328
6452
  error: reactivateMsg
@@ -6337,7 +6461,8 @@ var ReconciliationEngine = class {
6337
6461
  }
6338
6462
  }
6339
6463
  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`);
6464
+ const lastReactivateError = this.lastReactivateError.get(id);
6465
+ 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
6466
  report.errors.push({
6342
6467
  integrationId: id,
6343
6468
  error: "Integration is in error state"
@@ -6355,7 +6480,7 @@ var ReconciliationEngine = class {
6355
6480
  await this.ensureSuspended();
6356
6481
  log$5.info(`Reinstalling ${id} from stale installing state (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
6357
6482
  this.manager.resetReinstallAttempts(id);
6358
- this.activateAttempts.delete(id);
6483
+ this.resetSelfHealBudget(id);
6359
6484
  if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource, { reinstallRequestedAt: desired.reinstallRequestedAt })).configApplied) report.configApplied = true;
6360
6485
  report.installed.push(id);
6361
6486
  report.activated.push(id);
@@ -6370,7 +6495,7 @@ var ReconciliationEngine = class {
6370
6495
  if (attempts >= 3) {
6371
6496
  const errorMessage = `Stale installing state — max auto-reinstall attempts (${String(attempts)}) reached — manual reinstall required`;
6372
6497
  log$5.error(`Auto-reinstall blocked for ${id} — stuck in "installing", ${String(attempts)} consecutive failures. Manual reinstall required.`);
6373
- captureIntegrationFailure(id, "reinstall", errorMessage);
6498
+ this.captureFailureOnce(id, "reinstall", `blocked:${errorMessage}`, errorMessage);
6374
6499
  report.errors.push({
6375
6500
  integrationId: id,
6376
6501
  error: `Max auto-reinstall attempts (${String(attempts)}) reached`
@@ -6389,7 +6514,7 @@ var ReconciliationEngine = class {
6389
6514
  try {
6390
6515
  if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
6391
6516
  this.manager.resetReinstallAttempts(id);
6392
- this.activateAttempts.delete(id);
6517
+ this.resetSelfHealBudget(id);
6393
6518
  report.installed.push(id);
6394
6519
  report.activated.push(id);
6395
6520
  report.results.push({
@@ -6418,6 +6543,7 @@ var ReconciliationEngine = class {
6418
6543
  await this.ensureSuspended();
6419
6544
  log$5.info(`Activating ${id}`);
6420
6545
  if ((await this.manager.activate(id, { forcePlugins: true })).configApplied) report.configApplied = true;
6546
+ this.resetSelfHealBudget(id);
6421
6547
  report.activated.push(id);
6422
6548
  report.results.push({
6423
6549
  integrationId: id,
@@ -6430,7 +6556,7 @@ var ReconciliationEngine = class {
6430
6556
  await this.ensureSuspended();
6431
6557
  log$5.info(`Reinstall requested for ${id} (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
6432
6558
  this.manager.resetReinstallAttempts(id);
6433
- this.activateAttempts.delete(id);
6559
+ this.resetSelfHealBudget(id);
6434
6560
  if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource, { reinstallRequestedAt: desired.reinstallRequestedAt })).configApplied) report.configApplied = true;
6435
6561
  report.installed.push(id);
6436
6562
  report.activated.push(id);
@@ -6441,7 +6567,7 @@ var ReconciliationEngine = class {
6441
6567
  });
6442
6568
  return;
6443
6569
  }
6444
- this.activateAttempts.delete(id);
6570
+ this.resetSelfHealBudget(id);
6445
6571
  report.results.push({
6446
6572
  integrationId: id,
6447
6573
  action: "up_to_date",
@@ -6480,6 +6606,7 @@ var ReconciliationEngine = class {
6480
6606
  report.deactivated.push(id);
6481
6607
  }
6482
6608
  await this.manager.uninstall(id);
6609
+ this.resetSelfHealBudget(id);
6483
6610
  report.uninstalled.push(id);
6484
6611
  report.results.push({
6485
6612
  integrationId: id,
@@ -9885,6 +10012,45 @@ let runtimeGateRef = null;
9885
10012
  let firstRuntimeStart = null;
9886
10013
  const LEGACY_CONFIG_SET_KEYS = new Set(["agents.defaults.model"]);
9887
10014
  const MAX_LEGACY_CONFIG_VALUE_LENGTH = 16 * 1024;
10015
+ /** Daemon-only `upgradeRuntime` options for OpenClaw (see runtime-upgrade.ts). */
10016
+ function openClawUpgradeExtras() {
10017
+ const applier = runtimeAppliersRef?.get("openclaw");
10018
+ const isolation = detectUpgradeIsolation();
10019
+ return {
10020
+ markerPath: defaultRuntimeUpgradeMarkerPath(),
10021
+ ...applier ? { pluginOwnership: buildOpenClawPluginOwnership(applier) } : {},
10022
+ ...isolation ? { isolation } : {}
10023
+ };
10024
+ }
10025
+ /**
10026
+ * Boot-time repair of an interrupted OpenClaw upgrade (see
10027
+ * `startBootUpgradeRecovery`). Must be called BEFORE the runtime's first
10028
+ * `requestStart()`.
10029
+ */
10030
+ function recoverInterruptedOpenClawUpgrade(gate, cliLock) {
10031
+ const recovery = startBootUpgradeRecovery({
10032
+ gate,
10033
+ cliLock,
10034
+ markerPath: defaultRuntimeUpgradeMarkerPath(),
10035
+ recover: (markerPath) => {
10036
+ const isolation = detectUpgradeIsolation();
10037
+ return recoverInterruptedRuntimeUpgrade({
10038
+ markerPath,
10039
+ ...isolation ? { isolation } : {}
10040
+ });
10041
+ },
10042
+ onRecovered: (version) => {
10043
+ if (version === resolvedRuntimeVersion) return;
10044
+ resolvedRuntimeVersion = version;
10045
+ cloudClient.updateRuntimeVersionAndReRegister(version);
10046
+ }
10047
+ });
10048
+ if (!recovery) return;
10049
+ upgradingRuntime = true;
10050
+ recovery.finally(() => {
10051
+ upgradingRuntime = false;
10052
+ });
10053
+ }
9888
10054
  async function startDaemon() {
9889
10055
  startedAt = Date.now();
9890
10056
  const managed = isManagedMode();
@@ -10123,6 +10289,7 @@ async function startDaemon() {
10123
10289
  });
10124
10290
  }
10125
10291
  }
10292
+ if (config.autoStartRuntime && config.runtime === "openclaw") recoverInterruptedOpenClawUpgrade(runtimeGate, openclawCliLock);
10126
10293
  cloudClient.start();
10127
10294
  logger.debug("Cloud client started");
10128
10295
  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 +10515,15 @@ async function executeCloudCommand(command) {
10348
10515
  };
10349
10516
  const payload = command.payload;
10350
10517
  const runtime = config.runtime;
10351
- const version = payload?.version ?? (runtime === "openclaw" ? "2026.9.5" : runtime === "claude-code" ? "0.1.6" : void 0);
10518
+ let version;
10519
+ if (runtime === "openclaw") {
10520
+ const target = resolveOpenClawUpgradeTarget(payload?.version);
10521
+ if (target.clamped) logger.warn({
10522
+ requestedVersion: payload?.version,
10523
+ pinnedVersion: target.version
10524
+ }, "runtime.update requested an OpenClaw version above the pin — upgrading to the pin instead");
10525
+ version = target.version;
10526
+ } else version = payload?.version ?? (runtime === "claude-code" ? "0.1.6" : void 0);
10352
10527
  upgradingRuntime = true;
10353
10528
  setTimeout(() => {
10354
10529
  (async () => {
@@ -10357,7 +10532,8 @@ async function executeCloudCommand(command) {
10357
10532
  const result = await upgradeRuntime(runtime, version, runtimeProcess, {
10358
10533
  ...runtime === "openclaw" && openclawCliLockRef ? { cliLock: openclawCliLockRef } : {},
10359
10534
  ...runtime === "openclaw" && isManagedMode() && process.env.ALFE_PRIVATE_DATA_PLANE !== "true" ? { nodeRemediation: {} } : {},
10360
- ...runtime === "openclaw" && runtimeGateRef ? { gate: runtimeGateRef } : {}
10535
+ ...runtime === "openclaw" && runtimeGateRef ? { gate: runtimeGateRef } : {},
10536
+ ...runtime === "openclaw" ? openClawUpgradeExtras() : {}
10361
10537
  });
10362
10538
  if (result.success || result.runtimeChanged) {
10363
10539
  resolvedRuntimeVersion = await getRuntimeVersion(runtime);