@alfe.ai/gateway 0.10.5 → 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)
20
+ * Runtime-lock plugin ownership for the OpenClaw upgrade preflight.
24
21
  *
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.
31
- *
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)()}`;
221
- }
222
- function correlationId() {
223
- return createId(ID_PREFIXES.correlation);
224
- }
225
- function pluginConnectionId() {
226
- return createId(ID_PREFIXES.pluginConnection);
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);
227
36
  }
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) => {
@@ -3838,86 +3648,310 @@ function _default(innerType, defaultValue) {
3838
3648
  get defaultValue() {
3839
3649
  return typeof defaultValue === "function" ? defaultValue() : shallowClone(defaultValue);
3840
3650
  }
3841
- });
3842
- }
3843
- const ZodPrefault = /* @__PURE__ */ $constructor("ZodPrefault", (inst, def) => {
3844
- $ZodPrefault.init(inst, def);
3845
- ZodType.init(inst, def);
3846
- inst._zod.processJSONSchema = (ctx, json, params) => prefaultProcessor(inst, ctx, json, params);
3847
- inst.unwrap = () => inst._zod.def.innerType;
3848
- });
3849
- function prefault(innerType, defaultValue) {
3850
- return new ZodPrefault({
3851
- type: "prefault",
3852
- innerType,
3853
- get defaultValue() {
3854
- return typeof defaultValue === "function" ? defaultValue() : shallowClone(defaultValue);
3651
+ });
3652
+ }
3653
+ const ZodPrefault = /* @__PURE__ */ $constructor("ZodPrefault", (inst, def) => {
3654
+ $ZodPrefault.init(inst, def);
3655
+ ZodType.init(inst, def);
3656
+ inst._zod.processJSONSchema = (ctx, json, params) => prefaultProcessor(inst, ctx, json, params);
3657
+ inst.unwrap = () => inst._zod.def.innerType;
3658
+ });
3659
+ function prefault(innerType, defaultValue) {
3660
+ return new ZodPrefault({
3661
+ type: "prefault",
3662
+ innerType,
3663
+ get defaultValue() {
3664
+ return typeof defaultValue === "function" ? defaultValue() : shallowClone(defaultValue);
3665
+ }
3666
+ });
3667
+ }
3668
+ const ZodNonOptional = /* @__PURE__ */ $constructor("ZodNonOptional", (inst, def) => {
3669
+ $ZodNonOptional.init(inst, def);
3670
+ ZodType.init(inst, def);
3671
+ inst._zod.processJSONSchema = (ctx, json, params) => nonoptionalProcessor(inst, ctx, json, params);
3672
+ inst.unwrap = () => inst._zod.def.innerType;
3673
+ });
3674
+ function nonoptional(innerType, params) {
3675
+ return new ZodNonOptional({
3676
+ type: "nonoptional",
3677
+ innerType,
3678
+ ...normalizeParams(params)
3679
+ });
3680
+ }
3681
+ const ZodCatch = /* @__PURE__ */ $constructor("ZodCatch", (inst, def) => {
3682
+ $ZodCatch.init(inst, def);
3683
+ ZodType.init(inst, def);
3684
+ inst._zod.processJSONSchema = (ctx, json, params) => catchProcessor(inst, ctx, json, params);
3685
+ inst.unwrap = () => inst._zod.def.innerType;
3686
+ inst.removeCatch = inst.unwrap;
3687
+ });
3688
+ function _catch(innerType, catchValue) {
3689
+ return new ZodCatch({
3690
+ type: "catch",
3691
+ innerType,
3692
+ catchValue: typeof catchValue === "function" ? catchValue : () => catchValue
3693
+ });
3694
+ }
3695
+ const ZodPipe = /* @__PURE__ */ $constructor("ZodPipe", (inst, def) => {
3696
+ $ZodPipe.init(inst, def);
3697
+ ZodType.init(inst, def);
3698
+ inst._zod.processJSONSchema = (ctx, json, params) => pipeProcessor(inst, ctx, json, params);
3699
+ inst.in = def.in;
3700
+ inst.out = def.out;
3701
+ });
3702
+ function pipe(in_, out) {
3703
+ return new ZodPipe({
3704
+ type: "pipe",
3705
+ in: in_,
3706
+ out
3707
+ });
3708
+ }
3709
+ const ZodReadonly = /* @__PURE__ */ $constructor("ZodReadonly", (inst, def) => {
3710
+ $ZodReadonly.init(inst, def);
3711
+ ZodType.init(inst, def);
3712
+ inst._zod.processJSONSchema = (ctx, json, params) => readonlyProcessor(inst, ctx, json, params);
3713
+ inst.unwrap = () => inst._zod.def.innerType;
3714
+ });
3715
+ function readonly(innerType) {
3716
+ return new ZodReadonly({
3717
+ type: "readonly",
3718
+ innerType
3719
+ });
3720
+ }
3721
+ const ZodCustom = /* @__PURE__ */ $constructor("ZodCustom", (inst, def) => {
3722
+ $ZodCustom.init(inst, def);
3723
+ ZodType.init(inst, def);
3724
+ inst._zod.processJSONSchema = (ctx, json, params) => customProcessor(inst, ctx, json, params);
3725
+ });
3726
+ function refine(fn, _params = {}) {
3727
+ return /* @__PURE__ */ _refine(ZodCustom, fn, _params);
3728
+ }
3729
+ function superRefine(fn) {
3730
+ return /* @__PURE__ */ _superRefine(fn);
3731
+ }
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
+ };
3855
3905
  }
3856
- });
3857
- }
3858
- const ZodNonOptional = /* @__PURE__ */ $constructor("ZodNonOptional", (inst, def) => {
3859
- $ZodNonOptional.init(inst, def);
3860
- ZodType.init(inst, def);
3861
- inst._zod.processJSONSchema = (ctx, json, params) => nonoptionalProcessor(inst, ctx, json, params);
3862
- inst.unwrap = () => inst._zod.def.innerType;
3863
- });
3864
- function nonoptional(innerType, params) {
3865
- return new ZodNonOptional({
3866
- type: "nonoptional",
3867
- innerType,
3868
- ...normalizeParams(params)
3869
- });
3870
- }
3871
- const ZodCatch = /* @__PURE__ */ $constructor("ZodCatch", (inst, def) => {
3872
- $ZodCatch.init(inst, def);
3873
- ZodType.init(inst, def);
3874
- inst._zod.processJSONSchema = (ctx, json, params) => catchProcessor(inst, ctx, json, params);
3875
- inst.unwrap = () => inst._zod.def.innerType;
3876
- inst.removeCatch = inst.unwrap;
3877
- });
3878
- function _catch(innerType, catchValue) {
3879
- return new ZodCatch({
3880
- type: "catch",
3881
- innerType,
3882
- catchValue: typeof catchValue === "function" ? catchValue : () => catchValue
3883
- });
3884
- }
3885
- const ZodPipe = /* @__PURE__ */ $constructor("ZodPipe", (inst, def) => {
3886
- $ZodPipe.init(inst, def);
3887
- ZodType.init(inst, def);
3888
- inst._zod.processJSONSchema = (ctx, json, params) => pipeProcessor(inst, ctx, json, params);
3889
- inst.in = def.in;
3890
- inst.out = def.out;
3891
- });
3892
- function pipe(in_, out) {
3893
- return new ZodPipe({
3894
- type: "pipe",
3895
- in: in_,
3896
- out
3897
- });
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)()}`;
3898
3936
  }
3899
- const ZodReadonly = /* @__PURE__ */ $constructor("ZodReadonly", (inst, def) => {
3900
- $ZodReadonly.init(inst, def);
3901
- ZodType.init(inst, def);
3902
- inst._zod.processJSONSchema = (ctx, json, params) => readonlyProcessor(inst, ctx, json, params);
3903
- inst.unwrap = () => inst._zod.def.innerType;
3904
- });
3905
- function readonly(innerType) {
3906
- return new ZodReadonly({
3907
- type: "readonly",
3908
- innerType
3909
- });
3937
+ function correlationId() {
3938
+ return createId(ID_PREFIXES.correlation);
3910
3939
  }
3911
- const ZodCustom = /* @__PURE__ */ $constructor("ZodCustom", (inst, def) => {
3912
- $ZodCustom.init(inst, def);
3913
- ZodType.init(inst, def);
3914
- inst._zod.processJSONSchema = (ctx, json, params) => customProcessor(inst, ctx, json, params);
3915
- });
3916
- function refine(fn, _params = {}) {
3917
- return /* @__PURE__ */ _refine(ZodCustom, fn, _params);
3940
+ function pluginConnectionId() {
3941
+ return createId(ID_PREFIXES.pluginConnection);
3918
3942
  }
3919
- function superRefine(fn) {
3920
- return /* @__PURE__ */ _superRefine(fn);
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;
3921
3955
  }
3922
3956
  //#endregion
3923
3957
  //#region ../../packages-internal/types/dist/lib/usd.js
@@ -4349,6 +4383,7 @@ function isClerkReverificationResponse(value) {
4349
4383
  //#endregion
4350
4384
  //#region ../../packages-internal/types/dist/models.js
4351
4385
  const AnthropicModel = {
4386
+ Opus55: "claude-opus-5-5",
4352
4387
  Opus5: "claude-opus-5",
4353
4388
  Opus48: "claude-opus-4-8",
4354
4389
  Opus47: "claude-opus-4-7",
@@ -4359,6 +4394,7 @@ const AnthropicModel = {
4359
4394
  };
4360
4395
  const ANTHROPIC_MODELS = enumValues(AnthropicModel);
4361
4396
  const OpenAIModel = {
4397
+ GPT6Astra: "gpt-6-astra",
4362
4398
  GPT4o: "gpt-4o",
4363
4399
  GPT4oMini: "gpt-4o-mini",
4364
4400
  O3: "o3",
@@ -4381,20 +4417,22 @@ const OpenAIModel = {
4381
4417
  };
4382
4418
  const OPENAI_MODELS = enumValues(OpenAIModel);
4383
4419
  const DeepSeekModel = {
4384
- Chat: "deepseek-chat",
4385
- Reasoner: "deepseek-reasoner",
4386
- V4Flash: "deepseek-v4-flash",
4387
- V4Pro: "deepseek-v4-pro"
4420
+ Flash: "deepseek-flash",
4421
+ V4Pro: "deepseek-v4-pro",
4422
+ V4Flash: "deepseek-v4-flash"
4388
4423
  };
4389
4424
  const DEEPSEEK_MODELS = enumValues(DeepSeekModel);
4390
4425
  const GoogleModel = {
4426
+ Gemini38Flash: "gemini-3.8-flash",
4427
+ Gemini37Flash: "gemini-3.7-flash",
4428
+ Gemini36Flash: "gemini-3.6-flash",
4391
4429
  Gemini35Flash: "gemini-3.5-flash",
4430
+ Gemini35FlashLite: "gemini-3.5-flash-lite",
4392
4431
  Gemini31Pro: "gemini-3.1-pro-preview",
4393
4432
  Gemini31FlashLite: "gemini-3.1-flash-lite",
4394
4433
  Gemini25Pro: "gemini-2.5-pro",
4395
4434
  Gemini25Flash: "gemini-2.5-flash",
4396
- Gemini25FlashLite: "gemini-2.5-flash-lite",
4397
- Gemini20Flash: "gemini-2.0-flash"
4435
+ Gemini25FlashLite: "gemini-2.5-flash-lite"
4398
4436
  };
4399
4437
  const GOOGLE_MODELS = enumValues(GoogleModel);
4400
4438
  const MiniMaxModel = {
@@ -4412,20 +4450,23 @@ const MistralModel = {
4412
4450
  Small: "mistral-small-latest",
4413
4451
  Codestral: "codestral-latest",
4414
4452
  Ministral8b: "ministral-8b-latest",
4415
- Ministral3b: "ministral-3b-latest",
4416
- MagistralMedium: "magistral-medium-latest",
4417
- MagistralSmall: "magistral-small-latest",
4418
- DevstralMedium: "devstral-medium-latest"
4453
+ Ministral3b: "ministral-3b-latest"
4419
4454
  };
4420
4455
  const MISTRAL_MODELS = enumValues(MistralModel);
4421
4456
  const XAIModel = {
4457
+ Grok47: "grok-4.7",
4458
+ Grok46: "grok-4.6",
4422
4459
  Grok45: "grok-4.5",
4423
4460
  Grok43: "grok-4.3",
4461
+ GrokBuild01: "grok-build-0.1",
4424
4462
  Grok4: "grok-4",
4425
4463
  Grok41Fast: "grok-4.1-fast"
4426
4464
  };
4427
4465
  const XAI_MODELS = enumValues(XAIModel);
4428
4466
  const ZhipuModel = {
4467
+ GLM53: "glm-5.3",
4468
+ GLM53Flash: "glm-5.3-flash",
4469
+ GLM53FlashX: "glm-5.3-flashx",
4429
4470
  GLM52: "glm-5.2",
4430
4471
  GLM51: "glm-5.1",
4431
4472
  GLM46: "glm-4.6",
@@ -4441,6 +4482,8 @@ const MoonshotModel = {
4441
4482
  };
4442
4483
  const MOONSHOT_MODELS = enumValues(MoonshotModel);
4443
4484
  const QwenModel = {
4485
+ Qwen38Max: "qwen3.8-max",
4486
+ Qwen38Flash: "qwen3.8-flash",
4444
4487
  Qwen37Max: "qwen3.7-max",
4445
4488
  Qwen37Plus: "qwen3.7-plus",
4446
4489
  Qwen36Flash: "qwen3.6-flash",
@@ -4471,7 +4514,7 @@ _enum(ZHIPU_MODELS);
4471
4514
  _enum(MOONSHOT_MODELS);
4472
4515
  _enum(QWEN_MODELS);
4473
4516
  string().trim().min(1);
4474
- AnthropicModel.Opus5, AnthropicModel.Opus48, AnthropicModel.Opus47, AnthropicModel.Opus46, AnthropicModel.Sonnet5, AnthropicModel.Sonnet46, AnthropicModel.Haiku45, OpenAIModel.GPT4o, OpenAIModel.GPT4oMini, OpenAIModel.O3, OpenAIModel.GPT41, OpenAIModel.GPT41Mini, OpenAIModel.GPT41Nano, OpenAIModel.GPT54, OpenAIModel.GPT54Mini, OpenAIModel.GPT54Nano, OpenAIModel.GPT54Pro, OpenAIModel.GPT55, OpenAIModel.GPT55Pro, OpenAIModel.GPT56Sol, OpenAIModel.GPT56Terra, OpenAIModel.GPT56Luna, OpenAIModel.O3Mini, OpenAIModel.O4Mini, OpenAIModel.TextEmbedding3Small, OpenAIModel.TextEmbedding3Large, DeepSeekModel.Chat, DeepSeekModel.Reasoner, DeepSeekModel.V4Flash, DeepSeekModel.V4Pro, GoogleModel.Gemini35Flash, GoogleModel.Gemini31Pro, GoogleModel.Gemini31FlashLite, GoogleModel.Gemini25Pro, GoogleModel.Gemini25Flash, GoogleModel.Gemini25FlashLite, GoogleModel.Gemini20Flash, MiniMaxModel.M3, MiniMaxModel.M27, MiniMaxModel.M27HighSpeed, MiniMaxModel.M25, MiniMaxModel.M21, MiniMaxModel.M2, MistralModel.Large, MistralModel.Medium, MistralModel.Small, MistralModel.Codestral, MistralModel.Ministral8b, MistralModel.Ministral3b, MistralModel.MagistralMedium, MistralModel.MagistralSmall, MistralModel.DevstralMedium, XAIModel.Grok45, XAIModel.Grok43, XAIModel.Grok4, XAIModel.Grok41Fast, ZhipuModel.GLM52, ZhipuModel.GLM51, ZhipuModel.GLM46, ZhipuModel.GLM45, ZhipuModel.GLM45Air, MoonshotModel.K3, MoonshotModel.K26, MoonshotModel.K27Code, MoonshotModel.K27CodeHighSpeed, QwenModel.Qwen37Max, QwenModel.Qwen37Plus, QwenModel.Qwen36Flash, QwenModel.Qwen35Flash;
4517
+ AnthropicModel.Opus55, AnthropicModel.Opus5, AnthropicModel.Opus48, AnthropicModel.Opus47, AnthropicModel.Opus46, AnthropicModel.Sonnet5, AnthropicModel.Sonnet46, AnthropicModel.Haiku45, OpenAIModel.GPT6Astra, OpenAIModel.GPT4o, OpenAIModel.GPT4oMini, OpenAIModel.O3, OpenAIModel.GPT41, OpenAIModel.GPT41Mini, OpenAIModel.GPT41Nano, OpenAIModel.GPT54, OpenAIModel.GPT54Mini, OpenAIModel.GPT54Nano, OpenAIModel.GPT54Pro, OpenAIModel.GPT55, OpenAIModel.GPT55Pro, OpenAIModel.GPT56Sol, OpenAIModel.GPT56Terra, OpenAIModel.GPT56Luna, OpenAIModel.O3Mini, OpenAIModel.O4Mini, OpenAIModel.TextEmbedding3Small, OpenAIModel.TextEmbedding3Large, DeepSeekModel.Flash, DeepSeekModel.V4Pro, DeepSeekModel.V4Flash, GoogleModel.Gemini38Flash, GoogleModel.Gemini37Flash, GoogleModel.Gemini36Flash, GoogleModel.Gemini35Flash, GoogleModel.Gemini35FlashLite, GoogleModel.Gemini31Pro, GoogleModel.Gemini31FlashLite, GoogleModel.Gemini25Pro, GoogleModel.Gemini25Flash, GoogleModel.Gemini25FlashLite, MiniMaxModel.M3, MiniMaxModel.M27, MiniMaxModel.M27HighSpeed, MiniMaxModel.M25, MiniMaxModel.M21, MiniMaxModel.M2, MistralModel.Large, MistralModel.Medium, MistralModel.Small, MistralModel.Codestral, MistralModel.Ministral8b, MistralModel.Ministral3b, XAIModel.Grok47, XAIModel.Grok46, XAIModel.Grok45, XAIModel.Grok43, XAIModel.GrokBuild01, XAIModel.Grok4, XAIModel.Grok41Fast, ZhipuModel.GLM53, ZhipuModel.GLM53Flash, ZhipuModel.GLM53FlashX, ZhipuModel.GLM52, ZhipuModel.GLM51, ZhipuModel.GLM46, ZhipuModel.GLM45, ZhipuModel.GLM45Air, MoonshotModel.K3, MoonshotModel.K26, MoonshotModel.K27Code, MoonshotModel.K27CodeHighSpeed, QwenModel.Qwen38Max, QwenModel.Qwen38Flash, QwenModel.Qwen37Max, QwenModel.Qwen37Plus, QwenModel.Qwen36Flash, QwenModel.Qwen35Flash;
4475
4518
  /**
4476
4519
  * Alfe-hosted open-weight models are promoted dynamically only in regions
4477
4520
  * whose private data plane exists. Keep them out of ALL_MODELS so the standard
@@ -4531,7 +4574,7 @@ const AWS_PRIVATE_MODEL_PRICING = {
4531
4574
  [BedrockModel.TitanEmbedTextV2]: { input: .026004 }
4532
4575
  };
4533
4576
  Object.fromEntries(AWS_PRIVATE_BEDROCK_CHAT_MODELS.map((modelId) => [`alfe-bedrock-${modelId}`, AWS_PRIVATE_MODEL_PRICING[modelId]]));
4534
- AnthropicModel.Opus5, AnthropicModel.Opus48, AnthropicModel.Opus47, AnthropicModel.Opus46, AnthropicModel.Sonnet5, AnthropicModel.Sonnet46, AnthropicModel.Haiku45, OpenAIModel.GPT4o, OpenAIModel.GPT4oMini, OpenAIModel.O3, OpenAIModel.GPT41, OpenAIModel.GPT41Mini, OpenAIModel.GPT41Nano, OpenAIModel.GPT54, OpenAIModel.GPT54Mini, OpenAIModel.GPT54Nano, OpenAIModel.GPT54Pro, OpenAIModel.GPT55, OpenAIModel.GPT55Pro, OpenAIModel.GPT56Sol, OpenAIModel.GPT56Terra, OpenAIModel.GPT56Luna, OpenAIModel.O3Mini, OpenAIModel.O4Mini, OpenAIModel.TextEmbedding3Small, OpenAIModel.TextEmbedding3Large, DeepSeekModel.Chat, DeepSeekModel.Reasoner, DeepSeekModel.V4Flash, DeepSeekModel.V4Pro, GoogleModel.Gemini35Flash, GoogleModel.Gemini31Pro, GoogleModel.Gemini31FlashLite, GoogleModel.Gemini25Pro, GoogleModel.Gemini25Flash, GoogleModel.Gemini25FlashLite, GoogleModel.Gemini20Flash, MiniMaxModel.M3, MiniMaxModel.M27, MiniMaxModel.M27HighSpeed, MiniMaxModel.M25, MiniMaxModel.M21, MiniMaxModel.M2, MistralModel.Large, MistralModel.Medium, MistralModel.Small, MistralModel.Codestral, MistralModel.Ministral8b, MistralModel.Ministral3b, MistralModel.MagistralMedium, MistralModel.MagistralSmall, MistralModel.DevstralMedium, XAIModel.Grok45, XAIModel.Grok43, XAIModel.Grok4, XAIModel.Grok41Fast, ZhipuModel.GLM52, ZhipuModel.GLM51, ZhipuModel.GLM46, ZhipuModel.GLM45, ZhipuModel.GLM45Air, MoonshotModel.K3, MoonshotModel.K26, MoonshotModel.K27Code, MoonshotModel.K27CodeHighSpeed, QwenModel.Qwen37Max, QwenModel.Qwen37Plus, QwenModel.Qwen36Flash, QwenModel.Qwen35Flash;
4577
+ AnthropicModel.Opus55, AnthropicModel.Opus5, AnthropicModel.Opus48, AnthropicModel.Opus47, AnthropicModel.Opus46, AnthropicModel.Sonnet5, AnthropicModel.Sonnet46, AnthropicModel.Haiku45, OpenAIModel.GPT6Astra, OpenAIModel.GPT4o, OpenAIModel.GPT4oMini, OpenAIModel.O3, OpenAIModel.GPT41, OpenAIModel.GPT41Mini, OpenAIModel.GPT41Nano, OpenAIModel.GPT54, OpenAIModel.GPT54Mini, OpenAIModel.GPT54Nano, OpenAIModel.GPT54Pro, OpenAIModel.GPT55, OpenAIModel.GPT55Pro, OpenAIModel.GPT56Sol, OpenAIModel.GPT56Terra, OpenAIModel.GPT56Luna, OpenAIModel.O3Mini, OpenAIModel.O4Mini, OpenAIModel.TextEmbedding3Small, OpenAIModel.TextEmbedding3Large, DeepSeekModel.Flash, DeepSeekModel.V4Pro, DeepSeekModel.V4Flash, GoogleModel.Gemini38Flash, GoogleModel.Gemini37Flash, GoogleModel.Gemini36Flash, GoogleModel.Gemini35Flash, GoogleModel.Gemini35FlashLite, GoogleModel.Gemini31Pro, GoogleModel.Gemini31FlashLite, GoogleModel.Gemini25Pro, GoogleModel.Gemini25Flash, GoogleModel.Gemini25FlashLite, MiniMaxModel.M3, MiniMaxModel.M27, MiniMaxModel.M27HighSpeed, MiniMaxModel.M25, MiniMaxModel.M21, MiniMaxModel.M2, MistralModel.Large, MistralModel.Medium, MistralModel.Small, MistralModel.Codestral, MistralModel.Ministral8b, MistralModel.Ministral3b, XAIModel.Grok47, XAIModel.Grok46, XAIModel.Grok45, XAIModel.Grok43, XAIModel.GrokBuild01, XAIModel.Grok4, XAIModel.Grok41Fast, ZhipuModel.GLM53, ZhipuModel.GLM53Flash, ZhipuModel.GLM53FlashX, ZhipuModel.GLM52, ZhipuModel.GLM51, ZhipuModel.GLM46, ZhipuModel.GLM45, ZhipuModel.GLM45Air, MoonshotModel.K3, MoonshotModel.K26, MoonshotModel.K27Code, MoonshotModel.K27CodeHighSpeed, QwenModel.Qwen38Max, QwenModel.Qwen38Flash, QwenModel.Qwen37Max, QwenModel.Qwen37Plus, QwenModel.Qwen36Flash, QwenModel.Qwen35Flash;
4535
4578
  enumValues({
4536
4579
  Standard: "standard",
4537
4580
  AwsPrivate: "aws_private"
@@ -4746,7 +4789,8 @@ var AlfeApiClient = class {
4746
4789
  ok: false,
4747
4790
  error: errorMessage ?? (typeof body === "string" ? body : `API error: ${String(res.status)}`),
4748
4791
  status: res.status,
4749
- ...typeof errBody?.code === "string" ? { code: errBody.code } : {}
4792
+ ...typeof errBody?.code === "string" ? { code: errBody.code } : {},
4793
+ ...Array.isArray(errBody?.ineligibleUserIds) && errBody.ineligibleUserIds.every((id) => typeof id === "string") ? { ineligibleUserIds: [...errBody.ineligibleUserIds] } : {}
4750
4794
  }
4751
4795
  };
4752
4796
  }
@@ -4875,7 +4919,33 @@ var AlfeApiClient = class {
4875
4919
  }
4876
4920
  };
4877
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
4878
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
+ }
4879
4949
  var AuthService = class {
4880
4950
  client;
4881
4951
  constructor(client) {
@@ -5030,7 +5100,10 @@ var AuthService = class {
5030
5100
  });
5031
5101
  }
5032
5102
  getSubscriptionStatus() {
5033
- 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);
5034
5107
  }
5035
5108
  /**
5036
5109
  * Mint a short-lived, single-use Clerk sign-in token for the calling user so
@@ -5854,13 +5927,17 @@ function isIPCResponse(msg) {
5854
5927
  const PROTOCOL_VERSION = 1;
5855
5928
  //#endregion
5856
5929
  //#region src/runtime-gate.ts
5857
- const log$5 = logger.child({ component: "RuntimeGate" });
5930
+ const log$6 = logger.child({ component: "RuntimeGate" });
5858
5931
  var RuntimeGate = class {
5859
5932
  depth = 0;
5860
5933
  wasRunning = false;
5861
5934
  exclusive = false;
5862
5935
  /** Brackets parked behind an exclusive hold (or an exclusive request parked behind open brackets). */
5863
5936
  waiters = [];
5937
+ /** `suspendExclusive()` callers currently parked behind open brackets. */
5938
+ parkedExclusive = 0;
5939
+ /** The daemon is holding the runtime's FIRST start (fresh managed box); see {@link deferFirstStart}. */
5940
+ firstStartDeferred = false;
5864
5941
  constructor(getRuntime) {
5865
5942
  this.getRuntime = getRuntime;
5866
5943
  }
@@ -5872,6 +5949,9 @@ var RuntimeGate = class {
5872
5949
  get isExclusive() {
5873
5950
  return this.exclusive;
5874
5951
  }
5952
+ get hasParkedExclusive() {
5953
+ return this.parkedExclusive > 0;
5954
+ }
5875
5955
  waitFor(ready) {
5876
5956
  if (ready()) return Promise.resolve();
5877
5957
  return new Promise((resolve) => {
@@ -5894,26 +5974,33 @@ var RuntimeGate = class {
5894
5974
  * up (e.g. autoStartRuntime=false). Waits while an exclusive bracket is open.
5895
5975
  */
5896
5976
  async suspend() {
5897
- if (this.exclusive) log$5.info("Mutating reconcile parked behind an exclusive runtime hold (upgrade in flight)");
5977
+ if (this.exclusive) log$6.info("Mutating reconcile parked behind an exclusive runtime hold (upgrade in flight)");
5898
5978
  while (this.exclusive) await this.waitFor(() => !this.exclusive);
5899
5979
  await this.enter();
5900
5980
  }
5901
5981
  async suspendExclusive() {
5902
- if (this.exclusive || this.depth !== 0) log$5.info({
5982
+ if (this.exclusive || this.depth !== 0) log$6.info({
5903
5983
  depth: this.depth,
5904
5984
  exclusive: this.exclusive
5905
5985
  }, "Exclusive runtime hold parked behind open brackets (reconcile in flight)");
5906
- while (this.exclusive || this.depth !== 0) await this.waitFor(() => !this.exclusive && this.depth === 0);
5907
- log$5.info("Exclusive runtime hold acquired");
5986
+ if (this.exclusive || this.depth !== 0) {
5987
+ this.parkedExclusive += 1;
5988
+ try {
5989
+ while (this.exclusive || this.depth !== 0) await this.waitFor(() => !this.exclusive && this.depth === 0);
5990
+ } finally {
5991
+ this.parkedExclusive -= 1;
5992
+ }
5993
+ }
5994
+ log$6.info("Exclusive runtime hold acquired");
5908
5995
  this.exclusive = true;
5909
5996
  await this.enter();
5910
5997
  }
5911
5998
  async enter() {
5912
5999
  if (this.depth++ > 0) return;
5913
6000
  const rp = this.getRuntime();
5914
- this.wasRunning = rp?.isRunning ?? false;
6001
+ this.wasRunning = rp?.isActive ?? false;
5915
6002
  if (rp && this.wasRunning) {
5916
- log$5.info("Suspending runtime for mutating reconcile (avoid concurrent SQLite writers)");
6003
+ log$6.info("Suspending runtime for mutating reconcile (avoid concurrent SQLite writers)");
5917
6004
  await rp.stopWhenIdle();
5918
6005
  }
5919
6006
  }
@@ -5928,7 +6015,7 @@ var RuntimeGate = class {
5928
6015
  if (this.depth < 0) this.depth = 0;
5929
6016
  const rp = this.getRuntime();
5930
6017
  if (rp && this.wasRunning) {
5931
- log$5.info("Resuming runtime after mutating reconcile");
6018
+ log$6.info("Resuming runtime after mutating reconcile");
5932
6019
  rp.resume();
5933
6020
  }
5934
6021
  this.wasRunning = false;
@@ -5937,6 +6024,32 @@ var RuntimeGate = class {
5937
6024
  return Promise.resolve();
5938
6025
  }
5939
6026
  /**
6027
+ * Start a runtime that has not been started yet (daemon boot, deferred first
6028
+ * start). Never spawns while a bracket is open: the start is folded into the
6029
+ * pending outermost `resume()` instead, so a first start can't land in the
6030
+ * middle of a mutating reconcile or an exclusive upgrade.
6031
+ */
6032
+ requestStart() {
6033
+ this.firstStartDeferred = false;
6034
+ const rp = this.getRuntime();
6035
+ if (!rp) return;
6036
+ if (this.depth > 0) {
6037
+ log$6.info("Runtime start requested while suspended — deferring to pending resume");
6038
+ this.wasRunning = true;
6039
+ return;
6040
+ }
6041
+ rp.start();
6042
+ }
6043
+ /**
6044
+ * Mark the runtime's first start as deferred (fresh managed box, see
6045
+ * first-runtime-start.ts). Until {@link requestStart} runs, an out-of-band
6046
+ * {@link requestRestart} of the never-started runtime is folded into that
6047
+ * pending start instead of spawning it early.
6048
+ */
6049
+ deferFirstStart() {
6050
+ this.firstStartDeferred = true;
6051
+ }
6052
+ /**
5940
6053
  * Out-of-band restart request (e.g. `alfe.config_set`, Hermes MCP sync).
5941
6054
  * Deferred while a suspend is active — the pending `resume()` will bring the
5942
6055
  * runtime back with fresh config, so restarting mid-suspend would just fight
@@ -5945,12 +6058,16 @@ var RuntimeGate = class {
5945
6058
  */
5946
6059
  requestRestart() {
5947
6060
  if (this.depth > 0) {
5948
- log$5.info("Runtime restart requested while suspended — deferring to pending resume");
6061
+ log$6.info("Runtime restart requested while suspended — deferring to pending resume");
5949
6062
  return;
5950
6063
  }
5951
6064
  const rp = this.getRuntime();
6065
+ if (rp && this.firstStartDeferred && !rp.isActive) {
6066
+ log$6.info("Runtime restart requested before the deferred first start — folding into that start");
6067
+ return;
6068
+ }
5952
6069
  if (rp) rp.restart().catch((err) => {
5953
- log$5.error({ err: err instanceof Error ? err.message : String(err) }, "Failed to restart runtime");
6070
+ log$6.error({ err: err instanceof Error ? err.message : String(err) }, "Failed to restart runtime");
5954
6071
  });
5955
6072
  }
5956
6073
  };
@@ -5973,7 +6090,23 @@ var NoopRuntimeGate = class {
5973
6090
  };
5974
6091
  //#endregion
5975
6092
  //#region src/reconciliation.ts
5976
- const log$4 = logger.child({ component: "Reconciliation" });
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
+ */
6109
+ const log$5 = logger.child({ component: "Reconciliation" });
5977
6110
  /**
5978
6111
  * How many times reconcile will re-attempt activation of an intact integration
5979
6112
  * stuck in `error` (with no reinstall requested) before giving up and waiting
@@ -5994,6 +6127,32 @@ var ReconciliationEngine = class {
5994
6127
  */
5995
6128
  suspended = false;
5996
6129
  /**
6130
+ * A suspension carried from the previous pass because the caller already
6131
+ * had another DESIRED_STATE queued (see {@link ReconcileOptions.holdSuspension}).
6132
+ * The next pass adopts it instead of resuming → re-suspending the runtime;
6133
+ * {@link releaseHeldSuspension} gives it back once the queue is drained.
6134
+ */
6135
+ heldSuspension = false;
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
+ /**
5997
6156
  * @param manager The integration manager to converge.
5998
6157
  * @param gate Runtime gate used to suspend the runtime child around
5999
6158
  * mutating branches so `openclaw` CLI subprocesses don't write
@@ -6006,6 +6165,29 @@ var ReconciliationEngine = class {
6006
6165
  this.gate = gate;
6007
6166
  }
6008
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
+ /**
6009
6191
  * Suspend the runtime on the first mutating branch of a reconcile pass.
6010
6192
  * Idempotent within a pass — subsequent mutating branches are covered by the
6011
6193
  * single suspend held until the `finally` in {@link reconcile}.
@@ -6022,14 +6204,43 @@ var ReconciliationEngine = class {
6022
6204
  * so the mutation performed by {@link ensureSuspended} in a different method
6023
6205
  * isn't narrowed away by control-flow analysis at the callsite.
6024
6206
  */
6025
- async resumeIfSuspended() {
6026
- if (this.suspended) await this.gate.resume();
6207
+ async resumeIfSuspended(opts) {
6208
+ if (!this.suspended) return;
6209
+ if (opts?.holdSuspension?.()) {
6210
+ log$5.info("Holding runtime suspension across queued reconcile pass");
6211
+ if (this.gate.hasParkedExclusive) log$5.warn("Exclusive runtime hold (upgrade) is parked behind a suspension held for queued reconcile passes");
6212
+ this.heldSuspension = true;
6213
+ return;
6214
+ }
6215
+ await this.gate.resume();
6216
+ }
6217
+ /**
6218
+ * Resume a suspension held across passes by {@link ReconcileOptions.holdSuspension}.
6219
+ * The caller MUST call this once its queue of passes is drained (success or
6220
+ * failure), otherwise the runtime stays suspended. No-op when nothing is held.
6221
+ */
6222
+ async releaseHeldSuspension() {
6223
+ if (!this.heldSuspension) return;
6224
+ this.heldSuspension = false;
6225
+ await this.gate.resume();
6226
+ }
6227
+ /** True while a suspension is carried between passes (tests/diagnostics). */
6228
+ get isHoldingSuspension() {
6229
+ return this.heldSuspension;
6027
6230
  }
6028
6231
  /**
6029
6232
  * Reconcile local state against desired state from cloud.
6030
6233
  */
6031
- async reconcile(desiredIntegrations) {
6032
- this.suspended = false;
6234
+ async reconcile(desiredIntegrations, opts) {
6235
+ this.suspended = this.heldSuspension;
6236
+ this.heldSuspension = false;
6237
+ try {
6238
+ return await this.reconcilePass(desiredIntegrations);
6239
+ } finally {
6240
+ await this.resumeIfSuspended(opts);
6241
+ }
6242
+ }
6243
+ async reconcilePass(desiredIntegrations) {
6033
6244
  const report = {
6034
6245
  results: [],
6035
6246
  installed: [],
@@ -6043,7 +6254,7 @@ var ReconciliationEngine = class {
6043
6254
  try {
6044
6255
  localIntegrations = await this.manager.getInstalledIntegrations();
6045
6256
  } catch (err) {
6046
- log$4.error({ err }, "Failed to get local integrations");
6257
+ log$5.error({ err }, "Failed to get local integrations");
6047
6258
  captureIntegrationFailure("_reconciliation", "read_local_state", err);
6048
6259
  const errorMessage = "Failed to read local integration state; reconciliation skipped";
6049
6260
  const affectedIds = desiredIntegrations.length > 0 ? desiredIntegrations.map((desired) => desired.integrationId) : ["*"];
@@ -6061,34 +6272,30 @@ var ReconciliationEngine = class {
6061
6272
  }
6062
6273
  const localMap = new Map(localIntegrations.map((i) => [i.id, i]));
6063
6274
  const desiredMap = new Map(desiredIntegrations.map((d) => [d.integrationId, d]));
6064
- try {
6065
- for (const desired of desiredIntegrations) if (desired.desiredStatus === "active") {
6066
- const local = localMap.get(desired.integrationId);
6067
- if (desired.connectionAuthorityVersion !== void 0 && local?.connectionAuthorityPendingVersion !== void 0 && local.status === "error" && (local.version === desired.version || desired.version === "" || local.version === "unknown") && !(desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) && await this.manager.isInstallIntact(desired.integrationId)) report.results.push({
6275
+ for (const desired of desiredIntegrations) if (desired.desiredStatus === "active") {
6276
+ const local = localMap.get(desired.integrationId);
6277
+ if (desired.connectionAuthorityVersion !== void 0 && local?.connectionAuthorityPendingVersion !== void 0 && local.status === "error" && (local.version === desired.version || desired.version === "" || local.version === "unknown") && !(desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) && await this.manager.isInstallIntact(desired.integrationId)) report.results.push({
6278
+ integrationId: desired.integrationId,
6279
+ action: "activated",
6280
+ actualStatus: "active"
6281
+ });
6282
+ else await this.reconcileActive(desired, local, report);
6283
+ const result = report.results.find((item) => item.integrationId === desired.integrationId);
6284
+ if (result?.actualStatus === "active" && desired.connectionAuthorityVersion !== void 0 && (local?.connectionAuthorityPendingVersion !== void 0 || desired.connectionAuthorityVersion !== local?.connectionAuthorityVersion)) try {
6285
+ await this.manager.refreshConnections(desired.integrationId, desired.connectionAuthorityVersion, { onBeforeRuntimeMutation: () => this.ensureSuspended() });
6286
+ } catch (err) {
6287
+ captureIntegrationFailure(desired.integrationId, "refresh_connections", err);
6288
+ const error = err instanceof Error ? err.message : String(err);
6289
+ report.errors.push({
6068
6290
  integrationId: desired.integrationId,
6069
- action: "activated",
6070
- actualStatus: "active"
6291
+ error
6071
6292
  });
6072
- else await this.reconcileActive(desired, local, report);
6073
- const result = report.results.find((item) => item.integrationId === desired.integrationId);
6074
- if (result?.actualStatus === "active" && desired.connectionAuthorityVersion !== void 0 && (local?.connectionAuthorityPendingVersion !== void 0 || desired.connectionAuthorityVersion !== local?.connectionAuthorityVersion)) try {
6075
- await this.manager.refreshConnections(desired.integrationId, desired.connectionAuthorityVersion, { onBeforeRuntimeMutation: () => this.ensureSuspended() });
6076
- } catch (err) {
6077
- captureIntegrationFailure(desired.integrationId, "refresh_connections", err);
6078
- const error = err instanceof Error ? err.message : String(err);
6079
- report.errors.push({
6080
- integrationId: desired.integrationId,
6081
- error
6082
- });
6083
- result.action = "error";
6084
- result.actualStatus = "error";
6085
- result.errorMessage = error;
6086
- }
6087
- } else await this.reconcileRemoved(desired.integrationId, localMap.get(desired.integrationId), report);
6088
- for (const local of localIntegrations) if (!desiredMap.has(local.id)) await this.reconcileRemoved(local.id, local, report);
6089
- } finally {
6090
- await this.resumeIfSuspended();
6091
- }
6293
+ result.action = "error";
6294
+ result.actualStatus = "error";
6295
+ result.errorMessage = error;
6296
+ }
6297
+ } else await this.reconcileRemoved(desired.integrationId, localMap.get(desired.integrationId), report);
6298
+ for (const local of localIntegrations) if (!desiredMap.has(local.id)) await this.reconcileRemoved(local.id, local, report);
6092
6299
  return report;
6093
6300
  }
6094
6301
  async reconcileActive(desired, local, report) {
@@ -6096,10 +6303,11 @@ var ReconciliationEngine = class {
6096
6303
  try {
6097
6304
  if (!local) {
6098
6305
  await this.ensureSuspended();
6099
- log$4.info(`Installing ${id}@${desired.version}`);
6306
+ this.resetSelfHealBudget(id);
6307
+ log$5.info(`Installing ${id}@${desired.version}`);
6100
6308
  await this.manager.install(id, desired.version, desired.config, desired.customSource);
6101
6309
  report.installed.push(id);
6102
- log$4.info(`Activating ${id}`);
6310
+ log$5.info(`Activating ${id}`);
6103
6311
  if ((await this.manager.activate(id)).configApplied) report.configApplied = true;
6104
6312
  report.activated.push(id);
6105
6313
  report.results.push({
@@ -6112,9 +6320,9 @@ var ReconciliationEngine = class {
6112
6320
  if (local.version !== desired.version && desired.version !== "" && local.version !== "unknown") {
6113
6321
  if (desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) {
6114
6322
  await this.ensureSuspended();
6115
- log$4.info(`Reinstalling ${id} on version change (reinstall requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt}): ${local.version} → ${desired.version}`);
6323
+ log$5.info(`Reinstalling ${id} on version change (reinstall requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt}): ${local.version} → ${desired.version}`);
6116
6324
  this.manager.resetReinstallAttempts(id);
6117
- this.activateAttempts.delete(id);
6325
+ this.resetSelfHealBudget(id);
6118
6326
  if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource, { reinstallRequestedAt: desired.reinstallRequestedAt })).configApplied) report.configApplied = true;
6119
6327
  report.installed.push(id);
6120
6328
  report.activated.push(id);
@@ -6125,11 +6333,11 @@ var ReconciliationEngine = class {
6125
6333
  });
6126
6334
  return;
6127
6335
  }
6128
- log$4.info(`Upgrading ${id} in place: ${local.version} → ${desired.version}`);
6336
+ log$5.info(`Upgrading ${id} in place: ${local.version} → ${desired.version}`);
6129
6337
  try {
6130
6338
  if ((await this.manager.upgrade(id, desired.version, desired.config, desired.customSource, { onBeforeRuntimeMutation: () => this.ensureSuspended() })).configApplied) report.configApplied = true;
6131
6339
  this.manager.resetReinstallAttempts(id);
6132
- this.activateAttempts.delete(id);
6340
+ this.resetSelfHealBudget(id);
6133
6341
  report.installed.push(id);
6134
6342
  report.activated.push(id);
6135
6343
  report.results.push({
@@ -6139,7 +6347,7 @@ var ReconciliationEngine = class {
6139
6347
  });
6140
6348
  } catch (upgradeErr) {
6141
6349
  const upgradeMsg = upgradeErr instanceof Error ? upgradeErr.message : String(upgradeErr);
6142
- log$4.error({ err: upgradeErr }, `Upgrade failed for ${id}`);
6350
+ log$5.error({ err: upgradeErr }, `Upgrade failed for ${id}`);
6143
6351
  captureIntegrationFailure(id, "upgrade", upgradeErr);
6144
6352
  report.errors.push({
6145
6353
  integrationId: id,
@@ -6157,9 +6365,9 @@ var ReconciliationEngine = class {
6157
6365
  if (local.status === "error") {
6158
6366
  if (desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) {
6159
6367
  await this.ensureSuspended();
6160
- log$4.info(`Reinstalling ${id} from error state (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
6368
+ log$5.info(`Reinstalling ${id} from error state (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
6161
6369
  this.manager.resetReinstallAttempts(id);
6162
- this.activateAttempts.delete(id);
6370
+ this.resetSelfHealBudget(id);
6163
6371
  if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource, { reinstallRequestedAt: desired.reinstallRequestedAt })).configApplied) report.configApplied = true;
6164
6372
  report.installed.push(id);
6165
6373
  report.activated.push(id);
@@ -6173,8 +6381,9 @@ var ReconciliationEngine = class {
6173
6381
  if (!await this.manager.isInstallIntact(id)) {
6174
6382
  const attempts = this.manager.getReinstallAttempts(id);
6175
6383
  if (attempts >= 3) {
6176
- log$4.error(`Auto-reinstall blocked for ${id} — ${String(attempts)} consecutive failures. Manual reinstall required.`);
6177
- captureIntegrationFailure(id, "reinstall", `Max auto-reinstall attempts (${String(attempts)}) reached — manual reinstall required`);
6384
+ log$5.error(`Auto-reinstall blocked for ${id} — ${String(attempts)} consecutive failures. Manual reinstall required.`);
6385
+ const blockedMessage = `Max auto-reinstall attempts (${String(attempts)}) reached — manual reinstall required`;
6386
+ this.captureFailureOnce(id, "reinstall", `blocked:${blockedMessage}`, blockedMessage);
6178
6387
  report.errors.push({
6179
6388
  integrationId: id,
6180
6389
  error: `Max auto-reinstall attempts (${String(attempts)}) reached`
@@ -6188,12 +6397,12 @@ var ReconciliationEngine = class {
6188
6397
  return;
6189
6398
  }
6190
6399
  await this.ensureSuspended();
6191
- log$4.info(`Auto-reinstalling ${id} — install directory is corrupted or missing (attempt ${String(attempts + 1)}/3)`);
6400
+ log$5.info(`Auto-reinstalling ${id} — install directory is corrupted or missing (attempt ${String(attempts + 1)}/3)`);
6192
6401
  this.manager.incrementReinstallAttempts(id);
6193
6402
  try {
6194
6403
  if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
6195
6404
  this.manager.resetReinstallAttempts(id);
6196
- this.activateAttempts.delete(id);
6405
+ this.resetSelfHealBudget(id);
6197
6406
  report.installed.push(id);
6198
6407
  report.activated.push(id);
6199
6408
  report.results.push({
@@ -6203,7 +6412,7 @@ var ReconciliationEngine = class {
6203
6412
  });
6204
6413
  } catch (reinstallErr) {
6205
6414
  const reinstallMsg = reinstallErr instanceof Error ? reinstallErr.message : String(reinstallErr);
6206
- log$4.error({ err: reinstallErr }, `Auto-reinstall failed for ${id}`);
6415
+ log$5.error({ err: reinstallErr }, `Auto-reinstall failed for ${id}`);
6207
6416
  captureIntegrationFailure(id, "reinstall", reinstallErr);
6208
6417
  report.errors.push({
6209
6418
  integrationId: id,
@@ -6222,10 +6431,10 @@ var ReconciliationEngine = class {
6222
6431
  if (activateAttempts < MAX_ERROR_ACTIVATE_ATTEMPTS) {
6223
6432
  this.activateAttempts.set(id, activateAttempts + 1);
6224
6433
  await this.ensureSuspended();
6225
- log$4.info(`Re-activating ${id} from error state (attempt ${String(activateAttempts + 1)}/${String(MAX_ERROR_ACTIVATE_ATTEMPTS)})`);
6434
+ log$5.info(`Re-activating ${id} from error state (attempt ${String(activateAttempts + 1)}/${String(MAX_ERROR_ACTIVATE_ATTEMPTS)})`);
6226
6435
  try {
6227
6436
  if ((await this.manager.activate(id, { forcePlugins: true })).configApplied) report.configApplied = true;
6228
- this.activateAttempts.delete(id);
6437
+ this.resetSelfHealBudget(id);
6229
6438
  report.activated.push(id);
6230
6439
  report.results.push({
6231
6440
  integrationId: id,
@@ -6235,8 +6444,9 @@ var ReconciliationEngine = class {
6235
6444
  return;
6236
6445
  } catch (reactivateErr) {
6237
6446
  const reactivateMsg = reactivateErr instanceof Error ? reactivateErr.message : String(reactivateErr);
6238
- log$4.warn(`Re-activation of ${id} from error state failed (attempt ${String(activateAttempts + 1)}/${String(MAX_ERROR_ACTIVATE_ATTEMPTS)}): ${reactivateMsg}`);
6239
- captureIntegrationFailure(id, "reactivate", reactivateErr);
6447
+ log$5.warn(`Re-activation of ${id} from error state failed (attempt ${String(activateAttempts + 1)}/${String(MAX_ERROR_ACTIVATE_ATTEMPTS)}): ${reactivateMsg}`);
6448
+ this.lastReactivateError.set(id, reactivateMsg);
6449
+ this.captureFailureOnce(id, "reactivate", `attempt:${reactivateMsg}`, reactivateErr);
6240
6450
  report.errors.push({
6241
6451
  integrationId: id,
6242
6452
  error: reactivateMsg
@@ -6250,8 +6460,9 @@ var ReconciliationEngine = class {
6250
6460
  return;
6251
6461
  }
6252
6462
  }
6253
- log$4.warn(`Integration ${id} is in error state — re-activation exhausted, waiting for reinstall request`);
6254
- captureIntegrationFailure(id, "reactivate", `Integration ${id} stuck in error state — re-activation attempts exhausted`);
6463
+ log$5.warn(`Integration ${id} is in error state — re-activation exhausted, waiting for reinstall request`);
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]);
6255
6466
  report.errors.push({
6256
6467
  integrationId: id,
6257
6468
  error: "Integration is in error state"
@@ -6267,9 +6478,9 @@ var ReconciliationEngine = class {
6267
6478
  if (local.status === "installing") {
6268
6479
  if (desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) {
6269
6480
  await this.ensureSuspended();
6270
- log$4.info(`Reinstalling ${id} from stale installing state (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
6481
+ log$5.info(`Reinstalling ${id} from stale installing state (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
6271
6482
  this.manager.resetReinstallAttempts(id);
6272
- this.activateAttempts.delete(id);
6483
+ this.resetSelfHealBudget(id);
6273
6484
  if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource, { reinstallRequestedAt: desired.reinstallRequestedAt })).configApplied) report.configApplied = true;
6274
6485
  report.installed.push(id);
6275
6486
  report.activated.push(id);
@@ -6283,8 +6494,8 @@ var ReconciliationEngine = class {
6283
6494
  const attempts = this.manager.getReinstallAttempts(id);
6284
6495
  if (attempts >= 3) {
6285
6496
  const errorMessage = `Stale installing state — max auto-reinstall attempts (${String(attempts)}) reached — manual reinstall required`;
6286
- log$4.error(`Auto-reinstall blocked for ${id} — stuck in "installing", ${String(attempts)} consecutive failures. Manual reinstall required.`);
6287
- captureIntegrationFailure(id, "reinstall", errorMessage);
6497
+ log$5.error(`Auto-reinstall blocked for ${id} — stuck in "installing", ${String(attempts)} consecutive failures. Manual reinstall required.`);
6498
+ this.captureFailureOnce(id, "reinstall", `blocked:${errorMessage}`, errorMessage);
6288
6499
  report.errors.push({
6289
6500
  integrationId: id,
6290
6501
  error: `Max auto-reinstall attempts (${String(attempts)}) reached`
@@ -6298,12 +6509,12 @@ var ReconciliationEngine = class {
6298
6509
  return;
6299
6510
  }
6300
6511
  await this.ensureSuspended();
6301
- log$4.info(`Recovering ${id} from stale installing state via reinstall (attempt ${String(attempts + 1)}/3)`);
6512
+ log$5.info(`Recovering ${id} from stale installing state via reinstall (attempt ${String(attempts + 1)}/3)`);
6302
6513
  this.manager.incrementReinstallAttempts(id);
6303
6514
  try {
6304
6515
  if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
6305
6516
  this.manager.resetReinstallAttempts(id);
6306
- this.activateAttempts.delete(id);
6517
+ this.resetSelfHealBudget(id);
6307
6518
  report.installed.push(id);
6308
6519
  report.activated.push(id);
6309
6520
  report.results.push({
@@ -6313,7 +6524,7 @@ var ReconciliationEngine = class {
6313
6524
  });
6314
6525
  } catch (reinstallErr) {
6315
6526
  const reinstallMsg = reinstallErr instanceof Error ? reinstallErr.message : String(reinstallErr);
6316
- log$4.error({ err: reinstallErr }, `Reinstall from stale installing state failed for ${id}`);
6527
+ log$5.error({ err: reinstallErr }, `Reinstall from stale installing state failed for ${id}`);
6317
6528
  captureIntegrationFailure(id, "reinstall", reinstallErr);
6318
6529
  report.errors.push({
6319
6530
  integrationId: id,
@@ -6330,8 +6541,9 @@ var ReconciliationEngine = class {
6330
6541
  }
6331
6542
  if (local.status !== "active") {
6332
6543
  await this.ensureSuspended();
6333
- log$4.info(`Activating ${id}`);
6544
+ log$5.info(`Activating ${id}`);
6334
6545
  if ((await this.manager.activate(id, { forcePlugins: true })).configApplied) report.configApplied = true;
6546
+ this.resetSelfHealBudget(id);
6335
6547
  report.activated.push(id);
6336
6548
  report.results.push({
6337
6549
  integrationId: id,
@@ -6342,9 +6554,9 @@ var ReconciliationEngine = class {
6342
6554
  }
6343
6555
  if (desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) {
6344
6556
  await this.ensureSuspended();
6345
- log$4.info(`Reinstall requested for ${id} (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
6557
+ log$5.info(`Reinstall requested for ${id} (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
6346
6558
  this.manager.resetReinstallAttempts(id);
6347
- this.activateAttempts.delete(id);
6559
+ this.resetSelfHealBudget(id);
6348
6560
  if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource, { reinstallRequestedAt: desired.reinstallRequestedAt })).configApplied) report.configApplied = true;
6349
6561
  report.installed.push(id);
6350
6562
  report.activated.push(id);
@@ -6355,7 +6567,7 @@ var ReconciliationEngine = class {
6355
6567
  });
6356
6568
  return;
6357
6569
  }
6358
- this.activateAttempts.delete(id);
6570
+ this.resetSelfHealBudget(id);
6359
6571
  report.results.push({
6360
6572
  integrationId: id,
6361
6573
  action: "up_to_date",
@@ -6363,7 +6575,7 @@ var ReconciliationEngine = class {
6363
6575
  });
6364
6576
  } catch (err) {
6365
6577
  const message = err instanceof Error ? err.message : String(err);
6366
- log$4.error({ err }, `Error reconciling ${id}`);
6578
+ log$5.error({ err }, `Error reconciling ${id}`);
6367
6579
  captureIntegrationFailure(id, "reconcile_active", err);
6368
6580
  report.errors.push({
6369
6581
  integrationId: id,
@@ -6388,12 +6600,13 @@ var ReconciliationEngine = class {
6388
6600
  }
6389
6601
  try {
6390
6602
  await this.ensureSuspended();
6391
- log$4.info(`Removing ${id}`);
6603
+ log$5.info(`Removing ${id}`);
6392
6604
  if (local.status === "active") {
6393
6605
  await this.manager.deactivate(id);
6394
6606
  report.deactivated.push(id);
6395
6607
  }
6396
6608
  await this.manager.uninstall(id);
6609
+ this.resetSelfHealBudget(id);
6397
6610
  report.uninstalled.push(id);
6398
6611
  report.results.push({
6399
6612
  integrationId: id,
@@ -6402,7 +6615,7 @@ var ReconciliationEngine = class {
6402
6615
  });
6403
6616
  } catch (err) {
6404
6617
  const message = err instanceof Error ? err.message : String(err);
6405
- log$4.error({ err }, `Error removing ${id}`);
6618
+ log$5.error({ err }, `Error removing ${id}`);
6406
6619
  captureIntegrationFailure(id, "reconcile_removed", err);
6407
6620
  report.errors.push({
6408
6621
  integrationId: id,
@@ -6440,6 +6653,7 @@ var CloudClient = class {
6440
6653
  onConnectionChange = null;
6441
6654
  onRuntimeRestartNeeded = null;
6442
6655
  onReconciliationComplete = null;
6656
+ onReconciliationSettled = null;
6443
6657
  reconciliationEngine = null;
6444
6658
  configReconciler = null;
6445
6659
  reconciling = false;
@@ -6479,6 +6693,16 @@ var CloudClient = class {
6479
6693
  this.onReconciliationComplete = handler;
6480
6694
  }
6481
6695
  /**
6696
+ * Set a callback for when a DESIRED_STATE burst has fully SETTLED: the
6697
+ * received snapshot and every queued one have run (each may have succeeded
6698
+ * or failed) and any runtime suspension held across them was released.
6699
+ * Fires on success AND failure — the daemon's deferred first runtime start
6700
+ * hangs off it, and a failed reconcile must not strand the runtime.
6701
+ */
6702
+ setOnReconciliationSettled(handler) {
6703
+ this.onReconciliationSettled = handler;
6704
+ }
6705
+ /**
6482
6706
  * Set the integration manager (and optionally the runtime gate) for
6483
6707
  * reconciliation. When set, the cloud client handles DESIRED_STATE messages
6484
6708
  * automatically. The gate is passed to the ReconciliationEngine so mutating
@@ -6725,20 +6949,43 @@ var CloudClient = class {
6725
6949
  const message = err instanceof Error ? err.message : String(err);
6726
6950
  logger.error({ err: message }, "Cloud: reconciliation failed");
6727
6951
  } finally {
6728
- while (this.pendingDesiredState) {
6729
- const next = this.pendingDesiredState;
6730
- this.pendingDesiredState = null;
6731
- logger.info({ count: next.integrations.length }, "Cloud: processing queued DESIRED_STATE");
6952
+ for (;;) {
6953
+ while (this.pendingDesiredState) {
6954
+ const next = this.pendingDesiredState;
6955
+ this.pendingDesiredState = null;
6956
+ logger.info({ count: next.integrations.length }, "Cloud: processing queued DESIRED_STATE");
6957
+ try {
6958
+ await this.runReconciliation(next);
6959
+ } catch (err) {
6960
+ const message = err instanceof Error ? err.message : String(err);
6961
+ logger.error({ err: message }, "Cloud: queued reconciliation failed");
6962
+ }
6963
+ }
6732
6964
  try {
6733
- await this.runReconciliation(next);
6965
+ await this.reconciliationEngine.releaseHeldSuspension();
6734
6966
  } catch (err) {
6735
6967
  const message = err instanceof Error ? err.message : String(err);
6736
- logger.error({ err: message }, "Cloud: queued reconciliation failed");
6968
+ logger.error({ err: message }, "Cloud: failed to resume runtime after queued reconciliation");
6737
6969
  }
6970
+ if (!this.hasQueuedDesiredState()) break;
6738
6971
  }
6739
6972
  this.reconciling = false;
6973
+ try {
6974
+ this.onReconciliationSettled?.();
6975
+ } catch (err) {
6976
+ const message = err instanceof Error ? err.message : String(err);
6977
+ logger.error({ err: message }, "Cloud: reconciliation-settled handler failed");
6978
+ }
6740
6979
  }
6741
6980
  }
6981
+ /**
6982
+ * Read through a method so control-flow narrowing from the drain loop above
6983
+ * doesn't treat `pendingDesiredState` as still null across an `await` (a
6984
+ * snapshot can be queued by another message while we're suspended).
6985
+ */
6986
+ hasQueuedDesiredState() {
6987
+ return this.pendingDesiredState !== null;
6988
+ }
6742
6989
  async runReconciliation(msg) {
6743
6990
  if (msg.config && this.configReconciler) try {
6744
6991
  const configReport = await this.configReconciler.reconcile(msg.config);
@@ -6754,7 +7001,7 @@ var CloudClient = class {
6754
7001
  logger.error({ err: message }, "Cloud: config reconcile failed");
6755
7002
  }
6756
7003
  if (!this.reconciliationEngine) return;
6757
- const report = await this.reconciliationEngine.reconcile(msg.integrations);
7004
+ const report = await this.reconciliationEngine.reconcile(msg.integrations, { holdSuspension: () => this.hasQueuedDesiredState() });
6758
7005
  logger.info({
6759
7006
  installed: report.installed.length,
6760
7007
  activated: report.activated.length,
@@ -6817,7 +7064,7 @@ var CloudClient = class {
6817
7064
  };
6818
7065
  //#endregion
6819
7066
  //#region src/config-reconciler.ts
6820
- const log$3 = logger.child({ component: "ConfigReconciler" });
7067
+ const log$4 = logger.child({ component: "ConfigReconciler" });
6821
7068
  const DEFAULT_VERIFY_RETRIES = 3;
6822
7069
  const DEFAULT_VERIFY_RETRY_DELAY_MS = 750;
6823
7070
  const delay = (ms) => new Promise((resolve) => {
@@ -6839,7 +7086,7 @@ var ConfigReconciler = class {
6839
7086
  async reconcile(desired) {
6840
7087
  const applier = this.getApplier();
6841
7088
  if (!applier?.setConfigRaw) {
6842
- log$3.debug("No applier with setConfigRaw — skipping config reconcile");
7089
+ log$4.debug("No applier with setConfigRaw — skipping config reconcile");
6843
7090
  return null;
6844
7091
  }
6845
7092
  const setConfigRaw = applier.setConfigRaw.bind(applier);
@@ -6857,13 +7104,13 @@ var ConfigReconciler = class {
6857
7104
  try {
6858
7105
  current = await getConfigRaw(key);
6859
7106
  } catch (err) {
6860
- log$3.warn({
7107
+ log$4.warn({
6861
7108
  key,
6862
7109
  err: err instanceof Error ? err.message : String(err)
6863
7110
  }, "Config diff read failed — attempting the write");
6864
7111
  }
6865
7112
  if (current === want) {
6866
- log$3.debug({ key }, "Config already at desired value — no-op");
7113
+ log$4.debug({ key }, "Config already at desired value — no-op");
6867
7114
  continue;
6868
7115
  }
6869
7116
  }
@@ -6878,7 +7125,7 @@ var ConfigReconciler = class {
6878
7125
  if (getConfigRaw) {
6879
7126
  const outcome = await this.verify(getConfigRaw, key, want);
6880
7127
  if (outcome === "matched") {
6881
- log$3.warn({
7128
+ log$4.warn({
6882
7129
  key,
6883
7130
  err: msg
6884
7131
  }, "config set errored but value landed — treating as applied");
@@ -6894,7 +7141,7 @@ var ConfigReconciler = class {
6894
7141
  if (failures.length > 0) {
6895
7142
  const joined = failures.join("; ");
6896
7143
  const reason = joined.length > 480 ? `${joined.slice(0, 480)}… (truncated)` : joined;
6897
- log$3.warn({
7144
+ log$4.warn({
6898
7145
  version: desired.version,
6899
7146
  reason
6900
7147
  }, "Config reconcile failed");
@@ -6905,13 +7152,13 @@ var ConfigReconciler = class {
6905
7152
  };
6906
7153
  }
6907
7154
  if (inconclusiveKeys.length > 0) {
6908
- log$3.warn({
7155
+ log$4.warn({
6909
7156
  version: desired.version,
6910
7157
  keys: inconclusiveKeys
6911
7158
  }, "Config reconcile verification was inconclusive — leaving version pending");
6912
7159
  return null;
6913
7160
  }
6914
- log$3.info({
7161
+ log$4.info({
6915
7162
  version: desired.version,
6916
7163
  keys: entries.length
6917
7164
  }, "Config reconcile applied");
@@ -6928,7 +7175,7 @@ var ConfigReconciler = class {
6928
7175
  if (current === want) return "matched";
6929
7176
  if (current !== void 0) observedValue = true;
6930
7177
  } catch (err) {
6931
- log$3.debug({
7178
+ log$4.debug({
6932
7179
  key,
6933
7180
  err: err instanceof Error ? err.message : String(err)
6934
7181
  }, "Config verify read failed");
@@ -8945,7 +9192,7 @@ function reportAvailableCommands(deps) {
8945
9192
  * arrive — the grace deadline is a backstop, but fail-open-to-idle means the
8946
9193
  * common "nothing to drain" case restarts immediately with no added latency.
8947
9194
  */
8948
- const log$2 = logger.child({ component: "TurnActivity" });
9195
+ const log$3 = logger.child({ component: "TurnActivity" });
8949
9196
  /** The IPC method the daemon calls on plugins to ask "is a turn active?". */
8950
9197
  const RUNTIME_ACTIVITY_METHOD = "runtime.activity";
8951
9198
  /**
@@ -8964,17 +9211,122 @@ var IpcTurnActivityProbe = class {
8964
9211
  try {
8965
9212
  const responses = await ipc.broadcastRequest(RUNTIME_ACTIVITY_METHOD, {}, this.probeTimeoutMs);
8966
9213
  for (const [pluginId, res] of responses) if (res.ok && res.payload != null && typeof res.payload === "object" && res.payload.active === true) {
8967
- log$2.debug({ pluginId }, "Plugin reports an in-flight turn");
9214
+ log$3.debug({ pluginId }, "Plugin reports an in-flight turn");
8968
9215
  return true;
8969
9216
  }
8970
9217
  return false;
8971
9218
  } catch (err) {
8972
- log$2.debug({ err: err instanceof Error ? err.message : String(err) }, "Turn-activity probe failed — assuming idle");
9219
+ log$3.debug({ err: err instanceof Error ? err.message : String(err) }, "Turn-activity probe failed — assuming idle");
8973
9220
  return false;
8974
9221
  }
8975
9222
  }
8976
9223
  };
8977
9224
  //#endregion
9225
+ //#region src/first-runtime-start.ts
9226
+ /**
9227
+ * Deferred first runtime start for fresh managed boxes.
9228
+ *
9229
+ * On a brand-new managed VM the first DESIRED_STATE always installs the core
9230
+ * integrations, and every mutating reconcile suspends the runtime through the
9231
+ * RuntimeGate. Starting the runtime at daemon boot therefore buys nothing: it
9232
+ * boots (and runs its first-boot migrations) only to be stopped ~seconds later
9233
+ * by that first reconcile, then started again after it. Deferring the first
9234
+ * start until the first reconcile burst settles removes that whole cycle.
9235
+ *
9236
+ * The deferral is released by whichever comes first:
9237
+ * - the cloud client's "reconciliation settled" hook (success OR failure),
9238
+ * - a fallback timer armed when the cloud client first REGISTERS
9239
+ * ({@link DeferredRuntimeStart.armFallback}), so slow boot/connect time
9240
+ * doesn't eat the window the first DESIRED_STATE needs;
9241
+ * - a longer boot ceiling measured from daemon boot, so a daemon that never
9242
+ * registers (gateway down, registration failing) still brings the runtime up.
9243
+ *
9244
+ * Only fresh managed boxes defer (managed mode AND no integrations in local
9245
+ * state). Self-hosted daemons and restarts of an already-provisioned box start
9246
+ * the runtime immediately, exactly as before.
9247
+ */
9248
+ const log$2 = logger.child({ component: "FirstRuntimeStart" });
9249
+ /** How long a fresh box waits, after first registering with the cloud, for its first reconcile to settle. */
9250
+ const FIRST_START_FALLBACK_MS = 18e4;
9251
+ /** Hard ceiling from daemon boot, for a daemon that never registers with the cloud. */
9252
+ const FIRST_START_BOOT_CEILING_MS = 6e5;
9253
+ /**
9254
+ * Fresh managed box ⇔ managed mode AND local integration state is readable and
9255
+ * empty. An unreadable state is NOT treated as fresh — fall back to the old
9256
+ * start-immediately behaviour rather than delay a box we know nothing about.
9257
+ */
9258
+ function shouldDeferFirstRuntimeStart(input) {
9259
+ return input.managed && input.installedIntegrationCount === 0;
9260
+ }
9261
+ var DeferredRuntimeStart = class {
9262
+ released = false;
9263
+ cancelled = false;
9264
+ startFn = null;
9265
+ fallbackArmed = false;
9266
+ fallbackTimer = null;
9267
+ ceilingTimer;
9268
+ /** Arms the boot ceiling immediately; the fallback waits for {@link armFallback}. */
9269
+ constructor(fallbackMs = FIRST_START_FALLBACK_MS, bootCeilingMs = FIRST_START_BOOT_CEILING_MS) {
9270
+ this.fallbackMs = fallbackMs;
9271
+ this.ceilingTimer = setTimeout(() => {
9272
+ this.ceilingTimer = null;
9273
+ this.release("boot-ceiling");
9274
+ }, bootCeilingMs);
9275
+ }
9276
+ /**
9277
+ * Start the fallback clock. Called when the cloud client registers; only
9278
+ * the FIRST call arms it (a reconnect must not push the deadline out).
9279
+ */
9280
+ armFallback() {
9281
+ if (this.released || this.cancelled || this.fallbackArmed) return;
9282
+ this.fallbackArmed = true;
9283
+ this.fallbackTimer = setTimeout(() => {
9284
+ this.fallbackTimer = null;
9285
+ this.release("fallback-timeout");
9286
+ }, this.fallbackMs);
9287
+ }
9288
+ /** True once released (by either path). */
9289
+ get isReleased() {
9290
+ return this.released;
9291
+ }
9292
+ /**
9293
+ * Provide the start action once the runtime process exists. If the deferral
9294
+ * was already released (a reconcile settled before the runtime object was
9295
+ * built), starts right away.
9296
+ */
9297
+ attach(start) {
9298
+ if (this.cancelled) return;
9299
+ this.startFn = start;
9300
+ if (this.released) start();
9301
+ }
9302
+ /** Release the deferral. Idempotent — only the first call starts the runtime. */
9303
+ release(reason) {
9304
+ if (this.released || this.cancelled) return;
9305
+ this.released = true;
9306
+ this.clearTimers();
9307
+ log$2.info({
9308
+ reason,
9309
+ fallbackMs: this.fallbackMs
9310
+ }, "Releasing deferred first runtime start");
9311
+ this.startFn?.();
9312
+ }
9313
+ /** Daemon shutdown: never start the runtime after this. */
9314
+ cancel() {
9315
+ this.cancelled = true;
9316
+ this.clearTimers();
9317
+ }
9318
+ clearTimers() {
9319
+ if (this.fallbackTimer) {
9320
+ clearTimeout(this.fallbackTimer);
9321
+ this.fallbackTimer = null;
9322
+ }
9323
+ if (this.ceilingTimer) {
9324
+ clearTimeout(this.ceilingTimer);
9325
+ this.ceilingTimer = null;
9326
+ }
9327
+ }
9328
+ };
9329
+ //#endregion
8978
9330
  //#region src/command-registry.ts
8979
9331
  /**
8980
9332
  * Command Registry — maps integration command names to local handler files.
@@ -9656,8 +10008,49 @@ let stopPairingApprovalPoller = null;
9656
10008
  let runtimeAppliersRef = null;
9657
10009
  let openclawCliLockRef = null;
9658
10010
  let runtimeGateRef = null;
10011
+ /** Pending deferred first runtime start (fresh managed box only). */
10012
+ let firstRuntimeStart = null;
9659
10013
  const LEGACY_CONFIG_SET_KEYS = new Set(["agents.defaults.model"]);
9660
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
+ }
9661
10054
  async function startDaemon() {
9662
10055
  startedAt = Date.now();
9663
10056
  const managed = isManagedMode();
@@ -9744,6 +10137,7 @@ async function startDaemon() {
9744
10137
  cloudClient.setConnectionChangeHandler((connected) => {
9745
10138
  cloudConnected = connected;
9746
10139
  logger.info({ connected }, "Cloud connection state changed");
10140
+ if (connected) firstRuntimeStart?.armFallback();
9747
10141
  ipcServer?.broadcastEvent("cloud.status", { connected });
9748
10142
  });
9749
10143
  const openclawCliLock = new SerialOpenClawCliLock();
@@ -9874,6 +10268,28 @@ async function startDaemon() {
9874
10268
  cloudClient
9875
10269
  });
9876
10270
  });
10271
+ if (config.autoStartRuntime && config.runtime) {
10272
+ let installedIntegrationCount;
10273
+ try {
10274
+ installedIntegrationCount = integrationManager.list().length;
10275
+ } catch (err) {
10276
+ logger.warn({ err: err instanceof Error ? err.message : String(err) }, "Could not read local integration state — starting runtime immediately");
10277
+ installedIntegrationCount = null;
10278
+ }
10279
+ if (shouldDeferFirstRuntimeStart({
10280
+ managed,
10281
+ installedIntegrationCount
10282
+ })) {
10283
+ logger.info("Fresh managed box — deferring first runtime start until the first reconcile settles");
10284
+ const deferral = new DeferredRuntimeStart();
10285
+ firstRuntimeStart = deferral;
10286
+ runtimeGate.deferFirstStart();
10287
+ cloudClient.setOnReconciliationSettled(() => {
10288
+ deferral.release("reconcile-settled");
10289
+ });
10290
+ }
10291
+ }
10292
+ if (config.autoStartRuntime && config.runtime === "openclaw") recoverInterruptedOpenClawUpgrade(runtimeGate, openclawCliLock);
9877
10293
  cloudClient.start();
9878
10294
  logger.debug("Cloud client started");
9879
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");
@@ -9892,8 +10308,15 @@ async function startDaemon() {
9892
10308
  env: config.runtime === "claude-code" && config.defaultModel ? { ALFE_DEFAULT_MODEL: config.defaultModel } : {}
9893
10309
  });
9894
10310
  runtimeProcess.setTurnActivityProbe(turnActivityProbe);
9895
- runtimeProcess.start();
9896
- logger.debug("Runtime process started");
10311
+ if (firstRuntimeStart) {
10312
+ firstRuntimeStart.attach(() => {
10313
+ runtimeGate.requestStart();
10314
+ });
10315
+ logger.debug("Runtime process created — first start deferred");
10316
+ } else {
10317
+ runtimeGate.requestStart();
10318
+ logger.debug("Runtime process started");
10319
+ }
9897
10320
  if (config.runtime === "openclaw") {
9898
10321
  stopPairingApprovalPoller = startPairingApprovalPoller({
9899
10322
  log: logger,
@@ -9913,9 +10336,10 @@ async function startDaemon() {
9913
10336
  stopPairingApprovalPoller();
9914
10337
  stopPairingApprovalPoller = null;
9915
10338
  }
10339
+ firstRuntimeStart?.cancel();
9916
10340
  if (runtimeProcess) {
9917
10341
  logger.debug("Stopping runtime process...");
9918
- await runtimeProcess.stop();
10342
+ await runtimeProcess.shutdown();
9919
10343
  logger.debug("Runtime process stopped");
9920
10344
  }
9921
10345
  if (hermesMcpSync) {
@@ -10091,7 +10515,15 @@ async function executeCloudCommand(command) {
10091
10515
  };
10092
10516
  const payload = command.payload;
10093
10517
  const runtime = config.runtime;
10094
- 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);
10095
10527
  upgradingRuntime = true;
10096
10528
  setTimeout(() => {
10097
10529
  (async () => {
@@ -10100,7 +10532,8 @@ async function executeCloudCommand(command) {
10100
10532
  const result = await upgradeRuntime(runtime, version, runtimeProcess, {
10101
10533
  ...runtime === "openclaw" && openclawCliLockRef ? { cliLock: openclawCliLockRef } : {},
10102
10534
  ...runtime === "openclaw" && isManagedMode() && process.env.ALFE_PRIVATE_DATA_PLANE !== "true" ? { nodeRemediation: {} } : {},
10103
- ...runtime === "openclaw" && runtimeGateRef ? { gate: runtimeGateRef } : {}
10535
+ ...runtime === "openclaw" && runtimeGateRef ? { gate: runtimeGateRef } : {},
10536
+ ...runtime === "openclaw" ? openClawUpgradeExtras() : {}
10104
10537
  });
10105
10538
  if (result.success || result.runtimeChanged) {
10106
10539
  resolvedRuntimeVersion = await getRuntimeVersion(runtime);