@alfe.ai/gateway 0.6.1 → 0.7.0
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 +273 -32
- package/dist/src/index.d.ts +10 -1
- package/package.json +3 -3
package/dist/health.js
CHANGED
|
@@ -73,7 +73,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
73
73
|
* silent fleet outage with no Alfe deploy. Bump deliberately after validating a new
|
|
74
74
|
* OpenClaw, then cut a CLI release so the pin travels with the CLI version.
|
|
75
75
|
*/
|
|
76
|
-
const PINNED_OPENCLAW_VERSION = "2026.6.
|
|
76
|
+
const PINNED_OPENCLAW_VERSION = "2026.6.11";
|
|
77
77
|
//#endregion
|
|
78
78
|
//#region ../../packages-internal/ids/dist/prefixes.js
|
|
79
79
|
const ID_PREFIXES = {
|
|
@@ -493,6 +493,31 @@ var AuthService = class {
|
|
|
493
493
|
getMyPermissions() {
|
|
494
494
|
return this.client.request(`${this.prefix}/me/permissions`);
|
|
495
495
|
}
|
|
496
|
+
/**
|
|
497
|
+
* Preview what deleting the caller's own account will do. Drives the Danger
|
|
498
|
+
* Zone copy so the user sees an honest, kind-appropriate warning before the
|
|
499
|
+
* irreversible action (a last-admin cascade wipes the whole workspace).
|
|
500
|
+
*/
|
|
501
|
+
getDeletionPreview() {
|
|
502
|
+
return this.client.request(`${this.prefix}/me/deletion-preview`);
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Permanently delete the caller's own account. Returns `{ status: "deleting" }`
|
|
506
|
+
* and tears the account down asynchronously — the Clerk user is deleted shortly
|
|
507
|
+
* after, so the caller's session dies. On success, sign out and route the user
|
|
508
|
+
* to a terminal "account is being deleted" state; do NOT retry.
|
|
509
|
+
*
|
|
510
|
+
* Pass `confirmKind` (the accountKind the user actually confirmed, from the
|
|
511
|
+
* deletion preview): if membership changed between preview and execute, the
|
|
512
|
+
* server answers 409 `account_kind_changed` instead of cascading a scope the
|
|
513
|
+
* user never agreed to.
|
|
514
|
+
*/
|
|
515
|
+
deleteAccount(confirmKind) {
|
|
516
|
+
return this.client.request(`${this.prefix}/me`, {
|
|
517
|
+
method: "DELETE",
|
|
518
|
+
...confirmKind ? { body: JSON.stringify({ confirmKind }) } : {}
|
|
519
|
+
});
|
|
520
|
+
}
|
|
496
521
|
getSubscriptionStatus() {
|
|
497
522
|
return this.client.request(`${this.prefix}/subscription`);
|
|
498
523
|
}
|
|
@@ -5245,6 +5270,18 @@ function createReconciliationReport(results) {
|
|
|
5245
5270
|
};
|
|
5246
5271
|
}
|
|
5247
5272
|
/**
|
|
5273
|
+
* Create a config-only RECONCILIATION_REPORT (empty `results`) carrying just the
|
|
5274
|
+
* config-reconcile outcome. Sent immediately after the config pass so the
|
|
5275
|
+
* dashboard config chip updates without waiting for the integration passes.
|
|
5276
|
+
*/
|
|
5277
|
+
function createConfigReport(config) {
|
|
5278
|
+
return {
|
|
5279
|
+
type: "RECONCILIATION_REPORT",
|
|
5280
|
+
results: [],
|
|
5281
|
+
config
|
|
5282
|
+
};
|
|
5283
|
+
}
|
|
5284
|
+
/**
|
|
5248
5285
|
* Type guard: is this a cloud PING message?
|
|
5249
5286
|
*/
|
|
5250
5287
|
function isCloudPing(msg) {
|
|
@@ -5266,7 +5303,7 @@ function isIPCResponse(msg) {
|
|
|
5266
5303
|
const PROTOCOL_VERSION = 1;
|
|
5267
5304
|
//#endregion
|
|
5268
5305
|
//#region src/runtime-gate.ts
|
|
5269
|
-
const log$
|
|
5306
|
+
const log$5 = logger$1.child({ component: "RuntimeGate" });
|
|
5270
5307
|
var RuntimeGate = class {
|
|
5271
5308
|
depth = 0;
|
|
5272
5309
|
wasRunning = false;
|
|
@@ -5284,7 +5321,7 @@ var RuntimeGate = class {
|
|
|
5284
5321
|
const rp = this.getRuntime();
|
|
5285
5322
|
this.wasRunning = rp?.isRunning ?? false;
|
|
5286
5323
|
if (rp && this.wasRunning) {
|
|
5287
|
-
log$
|
|
5324
|
+
log$5.info("Suspending runtime for mutating reconcile (avoid concurrent SQLite writers)");
|
|
5288
5325
|
await rp.stop();
|
|
5289
5326
|
}
|
|
5290
5327
|
}
|
|
@@ -5299,7 +5336,7 @@ var RuntimeGate = class {
|
|
|
5299
5336
|
if (this.depth < 0) this.depth = 0;
|
|
5300
5337
|
const rp = this.getRuntime();
|
|
5301
5338
|
if (rp && this.wasRunning) {
|
|
5302
|
-
log$
|
|
5339
|
+
log$5.info("Resuming runtime after mutating reconcile");
|
|
5303
5340
|
rp.resume();
|
|
5304
5341
|
}
|
|
5305
5342
|
this.wasRunning = false;
|
|
@@ -5314,12 +5351,12 @@ var RuntimeGate = class {
|
|
|
5314
5351
|
*/
|
|
5315
5352
|
requestRestart() {
|
|
5316
5353
|
if (this.depth > 0) {
|
|
5317
|
-
log$
|
|
5354
|
+
log$5.info("Runtime restart requested while suspended — deferring to pending resume");
|
|
5318
5355
|
return;
|
|
5319
5356
|
}
|
|
5320
5357
|
const rp = this.getRuntime();
|
|
5321
5358
|
if (rp) rp.restart().catch((err) => {
|
|
5322
|
-
log$
|
|
5359
|
+
log$5.error({ err: err instanceof Error ? err.message : String(err) }, "Failed to restart runtime");
|
|
5323
5360
|
});
|
|
5324
5361
|
}
|
|
5325
5362
|
};
|
|
@@ -5721,7 +5758,7 @@ function captureFatal(cause) {
|
|
|
5721
5758
|
}
|
|
5722
5759
|
//#endregion
|
|
5723
5760
|
//#region src/reconciliation.ts
|
|
5724
|
-
const log$
|
|
5761
|
+
const log$4 = logger$1.child({ component: "Reconciliation" });
|
|
5725
5762
|
/**
|
|
5726
5763
|
* How many times reconcile will re-attempt activation of an intact integration
|
|
5727
5764
|
* stuck in `error` (with no reinstall requested) before giving up and waiting
|
|
@@ -5791,7 +5828,7 @@ var ReconciliationEngine = class {
|
|
|
5791
5828
|
try {
|
|
5792
5829
|
localIntegrations = await this.manager.getInstalledIntegrations();
|
|
5793
5830
|
} catch (err) {
|
|
5794
|
-
log$
|
|
5831
|
+
log$4.error({ err }, "Failed to get local integrations");
|
|
5795
5832
|
localIntegrations = [];
|
|
5796
5833
|
}
|
|
5797
5834
|
const localMap = new Map(localIntegrations.map((i) => [i.id, i]));
|
|
@@ -5810,10 +5847,10 @@ var ReconciliationEngine = class {
|
|
|
5810
5847
|
try {
|
|
5811
5848
|
if (!local) {
|
|
5812
5849
|
await this.ensureSuspended();
|
|
5813
|
-
log$
|
|
5850
|
+
log$4.info(`Installing ${id}@${desired.version}`);
|
|
5814
5851
|
await this.manager.install(id, desired.version, desired.config, desired.customSource);
|
|
5815
5852
|
report.installed.push(id);
|
|
5816
|
-
log$
|
|
5853
|
+
log$4.info(`Activating ${id}`);
|
|
5817
5854
|
if ((await this.manager.activate(id)).configApplied) report.configApplied = true;
|
|
5818
5855
|
report.activated.push(id);
|
|
5819
5856
|
report.results.push({
|
|
@@ -5826,7 +5863,7 @@ var ReconciliationEngine = class {
|
|
|
5826
5863
|
if (local.version !== desired.version && desired.version !== "" && local.version !== "unknown") {
|
|
5827
5864
|
if (desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) {
|
|
5828
5865
|
await this.ensureSuspended();
|
|
5829
|
-
log$
|
|
5866
|
+
log$4.info(`Reinstalling ${id} on version change (reinstall requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt}): ${local.version} → ${desired.version}`);
|
|
5830
5867
|
this.manager.resetReinstallAttempts(id);
|
|
5831
5868
|
this.activateAttempts.delete(id);
|
|
5832
5869
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
@@ -5839,7 +5876,7 @@ var ReconciliationEngine = class {
|
|
|
5839
5876
|
});
|
|
5840
5877
|
return;
|
|
5841
5878
|
}
|
|
5842
|
-
log$
|
|
5879
|
+
log$4.info(`Upgrading ${id} in place: ${local.version} → ${desired.version}`);
|
|
5843
5880
|
try {
|
|
5844
5881
|
if ((await this.manager.upgrade(id, desired.version, desired.config, desired.customSource, { onBeforeRuntimeMutation: () => this.ensureSuspended() })).configApplied) report.configApplied = true;
|
|
5845
5882
|
this.manager.resetReinstallAttempts(id);
|
|
@@ -5853,7 +5890,7 @@ var ReconciliationEngine = class {
|
|
|
5853
5890
|
});
|
|
5854
5891
|
} catch (upgradeErr) {
|
|
5855
5892
|
const upgradeMsg = upgradeErr instanceof Error ? upgradeErr.message : String(upgradeErr);
|
|
5856
|
-
log$
|
|
5893
|
+
log$4.error({ err: upgradeErr }, `Upgrade failed for ${id}`);
|
|
5857
5894
|
captureIntegrationFailure(id, "upgrade", upgradeErr);
|
|
5858
5895
|
report.errors.push({
|
|
5859
5896
|
integrationId: id,
|
|
@@ -5871,7 +5908,7 @@ var ReconciliationEngine = class {
|
|
|
5871
5908
|
if (local.status === "error") {
|
|
5872
5909
|
if (desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) {
|
|
5873
5910
|
await this.ensureSuspended();
|
|
5874
|
-
log$
|
|
5911
|
+
log$4.info(`Reinstalling ${id} from error state (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
|
|
5875
5912
|
this.manager.resetReinstallAttempts(id);
|
|
5876
5913
|
this.activateAttempts.delete(id);
|
|
5877
5914
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
@@ -5887,7 +5924,7 @@ var ReconciliationEngine = class {
|
|
|
5887
5924
|
if (!await this.manager.isInstallIntact(id)) {
|
|
5888
5925
|
const attempts = this.manager.getReinstallAttempts(id);
|
|
5889
5926
|
if (attempts >= 3) {
|
|
5890
|
-
log$
|
|
5927
|
+
log$4.error(`Auto-reinstall blocked for ${id} — ${String(attempts)} consecutive failures. Manual reinstall required.`);
|
|
5891
5928
|
captureIntegrationFailure(id, "reinstall", `Max auto-reinstall attempts (${String(attempts)}) reached — manual reinstall required`);
|
|
5892
5929
|
report.errors.push({
|
|
5893
5930
|
integrationId: id,
|
|
@@ -5902,7 +5939,7 @@ var ReconciliationEngine = class {
|
|
|
5902
5939
|
return;
|
|
5903
5940
|
}
|
|
5904
5941
|
await this.ensureSuspended();
|
|
5905
|
-
log$
|
|
5942
|
+
log$4.info(`Auto-reinstalling ${id} — install directory is corrupted or missing (attempt ${String(attempts + 1)}/3)`);
|
|
5906
5943
|
this.manager.incrementReinstallAttempts(id);
|
|
5907
5944
|
try {
|
|
5908
5945
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
@@ -5917,7 +5954,7 @@ var ReconciliationEngine = class {
|
|
|
5917
5954
|
});
|
|
5918
5955
|
} catch (reinstallErr) {
|
|
5919
5956
|
const reinstallMsg = reinstallErr instanceof Error ? reinstallErr.message : String(reinstallErr);
|
|
5920
|
-
log$
|
|
5957
|
+
log$4.error({ err: reinstallErr }, `Auto-reinstall failed for ${id}`);
|
|
5921
5958
|
captureIntegrationFailure(id, "reinstall", reinstallErr);
|
|
5922
5959
|
report.errors.push({
|
|
5923
5960
|
integrationId: id,
|
|
@@ -5936,7 +5973,7 @@ var ReconciliationEngine = class {
|
|
|
5936
5973
|
if (activateAttempts < MAX_ERROR_ACTIVATE_ATTEMPTS) {
|
|
5937
5974
|
this.activateAttempts.set(id, activateAttempts + 1);
|
|
5938
5975
|
await this.ensureSuspended();
|
|
5939
|
-
log$
|
|
5976
|
+
log$4.info(`Re-activating ${id} from error state (attempt ${String(activateAttempts + 1)}/${String(MAX_ERROR_ACTIVATE_ATTEMPTS)})`);
|
|
5940
5977
|
try {
|
|
5941
5978
|
if ((await this.manager.activate(id, { forcePlugins: true })).configApplied) report.configApplied = true;
|
|
5942
5979
|
this.activateAttempts.delete(id);
|
|
@@ -5949,7 +5986,7 @@ var ReconciliationEngine = class {
|
|
|
5949
5986
|
return;
|
|
5950
5987
|
} catch (reactivateErr) {
|
|
5951
5988
|
const reactivateMsg = reactivateErr instanceof Error ? reactivateErr.message : String(reactivateErr);
|
|
5952
|
-
log$
|
|
5989
|
+
log$4.warn(`Re-activation of ${id} from error state failed (attempt ${String(activateAttempts + 1)}/${String(MAX_ERROR_ACTIVATE_ATTEMPTS)}): ${reactivateMsg}`);
|
|
5953
5990
|
captureIntegrationFailure(id, "reactivate", reactivateErr);
|
|
5954
5991
|
report.errors.push({
|
|
5955
5992
|
integrationId: id,
|
|
@@ -5964,7 +6001,7 @@ var ReconciliationEngine = class {
|
|
|
5964
6001
|
return;
|
|
5965
6002
|
}
|
|
5966
6003
|
}
|
|
5967
|
-
log$
|
|
6004
|
+
log$4.warn(`Integration ${id} is in error state — re-activation exhausted, waiting for reinstall request`);
|
|
5968
6005
|
captureIntegrationFailure(id, "reactivate", `Integration ${id} stuck in error state — re-activation attempts exhausted`);
|
|
5969
6006
|
report.errors.push({
|
|
5970
6007
|
integrationId: id,
|
|
@@ -5981,7 +6018,7 @@ var ReconciliationEngine = class {
|
|
|
5981
6018
|
if (local.status === "installing") {
|
|
5982
6019
|
if (desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) {
|
|
5983
6020
|
await this.ensureSuspended();
|
|
5984
|
-
log$
|
|
6021
|
+
log$4.info(`Reinstalling ${id} from stale installing state (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
|
|
5985
6022
|
this.manager.resetReinstallAttempts(id);
|
|
5986
6023
|
this.activateAttempts.delete(id);
|
|
5987
6024
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
@@ -5997,7 +6034,7 @@ var ReconciliationEngine = class {
|
|
|
5997
6034
|
const attempts = this.manager.getReinstallAttempts(id);
|
|
5998
6035
|
if (attempts >= 3) {
|
|
5999
6036
|
const errorMessage = `Stale installing state — max auto-reinstall attempts (${String(attempts)}) reached — manual reinstall required`;
|
|
6000
|
-
log$
|
|
6037
|
+
log$4.error(`Auto-reinstall blocked for ${id} — stuck in "installing", ${String(attempts)} consecutive failures. Manual reinstall required.`);
|
|
6001
6038
|
captureIntegrationFailure(id, "reinstall", errorMessage);
|
|
6002
6039
|
report.errors.push({
|
|
6003
6040
|
integrationId: id,
|
|
@@ -6012,7 +6049,7 @@ var ReconciliationEngine = class {
|
|
|
6012
6049
|
return;
|
|
6013
6050
|
}
|
|
6014
6051
|
await this.ensureSuspended();
|
|
6015
|
-
log$
|
|
6052
|
+
log$4.info(`Recovering ${id} from stale installing state via reinstall (attempt ${String(attempts + 1)}/3)`);
|
|
6016
6053
|
this.manager.incrementReinstallAttempts(id);
|
|
6017
6054
|
try {
|
|
6018
6055
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
@@ -6027,7 +6064,7 @@ var ReconciliationEngine = class {
|
|
|
6027
6064
|
});
|
|
6028
6065
|
} catch (reinstallErr) {
|
|
6029
6066
|
const reinstallMsg = reinstallErr instanceof Error ? reinstallErr.message : String(reinstallErr);
|
|
6030
|
-
log$
|
|
6067
|
+
log$4.error({ err: reinstallErr }, `Reinstall from stale installing state failed for ${id}`);
|
|
6031
6068
|
captureIntegrationFailure(id, "reinstall", reinstallErr);
|
|
6032
6069
|
report.errors.push({
|
|
6033
6070
|
integrationId: id,
|
|
@@ -6044,7 +6081,7 @@ var ReconciliationEngine = class {
|
|
|
6044
6081
|
}
|
|
6045
6082
|
if (local.status !== "active") {
|
|
6046
6083
|
await this.ensureSuspended();
|
|
6047
|
-
log$
|
|
6084
|
+
log$4.info(`Activating ${id}`);
|
|
6048
6085
|
if ((await this.manager.activate(id, { forcePlugins: true })).configApplied) report.configApplied = true;
|
|
6049
6086
|
report.activated.push(id);
|
|
6050
6087
|
report.results.push({
|
|
@@ -6056,7 +6093,7 @@ var ReconciliationEngine = class {
|
|
|
6056
6093
|
}
|
|
6057
6094
|
if (desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) {
|
|
6058
6095
|
await this.ensureSuspended();
|
|
6059
|
-
log$
|
|
6096
|
+
log$4.info(`Reinstall requested for ${id} (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
|
|
6060
6097
|
this.manager.resetReinstallAttempts(id);
|
|
6061
6098
|
this.activateAttempts.delete(id);
|
|
6062
6099
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
@@ -6077,7 +6114,7 @@ var ReconciliationEngine = class {
|
|
|
6077
6114
|
});
|
|
6078
6115
|
} catch (err) {
|
|
6079
6116
|
const message = err instanceof Error ? err.message : String(err);
|
|
6080
|
-
log$
|
|
6117
|
+
log$4.error({ err }, `Error reconciling ${id}`);
|
|
6081
6118
|
captureIntegrationFailure(id, "reconcile_active", err);
|
|
6082
6119
|
report.errors.push({
|
|
6083
6120
|
integrationId: id,
|
|
@@ -6102,7 +6139,7 @@ var ReconciliationEngine = class {
|
|
|
6102
6139
|
}
|
|
6103
6140
|
try {
|
|
6104
6141
|
await this.ensureSuspended();
|
|
6105
|
-
log$
|
|
6142
|
+
log$4.info(`Removing ${id}`);
|
|
6106
6143
|
if (local.status === "active") {
|
|
6107
6144
|
await this.manager.deactivate(id);
|
|
6108
6145
|
report.deactivated.push(id);
|
|
@@ -6116,7 +6153,7 @@ var ReconciliationEngine = class {
|
|
|
6116
6153
|
});
|
|
6117
6154
|
} catch (err) {
|
|
6118
6155
|
const message = err instanceof Error ? err.message : String(err);
|
|
6119
|
-
log$
|
|
6156
|
+
log$4.error({ err }, `Error removing ${id}`);
|
|
6120
6157
|
captureIntegrationFailure(id, "reconcile_removed", err);
|
|
6121
6158
|
report.errors.push({
|
|
6122
6159
|
integrationId: id,
|
|
@@ -6154,6 +6191,7 @@ var CloudClient = class {
|
|
|
6154
6191
|
onRuntimeRestartNeeded = null;
|
|
6155
6192
|
onReconciliationComplete = null;
|
|
6156
6193
|
reconciliationEngine = null;
|
|
6194
|
+
configReconciler = null;
|
|
6157
6195
|
reconciling = false;
|
|
6158
6196
|
pendingDesiredState = null;
|
|
6159
6197
|
constructor(config) {
|
|
@@ -6202,6 +6240,14 @@ var CloudClient = class {
|
|
|
6202
6240
|
this.reconciliationEngine = new ReconciliationEngine(manager, gate);
|
|
6203
6241
|
}
|
|
6204
6242
|
/**
|
|
6243
|
+
* Set the config reconciler for the DESIRED_STATE `config` block (plan A4).
|
|
6244
|
+
* When set, a DESIRED_STATE carrying `config` runs a config-reconcile pass
|
|
6245
|
+
* BEFORE the integrations loop and reports config status immediately.
|
|
6246
|
+
*/
|
|
6247
|
+
setConfigReconciler(reconciler) {
|
|
6248
|
+
this.configReconciler = reconciler;
|
|
6249
|
+
}
|
|
6250
|
+
/**
|
|
6205
6251
|
* Start the cloud connection with auto-reconnect.
|
|
6206
6252
|
*/
|
|
6207
6253
|
start() {
|
|
@@ -6424,6 +6470,19 @@ var CloudClient = class {
|
|
|
6424
6470
|
}
|
|
6425
6471
|
}
|
|
6426
6472
|
async runReconciliation(msg) {
|
|
6473
|
+
if (msg.config && this.configReconciler) try {
|
|
6474
|
+
const configReport = await this.configReconciler.reconcile(msg.config);
|
|
6475
|
+
if (configReport) {
|
|
6476
|
+
this.send(createConfigReport(configReport));
|
|
6477
|
+
logger$1.info({
|
|
6478
|
+
version: configReport.appliedVersion,
|
|
6479
|
+
status: configReport.status
|
|
6480
|
+
}, "Cloud: sent config reconcile report");
|
|
6481
|
+
}
|
|
6482
|
+
} catch (err) {
|
|
6483
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6484
|
+
logger$1.error({ err: message }, "Cloud: config reconcile failed");
|
|
6485
|
+
}
|
|
6427
6486
|
if (!this.reconciliationEngine) return;
|
|
6428
6487
|
const report = await this.reconciliationEngine.reconcile(msg.integrations);
|
|
6429
6488
|
logger$1.info({
|
|
@@ -6476,6 +6535,86 @@ var CloudClient = class {
|
|
|
6476
6535
|
}
|
|
6477
6536
|
};
|
|
6478
6537
|
//#endregion
|
|
6538
|
+
//#region src/config-reconciler.ts
|
|
6539
|
+
const log$3 = logger$1.child({ component: "ConfigReconciler" });
|
|
6540
|
+
const DEFAULT_VERIFY_RETRIES = 3;
|
|
6541
|
+
const DEFAULT_VERIFY_RETRY_DELAY_MS = 750;
|
|
6542
|
+
const delay = (ms) => new Promise((resolve) => {
|
|
6543
|
+
setTimeout(resolve, ms);
|
|
6544
|
+
});
|
|
6545
|
+
var ConfigReconciler = class {
|
|
6546
|
+
getApplier;
|
|
6547
|
+
verifyRetries;
|
|
6548
|
+
verifyRetryDelayMs;
|
|
6549
|
+
constructor(options) {
|
|
6550
|
+
this.getApplier = options.getApplier;
|
|
6551
|
+
this.verifyRetries = options.verifyRetries ?? DEFAULT_VERIFY_RETRIES;
|
|
6552
|
+
this.verifyRetryDelayMs = options.verifyRetryDelayMs ?? DEFAULT_VERIFY_RETRY_DELAY_MS;
|
|
6553
|
+
}
|
|
6554
|
+
/**
|
|
6555
|
+
* Reconcile the desired config block. Returns the outcome to report, or `null`
|
|
6556
|
+
* when there is nothing to report (no applier able to apply config).
|
|
6557
|
+
*/
|
|
6558
|
+
async reconcile(desired) {
|
|
6559
|
+
const applier = this.getApplier();
|
|
6560
|
+
if (!applier?.setConfigRaw) {
|
|
6561
|
+
log$3.debug("No applier with setConfigRaw — skipping config reconcile");
|
|
6562
|
+
return null;
|
|
6563
|
+
}
|
|
6564
|
+
const setConfigRaw = applier.setConfigRaw.bind(applier);
|
|
6565
|
+
const getConfigRaw = applier.getConfigRaw?.bind(applier);
|
|
6566
|
+
const entries = Object.entries(desired.values).filter((e) => e[1] !== null);
|
|
6567
|
+
if (entries.length === 0) return {
|
|
6568
|
+
appliedVersion: desired.version,
|
|
6569
|
+
status: "applied"
|
|
6570
|
+
};
|
|
6571
|
+
const failures = [];
|
|
6572
|
+
for (const [key, want] of entries) try {
|
|
6573
|
+
if (getConfigRaw) {
|
|
6574
|
+
if (await getConfigRaw(key) === want) {
|
|
6575
|
+
log$3.debug({ key }, "Config already at desired value — no-op");
|
|
6576
|
+
continue;
|
|
6577
|
+
}
|
|
6578
|
+
}
|
|
6579
|
+
await setConfigRaw(key, want);
|
|
6580
|
+
if (getConfigRaw) {
|
|
6581
|
+
if (!await this.verify(getConfigRaw, key, want)) failures.push(`${key}: verify failed (read-back did not match)`);
|
|
6582
|
+
}
|
|
6583
|
+
} catch (err) {
|
|
6584
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6585
|
+
failures.push(`${key}: ${msg}`);
|
|
6586
|
+
}
|
|
6587
|
+
if (failures.length > 0) {
|
|
6588
|
+
const joined = failures.join("; ");
|
|
6589
|
+
const reason = joined.length > 480 ? `${joined.slice(0, 480)}… (truncated)` : joined;
|
|
6590
|
+
log$3.warn({
|
|
6591
|
+
version: desired.version,
|
|
6592
|
+
reason
|
|
6593
|
+
}, "Config reconcile failed");
|
|
6594
|
+
return {
|
|
6595
|
+
appliedVersion: desired.version,
|
|
6596
|
+
status: "failed",
|
|
6597
|
+
reason
|
|
6598
|
+
};
|
|
6599
|
+
}
|
|
6600
|
+
log$3.info({
|
|
6601
|
+
version: desired.version,
|
|
6602
|
+
keys: entries.length
|
|
6603
|
+
}, "Config reconcile applied");
|
|
6604
|
+
return {
|
|
6605
|
+
appliedVersion: desired.version,
|
|
6606
|
+
status: "applied"
|
|
6607
|
+
};
|
|
6608
|
+
}
|
|
6609
|
+
async verify(getConfigRaw, key, want) {
|
|
6610
|
+
for (let attempt = 0; attempt <= this.verifyRetries; attempt++) {
|
|
6611
|
+
if (await getConfigRaw(key) === want) return true;
|
|
6612
|
+
if (attempt < this.verifyRetries && this.verifyRetryDelayMs > 0) await delay(this.verifyRetryDelayMs * 2 ** attempt);
|
|
6613
|
+
}
|
|
6614
|
+
return false;
|
|
6615
|
+
}
|
|
6616
|
+
};
|
|
6617
|
+
//#endregion
|
|
6479
6618
|
//#region src/ipc-server.ts
|
|
6480
6619
|
/**
|
|
6481
6620
|
* IPC Server — Unix socket server for local plugin connections.
|
|
@@ -6823,12 +6962,12 @@ var IPCServer = class {
|
|
|
6823
6962
|
};
|
|
6824
6963
|
//#endregion
|
|
6825
6964
|
//#region src/command-queue.ts
|
|
6826
|
-
const DEFAULT_TTL_MS = 300 * 1e3;
|
|
6965
|
+
const DEFAULT_TTL_MS$1 = 300 * 1e3;
|
|
6827
6966
|
var CommandQueue = class {
|
|
6828
6967
|
queues = /* @__PURE__ */ new Map();
|
|
6829
6968
|
ttlMs;
|
|
6830
6969
|
gcTimer = null;
|
|
6831
|
-
constructor(ttlMs = DEFAULT_TTL_MS) {
|
|
6970
|
+
constructor(ttlMs = DEFAULT_TTL_MS$1) {
|
|
6832
6971
|
this.ttlMs = ttlMs;
|
|
6833
6972
|
}
|
|
6834
6973
|
/**
|
|
@@ -22838,6 +22977,73 @@ var CommandRegistry = class {
|
|
|
22838
22977
|
}
|
|
22839
22978
|
};
|
|
22840
22979
|
//#endregion
|
|
22980
|
+
//#region src/command-dedupe.ts
|
|
22981
|
+
const DEFAULT_MAX_ENTRIES = 200;
|
|
22982
|
+
const DEFAULT_TTL_MS = 600 * 1e3;
|
|
22983
|
+
var CommandDedupe = class {
|
|
22984
|
+
entries = /* @__PURE__ */ new Map();
|
|
22985
|
+
maxEntries;
|
|
22986
|
+
ttlMs;
|
|
22987
|
+
now;
|
|
22988
|
+
constructor(opts = {}) {
|
|
22989
|
+
this.maxEntries = opts.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
22990
|
+
this.ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
22991
|
+
this.now = opts.now ?? (() => Date.now());
|
|
22992
|
+
}
|
|
22993
|
+
/**
|
|
22994
|
+
* Run `exec` for `commandId` exactly once within the dedupe window. A repeat
|
|
22995
|
+
* call with the same id (while still remembered) returns the FIRST call's
|
|
22996
|
+
* promise — either the in-flight execution or the settled replayable ACK — so
|
|
22997
|
+
* the command body never runs twice.
|
|
22998
|
+
*/
|
|
22999
|
+
async run(commandId, exec) {
|
|
23000
|
+
this.evictExpired();
|
|
23001
|
+
const existing = this.entries.get(commandId);
|
|
23002
|
+
if (existing) return {
|
|
23003
|
+
ack: await existing.promise,
|
|
23004
|
+
duplicate: true
|
|
23005
|
+
};
|
|
23006
|
+
const promise = exec();
|
|
23007
|
+
this.entries.set(commandId, {
|
|
23008
|
+
insertedAt: this.now(),
|
|
23009
|
+
promise
|
|
23010
|
+
});
|
|
23011
|
+
this.evictOverCap();
|
|
23012
|
+
promise.catch(() => {
|
|
23013
|
+
this.entries.delete(commandId);
|
|
23014
|
+
});
|
|
23015
|
+
return {
|
|
23016
|
+
ack: await promise,
|
|
23017
|
+
duplicate: false
|
|
23018
|
+
};
|
|
23019
|
+
}
|
|
23020
|
+
/** Test/inspection helper. */
|
|
23021
|
+
size() {
|
|
23022
|
+
this.evictExpired();
|
|
23023
|
+
return this.entries.size;
|
|
23024
|
+
}
|
|
23025
|
+
has(commandId) {
|
|
23026
|
+
const e = this.entries.get(commandId);
|
|
23027
|
+
if (!e) return false;
|
|
23028
|
+
if (this.now() - e.insertedAt > this.ttlMs) {
|
|
23029
|
+
this.entries.delete(commandId);
|
|
23030
|
+
return false;
|
|
23031
|
+
}
|
|
23032
|
+
return true;
|
|
23033
|
+
}
|
|
23034
|
+
evictExpired() {
|
|
23035
|
+
const cutoff = this.now() - this.ttlMs;
|
|
23036
|
+
for (const [id, entry] of this.entries) if (entry.insertedAt <= cutoff) this.entries.delete(id);
|
|
23037
|
+
}
|
|
23038
|
+
evictOverCap() {
|
|
23039
|
+
while (this.entries.size > this.maxEntries) {
|
|
23040
|
+
const oldest = this.entries.keys().next().value;
|
|
23041
|
+
if (oldest === void 0) break;
|
|
23042
|
+
this.entries.delete(oldest);
|
|
23043
|
+
}
|
|
23044
|
+
}
|
|
23045
|
+
};
|
|
23046
|
+
//#endregion
|
|
22841
23047
|
//#region src/pairing-approval.ts
|
|
22842
23048
|
/**
|
|
22843
23049
|
* Pairing approval poller — auto-approves pending OpenClaw scope-upgrade
|
|
@@ -23245,6 +23451,13 @@ let runtimeProcess = null;
|
|
|
23245
23451
|
let aiProxyUrl = null;
|
|
23246
23452
|
let aiProxyRunning = false;
|
|
23247
23453
|
let cloudConnected = false;
|
|
23454
|
+
/**
|
|
23455
|
+
* Dedupe for at-least-once durable command delivery (Phase B). The cloud may
|
|
23456
|
+
* push a command over the live WS AND re-drain the same commandId on the next
|
|
23457
|
+
* SERVICE_REGISTER; both hit handleCloudCommand. This remembers recent outcomes
|
|
23458
|
+
* so a duplicate is ACKed with the prior result without re-executing.
|
|
23459
|
+
*/
|
|
23460
|
+
const commandDedupe = new CommandDedupe();
|
|
23248
23461
|
let shuttingDown = false;
|
|
23249
23462
|
let commandRegistry;
|
|
23250
23463
|
let resolvedCliVersion;
|
|
@@ -23641,6 +23854,7 @@ async function startDaemon() {
|
|
|
23641
23854
|
const integrationAdapter = new IntegrationManagerAdapter(integrationManager);
|
|
23642
23855
|
const runtimeGate = new RuntimeGate(() => runtimeProcess);
|
|
23643
23856
|
cloudClient.setIntegrationManager(integrationAdapter, runtimeGate);
|
|
23857
|
+
cloudClient.setConfigReconciler(new ConfigReconciler({ getApplier: () => runtimeAppliers.get(config.runtime) }));
|
|
23644
23858
|
const requestRuntimeRestart = () => {
|
|
23645
23859
|
logger$1.info("Runtime state changed — requesting runtime restart");
|
|
23646
23860
|
runtimeGate.requestRestart();
|
|
@@ -23767,7 +23981,34 @@ async function startDaemon() {
|
|
|
23767
23981
|
agentId: config.agentId
|
|
23768
23982
|
}, "Alfe Gateway Daemon started ✅");
|
|
23769
23983
|
}
|
|
23984
|
+
/**
|
|
23985
|
+
* Single command choke point. Dedupes at-least-once durable deliveries
|
|
23986
|
+
* (Phase B) — a repeat commandId within the dedupe window is ACKed with the
|
|
23987
|
+
* prior outcome (tagged `duplicate: true`) and NOT re-executed.
|
|
23988
|
+
*
|
|
23989
|
+
* Non-idempotent, replay-ordering-hazardous commands (daemon.update /
|
|
23990
|
+
* daemon.restart / runtime.restart) benefit most: a re-drained restart must not
|
|
23991
|
+
* bounce the box a second time.
|
|
23992
|
+
*/
|
|
23770
23993
|
async function handleCloudCommand(command) {
|
|
23994
|
+
const { ack, duplicate } = await commandDedupe.run(command.commandId, () => executeCloudCommand(command));
|
|
23995
|
+
if (duplicate) {
|
|
23996
|
+
logger$1.info({
|
|
23997
|
+
commandId: command.commandId,
|
|
23998
|
+
command: command.command
|
|
23999
|
+
}, "Duplicate command — replaying prior ACK without re-executing");
|
|
24000
|
+
const priorResult = ack.result && typeof ack.result === "object" && !Array.isArray(ack.result) ? ack.result : { result: ack.result };
|
|
24001
|
+
return {
|
|
24002
|
+
...ack,
|
|
24003
|
+
result: {
|
|
24004
|
+
...priorResult,
|
|
24005
|
+
duplicate: true
|
|
24006
|
+
}
|
|
24007
|
+
};
|
|
24008
|
+
}
|
|
24009
|
+
return ack;
|
|
24010
|
+
}
|
|
24011
|
+
async function executeCloudCommand(command) {
|
|
23771
24012
|
if (command.command === "daemon.update") {
|
|
23772
24013
|
const version = command.payload?.version ?? "latest";
|
|
23773
24014
|
setTimeout(() => {
|
|
@@ -23810,7 +24051,7 @@ async function handleCloudCommand(command) {
|
|
|
23810
24051
|
};
|
|
23811
24052
|
const payload = command.payload;
|
|
23812
24053
|
const runtime = config.runtime;
|
|
23813
|
-
const version = payload?.version ?? (runtime === "openclaw" ? "2026.6.
|
|
24054
|
+
const version = payload?.version ?? (runtime === "openclaw" ? "2026.6.11" : void 0);
|
|
23814
24055
|
upgradingRuntime = true;
|
|
23815
24056
|
setTimeout(() => {
|
|
23816
24057
|
(async () => {
|
package/dist/src/index.d.ts
CHANGED
|
@@ -28,6 +28,15 @@ declare const PROTOCOL_VERSION = 1;
|
|
|
28
28
|
//#region src/daemon.d.ts
|
|
29
29
|
|
|
30
30
|
declare function startDaemon(): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Single command choke point. Dedupes at-least-once durable deliveries
|
|
33
|
+
* (Phase B) — a repeat commandId within the dedupe window is ACKed with the
|
|
34
|
+
* prior outcome (tagged `duplicate: true`) and NOT re-executed.
|
|
35
|
+
*
|
|
36
|
+
* Non-idempotent, replay-ordering-hazardous commands (daemon.update /
|
|
37
|
+
* daemon.restart / runtime.restart) benefit most: a re-drained restart must not
|
|
38
|
+
* bounce the box a second time.
|
|
39
|
+
*/
|
|
31
40
|
//#endregion
|
|
32
41
|
//#region src/openclaw-version.d.ts
|
|
33
42
|
/**
|
|
@@ -44,7 +53,7 @@ declare function startDaemon(): Promise<void>;
|
|
|
44
53
|
* silent fleet outage with no Alfe deploy. Bump deliberately after validating a new
|
|
45
54
|
* OpenClaw, then cut a CLI release so the pin travels with the CLI version.
|
|
46
55
|
*/
|
|
47
|
-
declare const PINNED_OPENCLAW_VERSION = "2026.6.
|
|
56
|
+
declare const PINNED_OPENCLAW_VERSION = "2026.6.11";
|
|
48
57
|
//#endregion
|
|
49
58
|
//#region src/sentry.d.ts
|
|
50
59
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/gateway",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Alfe local gateway daemon — persistent control plane for agent integrations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
"pino-roll": "^1.2.0",
|
|
24
24
|
"smol-toml": ">=1.6.1",
|
|
25
25
|
"ws": "^8.18.0",
|
|
26
|
-
"@alfe.ai/ai-proxy-local": "^0.0.13",
|
|
27
26
|
"@alfe.ai/agent-api-client": "^0.8.0",
|
|
27
|
+
"@alfe.ai/ai-proxy-local": "^0.0.13",
|
|
28
28
|
"@alfe.ai/config": "^0.3.0",
|
|
29
29
|
"@alfe.ai/integration-manifest": "^0.3.1",
|
|
30
|
-
"@alfe.ai/integrations": "^0.
|
|
30
|
+
"@alfe.ai/integrations": "^0.4.0",
|
|
31
31
|
"@alfe.ai/mcp-bundler": "^0.3.0"
|
|
32
32
|
},
|
|
33
33
|
"license": "UNLICENSED",
|