@alfe.ai/gateway 0.7.2 → 0.7.4
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 +306 -61
- package/dist/runtime-upgrade.js +1 -1
- package/package.json +4 -4
package/dist/health.js
CHANGED
|
@@ -551,6 +551,18 @@ var AuthService = class {
|
|
|
551
551
|
body: JSON.stringify(input)
|
|
552
552
|
});
|
|
553
553
|
}
|
|
554
|
+
/**
|
|
555
|
+
* Change a personal (individual-tier) subscriber's platform plan in place on
|
|
556
|
+
* their existing Stripe subscription — no second Checkout, no duplicate sub,
|
|
557
|
+
* no double charge. Upgrades prorate immediately; downgrades apply with no
|
|
558
|
+
* immediate charge/refund. Returns the updated subscription.
|
|
559
|
+
*/
|
|
560
|
+
changePlatformPlan(input) {
|
|
561
|
+
return this.client.request(`${this.prefix}/subscription/change`, {
|
|
562
|
+
method: "POST",
|
|
563
|
+
body: JSON.stringify(input)
|
|
564
|
+
});
|
|
565
|
+
}
|
|
554
566
|
updateSeats(seats) {
|
|
555
567
|
return this.client.request(`${this.prefix}/subscription/seats`, {
|
|
556
568
|
method: "POST",
|
|
@@ -5313,7 +5325,7 @@ function isIPCResponse(msg) {
|
|
|
5313
5325
|
const PROTOCOL_VERSION = 1;
|
|
5314
5326
|
//#endregion
|
|
5315
5327
|
//#region src/runtime-gate.ts
|
|
5316
|
-
const log$
|
|
5328
|
+
const log$6 = logger$1.child({ component: "RuntimeGate" });
|
|
5317
5329
|
var RuntimeGate = class {
|
|
5318
5330
|
depth = 0;
|
|
5319
5331
|
wasRunning = false;
|
|
@@ -5331,8 +5343,8 @@ var RuntimeGate = class {
|
|
|
5331
5343
|
const rp = this.getRuntime();
|
|
5332
5344
|
this.wasRunning = rp?.isRunning ?? false;
|
|
5333
5345
|
if (rp && this.wasRunning) {
|
|
5334
|
-
log$
|
|
5335
|
-
await rp.
|
|
5346
|
+
log$6.info("Suspending runtime for mutating reconcile (avoid concurrent SQLite writers)");
|
|
5347
|
+
await rp.stopWhenIdle();
|
|
5336
5348
|
}
|
|
5337
5349
|
}
|
|
5338
5350
|
/**
|
|
@@ -5346,7 +5358,7 @@ var RuntimeGate = class {
|
|
|
5346
5358
|
if (this.depth < 0) this.depth = 0;
|
|
5347
5359
|
const rp = this.getRuntime();
|
|
5348
5360
|
if (rp && this.wasRunning) {
|
|
5349
|
-
log$
|
|
5361
|
+
log$6.info("Resuming runtime after mutating reconcile");
|
|
5350
5362
|
rp.resume();
|
|
5351
5363
|
}
|
|
5352
5364
|
this.wasRunning = false;
|
|
@@ -5361,12 +5373,12 @@ var RuntimeGate = class {
|
|
|
5361
5373
|
*/
|
|
5362
5374
|
requestRestart() {
|
|
5363
5375
|
if (this.depth > 0) {
|
|
5364
|
-
log$
|
|
5376
|
+
log$6.info("Runtime restart requested while suspended — deferring to pending resume");
|
|
5365
5377
|
return;
|
|
5366
5378
|
}
|
|
5367
5379
|
const rp = this.getRuntime();
|
|
5368
5380
|
if (rp) rp.restart().catch((err) => {
|
|
5369
|
-
log$
|
|
5381
|
+
log$6.error({ err: err instanceof Error ? err.message : String(err) }, "Failed to restart runtime");
|
|
5370
5382
|
});
|
|
5371
5383
|
}
|
|
5372
5384
|
};
|
|
@@ -5770,7 +5782,7 @@ function captureFatal(cause) {
|
|
|
5770
5782
|
}
|
|
5771
5783
|
//#endregion
|
|
5772
5784
|
//#region src/reconciliation.ts
|
|
5773
|
-
const log$
|
|
5785
|
+
const log$5 = logger$1.child({ component: "Reconciliation" });
|
|
5774
5786
|
/**
|
|
5775
5787
|
* How many times reconcile will re-attempt activation of an intact integration
|
|
5776
5788
|
* stuck in `error` (with no reinstall requested) before giving up and waiting
|
|
@@ -5840,7 +5852,7 @@ var ReconciliationEngine = class {
|
|
|
5840
5852
|
try {
|
|
5841
5853
|
localIntegrations = await this.manager.getInstalledIntegrations();
|
|
5842
5854
|
} catch (err) {
|
|
5843
|
-
log$
|
|
5855
|
+
log$5.error({ err }, "Failed to get local integrations");
|
|
5844
5856
|
localIntegrations = [];
|
|
5845
5857
|
}
|
|
5846
5858
|
const localMap = new Map(localIntegrations.map((i) => [i.id, i]));
|
|
@@ -5859,10 +5871,10 @@ var ReconciliationEngine = class {
|
|
|
5859
5871
|
try {
|
|
5860
5872
|
if (!local) {
|
|
5861
5873
|
await this.ensureSuspended();
|
|
5862
|
-
log$
|
|
5874
|
+
log$5.info(`Installing ${id}@${desired.version}`);
|
|
5863
5875
|
await this.manager.install(id, desired.version, desired.config, desired.customSource);
|
|
5864
5876
|
report.installed.push(id);
|
|
5865
|
-
log$
|
|
5877
|
+
log$5.info(`Activating ${id}`);
|
|
5866
5878
|
if ((await this.manager.activate(id)).configApplied) report.configApplied = true;
|
|
5867
5879
|
report.activated.push(id);
|
|
5868
5880
|
report.results.push({
|
|
@@ -5875,7 +5887,7 @@ var ReconciliationEngine = class {
|
|
|
5875
5887
|
if (local.version !== desired.version && desired.version !== "" && local.version !== "unknown") {
|
|
5876
5888
|
if (desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) {
|
|
5877
5889
|
await this.ensureSuspended();
|
|
5878
|
-
log$
|
|
5890
|
+
log$5.info(`Reinstalling ${id} on version change (reinstall requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt}): ${local.version} → ${desired.version}`);
|
|
5879
5891
|
this.manager.resetReinstallAttempts(id);
|
|
5880
5892
|
this.activateAttempts.delete(id);
|
|
5881
5893
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
@@ -5888,7 +5900,7 @@ var ReconciliationEngine = class {
|
|
|
5888
5900
|
});
|
|
5889
5901
|
return;
|
|
5890
5902
|
}
|
|
5891
|
-
log$
|
|
5903
|
+
log$5.info(`Upgrading ${id} in place: ${local.version} → ${desired.version}`);
|
|
5892
5904
|
try {
|
|
5893
5905
|
if ((await this.manager.upgrade(id, desired.version, desired.config, desired.customSource, { onBeforeRuntimeMutation: () => this.ensureSuspended() })).configApplied) report.configApplied = true;
|
|
5894
5906
|
this.manager.resetReinstallAttempts(id);
|
|
@@ -5902,7 +5914,7 @@ var ReconciliationEngine = class {
|
|
|
5902
5914
|
});
|
|
5903
5915
|
} catch (upgradeErr) {
|
|
5904
5916
|
const upgradeMsg = upgradeErr instanceof Error ? upgradeErr.message : String(upgradeErr);
|
|
5905
|
-
log$
|
|
5917
|
+
log$5.error({ err: upgradeErr }, `Upgrade failed for ${id}`);
|
|
5906
5918
|
captureIntegrationFailure(id, "upgrade", upgradeErr);
|
|
5907
5919
|
report.errors.push({
|
|
5908
5920
|
integrationId: id,
|
|
@@ -5920,7 +5932,7 @@ var ReconciliationEngine = class {
|
|
|
5920
5932
|
if (local.status === "error") {
|
|
5921
5933
|
if (desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) {
|
|
5922
5934
|
await this.ensureSuspended();
|
|
5923
|
-
log$
|
|
5935
|
+
log$5.info(`Reinstalling ${id} from error state (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
|
|
5924
5936
|
this.manager.resetReinstallAttempts(id);
|
|
5925
5937
|
this.activateAttempts.delete(id);
|
|
5926
5938
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
@@ -5936,7 +5948,7 @@ var ReconciliationEngine = class {
|
|
|
5936
5948
|
if (!await this.manager.isInstallIntact(id)) {
|
|
5937
5949
|
const attempts = this.manager.getReinstallAttempts(id);
|
|
5938
5950
|
if (attempts >= 3) {
|
|
5939
|
-
log$
|
|
5951
|
+
log$5.error(`Auto-reinstall blocked for ${id} — ${String(attempts)} consecutive failures. Manual reinstall required.`);
|
|
5940
5952
|
captureIntegrationFailure(id, "reinstall", `Max auto-reinstall attempts (${String(attempts)}) reached — manual reinstall required`);
|
|
5941
5953
|
report.errors.push({
|
|
5942
5954
|
integrationId: id,
|
|
@@ -5951,7 +5963,7 @@ var ReconciliationEngine = class {
|
|
|
5951
5963
|
return;
|
|
5952
5964
|
}
|
|
5953
5965
|
await this.ensureSuspended();
|
|
5954
|
-
log$
|
|
5966
|
+
log$5.info(`Auto-reinstalling ${id} — install directory is corrupted or missing (attempt ${String(attempts + 1)}/3)`);
|
|
5955
5967
|
this.manager.incrementReinstallAttempts(id);
|
|
5956
5968
|
try {
|
|
5957
5969
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
@@ -5966,7 +5978,7 @@ var ReconciliationEngine = class {
|
|
|
5966
5978
|
});
|
|
5967
5979
|
} catch (reinstallErr) {
|
|
5968
5980
|
const reinstallMsg = reinstallErr instanceof Error ? reinstallErr.message : String(reinstallErr);
|
|
5969
|
-
log$
|
|
5981
|
+
log$5.error({ err: reinstallErr }, `Auto-reinstall failed for ${id}`);
|
|
5970
5982
|
captureIntegrationFailure(id, "reinstall", reinstallErr);
|
|
5971
5983
|
report.errors.push({
|
|
5972
5984
|
integrationId: id,
|
|
@@ -5985,7 +5997,7 @@ var ReconciliationEngine = class {
|
|
|
5985
5997
|
if (activateAttempts < MAX_ERROR_ACTIVATE_ATTEMPTS) {
|
|
5986
5998
|
this.activateAttempts.set(id, activateAttempts + 1);
|
|
5987
5999
|
await this.ensureSuspended();
|
|
5988
|
-
log$
|
|
6000
|
+
log$5.info(`Re-activating ${id} from error state (attempt ${String(activateAttempts + 1)}/${String(MAX_ERROR_ACTIVATE_ATTEMPTS)})`);
|
|
5989
6001
|
try {
|
|
5990
6002
|
if ((await this.manager.activate(id, { forcePlugins: true })).configApplied) report.configApplied = true;
|
|
5991
6003
|
this.activateAttempts.delete(id);
|
|
@@ -5998,7 +6010,7 @@ var ReconciliationEngine = class {
|
|
|
5998
6010
|
return;
|
|
5999
6011
|
} catch (reactivateErr) {
|
|
6000
6012
|
const reactivateMsg = reactivateErr instanceof Error ? reactivateErr.message : String(reactivateErr);
|
|
6001
|
-
log$
|
|
6013
|
+
log$5.warn(`Re-activation of ${id} from error state failed (attempt ${String(activateAttempts + 1)}/${String(MAX_ERROR_ACTIVATE_ATTEMPTS)}): ${reactivateMsg}`);
|
|
6002
6014
|
captureIntegrationFailure(id, "reactivate", reactivateErr);
|
|
6003
6015
|
report.errors.push({
|
|
6004
6016
|
integrationId: id,
|
|
@@ -6013,7 +6025,7 @@ var ReconciliationEngine = class {
|
|
|
6013
6025
|
return;
|
|
6014
6026
|
}
|
|
6015
6027
|
}
|
|
6016
|
-
log$
|
|
6028
|
+
log$5.warn(`Integration ${id} is in error state — re-activation exhausted, waiting for reinstall request`);
|
|
6017
6029
|
captureIntegrationFailure(id, "reactivate", `Integration ${id} stuck in error state — re-activation attempts exhausted`);
|
|
6018
6030
|
report.errors.push({
|
|
6019
6031
|
integrationId: id,
|
|
@@ -6030,7 +6042,7 @@ var ReconciliationEngine = class {
|
|
|
6030
6042
|
if (local.status === "installing") {
|
|
6031
6043
|
if (desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) {
|
|
6032
6044
|
await this.ensureSuspended();
|
|
6033
|
-
log$
|
|
6045
|
+
log$5.info(`Reinstalling ${id} from stale installing state (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
|
|
6034
6046
|
this.manager.resetReinstallAttempts(id);
|
|
6035
6047
|
this.activateAttempts.delete(id);
|
|
6036
6048
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
@@ -6046,7 +6058,7 @@ var ReconciliationEngine = class {
|
|
|
6046
6058
|
const attempts = this.manager.getReinstallAttempts(id);
|
|
6047
6059
|
if (attempts >= 3) {
|
|
6048
6060
|
const errorMessage = `Stale installing state — max auto-reinstall attempts (${String(attempts)}) reached — manual reinstall required`;
|
|
6049
|
-
log$
|
|
6061
|
+
log$5.error(`Auto-reinstall blocked for ${id} — stuck in "installing", ${String(attempts)} consecutive failures. Manual reinstall required.`);
|
|
6050
6062
|
captureIntegrationFailure(id, "reinstall", errorMessage);
|
|
6051
6063
|
report.errors.push({
|
|
6052
6064
|
integrationId: id,
|
|
@@ -6061,7 +6073,7 @@ var ReconciliationEngine = class {
|
|
|
6061
6073
|
return;
|
|
6062
6074
|
}
|
|
6063
6075
|
await this.ensureSuspended();
|
|
6064
|
-
log$
|
|
6076
|
+
log$5.info(`Recovering ${id} from stale installing state via reinstall (attempt ${String(attempts + 1)}/3)`);
|
|
6065
6077
|
this.manager.incrementReinstallAttempts(id);
|
|
6066
6078
|
try {
|
|
6067
6079
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
@@ -6076,7 +6088,7 @@ var ReconciliationEngine = class {
|
|
|
6076
6088
|
});
|
|
6077
6089
|
} catch (reinstallErr) {
|
|
6078
6090
|
const reinstallMsg = reinstallErr instanceof Error ? reinstallErr.message : String(reinstallErr);
|
|
6079
|
-
log$
|
|
6091
|
+
log$5.error({ err: reinstallErr }, `Reinstall from stale installing state failed for ${id}`);
|
|
6080
6092
|
captureIntegrationFailure(id, "reinstall", reinstallErr);
|
|
6081
6093
|
report.errors.push({
|
|
6082
6094
|
integrationId: id,
|
|
@@ -6093,7 +6105,7 @@ var ReconciliationEngine = class {
|
|
|
6093
6105
|
}
|
|
6094
6106
|
if (local.status !== "active") {
|
|
6095
6107
|
await this.ensureSuspended();
|
|
6096
|
-
log$
|
|
6108
|
+
log$5.info(`Activating ${id}`);
|
|
6097
6109
|
if ((await this.manager.activate(id, { forcePlugins: true })).configApplied) report.configApplied = true;
|
|
6098
6110
|
report.activated.push(id);
|
|
6099
6111
|
report.results.push({
|
|
@@ -6105,7 +6117,7 @@ var ReconciliationEngine = class {
|
|
|
6105
6117
|
}
|
|
6106
6118
|
if (desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) {
|
|
6107
6119
|
await this.ensureSuspended();
|
|
6108
|
-
log$
|
|
6120
|
+
log$5.info(`Reinstall requested for ${id} (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
|
|
6109
6121
|
this.manager.resetReinstallAttempts(id);
|
|
6110
6122
|
this.activateAttempts.delete(id);
|
|
6111
6123
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
@@ -6126,7 +6138,7 @@ var ReconciliationEngine = class {
|
|
|
6126
6138
|
});
|
|
6127
6139
|
} catch (err) {
|
|
6128
6140
|
const message = err instanceof Error ? err.message : String(err);
|
|
6129
|
-
log$
|
|
6141
|
+
log$5.error({ err }, `Error reconciling ${id}`);
|
|
6130
6142
|
captureIntegrationFailure(id, "reconcile_active", err);
|
|
6131
6143
|
report.errors.push({
|
|
6132
6144
|
integrationId: id,
|
|
@@ -6151,7 +6163,7 @@ var ReconciliationEngine = class {
|
|
|
6151
6163
|
}
|
|
6152
6164
|
try {
|
|
6153
6165
|
await this.ensureSuspended();
|
|
6154
|
-
log$
|
|
6166
|
+
log$5.info(`Removing ${id}`);
|
|
6155
6167
|
if (local.status === "active") {
|
|
6156
6168
|
await this.manager.deactivate(id);
|
|
6157
6169
|
report.deactivated.push(id);
|
|
@@ -6165,7 +6177,7 @@ var ReconciliationEngine = class {
|
|
|
6165
6177
|
});
|
|
6166
6178
|
} catch (err) {
|
|
6167
6179
|
const message = err instanceof Error ? err.message : String(err);
|
|
6168
|
-
log$
|
|
6180
|
+
log$5.error({ err }, `Error removing ${id}`);
|
|
6169
6181
|
captureIntegrationFailure(id, "reconcile_removed", err);
|
|
6170
6182
|
report.errors.push({
|
|
6171
6183
|
integrationId: id,
|
|
@@ -6548,7 +6560,7 @@ var CloudClient = class {
|
|
|
6548
6560
|
};
|
|
6549
6561
|
//#endregion
|
|
6550
6562
|
//#region src/config-reconciler.ts
|
|
6551
|
-
const log$
|
|
6563
|
+
const log$4 = logger$1.child({ component: "ConfigReconciler" });
|
|
6552
6564
|
const DEFAULT_VERIFY_RETRIES = 3;
|
|
6553
6565
|
const DEFAULT_VERIFY_RETRY_DELAY_MS = 750;
|
|
6554
6566
|
const delay = (ms) => new Promise((resolve) => {
|
|
@@ -6570,7 +6582,7 @@ var ConfigReconciler = class {
|
|
|
6570
6582
|
async reconcile(desired) {
|
|
6571
6583
|
const applier = this.getApplier();
|
|
6572
6584
|
if (!applier?.setConfigRaw) {
|
|
6573
|
-
log$
|
|
6585
|
+
log$4.debug("No applier with setConfigRaw — skipping config reconcile");
|
|
6574
6586
|
return null;
|
|
6575
6587
|
}
|
|
6576
6588
|
const setConfigRaw = applier.setConfigRaw.bind(applier);
|
|
@@ -6584,7 +6596,7 @@ var ConfigReconciler = class {
|
|
|
6584
6596
|
for (const [key, want] of entries) try {
|
|
6585
6597
|
if (getConfigRaw) {
|
|
6586
6598
|
if (await getConfigRaw(key) === want) {
|
|
6587
|
-
log$
|
|
6599
|
+
log$4.debug({ key }, "Config already at desired value — no-op");
|
|
6588
6600
|
continue;
|
|
6589
6601
|
}
|
|
6590
6602
|
}
|
|
@@ -6594,12 +6606,19 @@ var ConfigReconciler = class {
|
|
|
6594
6606
|
}
|
|
6595
6607
|
} catch (err) {
|
|
6596
6608
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6609
|
+
if (getConfigRaw && await this.verify(getConfigRaw, key, want)) {
|
|
6610
|
+
log$4.warn({
|
|
6611
|
+
key,
|
|
6612
|
+
err: msg
|
|
6613
|
+
}, "config set errored but value landed — treating as applied");
|
|
6614
|
+
continue;
|
|
6615
|
+
}
|
|
6597
6616
|
failures.push(`${key}: ${msg}`);
|
|
6598
6617
|
}
|
|
6599
6618
|
if (failures.length > 0) {
|
|
6600
6619
|
const joined = failures.join("; ");
|
|
6601
6620
|
const reason = joined.length > 480 ? `${joined.slice(0, 480)}… (truncated)` : joined;
|
|
6602
|
-
log$
|
|
6621
|
+
log$4.warn({
|
|
6603
6622
|
version: desired.version,
|
|
6604
6623
|
reason
|
|
6605
6624
|
}, "Config reconcile failed");
|
|
@@ -6609,7 +6628,7 @@ var ConfigReconciler = class {
|
|
|
6609
6628
|
reason
|
|
6610
6629
|
};
|
|
6611
6630
|
}
|
|
6612
|
-
log$
|
|
6631
|
+
log$4.info({
|
|
6613
6632
|
version: desired.version,
|
|
6614
6633
|
keys: entries.length
|
|
6615
6634
|
}, "Config reconcile applied");
|
|
@@ -22585,13 +22604,21 @@ var CaptureThrottle = class {
|
|
|
22585
22604
|
* Spawns the runtime binary, pipes stdout/stderr to the daemon logger,
|
|
22586
22605
|
* and restarts on crash with exponential backoff.
|
|
22587
22606
|
*/
|
|
22588
|
-
const log$
|
|
22607
|
+
const log$3 = createLogger("RuntimeProcess");
|
|
22589
22608
|
/** Quiet-period flush for a pending multi-line error block (e.g. a traceback
|
|
22590
22609
|
* followed by silence — no closing line ever arrives to complete it). */
|
|
22591
22610
|
const DETECTOR_FLUSH_DEBOUNCE_MS = 1500;
|
|
22592
22611
|
const BACKOFF_INITIAL_MS = 1e3;
|
|
22593
22612
|
const BACKOFF_MAX_MS = 3e4;
|
|
22594
22613
|
const STABLE_UPTIME_MS = 6e4;
|
|
22614
|
+
/**
|
|
22615
|
+
* Max time a NON-crash restart waits for an in-flight chat turn to finish
|
|
22616
|
+
* before it forces the stop anyway. A turn that outlives this is almost
|
|
22617
|
+
* certainly wedged; a real reply streams in far less. Long enough to cover a
|
|
22618
|
+
* normal multi-tool turn, short enough that a stuck turn doesn't block a
|
|
22619
|
+
* platform-pushed `daemon.update` indefinitely.
|
|
22620
|
+
*/
|
|
22621
|
+
const DRAIN_GRACE_MS = 45e3;
|
|
22595
22622
|
var RuntimeProcess = class {
|
|
22596
22623
|
child = null;
|
|
22597
22624
|
stopped = false;
|
|
@@ -22602,10 +22629,20 @@ var RuntimeProcess = class {
|
|
|
22602
22629
|
detector = new ErrorLineDetector();
|
|
22603
22630
|
throttle = new CaptureThrottle(STABLE_UPTIME_MS);
|
|
22604
22631
|
detectorFlushTimer = null;
|
|
22632
|
+
turnActivityProbe = null;
|
|
22605
22633
|
constructor(options) {
|
|
22606
22634
|
this.options = options;
|
|
22607
22635
|
}
|
|
22608
22636
|
/**
|
|
22637
|
+
* Attach the turn-activity probe used by drained restarts. The daemon calls
|
|
22638
|
+
* this after the IPC server is up (plugins register there). Without it,
|
|
22639
|
+
* {@link stopWhenIdle} and drained {@link restart} fall back to an immediate
|
|
22640
|
+
* stop — safe, just not turn-aware.
|
|
22641
|
+
*/
|
|
22642
|
+
setTurnActivityProbe(probe) {
|
|
22643
|
+
this.turnActivityProbe = probe;
|
|
22644
|
+
}
|
|
22645
|
+
/**
|
|
22609
22646
|
* Resolve the binary command and args for a given runtime type.
|
|
22610
22647
|
*/
|
|
22611
22648
|
resolveCommand() {
|
|
@@ -22632,7 +22669,7 @@ var RuntimeProcess = class {
|
|
|
22632
22669
|
if (this.stopped) return;
|
|
22633
22670
|
const { command, args } = this.resolveCommand();
|
|
22634
22671
|
this.lastStartTime = Date.now();
|
|
22635
|
-
log$
|
|
22672
|
+
log$3.info({
|
|
22636
22673
|
runtime: this.options.runtime,
|
|
22637
22674
|
command,
|
|
22638
22675
|
args,
|
|
@@ -22653,7 +22690,7 @@ var RuntimeProcess = class {
|
|
|
22653
22690
|
this.child.stdout?.on("data", (data) => {
|
|
22654
22691
|
const lines = data.toString().trim().split("\n");
|
|
22655
22692
|
for (const line of lines) {
|
|
22656
|
-
log$
|
|
22693
|
+
log$3.info({
|
|
22657
22694
|
runtime: this.options.runtime,
|
|
22658
22695
|
stream: "stdout"
|
|
22659
22696
|
}, line);
|
|
@@ -22663,7 +22700,7 @@ var RuntimeProcess = class {
|
|
|
22663
22700
|
this.child.stderr?.on("data", (data) => {
|
|
22664
22701
|
const lines = data.toString().trim().split("\n");
|
|
22665
22702
|
for (const line of lines) {
|
|
22666
|
-
log$
|
|
22703
|
+
log$3.warn({
|
|
22667
22704
|
runtime: this.options.runtime,
|
|
22668
22705
|
stream: "stderr"
|
|
22669
22706
|
}, line);
|
|
@@ -22673,7 +22710,7 @@ var RuntimeProcess = class {
|
|
|
22673
22710
|
this.child.on("exit", (code, signal) => {
|
|
22674
22711
|
this.child = null;
|
|
22675
22712
|
if (this.stopped) {
|
|
22676
|
-
log$
|
|
22713
|
+
log$3.info({
|
|
22677
22714
|
runtime: this.options.runtime,
|
|
22678
22715
|
code,
|
|
22679
22716
|
signal
|
|
@@ -22681,7 +22718,7 @@ var RuntimeProcess = class {
|
|
|
22681
22718
|
return;
|
|
22682
22719
|
}
|
|
22683
22720
|
if (code === 0 && signal == null) {
|
|
22684
|
-
log$
|
|
22721
|
+
log$3.info({
|
|
22685
22722
|
runtime: this.options.runtime,
|
|
22686
22723
|
code
|
|
22687
22724
|
}, "Runtime exited gracefully — restarting");
|
|
@@ -22691,7 +22728,7 @@ var RuntimeProcess = class {
|
|
|
22691
22728
|
}, 500);
|
|
22692
22729
|
return;
|
|
22693
22730
|
}
|
|
22694
|
-
log$
|
|
22731
|
+
log$3.warn({
|
|
22695
22732
|
runtime: this.options.runtime,
|
|
22696
22733
|
code,
|
|
22697
22734
|
signal,
|
|
@@ -22712,7 +22749,7 @@ var RuntimeProcess = class {
|
|
|
22712
22749
|
recentOutput: this.ringBuffer.snapshot(),
|
|
22713
22750
|
crashesSuppressed: crash.suppressedCount
|
|
22714
22751
|
});
|
|
22715
|
-
else log$
|
|
22752
|
+
else log$3.debug({
|
|
22716
22753
|
runtime: this.options.runtime,
|
|
22717
22754
|
suppressed: crash.suppressedCount
|
|
22718
22755
|
}, "Crash capture suppressed by throttle");
|
|
@@ -22724,7 +22761,7 @@ var RuntimeProcess = class {
|
|
|
22724
22761
|
this.backoffMs = Math.min(this.backoffMs * 2, BACKOFF_MAX_MS);
|
|
22725
22762
|
});
|
|
22726
22763
|
this.child.on("error", (err) => {
|
|
22727
|
-
log$
|
|
22764
|
+
log$3.error({
|
|
22728
22765
|
runtime: this.options.runtime,
|
|
22729
22766
|
err: err.message
|
|
22730
22767
|
}, "Runtime process error");
|
|
@@ -22763,7 +22800,7 @@ var RuntimeProcess = class {
|
|
|
22763
22800
|
this.emitErrorBlock(this.detector.flush());
|
|
22764
22801
|
}, DETECTOR_FLUSH_DEBOUNCE_MS);
|
|
22765
22802
|
} catch (err) {
|
|
22766
|
-
log$
|
|
22803
|
+
log$3.debug({
|
|
22767
22804
|
runtime: this.options.runtime,
|
|
22768
22805
|
err: err instanceof Error ? err.message : String(err)
|
|
22769
22806
|
}, "Runtime output observation failed");
|
|
@@ -22774,7 +22811,7 @@ var RuntimeProcess = class {
|
|
|
22774
22811
|
if (!block) return;
|
|
22775
22812
|
const { allow, suppressedCount } = this.throttle.allowErrorCapture(block.fingerprintKey);
|
|
22776
22813
|
if (!allow) {
|
|
22777
|
-
log$
|
|
22814
|
+
log$3.debug({
|
|
22778
22815
|
runtime: this.options.runtime,
|
|
22779
22816
|
kind: block.kind,
|
|
22780
22817
|
suppressed: suppressedCount
|
|
@@ -22808,7 +22845,7 @@ var RuntimeProcess = class {
|
|
|
22808
22845
|
if (!child) return;
|
|
22809
22846
|
return new Promise((resolve) => {
|
|
22810
22847
|
const killTimer = setTimeout(() => {
|
|
22811
|
-
log$
|
|
22848
|
+
log$3.warn({ runtime: this.options.runtime }, "Runtime did not exit in time — sending SIGKILL");
|
|
22812
22849
|
child.kill("SIGKILL");
|
|
22813
22850
|
}, 5e3);
|
|
22814
22851
|
child.on("exit", () => {
|
|
@@ -22819,6 +22856,54 @@ var RuntimeProcess = class {
|
|
|
22819
22856
|
});
|
|
22820
22857
|
}
|
|
22821
22858
|
/**
|
|
22859
|
+
* Stop the runtime, but first wait for any in-flight chat turn to finish
|
|
22860
|
+
* (up to a grace deadline). This is the entry point for NON-crash restarts —
|
|
22861
|
+
* a mid-turn SIGTERM turns a healthy reply into an "unknown error" for the
|
|
22862
|
+
* user, so a planned stop drains first.
|
|
22863
|
+
*
|
|
22864
|
+
* Semantics:
|
|
22865
|
+
* - No probe attached, or probe reports idle up-front → stops immediately
|
|
22866
|
+
* (no added latency for the common case).
|
|
22867
|
+
* - A turn is in flight → polls the probe every {@link DRAIN_POLL_INTERVAL_MS}
|
|
22868
|
+
* until it reports idle, then stops.
|
|
22869
|
+
* - The turn never ends within `graceMs` → logs and forces the stop anyway
|
|
22870
|
+
* (a wedged turn must not block a platform-pushed restart forever).
|
|
22871
|
+
*
|
|
22872
|
+
* Crash restarts must NOT call this — the child is already dead/broken and
|
|
22873
|
+
* the probe would just burn the grace window. They keep using {@link stop}.
|
|
22874
|
+
*/
|
|
22875
|
+
async stopWhenIdle(opts) {
|
|
22876
|
+
const probe = opts?.probe ?? this.turnActivityProbe;
|
|
22877
|
+
const graceMs = opts?.graceMs ?? 45e3;
|
|
22878
|
+
if (!this.child || !probe) {
|
|
22879
|
+
await this.stop();
|
|
22880
|
+
return;
|
|
22881
|
+
}
|
|
22882
|
+
const deadline = Date.now() + graceMs;
|
|
22883
|
+
let deferred = false;
|
|
22884
|
+
for (;;) {
|
|
22885
|
+
if (!await probe.isTurnActive()) break;
|
|
22886
|
+
if (Date.now() >= deadline) {
|
|
22887
|
+
log$3.warn({
|
|
22888
|
+
runtime: this.options.runtime,
|
|
22889
|
+
graceMs
|
|
22890
|
+
}, "Runtime still mid-turn at drain grace deadline — forcing stop");
|
|
22891
|
+
break;
|
|
22892
|
+
}
|
|
22893
|
+
if (!deferred) {
|
|
22894
|
+
deferred = true;
|
|
22895
|
+
log$3.info({
|
|
22896
|
+
runtime: this.options.runtime,
|
|
22897
|
+
graceMs
|
|
22898
|
+
}, "Runtime is mid-turn — deferring planned stop until idle");
|
|
22899
|
+
}
|
|
22900
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
22901
|
+
if (!this.isRunning) return;
|
|
22902
|
+
}
|
|
22903
|
+
if (deferred) log$3.info({ runtime: this.options.runtime }, "Runtime idle — proceeding with planned stop");
|
|
22904
|
+
await this.stop();
|
|
22905
|
+
}
|
|
22906
|
+
/**
|
|
22822
22907
|
* Resume after a suspend (stop). `start()` alone is a no-op while stopped
|
|
22823
22908
|
* because `stop()` latches `this.stopped = true`; resume clears that latch,
|
|
22824
22909
|
* resets the crash backoff, and re-spawns. Used by the RuntimeGate to bring
|
|
@@ -22831,10 +22916,20 @@ var RuntimeProcess = class {
|
|
|
22831
22916
|
}
|
|
22832
22917
|
/**
|
|
22833
22918
|
* Restart the runtime process (stop then start with fresh backoff).
|
|
22919
|
+
*
|
|
22920
|
+
* This is a PLANNED restart (`runtime.restart`, post-upgrade cycle, gate
|
|
22921
|
+
* request), so by default it drains any in-flight chat turn before stopping
|
|
22922
|
+
* — see {@link stopWhenIdle}. Pass `{ drain: false }` to force an immediate
|
|
22923
|
+
* stop (e.g. when the caller has already established the child is unhealthy).
|
|
22834
22924
|
*/
|
|
22835
|
-
async restart() {
|
|
22836
|
-
|
|
22837
|
-
|
|
22925
|
+
async restart(opts) {
|
|
22926
|
+
const drain = opts?.drain ?? true;
|
|
22927
|
+
log$3.info({
|
|
22928
|
+
runtime: this.options.runtime,
|
|
22929
|
+
drain
|
|
22930
|
+
}, "Restarting runtime...");
|
|
22931
|
+
if (drain) await this.stopWhenIdle();
|
|
22932
|
+
else await this.stop();
|
|
22838
22933
|
this.stopped = false;
|
|
22839
22934
|
this.backoffMs = BACKOFF_INITIAL_MS;
|
|
22840
22935
|
this.start();
|
|
@@ -22847,6 +22942,78 @@ var RuntimeProcess = class {
|
|
|
22847
22942
|
}
|
|
22848
22943
|
};
|
|
22849
22944
|
//#endregion
|
|
22945
|
+
//#region src/turn-activity.ts
|
|
22946
|
+
/**
|
|
22947
|
+
* Turn-activity probe — lets a NON-crash runtime restart wait for the runtime
|
|
22948
|
+
* to finish any in-flight chat turn before the child is SIGTERM'd.
|
|
22949
|
+
*
|
|
22950
|
+
* ## Why this exists
|
|
22951
|
+
*
|
|
22952
|
+
* Every non-crash restart path (`runtime.restart`, `daemon.update`,
|
|
22953
|
+
* `daemon.restart`, the RuntimeGate resume after a mutating reconcile) used to
|
|
22954
|
+
* route straight into {@link RuntimeProcess.stop}, which is SIGTERM →
|
|
22955
|
+
* SIGKILL-after-5s with zero turn-awareness. A platform-pushed `daemon.update`
|
|
22956
|
+
* that landed mid-turn killed the runtime child while it was streaming a reply,
|
|
22957
|
+
* and the user saw an "unknown error" for a turn that was otherwise healthy.
|
|
22958
|
+
*
|
|
22959
|
+
* The fix is a small "is a turn active?" signal that the daemon can consult
|
|
22960
|
+
* before a *planned* stop, deferring the SIGTERM until the runtime reports idle
|
|
22961
|
+
* (or a grace deadline elapses). Crash restarts do NOT consult it — the child is
|
|
22962
|
+
* already dead or wedged, so there is nothing to drain.
|
|
22963
|
+
*
|
|
22964
|
+
* ## The runtime-side contract (the boundary)
|
|
22965
|
+
*
|
|
22966
|
+
* The runtime's activity is a property of the chat plugin's active-turn state,
|
|
22967
|
+
* which lives INSIDE the runtime child (e.g. `openclaw-chat`'s per-turn run
|
|
22968
|
+
* gate). The daemon cannot see it directly. The daemon reaches it over the same
|
|
22969
|
+
* IPC socket the plugin already uses to register:
|
|
22970
|
+
*
|
|
22971
|
+
* daemon → plugin request: { method: 'runtime.activity' }
|
|
22972
|
+
* plugin → daemon response: { ok: true, payload: { active: boolean } }
|
|
22973
|
+
*
|
|
22974
|
+
* `runtime.activity` is on the daemon→plugin method allowlist (see
|
|
22975
|
+
* protocol.ts). A plugin answers `active: true` while it is dispatching a turn
|
|
22976
|
+
* and `active: false` otherwise. This is the ONE thing the runtime must provide;
|
|
22977
|
+
* everything else in the drain lives on the gateway side.
|
|
22978
|
+
*
|
|
22979
|
+
* ## Fail-open to idle
|
|
22980
|
+
*
|
|
22981
|
+
* If NO connected plugin answers `runtime.activity` (older plugin build, the
|
|
22982
|
+
* chat plugin isn't loaded, the request times out), the probe reports **idle**.
|
|
22983
|
+
* A planned restart must never hang forever waiting on a signal that will never
|
|
22984
|
+
* arrive — the grace deadline is a backstop, but fail-open-to-idle means the
|
|
22985
|
+
* common "nothing to drain" case restarts immediately with no added latency.
|
|
22986
|
+
*/
|
|
22987
|
+
const log$2 = logger$1.child({ component: "TurnActivity" });
|
|
22988
|
+
/** The IPC method the daemon calls on plugins to ask "is a turn active?". */
|
|
22989
|
+
const RUNTIME_ACTIVITY_METHOD = "runtime.activity";
|
|
22990
|
+
/**
|
|
22991
|
+
* IPC-backed probe. Broadcasts `runtime.activity` to every registered plugin
|
|
22992
|
+
* and reports active if ANY plugin claims an in-flight turn. Fails open to idle
|
|
22993
|
+
* on any error / timeout / non-answer so a planned restart is never wedged.
|
|
22994
|
+
*/
|
|
22995
|
+
var IpcTurnActivityProbe = class {
|
|
22996
|
+
constructor(getIpcServer, probeTimeoutMs = 2e3) {
|
|
22997
|
+
this.getIpcServer = getIpcServer;
|
|
22998
|
+
this.probeTimeoutMs = probeTimeoutMs;
|
|
22999
|
+
}
|
|
23000
|
+
async isTurnActive() {
|
|
23001
|
+
const ipc = this.getIpcServer();
|
|
23002
|
+
if (!ipc || ipc.registeredCount === 0) return false;
|
|
23003
|
+
try {
|
|
23004
|
+
const responses = await ipc.broadcastRequest(RUNTIME_ACTIVITY_METHOD, {}, this.probeTimeoutMs);
|
|
23005
|
+
for (const [pluginId, res] of responses) if (res.ok && res.payload != null && typeof res.payload === "object" && res.payload.active === true) {
|
|
23006
|
+
log$2.debug({ pluginId }, "Plugin reports an in-flight turn");
|
|
23007
|
+
return true;
|
|
23008
|
+
}
|
|
23009
|
+
return false;
|
|
23010
|
+
} catch (err) {
|
|
23011
|
+
log$2.debug({ err: err instanceof Error ? err.message : String(err) }, "Turn-activity probe failed — assuming idle");
|
|
23012
|
+
return false;
|
|
23013
|
+
}
|
|
23014
|
+
}
|
|
23015
|
+
};
|
|
23016
|
+
//#endregion
|
|
22850
23017
|
//#region src/command-registry.ts
|
|
22851
23018
|
/**
|
|
22852
23019
|
* Command Registry — maps integration command names to local handler files.
|
|
@@ -23477,6 +23644,7 @@ let mcpManagerRef = null;
|
|
|
23477
23644
|
let hermesMcpSync = null;
|
|
23478
23645
|
let aiProxyServer = null;
|
|
23479
23646
|
let runtimeProcess = null;
|
|
23647
|
+
let turnActivityProbe = null;
|
|
23480
23648
|
let aiProxyUrl = null;
|
|
23481
23649
|
let aiProxyRunning = false;
|
|
23482
23650
|
let cloudConnected = false;
|
|
@@ -23770,6 +23938,7 @@ async function startDaemon() {
|
|
|
23770
23938
|
logger$1.debug({ socketPath: config.socketPath }, "Starting IPC server...");
|
|
23771
23939
|
ipcServer = new IPCServer(config.socketPath);
|
|
23772
23940
|
ipcServer.setRequestHandler(handlePluginRequest);
|
|
23941
|
+
turnActivityProbe = new IpcTurnActivityProbe(() => ipcServer);
|
|
23773
23942
|
try {
|
|
23774
23943
|
await ipcServer.start();
|
|
23775
23944
|
logger$1.debug("IPC server started");
|
|
@@ -23933,6 +24102,7 @@ async function startDaemon() {
|
|
|
23933
24102
|
workspace: runtimeCfg.workspace,
|
|
23934
24103
|
env: {}
|
|
23935
24104
|
});
|
|
24105
|
+
runtimeProcess.setTurnActivityProbe(turnActivityProbe);
|
|
23936
24106
|
runtimeProcess.start();
|
|
23937
24107
|
logger$1.debug("Runtime process started");
|
|
23938
24108
|
if (config.runtime === "openclaw") {
|
|
@@ -24038,11 +24208,46 @@ async function handleCloudCommand(command) {
|
|
|
24038
24208
|
}
|
|
24039
24209
|
return ack;
|
|
24040
24210
|
}
|
|
24211
|
+
/**
|
|
24212
|
+
* Wait for any in-flight chat turn to finish before a daemon PROCESS exit
|
|
24213
|
+
* (daemon.update / daemon.restart). Those paths don't stop the runtime via
|
|
24214
|
+
* `RuntimeProcess.stop` — they exit the daemon, and systemd/launchd cgroup-kill
|
|
24215
|
+
* the runtime child underneath. That kill is just as turn-destroying as a
|
|
24216
|
+
* SIGTERM, so we drain here too. Grace-deadline backstopped; fails open to
|
|
24217
|
+
* "proceed" so a wedged turn can't strand a platform-pushed update.
|
|
24218
|
+
*/
|
|
24219
|
+
async function drainRuntimeBeforeExit(reason) {
|
|
24220
|
+
const probe = turnActivityProbe;
|
|
24221
|
+
if (!probe || !runtimeProcess?.isRunning) return;
|
|
24222
|
+
const deadline = Date.now() + DRAIN_GRACE_MS;
|
|
24223
|
+
let deferred = false;
|
|
24224
|
+
while (Date.now() < deadline) {
|
|
24225
|
+
let active;
|
|
24226
|
+
try {
|
|
24227
|
+
active = await probe.isTurnActive();
|
|
24228
|
+
} catch {
|
|
24229
|
+
return;
|
|
24230
|
+
}
|
|
24231
|
+
if (!active) {
|
|
24232
|
+
if (deferred) logger$1.info({ reason }, "Runtime idle — proceeding with planned daemon exit");
|
|
24233
|
+
return;
|
|
24234
|
+
}
|
|
24235
|
+
if (!deferred) {
|
|
24236
|
+
deferred = true;
|
|
24237
|
+
logger$1.info({ reason }, "Runtime is mid-turn — deferring planned daemon exit until idle");
|
|
24238
|
+
}
|
|
24239
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
24240
|
+
}
|
|
24241
|
+
logger$1.warn({
|
|
24242
|
+
reason,
|
|
24243
|
+
graceMs: DRAIN_GRACE_MS
|
|
24244
|
+
}, "Runtime still mid-turn at drain grace deadline — proceeding with daemon exit");
|
|
24245
|
+
}
|
|
24041
24246
|
async function executeCloudCommand(command) {
|
|
24042
24247
|
if (command.command === "daemon.update") {
|
|
24043
24248
|
const version = command.payload?.version ?? "latest";
|
|
24044
24249
|
setTimeout(() => {
|
|
24045
|
-
import("./upgrade.js").then(({ upgradeAndExit }) => upgradeAndExit(version)).catch((err) => {
|
|
24250
|
+
drainRuntimeBeforeExit("daemon.update").then(() => import("./upgrade.js")).then(({ upgradeAndExit }) => upgradeAndExit(version)).catch((err) => {
|
|
24046
24251
|
logger$1.error({ err: err instanceof Error ? err.message : String(err) }, "Upgrade failed");
|
|
24047
24252
|
process.exit(1);
|
|
24048
24253
|
});
|
|
@@ -24060,7 +24265,9 @@ async function executeCloudCommand(command) {
|
|
|
24060
24265
|
if (command.command === "daemon.restart") {
|
|
24061
24266
|
logger$1.info("Restart requested — exiting for systemd restart");
|
|
24062
24267
|
setTimeout(() => {
|
|
24063
|
-
|
|
24268
|
+
drainRuntimeBeforeExit("daemon.restart").then(() => {
|
|
24269
|
+
process.exit(0);
|
|
24270
|
+
});
|
|
24064
24271
|
}, 500);
|
|
24065
24272
|
return {
|
|
24066
24273
|
type: "COMMAND_ACK",
|
|
@@ -24437,17 +24644,41 @@ function handleMcpListServers(manager = mcpManagerRef) {
|
|
|
24437
24644
|
message: "MCP manager not initialized"
|
|
24438
24645
|
}
|
|
24439
24646
|
};
|
|
24647
|
+
const statuses = manager.serverStatuses ? manager.serverStatuses() : [];
|
|
24648
|
+
const byName = new Map(statuses.map((s) => [s.name, s]));
|
|
24440
24649
|
return {
|
|
24441
24650
|
ok: true,
|
|
24442
|
-
payload: { servers: manager.listServers() }
|
|
24651
|
+
payload: { servers: manager.listServers().map(({ id, entry }) => ({
|
|
24652
|
+
id,
|
|
24653
|
+
entry,
|
|
24654
|
+
status: byName.get(id) ?? {
|
|
24655
|
+
name: id,
|
|
24656
|
+
connected: false,
|
|
24657
|
+
toolCount: 0,
|
|
24658
|
+
consecutiveFailures: 0
|
|
24659
|
+
}
|
|
24660
|
+
})) }
|
|
24443
24661
|
};
|
|
24444
24662
|
}
|
|
24445
24663
|
/**
|
|
24446
|
-
*
|
|
24447
|
-
*
|
|
24448
|
-
*
|
|
24449
|
-
*
|
|
24450
|
-
*
|
|
24664
|
+
* How long the add-confirm probe waits for the freshly-added server to
|
|
24665
|
+
* connect before replying. This is effectively the whole budget for
|
|
24666
|
+
* `bundler.warmServer`: the `Manager.warmServer` pre-reconcile is cheap here
|
|
24667
|
+
* because `addServer` already queued the Connection object into the store +
|
|
24668
|
+
* bundler before we probe, so `reconcileBundler()` finds no diff and returns
|
|
24669
|
+
* fast. Kept well under the plugin caller's 30s IPC timeout. A slower server
|
|
24670
|
+
* still connects in the background and surfaces on the next
|
|
24671
|
+
* `alfe_mcp_list_tools` — the probe just reports what it saw within the window.
|
|
24672
|
+
*/
|
|
24673
|
+
const MCP_ADD_WARM_TIMEOUT_MS = 12e3;
|
|
24674
|
+
/**
|
|
24675
|
+
* Register a new MCP server in the alfe bundler store on behalf of the agent,
|
|
24676
|
+
* then CONFIRM the connect before replying so the agent learns whether the
|
|
24677
|
+
* server actually works. Owned as `'manual'` so the agent can later remove it
|
|
24678
|
+
* without an expectedOwner conflict — matches what `alfe mcp add` does from the
|
|
24679
|
+
* CLI. Registration is durable regardless of the probe outcome: a probe
|
|
24680
|
+
* failure (or a runtime with no daemon bundler, e.g. hermes) still returns
|
|
24681
|
+
* `ok` with `connected: false`; the store watcher / runtime picks the entry up.
|
|
24451
24682
|
*/
|
|
24452
24683
|
async function handleMcpAddServer(params, manager = mcpManagerRef) {
|
|
24453
24684
|
if (!manager) return {
|
|
@@ -24478,10 +24709,6 @@ async function handleMcpAddServer(params, manager = mcpManagerRef) {
|
|
|
24478
24709
|
id: p.id,
|
|
24479
24710
|
owner: "manual"
|
|
24480
24711
|
});
|
|
24481
|
-
return {
|
|
24482
|
-
ok: true,
|
|
24483
|
-
payload: { id: p.id }
|
|
24484
|
-
};
|
|
24485
24712
|
} catch (err) {
|
|
24486
24713
|
const message = err instanceof Error ? err.message : String(err);
|
|
24487
24714
|
logger$1.warn({
|
|
@@ -24496,6 +24723,24 @@ async function handleMcpAddServer(params, manager = mcpManagerRef) {
|
|
|
24496
24723
|
}
|
|
24497
24724
|
};
|
|
24498
24725
|
}
|
|
24726
|
+
let status = null;
|
|
24727
|
+
if (manager.warmServer) try {
|
|
24728
|
+
status = await manager.warmServer(p.id, MCP_ADD_WARM_TIMEOUT_MS);
|
|
24729
|
+
} catch (err) {
|
|
24730
|
+
logger$1.warn({
|
|
24731
|
+
id: p.id,
|
|
24732
|
+
err: err instanceof Error ? err.message : String(err)
|
|
24733
|
+
}, "mcp.add_server warm probe threw");
|
|
24734
|
+
}
|
|
24735
|
+
return {
|
|
24736
|
+
ok: true,
|
|
24737
|
+
payload: {
|
|
24738
|
+
id: p.id,
|
|
24739
|
+
connected: status?.connected ?? false,
|
|
24740
|
+
toolCount: status?.toolCount ?? 0,
|
|
24741
|
+
...status?.lastError !== void 0 ? { error: status.lastError } : {}
|
|
24742
|
+
}
|
|
24743
|
+
};
|
|
24499
24744
|
}
|
|
24500
24745
|
function buildServerConfig(p) {
|
|
24501
24746
|
if (typeof p.command === "string" && p.command.length > 0) {
|
package/dist/runtime-upgrade.js
CHANGED
|
@@ -67,7 +67,7 @@ async function upgradeRuntime(runtime, version, runtimeProcess) {
|
|
|
67
67
|
}, "Upgrading runtime...");
|
|
68
68
|
if (runtimeProcess) {
|
|
69
69
|
logger.info("Stopping runtime for upgrade...");
|
|
70
|
-
await runtimeProcess.
|
|
70
|
+
await runtimeProcess.stopWhenIdle();
|
|
71
71
|
}
|
|
72
72
|
try {
|
|
73
73
|
const { stdout, stderr } = await execFileAsync(cmd.command, cmd.args, { timeout: 12e4 });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/gateway",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
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.
|
|
26
|
+
"@alfe.ai/agent-api-client": "^0.10.0",
|
|
27
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.4.
|
|
31
|
-
"@alfe.ai/mcp-bundler": "^0.3.
|
|
30
|
+
"@alfe.ai/integrations": "^0.4.2",
|
|
31
|
+
"@alfe.ai/mcp-bundler": "^0.3.1"
|
|
32
32
|
},
|
|
33
33
|
"license": "UNLICENSED",
|
|
34
34
|
"homepage": "https://alfe.ai",
|