@alfe.ai/gateway 0.10.6 → 0.10.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { f as parseOpenClawVersion, n as OPENCLAW_EXTERNAL_SUPERVISOR_ENV, p as supportsOpenClawNode, u as OPENCLAW_NODE_REQUIREMENT } from "./runtime-process.js";
1
+ import { S as parseOpenClawVersion, a as recoverInterruptedRuntimeUpgrade, c as wrapInUpgradeScope, d as OPENCLAW_EXTERNAL_SUPERVISOR_ENV, l as writeRuntimeUpgradeMarker, n as clearRuntimeUpgradeMarker, s as upgradeScopeUnitName, t as OPENCLAW_DOCTOR_FIX_ARGS, w as supportsOpenClawNode, x as openClawRequiresRegisterExport, y as OPENCLAW_NODE_REQUIREMENT } from "./runtime-upgrade-recovery.js";
2
2
  import { c as captureNodeRemediation, h as logger, i as captureCliFailure } from "./sentry.js";
3
3
  import { n as remediateManagedNode, t as NodeRemediationError } from "./node-remediation.js";
4
4
  import { mkdir } from "node:fs/promises";
@@ -6,6 +6,236 @@ import { join } from "node:path";
6
6
  import { homedir } from "node:os";
7
7
  import { execFile } from "node:child_process";
8
8
  import { promisify } from "node:util";
9
+ //#region src/openclaw-plugin-compat.ts
10
+ /**
11
+ * Pre-upgrade plugin-export compatibility check for OpenClaw ≥ 2026.8.
12
+ *
13
+ * OpenClaw 2026.8.1 dropped the 2026.6.x `register ?? activate` export alias.
14
+ * An `@alfe.ai/*` plugin installed from an alfe manifest ≤ 1.1.49 only exports
15
+ * `activate`, so after a runtime upgrade it fails to load and `doctor --fix`
16
+ * exits non-zero AFTER migrating state — past the point where `upgradeRuntime`
17
+ * can roll the package back (Sentry AGENT-DAEMON-P/Q/R; Designer Tester went
18
+ * dark with its core plugins unloaded). This check runs BEFORE the gate
19
+ * suspends and before the install, while refusing costs nothing.
20
+ *
21
+ * Mechanism:
22
+ * 1. `openclaw plugins inspect --all --json` (the same structured read the
23
+ * applier batches; 2026.6.11/8.1/9.5 all print `plugin.{id, packageName,
24
+ * enabled, origin, source}`), filtered to enabled, non-bundled
25
+ * `@alfe.ai/*` plugins;
26
+ * 2. ONE child `node` process imports each plugin's entry (`plugin.source`)
27
+ * and resolves its export the way OpenClaw 2026.8+'s
28
+ * `resolvePluginModuleExport` does (unwrap `default`, accept a function or
29
+ * an object with a `register` function, follow `default`/`module`).
30
+ *
31
+ * The child process isolates the daemon from whatever a plugin does at import
32
+ * time (a throw, an open handle); it prints one sentinel-prefixed JSON line and
33
+ * exits. The check never throws: an unreadable inventory or a failed probe is
34
+ * `unknown` and the caller proceeds exactly as before (fail open — the guard
35
+ * targets one proven failure mode and must not become a new way to wedge
36
+ * upgrades). Only a DEFINITE "imported fine, no register" verdict blocks.
37
+ */
38
+ const INSPECT_TIMEOUT_MS = 12e4;
39
+ const INSPECT_MAX_BUFFER_BYTES = 32 * 1024 * 1024;
40
+ const PROBE_TIMEOUT_MS = 6e4;
41
+ const PROBE_MAX_BUFFER_BYTES = 4 * 1024 * 1024;
42
+ const PLUGIN_EXPORT_PROBE_SENTINEL = "__ALFE_PLUGIN_EXPORT_PROBE__";
43
+ /**
44
+ * Runs as `node --input-type=module -e <script> <json-array-of-paths>`.
45
+ * Mirrors OpenClaw 2026.8.1/9.5 `unwrapDefaultModuleExport` +
46
+ * `resolvePluginModuleExport` (dist `loader-*.mjs`). Verdicts: `register`,
47
+ * `missing`, or `error:<message>`.
48
+ */
49
+ const PLUGIN_EXPORT_PROBE_SCRIPT = `
50
+ import { pathToFileURL } from 'node:url';
51
+ const unwrap = (value) => {
52
+ const seen = new Set();
53
+ while (value && typeof value === 'object' && 'default' in value && !seen.has(value)) {
54
+ seen.add(value);
55
+ value = value.default;
56
+ }
57
+ return value;
58
+ };
59
+ const hasRegister = (moduleExport) => {
60
+ const seen = new Set();
61
+ const candidates = [unwrap(moduleExport), moduleExport];
62
+ for (let i = 0; i < candidates.length && i < 12; i += 1) {
63
+ const resolved = candidates[i];
64
+ if (seen.has(resolved)) continue;
65
+ seen.add(resolved);
66
+ if (typeof resolved === 'function') return true;
67
+ if (resolved && typeof resolved === 'object') {
68
+ if (typeof resolved.register === 'function') return true;
69
+ for (const key of ['default', 'module']) if (key in resolved) candidates.push(resolved[key]);
70
+ }
71
+ }
72
+ return false;
73
+ };
74
+ const verdicts = {};
75
+ for (const entry of JSON.parse(process.argv[1] ?? '[]')) {
76
+ try {
77
+ const mod = await import(pathToFileURL(entry).href);
78
+ verdicts[entry] = hasRegister(mod) ? 'register' : 'missing';
79
+ } catch (err) {
80
+ verdicts[entry] = 'error:' + String((err && err.message) || err).slice(0, 200);
81
+ }
82
+ }
83
+ process.stdout.write('\\n${PLUGIN_EXPORT_PROBE_SENTINEL}' + JSON.stringify(verdicts) + '\\n');
84
+ process.exit(0);
85
+ `;
86
+ /** Enabled, non-bundled `@alfe.ai/*` plugins with a loadable entry path, or `undefined` for an unexpected payload. */
87
+ function selectAlfePluginRecords(stdout) {
88
+ let parsed;
89
+ try {
90
+ parsed = JSON.parse(stdout.trim());
91
+ } catch {
92
+ return;
93
+ }
94
+ if (!Array.isArray(parsed)) return void 0;
95
+ const records = [];
96
+ for (const entry of parsed) {
97
+ const plugin = entry?.plugin;
98
+ if (!plugin || typeof plugin !== "object") return void 0;
99
+ const id = typeof plugin.id === "string" ? plugin.id : void 0;
100
+ const name = [typeof plugin.packageName === "string" ? plugin.packageName : void 0, id].find((value) => value?.startsWith("@alfe.ai/"));
101
+ if (!id || !name) continue;
102
+ if (plugin.enabled === false || plugin.origin === "bundled") continue;
103
+ if (typeof plugin.source !== "string" || !plugin.source.startsWith("/")) continue;
104
+ const install = entry.install;
105
+ const version = [
106
+ install?.resolvedVersion,
107
+ install?.version,
108
+ plugin.packageVersion,
109
+ plugin.version
110
+ ].find((value) => typeof value === "string");
111
+ records.push({
112
+ id: name,
113
+ source: plugin.source,
114
+ ...version ? { version } : {}
115
+ });
116
+ }
117
+ return records;
118
+ }
119
+ function parseProbeOutput(stdout) {
120
+ const line = stdout.split("\n").reverse().find((candidate) => candidate.startsWith(PLUGIN_EXPORT_PROBE_SENTINEL));
121
+ if (!line) return void 0;
122
+ try {
123
+ const parsed = JSON.parse(line.slice(28));
124
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
125
+ return parsed;
126
+ } catch {
127
+ return;
128
+ }
129
+ }
130
+ /**
131
+ * Find enabled `@alfe.ai/*` plugins that OpenClaw ≥ 2026.8 cannot load because
132
+ * they export `activate` without `register`. Never throws.
133
+ */
134
+ async function findActivateOnlyAlfePlugins(runCommand, openclawEnv) {
135
+ let records;
136
+ try {
137
+ const { stdout } = await runCommand("openclaw", [
138
+ "plugins",
139
+ "inspect",
140
+ "--all",
141
+ "--json"
142
+ ], {
143
+ timeout: INSPECT_TIMEOUT_MS,
144
+ env: openclawEnv,
145
+ maxBuffer: INSPECT_MAX_BUFFER_BYTES
146
+ });
147
+ records = selectAlfePluginRecords(stdout);
148
+ } catch (err) {
149
+ return {
150
+ status: "unknown",
151
+ reason: `plugin inventory unavailable: ${err instanceof Error ? err.message : String(err)}`
152
+ };
153
+ }
154
+ if (!records) return {
155
+ status: "unknown",
156
+ reason: "plugin inventory had an unexpected shape"
157
+ };
158
+ if (records.length === 0) return {
159
+ status: "checked",
160
+ activateOnly: []
161
+ };
162
+ let verdicts;
163
+ try {
164
+ const { stdout } = await runCommand("node", [
165
+ "--input-type=module",
166
+ "-e",
167
+ PLUGIN_EXPORT_PROBE_SCRIPT,
168
+ JSON.stringify(records.map((r) => r.source))
169
+ ], {
170
+ timeout: PROBE_TIMEOUT_MS,
171
+ maxBuffer: PROBE_MAX_BUFFER_BYTES
172
+ });
173
+ verdicts = parseProbeOutput(stdout);
174
+ } catch (err) {
175
+ return {
176
+ status: "unknown",
177
+ reason: `plugin export probe failed: ${err instanceof Error ? err.message : String(err)}`
178
+ };
179
+ }
180
+ if (!verdicts) return {
181
+ status: "unknown",
182
+ reason: "plugin export probe printed no verdicts"
183
+ };
184
+ const activateOnly = [];
185
+ for (const record of records) {
186
+ const verdict = verdicts[record.source];
187
+ if (verdict === "missing") activateOnly.push({
188
+ id: record.id,
189
+ ...record.version ? { version: record.version } : {}
190
+ });
191
+ else if (verdict !== "register") logger.warn({
192
+ plugin: record.id,
193
+ verdict
194
+ }, "Plugin export probe could not classify plugin — not blocking the upgrade");
195
+ }
196
+ return {
197
+ status: "checked",
198
+ activateOnly
199
+ };
200
+ }
201
+ function classifyActivateOnlyPlugins(activateOnly, ownership) {
202
+ if (activateOnly.length === 0) return {
203
+ blocked: [],
204
+ orphans: []
205
+ };
206
+ if (!ownership) return {
207
+ blocked: activateOnly,
208
+ orphans: []
209
+ };
210
+ let owned;
211
+ try {
212
+ owned = ownership.ownedPlugins();
213
+ } catch (err) {
214
+ return {
215
+ blocked: activateOnly,
216
+ orphans: [],
217
+ ownershipError: err instanceof Error ? err.message : String(err)
218
+ };
219
+ }
220
+ const blocked = [];
221
+ const orphans = [];
222
+ for (const plugin of activateOnly) {
223
+ const owner = owned.get(plugin.id);
224
+ if (owner) blocked.push({
225
+ ...plugin,
226
+ owner
227
+ });
228
+ else orphans.push(plugin);
229
+ }
230
+ return {
231
+ blocked,
232
+ orphans
233
+ };
234
+ }
235
+ function activateOnlyPluginsMessage(targetVersion, decision) {
236
+ return `Refusing to upgrade OpenClaw to ${targetVersion}: OpenClaw 2026.8+ only loads plugins that export register(), and these enabled Alfe plugins are activate-only builds: ${decision.blocked.map((plugin) => `${plugin.id}${plugin.version ? `@${plugin.version}` : ""}${plugin.owner ? ` (integration "${plugin.owner}")` : ""}`).join(", ")}. Upgrade or reinstall the integration(s) that own them (usually "alfe") so they install current plugin builds, then retry the runtime upgrade. The runtime was not changed.` + (decision.ownershipError ? ` (Plugin ownership could not be read: ${decision.ownershipError}.)` : "");
237
+ }
238
+ //#endregion
9
239
  //#region src/runtime-upgrade.ts
10
240
  /**
11
241
  * Runtime upgrade — safely replace a runtime binary and cycle its child.
@@ -42,19 +272,6 @@ const OPENCLAW_DOCTOR_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
42
272
  * runtime regardless of config contents.
43
273
  */
44
274
  const OPENCLAW_ADMISSION_PROBE_ARGS = ["config", "file"];
45
- /**
46
- * `doctor --fix` runs the state/config migration. On 2026.9.x `--json` puts
47
- * doctor into READ-ONLY lint mode and it refuses the combination outright
48
- * (exit 2: "doctor --json runs read-only lint checks and cannot be combined
49
- * with --repair, --fix, or --force"), so the migration must run without it.
50
- * The post-upgrade audit is read-only and keeps `--json`.
51
- */
52
- const OPENCLAW_DOCTOR_FIX_ARGS = [
53
- "doctor",
54
- "--fix",
55
- "--non-interactive",
56
- "--yes"
57
- ];
58
275
  const OPENCLAW_DOCTOR_AUDIT_ARGS = [
59
276
  "doctor",
60
277
  "--post-upgrade",
@@ -126,6 +343,36 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
126
343
  error: message
127
344
  };
128
345
  }
346
+ let orphanPlugins = [];
347
+ if (runtime === "openclaw" && version && openClawRequiresRegisterExport(version)) {
348
+ const found = await findActivateOnlyAlfePlugins(runCommand, openclawEnv);
349
+ if (found.status === "unknown") logger.warn({
350
+ runtime,
351
+ version,
352
+ reason: found.reason
353
+ }, "Plugin export preflight inconclusive — proceeding with the upgrade");
354
+ else if (found.activateOnly.length > 0) {
355
+ const decision = classifyActivateOnlyPlugins(found.activateOnly, options.pluginOwnership);
356
+ if (decision.blocked.length > 0) {
357
+ const message = activateOnlyPluginsMessage(version, decision);
358
+ logger.error({
359
+ runtime,
360
+ version,
361
+ blocked: decision.blocked,
362
+ orphans: decision.orphans
363
+ }, message);
364
+ captureCliFailure("openclaw-upgrade-preflight", new Error(message));
365
+ return {
366
+ success: false,
367
+ runtime,
368
+ version,
369
+ nodeUpgraded: false,
370
+ error: message
371
+ };
372
+ }
373
+ orphanPlugins = decision.orphans.map((plugin) => plugin.id);
374
+ }
375
+ }
129
376
  let previousOpenClawVersion;
130
377
  let nodeUpgraded = false;
131
378
  if (runtime === "openclaw") try {
@@ -195,6 +442,32 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
195
442
  error: message
196
443
  };
197
444
  }
445
+ const markerPath = options.markerPath;
446
+ const scopeUnits = [];
447
+ /** Wrap a long, state-mutating command in its own scope (records the unit for boot recovery). */
448
+ const isolated = (command, args) => {
449
+ if (!options.isolation) return {
450
+ command,
451
+ args
452
+ };
453
+ const unit = upgradeScopeUnitName(Date.now() + scopeUnits.length);
454
+ scopeUnits.push(unit);
455
+ return wrapInUpgradeScope(options.isolation, unit, command, args);
456
+ };
457
+ const markerState = { keep: false };
458
+ const writeMarker = async (phase) => {
459
+ if (!markerPath || runtime !== "openclaw" || !version) return;
460
+ const marker = {
461
+ runtime: "openclaw",
462
+ phase,
463
+ targetVersion: version,
464
+ ...previousOpenClawVersion ? { previousVersion: previousOpenClawVersion } : {},
465
+ startedAt: markerState.current?.startedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
466
+ scopeUnits: [...scopeUnits]
467
+ };
468
+ await writeRuntimeUpgradeMarker(markerPath, marker);
469
+ markerState.current = marker;
470
+ };
198
471
  logger.info({
199
472
  runtime,
200
473
  version
@@ -222,8 +495,10 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
222
495
  ], commandOptions(OPENCLAW_UPGRADE_TIMEOUT_MS));
223
496
  if (backup.stdout) logger.info({ backup: backup.stdout.trim() }, "Verified pre-upgrade OpenClaw backup created");
224
497
  }
498
+ const install = runtime === "openclaw" ? isolated(cmd.command, cmd.args) : cmd;
499
+ await writeMarker("installing");
225
500
  packageInstallAttempted = true;
226
- const { stdout, stderr } = await runCommand(cmd.command, cmd.args, commandOptions(OPENCLAW_UPGRADE_TIMEOUT_MS));
501
+ const { stdout, stderr } = await runCommand(install.command, install.args, commandOptions(OPENCLAW_UPGRADE_TIMEOUT_MS));
227
502
  if (stdout) logger.debug({ stdout: stdout.trim() }, "runtime upgrade stdout");
228
503
  if (stderr) logger.debug({ stderr: stderr.trim() }, "runtime upgrade stderr");
229
504
  if (runtime === "openclaw" && version) {
@@ -231,9 +506,11 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
231
506
  if (installedVersion !== version) throw new Error(`OpenClaw package verification failed: requested ${version}, resolved ${installedVersion ?? "unknown"}`);
232
507
  runtimeChanged = installedVersion !== previousOpenClawVersion;
233
508
  await runCommand("openclaw", [...OPENCLAW_ADMISSION_PROBE_ARGS], commandOptions(6e4));
509
+ const doctorCommand = isolated("openclaw", [...OPENCLAW_DOCTOR_FIX_ARGS]);
510
+ await writeMarker("migrating");
234
511
  doctorStarted = true;
235
512
  try {
236
- const doctor = await runCommand("openclaw", [...OPENCLAW_DOCTOR_FIX_ARGS], {
513
+ const doctor = await runCommand(doctorCommand.command, doctorCommand.args, {
237
514
  ...commandOptions(OPENCLAW_DOCTOR_TIMEOUT_MS),
238
515
  maxBuffer: OPENCLAW_DOCTOR_MAX_BUFFER_BYTES
239
516
  });
@@ -256,14 +533,19 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
256
533
  } catch (err) {
257
534
  if (runtime === "openclaw" && packageInstallAttempted && !doctorStarted && previousOpenClawVersion) try {
258
535
  logger.warn({ previousOpenClawVersion }, "OpenClaw package replacement failed before state migration — restoring previous package");
259
- await runCommand("npm", [
536
+ const restore = isolated("npm", [
260
537
  "install",
261
538
  "-g",
262
539
  `openclaw@${previousOpenClawVersion}`
263
- ], commandOptions(OPENCLAW_UPGRADE_TIMEOUT_MS));
540
+ ]);
541
+ await writeMarker("installing").catch((markerErr) => {
542
+ logger.warn({ err: markerErr instanceof Error ? markerErr.message : String(markerErr) }, "Could not update the runtime upgrade marker for the rollback");
543
+ });
544
+ await runCommand(restore.command, restore.args, commandOptions(OPENCLAW_UPGRADE_TIMEOUT_MS));
264
545
  if (parseOpenClawVersion((await runCommand("openclaw", ["--version"], commandOptions(1e4))).stdout) !== previousOpenClawVersion) throw new Error("restored OpenClaw version did not verify");
265
546
  runtimeChanged = false;
266
547
  } catch (rollbackError) {
548
+ markerState.keep = true;
267
549
  logger.error({ err: rollbackError instanceof Error ? rollbackError.message : String(rollbackError) }, "Failed to restore the previous OpenClaw package after upgrade failure");
268
550
  }
269
551
  throw err;
@@ -274,6 +556,17 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
274
556
  gateSuspended = true;
275
557
  await options.gate.suspendExclusive();
276
558
  } else if (runtimeProcess) await runtimeProcess.stopWhenIdle();
559
+ for (const pkg of orphanPlugins) {
560
+ logger.warn({
561
+ pkg,
562
+ version
563
+ }, "Uninstalling orphaned activate-only Alfe plugin before the OpenClaw upgrade");
564
+ try {
565
+ await options.pluginOwnership?.removePlugin(pkg);
566
+ } catch (err) {
567
+ throw new Error(`Could not uninstall orphaned plugin ${pkg} (activate-only, owned by no integration) before upgrading OpenClaw to ${version ?? "the pin"}: ` + (err instanceof Error ? err.message : String(err)), { cause: err });
568
+ }
569
+ }
277
570
  if (runtime === "openclaw" && options.cliLock) await options.cliLock.run(performUpgrade);
278
571
  else await performUpgrade();
279
572
  logger.info({
@@ -304,6 +597,22 @@ async function upgradeRuntime(runtime, version, runtimeProcess, options = {}) {
304
597
  error: message
305
598
  };
306
599
  } finally {
600
+ if (markerPath && markerState.keep) {
601
+ const repair = () => recoverInterruptedRuntimeUpgrade({
602
+ markerPath,
603
+ ...options.isolation ? { isolation: options.isolation } : {},
604
+ ...options.runCommand ? { runCommand: options.runCommand } : {}
605
+ });
606
+ try {
607
+ const outcome = options.cliLock ? await options.cliLock.run(repair) : await repair();
608
+ logger.warn({ outcome }, "In-process repair after a failed pre-doctor rollback");
609
+ } catch (repairErr) {
610
+ logger.error({ err: repairErr instanceof Error ? repairErr.message : String(repairErr) }, "In-process repair after a failed pre-doctor rollback threw — marker kept for the next daemon start");
611
+ }
612
+ }
613
+ if (markerPath && markerState.current && !markerState.keep) await clearRuntimeUpgradeMarker(markerPath).catch((err) => {
614
+ logger.warn({ err: err instanceof Error ? err.message : String(err) }, "Failed to clear the runtime upgrade marker");
615
+ });
307
616
  try {
308
617
  if (gateSuspended && options.gate) await options.gate.resume();
309
618
  else if (!options.gate && runtimeProcess) await runtimeProcess.restart();
package/dist/sentry.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { join } from "node:path";
2
2
  import { homedir } from "node:os";
3
- import pino from "pino";
4
3
  import { mkdirSync } from "node:fs";
4
+ import pino from "pino";
5
5
  import { readConfig, resolveConfig } from "@alfe.ai/config";
6
6
  //#region src/logger.ts
7
7
  /**
@@ -273,22 +273,29 @@ function setAgentContext(context) {
273
273
  /**
274
274
  * Capture an integration lifecycle failure (install/activate/reinstall/remove)
275
275
  * with `{ integration, phase }` tags. Accepts an `Error` (captured with stack)
276
- * or a plain message string (captured as an error-level message). No-op when
277
- * Sentry is not initialised.
276
+ * or a plain message string (captured as an error-level message). Pass
277
+ * `opts.fingerprint` when the message carries variable text that must not
278
+ * split one problem across many Sentry issues. No-op when Sentry is not
279
+ * initialised.
278
280
  */
279
- function captureIntegrationFailure(integration, phase, cause) {
281
+ function captureIntegrationFailure(integration, phase, cause, opts) {
280
282
  if (!sentry) return;
281
283
  try {
282
- if (cause instanceof Error) sentry.captureException(cause, { tags: {
283
- integration,
284
- phase
285
- } });
284
+ const fingerprint = opts?.fingerprint ? { fingerprint: opts.fingerprint } : {};
285
+ if (cause instanceof Error) sentry.captureException(cause, {
286
+ tags: {
287
+ integration,
288
+ phase
289
+ },
290
+ ...fingerprint
291
+ });
286
292
  else sentry.captureMessage(String(cause), {
287
293
  level: "error",
288
294
  tags: {
289
295
  integration,
290
296
  phase
291
- }
297
+ },
298
+ ...fingerprint
292
299
  });
293
300
  } catch {}
294
301
  }
@@ -46,6 +46,9 @@ declare function startDaemon(): Promise<void>;
46
46
  * - the daemon `runtime.update` default (this package)
47
47
  * - managed provisioning (`services/compute/src/lib/provisioner.ts` installs the
48
48
  * Node.js 24 channel before the CLI consumes this pin)
49
+ * - the dashboard/fleet upgrade ceiling: `services/agents` mirrors this literal
50
+ * as `PINNED_OPENCLAW_VERSION` in `version-catalog.service.ts` (its test fails
51
+ * on drift) — bump both in the same change
49
52
  *
50
53
  * Pinning (rather than floating `npm install -g openclaw` → `latest`) is deliberate:
51
54
  * OpenClaw runs on every managed agent, so a `latest` that changes runtime semantics —
@@ -216,10 +219,14 @@ declare function setAgentContext(context: {
216
219
  /**
217
220
  * Capture an integration lifecycle failure (install/activate/reinstall/remove)
218
221
  * with `{ integration, phase }` tags. Accepts an `Error` (captured with stack)
219
- * or a plain message string (captured as an error-level message). No-op when
220
- * Sentry is not initialised.
222
+ * or a plain message string (captured as an error-level message). Pass
223
+ * `opts.fingerprint` when the message carries variable text that must not
224
+ * split one problem across many Sentry issues. No-op when Sentry is not
225
+ * initialised.
221
226
  */
222
- declare function captureIntegrationFailure(integration: string, phase: string, cause: unknown): void;
227
+ declare function captureIntegrationFailure(integration: string, phase: string, cause: unknown, opts?: {
228
+ fingerprint?: string[];
229
+ }): void;
223
230
  /**
224
231
  * Capture a generic CLI failure with a `{ context }` tag. The CLI counterpart
225
232
  * to `captureIntegrationFailure` — used by `exitWithError` at the handled-error
package/dist/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { d as PINNED_OPENCLAW_VERSION, l as OPENCLAW_MANAGED_NODE_MAJOR, p as supportsOpenClawNode, u as OPENCLAW_NODE_REQUIREMENT } from "../runtime-process.js";
1
+ import { b as PINNED_OPENCLAW_VERSION, v as OPENCLAW_MANAGED_NODE_MAJOR, w as supportsOpenClawNode, y as OPENCLAW_NODE_REQUIREMENT } from "../runtime-upgrade-recovery.js";
2
2
  import { _ as loadDaemonConfig, a as installService, c as startService, d as uninstallService, f as PROTOCOL_VERSION, g as fetchAgentConfig, h as SOCKET_PATH, i as checkExistingDaemon, l as stopExistingDaemon, m as PID_PATH, n as queryDaemonHealth, o as isServiceInstalled, p as ALFE_DIR, r as startDaemon, s as restartService, t as formatHealthReport, u as stopService, v as resolveAgentIdentity, y as PINNED_CLAUDE_CODE_HOST_VERSION } from "../health.js";
3
3
  import { a as captureFatal, d as flushSentry, f as initAgentSentry, h as logger, i as captureCliFailure, l as captureRuntimeCrash, n as AGENT_MCP_SENTRY_DSN, o as captureIntegrationFailure, p as setAgentContext, r as AGENT_RUNTIME_SENTRY_DSN, s as captureMcpFailure, t as AGENT_DAEMON_SENTRY_DSN, u as captureRuntimeErrorOutput } from "../sentry.js";
4
4
  import { n as remediateManagedNode } from "../node-remediation.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/gateway",
3
- "version": "0.10.6",
3
+ "version": "0.10.8",
4
4
  "description": "Alfe local gateway daemon — persistent control plane for agent integrations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,8 +27,8 @@
27
27
  "@alfe.ai/ai-proxy-local": "^0.0.17",
28
28
  "@alfe.ai/config": "^0.4.1",
29
29
  "@alfe.ai/integration-manifest": "^0.4.2",
30
- "@alfe.ai/integrations": "^0.6.12",
31
- "@alfe.ai/mcp-bundler": "^0.4.2"
30
+ "@alfe.ai/integrations": "^0.6.15",
31
+ "@alfe.ai/mcp-bundler": "^0.4.3"
32
32
  },
33
33
  "license": "UNLICENSED",
34
34
  "homepage": "https://alfe.ai",