@alfe.ai/gateway 0.10.7 → 0.10.9

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
@@ -4718,6 +4718,14 @@ NotificationType.PaymentSucceeded, NotificationCategory.Billing, RecipientStrate
4718
4718
  * Platform-agnostic: works in React Native and browser environments.
4719
4719
  * Uses standard fetch() API — no Node.js dependencies.
4720
4720
  */
4721
+ /** A sane 429 window hint: a positive whole number of seconds (≤ 1 day). */
4722
+ function isRetryAfterSeconds(value) {
4723
+ return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= 86400;
4724
+ }
4725
+ /** Narrow allowlist: a non-empty list of `{ profile: string, message: string }`. */
4726
+ function isProfileErrorList(value) {
4727
+ return Array.isArray(value) && value.length > 0 && value.every((entry) => typeof entry === "object" && entry !== null && typeof entry.profile === "string" && typeof entry.message === "string");
4728
+ }
4721
4729
  var AlfeApiClient = class {
4722
4730
  apiBaseUrl;
4723
4731
  getToken;
@@ -4790,7 +4798,12 @@ var AlfeApiClient = class {
4790
4798
  error: errorMessage ?? (typeof body === "string" ? body : `API error: ${String(res.status)}`),
4791
4799
  status: res.status,
4792
4800
  ...typeof errBody?.code === "string" ? { code: errBody.code } : {},
4793
- ...Array.isArray(errBody?.ineligibleUserIds) && errBody.ineligibleUserIds.every((id) => typeof id === "string") ? { ineligibleUserIds: [...errBody.ineligibleUserIds] } : {}
4801
+ ...Array.isArray(errBody?.ineligibleUserIds) && errBody.ineligibleUserIds.every((id) => typeof id === "string") ? { ineligibleUserIds: [...errBody.ineligibleUserIds] } : {},
4802
+ ...isProfileErrorList(errBody?.errors) ? { profileErrors: errBody.errors.map(({ profile, message }) => ({
4803
+ profile,
4804
+ message
4805
+ })) } : {},
4806
+ ...isRetryAfterSeconds(errBody?.retryAfterSeconds) ? { retryAfterSeconds: errBody.retryAfterSeconds } : {}
4794
4807
  }
4795
4808
  };
4796
4809
  }
@@ -5931,6 +5944,13 @@ const log$6 = logger.child({ component: "RuntimeGate" });
5931
5944
  var RuntimeGate = class {
5932
5945
  depth = 0;
5933
5946
  wasRunning = false;
5947
+ /**
5948
+ * True once the OUTERMOST `enter()` has finished stopping the runtime child
5949
+ * (its `stopWhenIdle()` resolved, i.e. the process exited), until the gate
5950
+ * fully reopens. `depth > 0` alone is not enough: depth is bumped before the
5951
+ * stop is awaited, so it is already set while the old child is draining.
5952
+ */
5953
+ stopSettled = false;
5934
5954
  exclusive = false;
5935
5955
  /** Brackets parked behind an exclusive hold (or an exclusive request parked behind open brackets). */
5936
5956
  waiters = [];
@@ -5945,6 +5965,14 @@ var RuntimeGate = class {
5945
5965
  get isSuspended() {
5946
5966
  return this.depth > 0;
5947
5967
  }
5968
+ /**
5969
+ * Whether the runtime child is PROVABLY down right now: a bracket is open and
5970
+ * its stop has completed. The OpenClaw applier's one direct openclaw.json
5971
+ * write (the dangling channel-ref repair) is allow-listed on this.
5972
+ */
5973
+ get isRuntimeStopped() {
5974
+ return this.depth > 0 && this.stopSettled;
5975
+ }
5948
5976
  /** True while an exclusive bracket is open. */
5949
5977
  get isExclusive() {
5950
5978
  return this.exclusive;
@@ -5997,12 +6025,14 @@ var RuntimeGate = class {
5997
6025
  }
5998
6026
  async enter() {
5999
6027
  if (this.depth++ > 0) return;
6028
+ this.stopSettled = false;
6000
6029
  const rp = this.getRuntime();
6001
6030
  this.wasRunning = rp?.isActive ?? false;
6002
6031
  if (rp && this.wasRunning) {
6003
6032
  log$6.info("Suspending runtime for mutating reconcile (avoid concurrent SQLite writers)");
6004
6033
  await rp.stopWhenIdle();
6005
6034
  }
6035
+ if (this.depth > 0) this.stopSettled = true;
6006
6036
  }
6007
6037
  /**
6008
6038
  * Resume the runtime child. Only the outermost call (depth 1 → 0) actually
@@ -6019,6 +6049,7 @@ var RuntimeGate = class {
6019
6049
  rp.resume();
6020
6050
  }
6021
6051
  this.wasRunning = false;
6052
+ this.stopSettled = false;
6022
6053
  if (this.exclusive) this.exclusive = false;
6023
6054
  this.wakeWaiters();
6024
6055
  return Promise.resolve();
@@ -7465,7 +7496,8 @@ function generateLaunchdPlist() {
7465
7496
  * ~13,570 restarts over 2 days).
7466
7497
  *
7467
7498
  * The guard:
7468
- * - checks the CLI is intact: the symlink target (`dist/index.js`) exists AND
7499
+ * - checks the CLI is intact: the symlink target (the package's `bin.alfe`,
7500
+ * `dist/bin.js` today) exists AND
7469
7501
  * `alfe --version` exits 0;
7470
7502
  * - if broken, reinstalls `@alfe.ai/cli@$ALFE_CLI_VERSION` (the exact version
7471
7503
  * baked into the unit's `Environment=`; falls back to `@latest` when
@@ -7489,7 +7521,7 @@ set -u
7489
7521
  TARGET='${exactVersion ? `@alfe.ai/cli@${exactVersion}` : "@alfe.ai/cli@latest"}'
7490
7522
  log() { echo "[alfe-cli-guard] $*" >&2; }
7491
7523
 
7492
- # Resolve the alfe bin and its real target (dist/index.js). readlink -f follows
7524
+ # Resolve the alfe bin and its real target (dist/bin.js). readlink -f follows
7493
7525
  # the whole symlink chain; fall back to the bin path itself if unavailable.
7494
7526
  BIN="$(command -v alfe 2>/dev/null || true)"
7495
7527
  if [ -n "$BIN" ]; then
@@ -10147,7 +10179,8 @@ async function startDaemon() {
10147
10179
  runtimeAppliers.set(name, new OpenClawApplier({
10148
10180
  home: runtimeCfg.workspace,
10149
10181
  agentWorkspace: runtimeCfg.agentWorkspace,
10150
- cliLock: openclawCliLock
10182
+ cliLock: openclawCliLock,
10183
+ isRuntimeStopped: () => name === config.runtime && runtimeGateRef instanceof RuntimeGate && runtimeGateRef.isRuntimeStopped && runtimeProcess !== null && !runtimeProcess.isActive
10151
10184
  }));
10152
10185
  logger.info({
10153
10186
  runtime: name,
package/dist/upgrade.js CHANGED
@@ -21,8 +21,8 @@ import { promisify } from "node:util";
21
21
  * broken) `alfe` binary. Keep the two in lockstep.
22
22
  *
23
23
  * The two checks:
24
- * - the resolved CLI entry (`.../dist/index.js`, the symlink target of the
25
- * `alfe` bin) exists on disk; and
24
+ * - the resolved CLI entry (the package's `bin.alfe`, `.../dist/bin.js`
25
+ * today, i.e. the symlink target of the `alfe` bin) exists on disk; and
26
26
  * - running it (`node <entry> --version`, or `alfe --version`) exits 0 and
27
27
  * prints the expected version.
28
28
  *
@@ -61,7 +61,7 @@ function decideCliInstallOk(probe, expectedVersion) {
61
61
  /**
62
62
  * Resolve the CLI entry file this daemon is running from. When the gateway is
63
63
  * loaded as a dependency of the globally-installed CLI the layout is
64
- * `.../@alfe.ai/cli/dist/index.js` with `.../@alfe.ai/gateway/dist/...`
64
+ * `.../@alfe.ai/cli/dist/bin.js` with `.../@alfe.ai/gateway/dist/...`
65
65
  * alongside — walk up to the sibling `@alfe.ai/cli/package.json` and derive its
66
66
  * `bin.alfe` / main entry. Returns undefined when it can't be resolved (e.g.
67
67
  * `alfe gateway daemon` run from a dev checkout); the caller then skips the
@@ -106,7 +106,7 @@ async function tryVersion(cmd, args) {
106
106
  * Probe + decide whether the on-disk global CLI install is intact.
107
107
  *
108
108
  * @param expectedVersion when set, the reported version must match exactly.
109
- * @param entryPath the resolved `.../dist/index.js` (from
109
+ * @param entryPath the resolved `bin.alfe` entry, `.../dist/bin.js` (from
110
110
  * `resolveCliEntryPath`). When provided and missing,
111
111
  * the check fails fast. Also used to run the CLI
112
112
  * directly via `node <entry> --version`, which is more
@@ -174,7 +174,7 @@ function findOrphanReifyTempDirs(siblings, packageBasename) {
174
174
  * Detection order (robust — never hardcode `/usr/lib`):
175
175
  * 1. `npm root -g` → the global `node_modules` root. Authoritative on the box.
176
176
  * 2. Derive from the resolved CLI entry path (`.../node_modules/@alfe.ai/cli/
177
- * dist/index.js`) by walking up to the `node_modules` segment.
177
+ * dist/bin.js`) by walking up to the `node_modules` segment.
178
178
  *
179
179
  * Returns undefined when neither resolves (dev checkout / non-global run); the
180
180
  * caller then skips the sweep (there is no global install to clean).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfe.ai/gateway",
3
- "version": "0.10.7",
3
+ "version": "0.10.9",
4
4
  "description": "Alfe local gateway daemon — persistent control plane for agent integrations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,12 +23,12 @@
23
23
  "pino-roll": "^1.2.0",
24
24
  "smol-toml": ">=1.6.1",
25
25
  "ws": "^8.18.0",
26
- "@alfe.ai/agent-api-client": "^0.19.2",
26
+ "@alfe.ai/agent-api-client": "^0.20.0",
27
+ "@alfe.ai/ai-proxy-local": "^0.0.17",
27
28
  "@alfe.ai/config": "^0.4.1",
28
- "@alfe.ai/integration-manifest": "^0.4.2",
29
- "@alfe.ai/integrations": "^0.6.13",
30
- "@alfe.ai/mcp-bundler": "^0.4.2",
31
- "@alfe.ai/ai-proxy-local": "^0.0.17"
29
+ "@alfe.ai/integration-manifest": "^0.5.0",
30
+ "@alfe.ai/integrations": "^0.6.16",
31
+ "@alfe.ai/mcp-bundler": "^0.4.3"
32
32
  },
33
33
  "license": "UNLICENSED",
34
34
  "homepage": "https://alfe.ai",