@alfe.ai/gateway 0.10.5 → 0.10.6
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 +355 -98
- package/dist/runtime-process.js +60 -4
- package/package.json +2 -2
package/dist/health.js
CHANGED
|
@@ -4349,6 +4349,7 @@ function isClerkReverificationResponse(value) {
|
|
|
4349
4349
|
//#endregion
|
|
4350
4350
|
//#region ../../packages-internal/types/dist/models.js
|
|
4351
4351
|
const AnthropicModel = {
|
|
4352
|
+
Opus55: "claude-opus-5-5",
|
|
4352
4353
|
Opus5: "claude-opus-5",
|
|
4353
4354
|
Opus48: "claude-opus-4-8",
|
|
4354
4355
|
Opus47: "claude-opus-4-7",
|
|
@@ -4359,6 +4360,7 @@ const AnthropicModel = {
|
|
|
4359
4360
|
};
|
|
4360
4361
|
const ANTHROPIC_MODELS = enumValues(AnthropicModel);
|
|
4361
4362
|
const OpenAIModel = {
|
|
4363
|
+
GPT6Astra: "gpt-6-astra",
|
|
4362
4364
|
GPT4o: "gpt-4o",
|
|
4363
4365
|
GPT4oMini: "gpt-4o-mini",
|
|
4364
4366
|
O3: "o3",
|
|
@@ -4381,20 +4383,22 @@ const OpenAIModel = {
|
|
|
4381
4383
|
};
|
|
4382
4384
|
const OPENAI_MODELS = enumValues(OpenAIModel);
|
|
4383
4385
|
const DeepSeekModel = {
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
V4Flash: "deepseek-v4-flash"
|
|
4387
|
-
V4Pro: "deepseek-v4-pro"
|
|
4386
|
+
Flash: "deepseek-flash",
|
|
4387
|
+
V4Pro: "deepseek-v4-pro",
|
|
4388
|
+
V4Flash: "deepseek-v4-flash"
|
|
4388
4389
|
};
|
|
4389
4390
|
const DEEPSEEK_MODELS = enumValues(DeepSeekModel);
|
|
4390
4391
|
const GoogleModel = {
|
|
4392
|
+
Gemini38Flash: "gemini-3.8-flash",
|
|
4393
|
+
Gemini37Flash: "gemini-3.7-flash",
|
|
4394
|
+
Gemini36Flash: "gemini-3.6-flash",
|
|
4391
4395
|
Gemini35Flash: "gemini-3.5-flash",
|
|
4396
|
+
Gemini35FlashLite: "gemini-3.5-flash-lite",
|
|
4392
4397
|
Gemini31Pro: "gemini-3.1-pro-preview",
|
|
4393
4398
|
Gemini31FlashLite: "gemini-3.1-flash-lite",
|
|
4394
4399
|
Gemini25Pro: "gemini-2.5-pro",
|
|
4395
4400
|
Gemini25Flash: "gemini-2.5-flash",
|
|
4396
|
-
Gemini25FlashLite: "gemini-2.5-flash-lite"
|
|
4397
|
-
Gemini20Flash: "gemini-2.0-flash"
|
|
4401
|
+
Gemini25FlashLite: "gemini-2.5-flash-lite"
|
|
4398
4402
|
};
|
|
4399
4403
|
const GOOGLE_MODELS = enumValues(GoogleModel);
|
|
4400
4404
|
const MiniMaxModel = {
|
|
@@ -4412,20 +4416,23 @@ const MistralModel = {
|
|
|
4412
4416
|
Small: "mistral-small-latest",
|
|
4413
4417
|
Codestral: "codestral-latest",
|
|
4414
4418
|
Ministral8b: "ministral-8b-latest",
|
|
4415
|
-
Ministral3b: "ministral-3b-latest"
|
|
4416
|
-
MagistralMedium: "magistral-medium-latest",
|
|
4417
|
-
MagistralSmall: "magistral-small-latest",
|
|
4418
|
-
DevstralMedium: "devstral-medium-latest"
|
|
4419
|
+
Ministral3b: "ministral-3b-latest"
|
|
4419
4420
|
};
|
|
4420
4421
|
const MISTRAL_MODELS = enumValues(MistralModel);
|
|
4421
4422
|
const XAIModel = {
|
|
4423
|
+
Grok47: "grok-4.7",
|
|
4424
|
+
Grok46: "grok-4.6",
|
|
4422
4425
|
Grok45: "grok-4.5",
|
|
4423
4426
|
Grok43: "grok-4.3",
|
|
4427
|
+
GrokBuild01: "grok-build-0.1",
|
|
4424
4428
|
Grok4: "grok-4",
|
|
4425
4429
|
Grok41Fast: "grok-4.1-fast"
|
|
4426
4430
|
};
|
|
4427
4431
|
const XAI_MODELS = enumValues(XAIModel);
|
|
4428
4432
|
const ZhipuModel = {
|
|
4433
|
+
GLM53: "glm-5.3",
|
|
4434
|
+
GLM53Flash: "glm-5.3-flash",
|
|
4435
|
+
GLM53FlashX: "glm-5.3-flashx",
|
|
4429
4436
|
GLM52: "glm-5.2",
|
|
4430
4437
|
GLM51: "glm-5.1",
|
|
4431
4438
|
GLM46: "glm-4.6",
|
|
@@ -4441,6 +4448,8 @@ const MoonshotModel = {
|
|
|
4441
4448
|
};
|
|
4442
4449
|
const MOONSHOT_MODELS = enumValues(MoonshotModel);
|
|
4443
4450
|
const QwenModel = {
|
|
4451
|
+
Qwen38Max: "qwen3.8-max",
|
|
4452
|
+
Qwen38Flash: "qwen3.8-flash",
|
|
4444
4453
|
Qwen37Max: "qwen3.7-max",
|
|
4445
4454
|
Qwen37Plus: "qwen3.7-plus",
|
|
4446
4455
|
Qwen36Flash: "qwen3.6-flash",
|
|
@@ -4471,7 +4480,7 @@ _enum(ZHIPU_MODELS);
|
|
|
4471
4480
|
_enum(MOONSHOT_MODELS);
|
|
4472
4481
|
_enum(QWEN_MODELS);
|
|
4473
4482
|
string().trim().min(1);
|
|
4474
|
-
AnthropicModel.Opus5, AnthropicModel.Opus48, AnthropicModel.Opus47, AnthropicModel.Opus46, AnthropicModel.Sonnet5, AnthropicModel.Sonnet46, AnthropicModel.Haiku45, OpenAIModel.GPT4o, OpenAIModel.GPT4oMini, OpenAIModel.O3, OpenAIModel.GPT41, OpenAIModel.GPT41Mini, OpenAIModel.GPT41Nano, OpenAIModel.GPT54, OpenAIModel.GPT54Mini, OpenAIModel.GPT54Nano, OpenAIModel.GPT54Pro, OpenAIModel.GPT55, OpenAIModel.GPT55Pro, OpenAIModel.GPT56Sol, OpenAIModel.GPT56Terra, OpenAIModel.GPT56Luna, OpenAIModel.O3Mini, OpenAIModel.O4Mini, OpenAIModel.TextEmbedding3Small, OpenAIModel.TextEmbedding3Large, DeepSeekModel.
|
|
4483
|
+
AnthropicModel.Opus55, AnthropicModel.Opus5, AnthropicModel.Opus48, AnthropicModel.Opus47, AnthropicModel.Opus46, AnthropicModel.Sonnet5, AnthropicModel.Sonnet46, AnthropicModel.Haiku45, OpenAIModel.GPT6Astra, OpenAIModel.GPT4o, OpenAIModel.GPT4oMini, OpenAIModel.O3, OpenAIModel.GPT41, OpenAIModel.GPT41Mini, OpenAIModel.GPT41Nano, OpenAIModel.GPT54, OpenAIModel.GPT54Mini, OpenAIModel.GPT54Nano, OpenAIModel.GPT54Pro, OpenAIModel.GPT55, OpenAIModel.GPT55Pro, OpenAIModel.GPT56Sol, OpenAIModel.GPT56Terra, OpenAIModel.GPT56Luna, OpenAIModel.O3Mini, OpenAIModel.O4Mini, OpenAIModel.TextEmbedding3Small, OpenAIModel.TextEmbedding3Large, DeepSeekModel.Flash, DeepSeekModel.V4Pro, DeepSeekModel.V4Flash, GoogleModel.Gemini38Flash, GoogleModel.Gemini37Flash, GoogleModel.Gemini36Flash, GoogleModel.Gemini35Flash, GoogleModel.Gemini35FlashLite, GoogleModel.Gemini31Pro, GoogleModel.Gemini31FlashLite, GoogleModel.Gemini25Pro, GoogleModel.Gemini25Flash, GoogleModel.Gemini25FlashLite, MiniMaxModel.M3, MiniMaxModel.M27, MiniMaxModel.M27HighSpeed, MiniMaxModel.M25, MiniMaxModel.M21, MiniMaxModel.M2, MistralModel.Large, MistralModel.Medium, MistralModel.Small, MistralModel.Codestral, MistralModel.Ministral8b, MistralModel.Ministral3b, XAIModel.Grok47, XAIModel.Grok46, XAIModel.Grok45, XAIModel.Grok43, XAIModel.GrokBuild01, XAIModel.Grok4, XAIModel.Grok41Fast, ZhipuModel.GLM53, ZhipuModel.GLM53Flash, ZhipuModel.GLM53FlashX, ZhipuModel.GLM52, ZhipuModel.GLM51, ZhipuModel.GLM46, ZhipuModel.GLM45, ZhipuModel.GLM45Air, MoonshotModel.K3, MoonshotModel.K26, MoonshotModel.K27Code, MoonshotModel.K27CodeHighSpeed, QwenModel.Qwen38Max, QwenModel.Qwen38Flash, QwenModel.Qwen37Max, QwenModel.Qwen37Plus, QwenModel.Qwen36Flash, QwenModel.Qwen35Flash;
|
|
4475
4484
|
/**
|
|
4476
4485
|
* Alfe-hosted open-weight models are promoted dynamically only in regions
|
|
4477
4486
|
* whose private data plane exists. Keep them out of ALL_MODELS so the standard
|
|
@@ -4531,7 +4540,7 @@ const AWS_PRIVATE_MODEL_PRICING = {
|
|
|
4531
4540
|
[BedrockModel.TitanEmbedTextV2]: { input: .026004 }
|
|
4532
4541
|
};
|
|
4533
4542
|
Object.fromEntries(AWS_PRIVATE_BEDROCK_CHAT_MODELS.map((modelId) => [`alfe-bedrock-${modelId}`, AWS_PRIVATE_MODEL_PRICING[modelId]]));
|
|
4534
|
-
AnthropicModel.Opus5, AnthropicModel.Opus48, AnthropicModel.Opus47, AnthropicModel.Opus46, AnthropicModel.Sonnet5, AnthropicModel.Sonnet46, AnthropicModel.Haiku45, OpenAIModel.GPT4o, OpenAIModel.GPT4oMini, OpenAIModel.O3, OpenAIModel.GPT41, OpenAIModel.GPT41Mini, OpenAIModel.GPT41Nano, OpenAIModel.GPT54, OpenAIModel.GPT54Mini, OpenAIModel.GPT54Nano, OpenAIModel.GPT54Pro, OpenAIModel.GPT55, OpenAIModel.GPT55Pro, OpenAIModel.GPT56Sol, OpenAIModel.GPT56Terra, OpenAIModel.GPT56Luna, OpenAIModel.O3Mini, OpenAIModel.O4Mini, OpenAIModel.TextEmbedding3Small, OpenAIModel.TextEmbedding3Large, DeepSeekModel.
|
|
4543
|
+
AnthropicModel.Opus55, AnthropicModel.Opus5, AnthropicModel.Opus48, AnthropicModel.Opus47, AnthropicModel.Opus46, AnthropicModel.Sonnet5, AnthropicModel.Sonnet46, AnthropicModel.Haiku45, OpenAIModel.GPT6Astra, OpenAIModel.GPT4o, OpenAIModel.GPT4oMini, OpenAIModel.O3, OpenAIModel.GPT41, OpenAIModel.GPT41Mini, OpenAIModel.GPT41Nano, OpenAIModel.GPT54, OpenAIModel.GPT54Mini, OpenAIModel.GPT54Nano, OpenAIModel.GPT54Pro, OpenAIModel.GPT55, OpenAIModel.GPT55Pro, OpenAIModel.GPT56Sol, OpenAIModel.GPT56Terra, OpenAIModel.GPT56Luna, OpenAIModel.O3Mini, OpenAIModel.O4Mini, OpenAIModel.TextEmbedding3Small, OpenAIModel.TextEmbedding3Large, DeepSeekModel.Flash, DeepSeekModel.V4Pro, DeepSeekModel.V4Flash, GoogleModel.Gemini38Flash, GoogleModel.Gemini37Flash, GoogleModel.Gemini36Flash, GoogleModel.Gemini35Flash, GoogleModel.Gemini35FlashLite, GoogleModel.Gemini31Pro, GoogleModel.Gemini31FlashLite, GoogleModel.Gemini25Pro, GoogleModel.Gemini25Flash, GoogleModel.Gemini25FlashLite, MiniMaxModel.M3, MiniMaxModel.M27, MiniMaxModel.M27HighSpeed, MiniMaxModel.M25, MiniMaxModel.M21, MiniMaxModel.M2, MistralModel.Large, MistralModel.Medium, MistralModel.Small, MistralModel.Codestral, MistralModel.Ministral8b, MistralModel.Ministral3b, XAIModel.Grok47, XAIModel.Grok46, XAIModel.Grok45, XAIModel.Grok43, XAIModel.GrokBuild01, XAIModel.Grok4, XAIModel.Grok41Fast, ZhipuModel.GLM53, ZhipuModel.GLM53Flash, ZhipuModel.GLM53FlashX, ZhipuModel.GLM52, ZhipuModel.GLM51, ZhipuModel.GLM46, ZhipuModel.GLM45, ZhipuModel.GLM45Air, MoonshotModel.K3, MoonshotModel.K26, MoonshotModel.K27Code, MoonshotModel.K27CodeHighSpeed, QwenModel.Qwen38Max, QwenModel.Qwen38Flash, QwenModel.Qwen37Max, QwenModel.Qwen37Plus, QwenModel.Qwen36Flash, QwenModel.Qwen35Flash;
|
|
4535
4544
|
enumValues({
|
|
4536
4545
|
Standard: "standard",
|
|
4537
4546
|
AwsPrivate: "aws_private"
|
|
@@ -4746,7 +4755,8 @@ var AlfeApiClient = class {
|
|
|
4746
4755
|
ok: false,
|
|
4747
4756
|
error: errorMessage ?? (typeof body === "string" ? body : `API error: ${String(res.status)}`),
|
|
4748
4757
|
status: res.status,
|
|
4749
|
-
...typeof errBody?.code === "string" ? { code: errBody.code } : {}
|
|
4758
|
+
...typeof errBody?.code === "string" ? { code: errBody.code } : {},
|
|
4759
|
+
...Array.isArray(errBody?.ineligibleUserIds) && errBody.ineligibleUserIds.every((id) => typeof id === "string") ? { ineligibleUserIds: [...errBody.ineligibleUserIds] } : {}
|
|
4750
4760
|
}
|
|
4751
4761
|
};
|
|
4752
4762
|
}
|
|
@@ -5854,13 +5864,17 @@ function isIPCResponse(msg) {
|
|
|
5854
5864
|
const PROTOCOL_VERSION = 1;
|
|
5855
5865
|
//#endregion
|
|
5856
5866
|
//#region src/runtime-gate.ts
|
|
5857
|
-
const log$
|
|
5867
|
+
const log$6 = logger.child({ component: "RuntimeGate" });
|
|
5858
5868
|
var RuntimeGate = class {
|
|
5859
5869
|
depth = 0;
|
|
5860
5870
|
wasRunning = false;
|
|
5861
5871
|
exclusive = false;
|
|
5862
5872
|
/** Brackets parked behind an exclusive hold (or an exclusive request parked behind open brackets). */
|
|
5863
5873
|
waiters = [];
|
|
5874
|
+
/** `suspendExclusive()` callers currently parked behind open brackets. */
|
|
5875
|
+
parkedExclusive = 0;
|
|
5876
|
+
/** The daemon is holding the runtime's FIRST start (fresh managed box); see {@link deferFirstStart}. */
|
|
5877
|
+
firstStartDeferred = false;
|
|
5864
5878
|
constructor(getRuntime) {
|
|
5865
5879
|
this.getRuntime = getRuntime;
|
|
5866
5880
|
}
|
|
@@ -5872,6 +5886,9 @@ var RuntimeGate = class {
|
|
|
5872
5886
|
get isExclusive() {
|
|
5873
5887
|
return this.exclusive;
|
|
5874
5888
|
}
|
|
5889
|
+
get hasParkedExclusive() {
|
|
5890
|
+
return this.parkedExclusive > 0;
|
|
5891
|
+
}
|
|
5875
5892
|
waitFor(ready) {
|
|
5876
5893
|
if (ready()) return Promise.resolve();
|
|
5877
5894
|
return new Promise((resolve) => {
|
|
@@ -5894,26 +5911,33 @@ var RuntimeGate = class {
|
|
|
5894
5911
|
* up (e.g. autoStartRuntime=false). Waits while an exclusive bracket is open.
|
|
5895
5912
|
*/
|
|
5896
5913
|
async suspend() {
|
|
5897
|
-
if (this.exclusive) log$
|
|
5914
|
+
if (this.exclusive) log$6.info("Mutating reconcile parked behind an exclusive runtime hold (upgrade in flight)");
|
|
5898
5915
|
while (this.exclusive) await this.waitFor(() => !this.exclusive);
|
|
5899
5916
|
await this.enter();
|
|
5900
5917
|
}
|
|
5901
5918
|
async suspendExclusive() {
|
|
5902
|
-
if (this.exclusive || this.depth !== 0) log$
|
|
5919
|
+
if (this.exclusive || this.depth !== 0) log$6.info({
|
|
5903
5920
|
depth: this.depth,
|
|
5904
5921
|
exclusive: this.exclusive
|
|
5905
5922
|
}, "Exclusive runtime hold parked behind open brackets (reconcile in flight)");
|
|
5906
|
-
|
|
5907
|
-
|
|
5923
|
+
if (this.exclusive || this.depth !== 0) {
|
|
5924
|
+
this.parkedExclusive += 1;
|
|
5925
|
+
try {
|
|
5926
|
+
while (this.exclusive || this.depth !== 0) await this.waitFor(() => !this.exclusive && this.depth === 0);
|
|
5927
|
+
} finally {
|
|
5928
|
+
this.parkedExclusive -= 1;
|
|
5929
|
+
}
|
|
5930
|
+
}
|
|
5931
|
+
log$6.info("Exclusive runtime hold acquired");
|
|
5908
5932
|
this.exclusive = true;
|
|
5909
5933
|
await this.enter();
|
|
5910
5934
|
}
|
|
5911
5935
|
async enter() {
|
|
5912
5936
|
if (this.depth++ > 0) return;
|
|
5913
5937
|
const rp = this.getRuntime();
|
|
5914
|
-
this.wasRunning = rp?.
|
|
5938
|
+
this.wasRunning = rp?.isActive ?? false;
|
|
5915
5939
|
if (rp && this.wasRunning) {
|
|
5916
|
-
log$
|
|
5940
|
+
log$6.info("Suspending runtime for mutating reconcile (avoid concurrent SQLite writers)");
|
|
5917
5941
|
await rp.stopWhenIdle();
|
|
5918
5942
|
}
|
|
5919
5943
|
}
|
|
@@ -5928,7 +5952,7 @@ var RuntimeGate = class {
|
|
|
5928
5952
|
if (this.depth < 0) this.depth = 0;
|
|
5929
5953
|
const rp = this.getRuntime();
|
|
5930
5954
|
if (rp && this.wasRunning) {
|
|
5931
|
-
log$
|
|
5955
|
+
log$6.info("Resuming runtime after mutating reconcile");
|
|
5932
5956
|
rp.resume();
|
|
5933
5957
|
}
|
|
5934
5958
|
this.wasRunning = false;
|
|
@@ -5937,6 +5961,32 @@ var RuntimeGate = class {
|
|
|
5937
5961
|
return Promise.resolve();
|
|
5938
5962
|
}
|
|
5939
5963
|
/**
|
|
5964
|
+
* Start a runtime that has not been started yet (daemon boot, deferred first
|
|
5965
|
+
* start). Never spawns while a bracket is open: the start is folded into the
|
|
5966
|
+
* pending outermost `resume()` instead, so a first start can't land in the
|
|
5967
|
+
* middle of a mutating reconcile or an exclusive upgrade.
|
|
5968
|
+
*/
|
|
5969
|
+
requestStart() {
|
|
5970
|
+
this.firstStartDeferred = false;
|
|
5971
|
+
const rp = this.getRuntime();
|
|
5972
|
+
if (!rp) return;
|
|
5973
|
+
if (this.depth > 0) {
|
|
5974
|
+
log$6.info("Runtime start requested while suspended — deferring to pending resume");
|
|
5975
|
+
this.wasRunning = true;
|
|
5976
|
+
return;
|
|
5977
|
+
}
|
|
5978
|
+
rp.start();
|
|
5979
|
+
}
|
|
5980
|
+
/**
|
|
5981
|
+
* Mark the runtime's first start as deferred (fresh managed box, see
|
|
5982
|
+
* first-runtime-start.ts). Until {@link requestStart} runs, an out-of-band
|
|
5983
|
+
* {@link requestRestart} of the never-started runtime is folded into that
|
|
5984
|
+
* pending start instead of spawning it early.
|
|
5985
|
+
*/
|
|
5986
|
+
deferFirstStart() {
|
|
5987
|
+
this.firstStartDeferred = true;
|
|
5988
|
+
}
|
|
5989
|
+
/**
|
|
5940
5990
|
* Out-of-band restart request (e.g. `alfe.config_set`, Hermes MCP sync).
|
|
5941
5991
|
* Deferred while a suspend is active — the pending `resume()` will bring the
|
|
5942
5992
|
* runtime back with fresh config, so restarting mid-suspend would just fight
|
|
@@ -5945,12 +5995,16 @@ var RuntimeGate = class {
|
|
|
5945
5995
|
*/
|
|
5946
5996
|
requestRestart() {
|
|
5947
5997
|
if (this.depth > 0) {
|
|
5948
|
-
log$
|
|
5998
|
+
log$6.info("Runtime restart requested while suspended — deferring to pending resume");
|
|
5949
5999
|
return;
|
|
5950
6000
|
}
|
|
5951
6001
|
const rp = this.getRuntime();
|
|
6002
|
+
if (rp && this.firstStartDeferred && !rp.isActive) {
|
|
6003
|
+
log$6.info("Runtime restart requested before the deferred first start — folding into that start");
|
|
6004
|
+
return;
|
|
6005
|
+
}
|
|
5952
6006
|
if (rp) rp.restart().catch((err) => {
|
|
5953
|
-
log$
|
|
6007
|
+
log$6.error({ err: err instanceof Error ? err.message : String(err) }, "Failed to restart runtime");
|
|
5954
6008
|
});
|
|
5955
6009
|
}
|
|
5956
6010
|
};
|
|
@@ -5973,7 +6027,7 @@ var NoopRuntimeGate = class {
|
|
|
5973
6027
|
};
|
|
5974
6028
|
//#endregion
|
|
5975
6029
|
//#region src/reconciliation.ts
|
|
5976
|
-
const log$
|
|
6030
|
+
const log$5 = logger.child({ component: "Reconciliation" });
|
|
5977
6031
|
/**
|
|
5978
6032
|
* How many times reconcile will re-attempt activation of an intact integration
|
|
5979
6033
|
* stuck in `error` (with no reinstall requested) before giving up and waiting
|
|
@@ -5994,6 +6048,13 @@ var ReconciliationEngine = class {
|
|
|
5994
6048
|
*/
|
|
5995
6049
|
suspended = false;
|
|
5996
6050
|
/**
|
|
6051
|
+
* A suspension carried from the previous pass because the caller already
|
|
6052
|
+
* had another DESIRED_STATE queued (see {@link ReconcileOptions.holdSuspension}).
|
|
6053
|
+
* The next pass adopts it instead of resuming → re-suspending the runtime;
|
|
6054
|
+
* {@link releaseHeldSuspension} gives it back once the queue is drained.
|
|
6055
|
+
*/
|
|
6056
|
+
heldSuspension = false;
|
|
6057
|
+
/**
|
|
5997
6058
|
* @param manager The integration manager to converge.
|
|
5998
6059
|
* @param gate Runtime gate used to suspend the runtime child around
|
|
5999
6060
|
* mutating branches so `openclaw` CLI subprocesses don't write
|
|
@@ -6022,14 +6083,43 @@ var ReconciliationEngine = class {
|
|
|
6022
6083
|
* so the mutation performed by {@link ensureSuspended} in a different method
|
|
6023
6084
|
* isn't narrowed away by control-flow analysis at the callsite.
|
|
6024
6085
|
*/
|
|
6025
|
-
async resumeIfSuspended() {
|
|
6026
|
-
if (this.suspended)
|
|
6086
|
+
async resumeIfSuspended(opts) {
|
|
6087
|
+
if (!this.suspended) return;
|
|
6088
|
+
if (opts?.holdSuspension?.()) {
|
|
6089
|
+
log$5.info("Holding runtime suspension across queued reconcile pass");
|
|
6090
|
+
if (this.gate.hasParkedExclusive) log$5.warn("Exclusive runtime hold (upgrade) is parked behind a suspension held for queued reconcile passes");
|
|
6091
|
+
this.heldSuspension = true;
|
|
6092
|
+
return;
|
|
6093
|
+
}
|
|
6094
|
+
await this.gate.resume();
|
|
6095
|
+
}
|
|
6096
|
+
/**
|
|
6097
|
+
* Resume a suspension held across passes by {@link ReconcileOptions.holdSuspension}.
|
|
6098
|
+
* The caller MUST call this once its queue of passes is drained (success or
|
|
6099
|
+
* failure), otherwise the runtime stays suspended. No-op when nothing is held.
|
|
6100
|
+
*/
|
|
6101
|
+
async releaseHeldSuspension() {
|
|
6102
|
+
if (!this.heldSuspension) return;
|
|
6103
|
+
this.heldSuspension = false;
|
|
6104
|
+
await this.gate.resume();
|
|
6105
|
+
}
|
|
6106
|
+
/** True while a suspension is carried between passes (tests/diagnostics). */
|
|
6107
|
+
get isHoldingSuspension() {
|
|
6108
|
+
return this.heldSuspension;
|
|
6027
6109
|
}
|
|
6028
6110
|
/**
|
|
6029
6111
|
* Reconcile local state against desired state from cloud.
|
|
6030
6112
|
*/
|
|
6031
|
-
async reconcile(desiredIntegrations) {
|
|
6032
|
-
this.suspended =
|
|
6113
|
+
async reconcile(desiredIntegrations, opts) {
|
|
6114
|
+
this.suspended = this.heldSuspension;
|
|
6115
|
+
this.heldSuspension = false;
|
|
6116
|
+
try {
|
|
6117
|
+
return await this.reconcilePass(desiredIntegrations);
|
|
6118
|
+
} finally {
|
|
6119
|
+
await this.resumeIfSuspended(opts);
|
|
6120
|
+
}
|
|
6121
|
+
}
|
|
6122
|
+
async reconcilePass(desiredIntegrations) {
|
|
6033
6123
|
const report = {
|
|
6034
6124
|
results: [],
|
|
6035
6125
|
installed: [],
|
|
@@ -6043,7 +6133,7 @@ var ReconciliationEngine = class {
|
|
|
6043
6133
|
try {
|
|
6044
6134
|
localIntegrations = await this.manager.getInstalledIntegrations();
|
|
6045
6135
|
} catch (err) {
|
|
6046
|
-
log$
|
|
6136
|
+
log$5.error({ err }, "Failed to get local integrations");
|
|
6047
6137
|
captureIntegrationFailure("_reconciliation", "read_local_state", err);
|
|
6048
6138
|
const errorMessage = "Failed to read local integration state; reconciliation skipped";
|
|
6049
6139
|
const affectedIds = desiredIntegrations.length > 0 ? desiredIntegrations.map((desired) => desired.integrationId) : ["*"];
|
|
@@ -6061,34 +6151,30 @@ var ReconciliationEngine = class {
|
|
|
6061
6151
|
}
|
|
6062
6152
|
const localMap = new Map(localIntegrations.map((i) => [i.id, i]));
|
|
6063
6153
|
const desiredMap = new Map(desiredIntegrations.map((d) => [d.integrationId, d]));
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6154
|
+
for (const desired of desiredIntegrations) if (desired.desiredStatus === "active") {
|
|
6155
|
+
const local = localMap.get(desired.integrationId);
|
|
6156
|
+
if (desired.connectionAuthorityVersion !== void 0 && local?.connectionAuthorityPendingVersion !== void 0 && local.status === "error" && (local.version === desired.version || desired.version === "" || local.version === "unknown") && !(desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) && await this.manager.isInstallIntact(desired.integrationId)) report.results.push({
|
|
6157
|
+
integrationId: desired.integrationId,
|
|
6158
|
+
action: "activated",
|
|
6159
|
+
actualStatus: "active"
|
|
6160
|
+
});
|
|
6161
|
+
else await this.reconcileActive(desired, local, report);
|
|
6162
|
+
const result = report.results.find((item) => item.integrationId === desired.integrationId);
|
|
6163
|
+
if (result?.actualStatus === "active" && desired.connectionAuthorityVersion !== void 0 && (local?.connectionAuthorityPendingVersion !== void 0 || desired.connectionAuthorityVersion !== local?.connectionAuthorityVersion)) try {
|
|
6164
|
+
await this.manager.refreshConnections(desired.integrationId, desired.connectionAuthorityVersion, { onBeforeRuntimeMutation: () => this.ensureSuspended() });
|
|
6165
|
+
} catch (err) {
|
|
6166
|
+
captureIntegrationFailure(desired.integrationId, "refresh_connections", err);
|
|
6167
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
6168
|
+
report.errors.push({
|
|
6068
6169
|
integrationId: desired.integrationId,
|
|
6069
|
-
|
|
6070
|
-
actualStatus: "active"
|
|
6170
|
+
error
|
|
6071
6171
|
});
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
const error = err instanceof Error ? err.message : String(err);
|
|
6079
|
-
report.errors.push({
|
|
6080
|
-
integrationId: desired.integrationId,
|
|
6081
|
-
error
|
|
6082
|
-
});
|
|
6083
|
-
result.action = "error";
|
|
6084
|
-
result.actualStatus = "error";
|
|
6085
|
-
result.errorMessage = error;
|
|
6086
|
-
}
|
|
6087
|
-
} else await this.reconcileRemoved(desired.integrationId, localMap.get(desired.integrationId), report);
|
|
6088
|
-
for (const local of localIntegrations) if (!desiredMap.has(local.id)) await this.reconcileRemoved(local.id, local, report);
|
|
6089
|
-
} finally {
|
|
6090
|
-
await this.resumeIfSuspended();
|
|
6091
|
-
}
|
|
6172
|
+
result.action = "error";
|
|
6173
|
+
result.actualStatus = "error";
|
|
6174
|
+
result.errorMessage = error;
|
|
6175
|
+
}
|
|
6176
|
+
} else await this.reconcileRemoved(desired.integrationId, localMap.get(desired.integrationId), report);
|
|
6177
|
+
for (const local of localIntegrations) if (!desiredMap.has(local.id)) await this.reconcileRemoved(local.id, local, report);
|
|
6092
6178
|
return report;
|
|
6093
6179
|
}
|
|
6094
6180
|
async reconcileActive(desired, local, report) {
|
|
@@ -6096,10 +6182,10 @@ var ReconciliationEngine = class {
|
|
|
6096
6182
|
try {
|
|
6097
6183
|
if (!local) {
|
|
6098
6184
|
await this.ensureSuspended();
|
|
6099
|
-
log$
|
|
6185
|
+
log$5.info(`Installing ${id}@${desired.version}`);
|
|
6100
6186
|
await this.manager.install(id, desired.version, desired.config, desired.customSource);
|
|
6101
6187
|
report.installed.push(id);
|
|
6102
|
-
log$
|
|
6188
|
+
log$5.info(`Activating ${id}`);
|
|
6103
6189
|
if ((await this.manager.activate(id)).configApplied) report.configApplied = true;
|
|
6104
6190
|
report.activated.push(id);
|
|
6105
6191
|
report.results.push({
|
|
@@ -6112,7 +6198,7 @@ var ReconciliationEngine = class {
|
|
|
6112
6198
|
if (local.version !== desired.version && desired.version !== "" && local.version !== "unknown") {
|
|
6113
6199
|
if (desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) {
|
|
6114
6200
|
await this.ensureSuspended();
|
|
6115
|
-
log$
|
|
6201
|
+
log$5.info(`Reinstalling ${id} on version change (reinstall requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt}): ${local.version} → ${desired.version}`);
|
|
6116
6202
|
this.manager.resetReinstallAttempts(id);
|
|
6117
6203
|
this.activateAttempts.delete(id);
|
|
6118
6204
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource, { reinstallRequestedAt: desired.reinstallRequestedAt })).configApplied) report.configApplied = true;
|
|
@@ -6125,7 +6211,7 @@ var ReconciliationEngine = class {
|
|
|
6125
6211
|
});
|
|
6126
6212
|
return;
|
|
6127
6213
|
}
|
|
6128
|
-
log$
|
|
6214
|
+
log$5.info(`Upgrading ${id} in place: ${local.version} → ${desired.version}`);
|
|
6129
6215
|
try {
|
|
6130
6216
|
if ((await this.manager.upgrade(id, desired.version, desired.config, desired.customSource, { onBeforeRuntimeMutation: () => this.ensureSuspended() })).configApplied) report.configApplied = true;
|
|
6131
6217
|
this.manager.resetReinstallAttempts(id);
|
|
@@ -6139,7 +6225,7 @@ var ReconciliationEngine = class {
|
|
|
6139
6225
|
});
|
|
6140
6226
|
} catch (upgradeErr) {
|
|
6141
6227
|
const upgradeMsg = upgradeErr instanceof Error ? upgradeErr.message : String(upgradeErr);
|
|
6142
|
-
log$
|
|
6228
|
+
log$5.error({ err: upgradeErr }, `Upgrade failed for ${id}`);
|
|
6143
6229
|
captureIntegrationFailure(id, "upgrade", upgradeErr);
|
|
6144
6230
|
report.errors.push({
|
|
6145
6231
|
integrationId: id,
|
|
@@ -6157,7 +6243,7 @@ var ReconciliationEngine = class {
|
|
|
6157
6243
|
if (local.status === "error") {
|
|
6158
6244
|
if (desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) {
|
|
6159
6245
|
await this.ensureSuspended();
|
|
6160
|
-
log$
|
|
6246
|
+
log$5.info(`Reinstalling ${id} from error state (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
|
|
6161
6247
|
this.manager.resetReinstallAttempts(id);
|
|
6162
6248
|
this.activateAttempts.delete(id);
|
|
6163
6249
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource, { reinstallRequestedAt: desired.reinstallRequestedAt })).configApplied) report.configApplied = true;
|
|
@@ -6173,7 +6259,7 @@ var ReconciliationEngine = class {
|
|
|
6173
6259
|
if (!await this.manager.isInstallIntact(id)) {
|
|
6174
6260
|
const attempts = this.manager.getReinstallAttempts(id);
|
|
6175
6261
|
if (attempts >= 3) {
|
|
6176
|
-
log$
|
|
6262
|
+
log$5.error(`Auto-reinstall blocked for ${id} — ${String(attempts)} consecutive failures. Manual reinstall required.`);
|
|
6177
6263
|
captureIntegrationFailure(id, "reinstall", `Max auto-reinstall attempts (${String(attempts)}) reached — manual reinstall required`);
|
|
6178
6264
|
report.errors.push({
|
|
6179
6265
|
integrationId: id,
|
|
@@ -6188,7 +6274,7 @@ var ReconciliationEngine = class {
|
|
|
6188
6274
|
return;
|
|
6189
6275
|
}
|
|
6190
6276
|
await this.ensureSuspended();
|
|
6191
|
-
log$
|
|
6277
|
+
log$5.info(`Auto-reinstalling ${id} — install directory is corrupted or missing (attempt ${String(attempts + 1)}/3)`);
|
|
6192
6278
|
this.manager.incrementReinstallAttempts(id);
|
|
6193
6279
|
try {
|
|
6194
6280
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
@@ -6203,7 +6289,7 @@ var ReconciliationEngine = class {
|
|
|
6203
6289
|
});
|
|
6204
6290
|
} catch (reinstallErr) {
|
|
6205
6291
|
const reinstallMsg = reinstallErr instanceof Error ? reinstallErr.message : String(reinstallErr);
|
|
6206
|
-
log$
|
|
6292
|
+
log$5.error({ err: reinstallErr }, `Auto-reinstall failed for ${id}`);
|
|
6207
6293
|
captureIntegrationFailure(id, "reinstall", reinstallErr);
|
|
6208
6294
|
report.errors.push({
|
|
6209
6295
|
integrationId: id,
|
|
@@ -6222,7 +6308,7 @@ var ReconciliationEngine = class {
|
|
|
6222
6308
|
if (activateAttempts < MAX_ERROR_ACTIVATE_ATTEMPTS) {
|
|
6223
6309
|
this.activateAttempts.set(id, activateAttempts + 1);
|
|
6224
6310
|
await this.ensureSuspended();
|
|
6225
|
-
log$
|
|
6311
|
+
log$5.info(`Re-activating ${id} from error state (attempt ${String(activateAttempts + 1)}/${String(MAX_ERROR_ACTIVATE_ATTEMPTS)})`);
|
|
6226
6312
|
try {
|
|
6227
6313
|
if ((await this.manager.activate(id, { forcePlugins: true })).configApplied) report.configApplied = true;
|
|
6228
6314
|
this.activateAttempts.delete(id);
|
|
@@ -6235,7 +6321,7 @@ var ReconciliationEngine = class {
|
|
|
6235
6321
|
return;
|
|
6236
6322
|
} catch (reactivateErr) {
|
|
6237
6323
|
const reactivateMsg = reactivateErr instanceof Error ? reactivateErr.message : String(reactivateErr);
|
|
6238
|
-
log$
|
|
6324
|
+
log$5.warn(`Re-activation of ${id} from error state failed (attempt ${String(activateAttempts + 1)}/${String(MAX_ERROR_ACTIVATE_ATTEMPTS)}): ${reactivateMsg}`);
|
|
6239
6325
|
captureIntegrationFailure(id, "reactivate", reactivateErr);
|
|
6240
6326
|
report.errors.push({
|
|
6241
6327
|
integrationId: id,
|
|
@@ -6250,7 +6336,7 @@ var ReconciliationEngine = class {
|
|
|
6250
6336
|
return;
|
|
6251
6337
|
}
|
|
6252
6338
|
}
|
|
6253
|
-
log$
|
|
6339
|
+
log$5.warn(`Integration ${id} is in error state — re-activation exhausted, waiting for reinstall request`);
|
|
6254
6340
|
captureIntegrationFailure(id, "reactivate", `Integration ${id} stuck in error state — re-activation attempts exhausted`);
|
|
6255
6341
|
report.errors.push({
|
|
6256
6342
|
integrationId: id,
|
|
@@ -6267,7 +6353,7 @@ var ReconciliationEngine = class {
|
|
|
6267
6353
|
if (local.status === "installing") {
|
|
6268
6354
|
if (desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) {
|
|
6269
6355
|
await this.ensureSuspended();
|
|
6270
|
-
log$
|
|
6356
|
+
log$5.info(`Reinstalling ${id} from stale installing state (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
|
|
6271
6357
|
this.manager.resetReinstallAttempts(id);
|
|
6272
6358
|
this.activateAttempts.delete(id);
|
|
6273
6359
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource, { reinstallRequestedAt: desired.reinstallRequestedAt })).configApplied) report.configApplied = true;
|
|
@@ -6283,7 +6369,7 @@ var ReconciliationEngine = class {
|
|
|
6283
6369
|
const attempts = this.manager.getReinstallAttempts(id);
|
|
6284
6370
|
if (attempts >= 3) {
|
|
6285
6371
|
const errorMessage = `Stale installing state — max auto-reinstall attempts (${String(attempts)}) reached — manual reinstall required`;
|
|
6286
|
-
log$
|
|
6372
|
+
log$5.error(`Auto-reinstall blocked for ${id} — stuck in "installing", ${String(attempts)} consecutive failures. Manual reinstall required.`);
|
|
6287
6373
|
captureIntegrationFailure(id, "reinstall", errorMessage);
|
|
6288
6374
|
report.errors.push({
|
|
6289
6375
|
integrationId: id,
|
|
@@ -6298,7 +6384,7 @@ var ReconciliationEngine = class {
|
|
|
6298
6384
|
return;
|
|
6299
6385
|
}
|
|
6300
6386
|
await this.ensureSuspended();
|
|
6301
|
-
log$
|
|
6387
|
+
log$5.info(`Recovering ${id} from stale installing state via reinstall (attempt ${String(attempts + 1)}/3)`);
|
|
6302
6388
|
this.manager.incrementReinstallAttempts(id);
|
|
6303
6389
|
try {
|
|
6304
6390
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource)).configApplied) report.configApplied = true;
|
|
@@ -6313,7 +6399,7 @@ var ReconciliationEngine = class {
|
|
|
6313
6399
|
});
|
|
6314
6400
|
} catch (reinstallErr) {
|
|
6315
6401
|
const reinstallMsg = reinstallErr instanceof Error ? reinstallErr.message : String(reinstallErr);
|
|
6316
|
-
log$
|
|
6402
|
+
log$5.error({ err: reinstallErr }, `Reinstall from stale installing state failed for ${id}`);
|
|
6317
6403
|
captureIntegrationFailure(id, "reinstall", reinstallErr);
|
|
6318
6404
|
report.errors.push({
|
|
6319
6405
|
integrationId: id,
|
|
@@ -6330,7 +6416,7 @@ var ReconciliationEngine = class {
|
|
|
6330
6416
|
}
|
|
6331
6417
|
if (local.status !== "active") {
|
|
6332
6418
|
await this.ensureSuspended();
|
|
6333
|
-
log$
|
|
6419
|
+
log$5.info(`Activating ${id}`);
|
|
6334
6420
|
if ((await this.manager.activate(id, { forcePlugins: true })).configApplied) report.configApplied = true;
|
|
6335
6421
|
report.activated.push(id);
|
|
6336
6422
|
report.results.push({
|
|
@@ -6342,7 +6428,7 @@ var ReconciliationEngine = class {
|
|
|
6342
6428
|
}
|
|
6343
6429
|
if (desired.reinstallRequestedAt && local.installedAt && desired.reinstallRequestedAt > local.installedAt) {
|
|
6344
6430
|
await this.ensureSuspended();
|
|
6345
|
-
log$
|
|
6431
|
+
log$5.info(`Reinstall requested for ${id} (requested: ${desired.reinstallRequestedAt}, installed: ${local.installedAt})`);
|
|
6346
6432
|
this.manager.resetReinstallAttempts(id);
|
|
6347
6433
|
this.activateAttempts.delete(id);
|
|
6348
6434
|
if ((await this.manager.reinstall(id, desired.version, desired.config, desired.customSource, { reinstallRequestedAt: desired.reinstallRequestedAt })).configApplied) report.configApplied = true;
|
|
@@ -6363,7 +6449,7 @@ var ReconciliationEngine = class {
|
|
|
6363
6449
|
});
|
|
6364
6450
|
} catch (err) {
|
|
6365
6451
|
const message = err instanceof Error ? err.message : String(err);
|
|
6366
|
-
log$
|
|
6452
|
+
log$5.error({ err }, `Error reconciling ${id}`);
|
|
6367
6453
|
captureIntegrationFailure(id, "reconcile_active", err);
|
|
6368
6454
|
report.errors.push({
|
|
6369
6455
|
integrationId: id,
|
|
@@ -6388,7 +6474,7 @@ var ReconciliationEngine = class {
|
|
|
6388
6474
|
}
|
|
6389
6475
|
try {
|
|
6390
6476
|
await this.ensureSuspended();
|
|
6391
|
-
log$
|
|
6477
|
+
log$5.info(`Removing ${id}`);
|
|
6392
6478
|
if (local.status === "active") {
|
|
6393
6479
|
await this.manager.deactivate(id);
|
|
6394
6480
|
report.deactivated.push(id);
|
|
@@ -6402,7 +6488,7 @@ var ReconciliationEngine = class {
|
|
|
6402
6488
|
});
|
|
6403
6489
|
} catch (err) {
|
|
6404
6490
|
const message = err instanceof Error ? err.message : String(err);
|
|
6405
|
-
log$
|
|
6491
|
+
log$5.error({ err }, `Error removing ${id}`);
|
|
6406
6492
|
captureIntegrationFailure(id, "reconcile_removed", err);
|
|
6407
6493
|
report.errors.push({
|
|
6408
6494
|
integrationId: id,
|
|
@@ -6440,6 +6526,7 @@ var CloudClient = class {
|
|
|
6440
6526
|
onConnectionChange = null;
|
|
6441
6527
|
onRuntimeRestartNeeded = null;
|
|
6442
6528
|
onReconciliationComplete = null;
|
|
6529
|
+
onReconciliationSettled = null;
|
|
6443
6530
|
reconciliationEngine = null;
|
|
6444
6531
|
configReconciler = null;
|
|
6445
6532
|
reconciling = false;
|
|
@@ -6479,6 +6566,16 @@ var CloudClient = class {
|
|
|
6479
6566
|
this.onReconciliationComplete = handler;
|
|
6480
6567
|
}
|
|
6481
6568
|
/**
|
|
6569
|
+
* Set a callback for when a DESIRED_STATE burst has fully SETTLED: the
|
|
6570
|
+
* received snapshot and every queued one have run (each may have succeeded
|
|
6571
|
+
* or failed) and any runtime suspension held across them was released.
|
|
6572
|
+
* Fires on success AND failure — the daemon's deferred first runtime start
|
|
6573
|
+
* hangs off it, and a failed reconcile must not strand the runtime.
|
|
6574
|
+
*/
|
|
6575
|
+
setOnReconciliationSettled(handler) {
|
|
6576
|
+
this.onReconciliationSettled = handler;
|
|
6577
|
+
}
|
|
6578
|
+
/**
|
|
6482
6579
|
* Set the integration manager (and optionally the runtime gate) for
|
|
6483
6580
|
* reconciliation. When set, the cloud client handles DESIRED_STATE messages
|
|
6484
6581
|
* automatically. The gate is passed to the ReconciliationEngine so mutating
|
|
@@ -6725,20 +6822,43 @@ var CloudClient = class {
|
|
|
6725
6822
|
const message = err instanceof Error ? err.message : String(err);
|
|
6726
6823
|
logger.error({ err: message }, "Cloud: reconciliation failed");
|
|
6727
6824
|
} finally {
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6825
|
+
for (;;) {
|
|
6826
|
+
while (this.pendingDesiredState) {
|
|
6827
|
+
const next = this.pendingDesiredState;
|
|
6828
|
+
this.pendingDesiredState = null;
|
|
6829
|
+
logger.info({ count: next.integrations.length }, "Cloud: processing queued DESIRED_STATE");
|
|
6830
|
+
try {
|
|
6831
|
+
await this.runReconciliation(next);
|
|
6832
|
+
} catch (err) {
|
|
6833
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6834
|
+
logger.error({ err: message }, "Cloud: queued reconciliation failed");
|
|
6835
|
+
}
|
|
6836
|
+
}
|
|
6732
6837
|
try {
|
|
6733
|
-
await this.
|
|
6838
|
+
await this.reconciliationEngine.releaseHeldSuspension();
|
|
6734
6839
|
} catch (err) {
|
|
6735
6840
|
const message = err instanceof Error ? err.message : String(err);
|
|
6736
|
-
logger.error({ err: message }, "Cloud: queued reconciliation
|
|
6841
|
+
logger.error({ err: message }, "Cloud: failed to resume runtime after queued reconciliation");
|
|
6737
6842
|
}
|
|
6843
|
+
if (!this.hasQueuedDesiredState()) break;
|
|
6738
6844
|
}
|
|
6739
6845
|
this.reconciling = false;
|
|
6846
|
+
try {
|
|
6847
|
+
this.onReconciliationSettled?.();
|
|
6848
|
+
} catch (err) {
|
|
6849
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6850
|
+
logger.error({ err: message }, "Cloud: reconciliation-settled handler failed");
|
|
6851
|
+
}
|
|
6740
6852
|
}
|
|
6741
6853
|
}
|
|
6854
|
+
/**
|
|
6855
|
+
* Read through a method so control-flow narrowing from the drain loop above
|
|
6856
|
+
* doesn't treat `pendingDesiredState` as still null across an `await` (a
|
|
6857
|
+
* snapshot can be queued by another message while we're suspended).
|
|
6858
|
+
*/
|
|
6859
|
+
hasQueuedDesiredState() {
|
|
6860
|
+
return this.pendingDesiredState !== null;
|
|
6861
|
+
}
|
|
6742
6862
|
async runReconciliation(msg) {
|
|
6743
6863
|
if (msg.config && this.configReconciler) try {
|
|
6744
6864
|
const configReport = await this.configReconciler.reconcile(msg.config);
|
|
@@ -6754,7 +6874,7 @@ var CloudClient = class {
|
|
|
6754
6874
|
logger.error({ err: message }, "Cloud: config reconcile failed");
|
|
6755
6875
|
}
|
|
6756
6876
|
if (!this.reconciliationEngine) return;
|
|
6757
|
-
const report = await this.reconciliationEngine.reconcile(msg.integrations);
|
|
6877
|
+
const report = await this.reconciliationEngine.reconcile(msg.integrations, { holdSuspension: () => this.hasQueuedDesiredState() });
|
|
6758
6878
|
logger.info({
|
|
6759
6879
|
installed: report.installed.length,
|
|
6760
6880
|
activated: report.activated.length,
|
|
@@ -6817,7 +6937,7 @@ var CloudClient = class {
|
|
|
6817
6937
|
};
|
|
6818
6938
|
//#endregion
|
|
6819
6939
|
//#region src/config-reconciler.ts
|
|
6820
|
-
const log$
|
|
6940
|
+
const log$4 = logger.child({ component: "ConfigReconciler" });
|
|
6821
6941
|
const DEFAULT_VERIFY_RETRIES = 3;
|
|
6822
6942
|
const DEFAULT_VERIFY_RETRY_DELAY_MS = 750;
|
|
6823
6943
|
const delay = (ms) => new Promise((resolve) => {
|
|
@@ -6839,7 +6959,7 @@ var ConfigReconciler = class {
|
|
|
6839
6959
|
async reconcile(desired) {
|
|
6840
6960
|
const applier = this.getApplier();
|
|
6841
6961
|
if (!applier?.setConfigRaw) {
|
|
6842
|
-
log$
|
|
6962
|
+
log$4.debug("No applier with setConfigRaw — skipping config reconcile");
|
|
6843
6963
|
return null;
|
|
6844
6964
|
}
|
|
6845
6965
|
const setConfigRaw = applier.setConfigRaw.bind(applier);
|
|
@@ -6857,13 +6977,13 @@ var ConfigReconciler = class {
|
|
|
6857
6977
|
try {
|
|
6858
6978
|
current = await getConfigRaw(key);
|
|
6859
6979
|
} catch (err) {
|
|
6860
|
-
log$
|
|
6980
|
+
log$4.warn({
|
|
6861
6981
|
key,
|
|
6862
6982
|
err: err instanceof Error ? err.message : String(err)
|
|
6863
6983
|
}, "Config diff read failed — attempting the write");
|
|
6864
6984
|
}
|
|
6865
6985
|
if (current === want) {
|
|
6866
|
-
log$
|
|
6986
|
+
log$4.debug({ key }, "Config already at desired value — no-op");
|
|
6867
6987
|
continue;
|
|
6868
6988
|
}
|
|
6869
6989
|
}
|
|
@@ -6878,7 +6998,7 @@ var ConfigReconciler = class {
|
|
|
6878
6998
|
if (getConfigRaw) {
|
|
6879
6999
|
const outcome = await this.verify(getConfigRaw, key, want);
|
|
6880
7000
|
if (outcome === "matched") {
|
|
6881
|
-
log$
|
|
7001
|
+
log$4.warn({
|
|
6882
7002
|
key,
|
|
6883
7003
|
err: msg
|
|
6884
7004
|
}, "config set errored but value landed — treating as applied");
|
|
@@ -6894,7 +7014,7 @@ var ConfigReconciler = class {
|
|
|
6894
7014
|
if (failures.length > 0) {
|
|
6895
7015
|
const joined = failures.join("; ");
|
|
6896
7016
|
const reason = joined.length > 480 ? `${joined.slice(0, 480)}… (truncated)` : joined;
|
|
6897
|
-
log$
|
|
7017
|
+
log$4.warn({
|
|
6898
7018
|
version: desired.version,
|
|
6899
7019
|
reason
|
|
6900
7020
|
}, "Config reconcile failed");
|
|
@@ -6905,13 +7025,13 @@ var ConfigReconciler = class {
|
|
|
6905
7025
|
};
|
|
6906
7026
|
}
|
|
6907
7027
|
if (inconclusiveKeys.length > 0) {
|
|
6908
|
-
log$
|
|
7028
|
+
log$4.warn({
|
|
6909
7029
|
version: desired.version,
|
|
6910
7030
|
keys: inconclusiveKeys
|
|
6911
7031
|
}, "Config reconcile verification was inconclusive — leaving version pending");
|
|
6912
7032
|
return null;
|
|
6913
7033
|
}
|
|
6914
|
-
log$
|
|
7034
|
+
log$4.info({
|
|
6915
7035
|
version: desired.version,
|
|
6916
7036
|
keys: entries.length
|
|
6917
7037
|
}, "Config reconcile applied");
|
|
@@ -6928,7 +7048,7 @@ var ConfigReconciler = class {
|
|
|
6928
7048
|
if (current === want) return "matched";
|
|
6929
7049
|
if (current !== void 0) observedValue = true;
|
|
6930
7050
|
} catch (err) {
|
|
6931
|
-
log$
|
|
7051
|
+
log$4.debug({
|
|
6932
7052
|
key,
|
|
6933
7053
|
err: err instanceof Error ? err.message : String(err)
|
|
6934
7054
|
}, "Config verify read failed");
|
|
@@ -8945,7 +9065,7 @@ function reportAvailableCommands(deps) {
|
|
|
8945
9065
|
* arrive — the grace deadline is a backstop, but fail-open-to-idle means the
|
|
8946
9066
|
* common "nothing to drain" case restarts immediately with no added latency.
|
|
8947
9067
|
*/
|
|
8948
|
-
const log$
|
|
9068
|
+
const log$3 = logger.child({ component: "TurnActivity" });
|
|
8949
9069
|
/** The IPC method the daemon calls on plugins to ask "is a turn active?". */
|
|
8950
9070
|
const RUNTIME_ACTIVITY_METHOD = "runtime.activity";
|
|
8951
9071
|
/**
|
|
@@ -8964,17 +9084,122 @@ var IpcTurnActivityProbe = class {
|
|
|
8964
9084
|
try {
|
|
8965
9085
|
const responses = await ipc.broadcastRequest(RUNTIME_ACTIVITY_METHOD, {}, this.probeTimeoutMs);
|
|
8966
9086
|
for (const [pluginId, res] of responses) if (res.ok && res.payload != null && typeof res.payload === "object" && res.payload.active === true) {
|
|
8967
|
-
log$
|
|
9087
|
+
log$3.debug({ pluginId }, "Plugin reports an in-flight turn");
|
|
8968
9088
|
return true;
|
|
8969
9089
|
}
|
|
8970
9090
|
return false;
|
|
8971
9091
|
} catch (err) {
|
|
8972
|
-
log$
|
|
9092
|
+
log$3.debug({ err: err instanceof Error ? err.message : String(err) }, "Turn-activity probe failed — assuming idle");
|
|
8973
9093
|
return false;
|
|
8974
9094
|
}
|
|
8975
9095
|
}
|
|
8976
9096
|
};
|
|
8977
9097
|
//#endregion
|
|
9098
|
+
//#region src/first-runtime-start.ts
|
|
9099
|
+
/**
|
|
9100
|
+
* Deferred first runtime start for fresh managed boxes.
|
|
9101
|
+
*
|
|
9102
|
+
* On a brand-new managed VM the first DESIRED_STATE always installs the core
|
|
9103
|
+
* integrations, and every mutating reconcile suspends the runtime through the
|
|
9104
|
+
* RuntimeGate. Starting the runtime at daemon boot therefore buys nothing: it
|
|
9105
|
+
* boots (and runs its first-boot migrations) only to be stopped ~seconds later
|
|
9106
|
+
* by that first reconcile, then started again after it. Deferring the first
|
|
9107
|
+
* start until the first reconcile burst settles removes that whole cycle.
|
|
9108
|
+
*
|
|
9109
|
+
* The deferral is released by whichever comes first:
|
|
9110
|
+
* - the cloud client's "reconciliation settled" hook (success OR failure),
|
|
9111
|
+
* - a fallback timer armed when the cloud client first REGISTERS
|
|
9112
|
+
* ({@link DeferredRuntimeStart.armFallback}), so slow boot/connect time
|
|
9113
|
+
* doesn't eat the window the first DESIRED_STATE needs;
|
|
9114
|
+
* - a longer boot ceiling measured from daemon boot, so a daemon that never
|
|
9115
|
+
* registers (gateway down, registration failing) still brings the runtime up.
|
|
9116
|
+
*
|
|
9117
|
+
* Only fresh managed boxes defer (managed mode AND no integrations in local
|
|
9118
|
+
* state). Self-hosted daemons and restarts of an already-provisioned box start
|
|
9119
|
+
* the runtime immediately, exactly as before.
|
|
9120
|
+
*/
|
|
9121
|
+
const log$2 = logger.child({ component: "FirstRuntimeStart" });
|
|
9122
|
+
/** How long a fresh box waits, after first registering with the cloud, for its first reconcile to settle. */
|
|
9123
|
+
const FIRST_START_FALLBACK_MS = 18e4;
|
|
9124
|
+
/** Hard ceiling from daemon boot, for a daemon that never registers with the cloud. */
|
|
9125
|
+
const FIRST_START_BOOT_CEILING_MS = 6e5;
|
|
9126
|
+
/**
|
|
9127
|
+
* Fresh managed box ⇔ managed mode AND local integration state is readable and
|
|
9128
|
+
* empty. An unreadable state is NOT treated as fresh — fall back to the old
|
|
9129
|
+
* start-immediately behaviour rather than delay a box we know nothing about.
|
|
9130
|
+
*/
|
|
9131
|
+
function shouldDeferFirstRuntimeStart(input) {
|
|
9132
|
+
return input.managed && input.installedIntegrationCount === 0;
|
|
9133
|
+
}
|
|
9134
|
+
var DeferredRuntimeStart = class {
|
|
9135
|
+
released = false;
|
|
9136
|
+
cancelled = false;
|
|
9137
|
+
startFn = null;
|
|
9138
|
+
fallbackArmed = false;
|
|
9139
|
+
fallbackTimer = null;
|
|
9140
|
+
ceilingTimer;
|
|
9141
|
+
/** Arms the boot ceiling immediately; the fallback waits for {@link armFallback}. */
|
|
9142
|
+
constructor(fallbackMs = FIRST_START_FALLBACK_MS, bootCeilingMs = FIRST_START_BOOT_CEILING_MS) {
|
|
9143
|
+
this.fallbackMs = fallbackMs;
|
|
9144
|
+
this.ceilingTimer = setTimeout(() => {
|
|
9145
|
+
this.ceilingTimer = null;
|
|
9146
|
+
this.release("boot-ceiling");
|
|
9147
|
+
}, bootCeilingMs);
|
|
9148
|
+
}
|
|
9149
|
+
/**
|
|
9150
|
+
* Start the fallback clock. Called when the cloud client registers; only
|
|
9151
|
+
* the FIRST call arms it (a reconnect must not push the deadline out).
|
|
9152
|
+
*/
|
|
9153
|
+
armFallback() {
|
|
9154
|
+
if (this.released || this.cancelled || this.fallbackArmed) return;
|
|
9155
|
+
this.fallbackArmed = true;
|
|
9156
|
+
this.fallbackTimer = setTimeout(() => {
|
|
9157
|
+
this.fallbackTimer = null;
|
|
9158
|
+
this.release("fallback-timeout");
|
|
9159
|
+
}, this.fallbackMs);
|
|
9160
|
+
}
|
|
9161
|
+
/** True once released (by either path). */
|
|
9162
|
+
get isReleased() {
|
|
9163
|
+
return this.released;
|
|
9164
|
+
}
|
|
9165
|
+
/**
|
|
9166
|
+
* Provide the start action once the runtime process exists. If the deferral
|
|
9167
|
+
* was already released (a reconcile settled before the runtime object was
|
|
9168
|
+
* built), starts right away.
|
|
9169
|
+
*/
|
|
9170
|
+
attach(start) {
|
|
9171
|
+
if (this.cancelled) return;
|
|
9172
|
+
this.startFn = start;
|
|
9173
|
+
if (this.released) start();
|
|
9174
|
+
}
|
|
9175
|
+
/** Release the deferral. Idempotent — only the first call starts the runtime. */
|
|
9176
|
+
release(reason) {
|
|
9177
|
+
if (this.released || this.cancelled) return;
|
|
9178
|
+
this.released = true;
|
|
9179
|
+
this.clearTimers();
|
|
9180
|
+
log$2.info({
|
|
9181
|
+
reason,
|
|
9182
|
+
fallbackMs: this.fallbackMs
|
|
9183
|
+
}, "Releasing deferred first runtime start");
|
|
9184
|
+
this.startFn?.();
|
|
9185
|
+
}
|
|
9186
|
+
/** Daemon shutdown: never start the runtime after this. */
|
|
9187
|
+
cancel() {
|
|
9188
|
+
this.cancelled = true;
|
|
9189
|
+
this.clearTimers();
|
|
9190
|
+
}
|
|
9191
|
+
clearTimers() {
|
|
9192
|
+
if (this.fallbackTimer) {
|
|
9193
|
+
clearTimeout(this.fallbackTimer);
|
|
9194
|
+
this.fallbackTimer = null;
|
|
9195
|
+
}
|
|
9196
|
+
if (this.ceilingTimer) {
|
|
9197
|
+
clearTimeout(this.ceilingTimer);
|
|
9198
|
+
this.ceilingTimer = null;
|
|
9199
|
+
}
|
|
9200
|
+
}
|
|
9201
|
+
};
|
|
9202
|
+
//#endregion
|
|
8978
9203
|
//#region src/command-registry.ts
|
|
8979
9204
|
/**
|
|
8980
9205
|
* Command Registry — maps integration command names to local handler files.
|
|
@@ -9656,6 +9881,8 @@ let stopPairingApprovalPoller = null;
|
|
|
9656
9881
|
let runtimeAppliersRef = null;
|
|
9657
9882
|
let openclawCliLockRef = null;
|
|
9658
9883
|
let runtimeGateRef = null;
|
|
9884
|
+
/** Pending deferred first runtime start (fresh managed box only). */
|
|
9885
|
+
let firstRuntimeStart = null;
|
|
9659
9886
|
const LEGACY_CONFIG_SET_KEYS = new Set(["agents.defaults.model"]);
|
|
9660
9887
|
const MAX_LEGACY_CONFIG_VALUE_LENGTH = 16 * 1024;
|
|
9661
9888
|
async function startDaemon() {
|
|
@@ -9744,6 +9971,7 @@ async function startDaemon() {
|
|
|
9744
9971
|
cloudClient.setConnectionChangeHandler((connected) => {
|
|
9745
9972
|
cloudConnected = connected;
|
|
9746
9973
|
logger.info({ connected }, "Cloud connection state changed");
|
|
9974
|
+
if (connected) firstRuntimeStart?.armFallback();
|
|
9747
9975
|
ipcServer?.broadcastEvent("cloud.status", { connected });
|
|
9748
9976
|
});
|
|
9749
9977
|
const openclawCliLock = new SerialOpenClawCliLock();
|
|
@@ -9874,6 +10102,27 @@ async function startDaemon() {
|
|
|
9874
10102
|
cloudClient
|
|
9875
10103
|
});
|
|
9876
10104
|
});
|
|
10105
|
+
if (config.autoStartRuntime && config.runtime) {
|
|
10106
|
+
let installedIntegrationCount;
|
|
10107
|
+
try {
|
|
10108
|
+
installedIntegrationCount = integrationManager.list().length;
|
|
10109
|
+
} catch (err) {
|
|
10110
|
+
logger.warn({ err: err instanceof Error ? err.message : String(err) }, "Could not read local integration state — starting runtime immediately");
|
|
10111
|
+
installedIntegrationCount = null;
|
|
10112
|
+
}
|
|
10113
|
+
if (shouldDeferFirstRuntimeStart({
|
|
10114
|
+
managed,
|
|
10115
|
+
installedIntegrationCount
|
|
10116
|
+
})) {
|
|
10117
|
+
logger.info("Fresh managed box — deferring first runtime start until the first reconcile settles");
|
|
10118
|
+
const deferral = new DeferredRuntimeStart();
|
|
10119
|
+
firstRuntimeStart = deferral;
|
|
10120
|
+
runtimeGate.deferFirstStart();
|
|
10121
|
+
cloudClient.setOnReconciliationSettled(() => {
|
|
10122
|
+
deferral.release("reconcile-settled");
|
|
10123
|
+
});
|
|
10124
|
+
}
|
|
10125
|
+
}
|
|
9877
10126
|
cloudClient.start();
|
|
9878
10127
|
logger.debug("Cloud client started");
|
|
9879
10128
|
if (!config.autoStartRuntime && config.runtime) logger.warn({ runtime: config.runtime }, "Runtime configured but auto_start_runtime=false — runtime will not be spawned; in-runtime plugins (chat/console/sync) will be offline until you start it manually");
|
|
@@ -9892,8 +10141,15 @@ async function startDaemon() {
|
|
|
9892
10141
|
env: config.runtime === "claude-code" && config.defaultModel ? { ALFE_DEFAULT_MODEL: config.defaultModel } : {}
|
|
9893
10142
|
});
|
|
9894
10143
|
runtimeProcess.setTurnActivityProbe(turnActivityProbe);
|
|
9895
|
-
|
|
9896
|
-
|
|
10144
|
+
if (firstRuntimeStart) {
|
|
10145
|
+
firstRuntimeStart.attach(() => {
|
|
10146
|
+
runtimeGate.requestStart();
|
|
10147
|
+
});
|
|
10148
|
+
logger.debug("Runtime process created — first start deferred");
|
|
10149
|
+
} else {
|
|
10150
|
+
runtimeGate.requestStart();
|
|
10151
|
+
logger.debug("Runtime process started");
|
|
10152
|
+
}
|
|
9897
10153
|
if (config.runtime === "openclaw") {
|
|
9898
10154
|
stopPairingApprovalPoller = startPairingApprovalPoller({
|
|
9899
10155
|
log: logger,
|
|
@@ -9913,9 +10169,10 @@ async function startDaemon() {
|
|
|
9913
10169
|
stopPairingApprovalPoller();
|
|
9914
10170
|
stopPairingApprovalPoller = null;
|
|
9915
10171
|
}
|
|
10172
|
+
firstRuntimeStart?.cancel();
|
|
9916
10173
|
if (runtimeProcess) {
|
|
9917
10174
|
logger.debug("Stopping runtime process...");
|
|
9918
|
-
await runtimeProcess.
|
|
10175
|
+
await runtimeProcess.shutdown();
|
|
9919
10176
|
logger.debug("Runtime process stopped");
|
|
9920
10177
|
}
|
|
9921
10178
|
if (hermesMcpSync) {
|
package/dist/runtime-process.js
CHANGED
|
@@ -15413,6 +15413,14 @@ const BACKOFF_INITIAL_MS = 1e3;
|
|
|
15413
15413
|
const BACKOFF_MAX_MS = 3e4;
|
|
15414
15414
|
const STABLE_UPTIME_MS = 6e4;
|
|
15415
15415
|
/**
|
|
15416
|
+
* Output markers (lower-cased substrings) that distinguish the planned exit-78
|
|
15417
|
+
* from any other EX_CONFIG. 2026.9.5 raises the same startup race two ways:
|
|
15418
|
+
* "OpenClaw migration inputs changed during startup", and
|
|
15419
|
+
* `throwStartupMigrationGuardRejected`'s "OpenClaw startup migrations were
|
|
15420
|
+
* skipped because the selected config changed during startup… Retry startup".
|
|
15421
|
+
*/
|
|
15422
|
+
const MIGRATION_RESTART_MARKERS = ["migration inputs changed during startup", "selected config changed during startup"];
|
|
15423
|
+
/**
|
|
15416
15424
|
* Alfe owns supervision and version rollout; OpenClaw must not self-manage.
|
|
15417
15425
|
*
|
|
15418
15426
|
* `OPENCLAW_NODE_UPDATE_RESPAWNED=1` also disables 2026.9.x's node-runtime
|
|
@@ -15446,6 +15454,17 @@ var RuntimeProcess = class {
|
|
|
15446
15454
|
backoffMs = BACKOFF_INITIAL_MS;
|
|
15447
15455
|
lastStartTime = 0;
|
|
15448
15456
|
restartTimer = null;
|
|
15457
|
+
/** Whether the CURRENT run printed one of {@link MIGRATION_RESTART_MARKERS}. Reset on every spawn. */
|
|
15458
|
+
sawMigrationInputsChanged = false;
|
|
15459
|
+
/** Consecutive exit-78 migration restarts treated as planned (see {@link MAX_CONSECUTIVE_MIGRATION_RESTARTS}). */
|
|
15460
|
+
consecutiveMigrationRestarts = 0;
|
|
15461
|
+
/**
|
|
15462
|
+
* Daemon shutdown latch. Once set, `start()` / `resume()` / `restart()` are
|
|
15463
|
+
* permanent no-ops: an in-flight reconcile's final gate resume (or a held
|
|
15464
|
+
* suspension released by the drain loop) must not respawn the runtime after
|
|
15465
|
+
* shutdown stopped it.
|
|
15466
|
+
*/
|
|
15467
|
+
shutdownLatched = false;
|
|
15449
15468
|
ringBuffer = new OutputRingBuffer();
|
|
15450
15469
|
detector = new ErrorLineDetector();
|
|
15451
15470
|
throttle = new CaptureThrottle(STABLE_UPTIME_MS);
|
|
@@ -15492,9 +15511,10 @@ var RuntimeProcess = class {
|
|
|
15492
15511
|
* Start the runtime process.
|
|
15493
15512
|
*/
|
|
15494
15513
|
start() {
|
|
15495
|
-
if (this.stopped || this.child !== null) return;
|
|
15514
|
+
if (this.stopped || this.shutdownLatched || this.child !== null) return;
|
|
15496
15515
|
const { command, args } = this.resolveCommand();
|
|
15497
15516
|
this.lastStartTime = Date.now();
|
|
15517
|
+
this.sawMigrationInputsChanged = false;
|
|
15498
15518
|
log.info({
|
|
15499
15519
|
runtime: this.options.runtime,
|
|
15500
15520
|
command,
|
|
@@ -15555,8 +15575,18 @@ var RuntimeProcess = class {
|
|
|
15555
15575
|
}, "Runtime stopped (expected)");
|
|
15556
15576
|
return;
|
|
15557
15577
|
}
|
|
15558
|
-
|
|
15559
|
-
|
|
15578
|
+
const uptime = Date.now() - this.lastStartTime;
|
|
15579
|
+
if (uptime >= 6e4) this.consecutiveMigrationRestarts = 0;
|
|
15580
|
+
const isGraceful = !spawnError && code === 0 && signal == null;
|
|
15581
|
+
const isPlannedMigrationRestart = !spawnError && signal == null && code === 78 && this.sawMigrationInputsChanged && this.consecutiveMigrationRestarts < 1;
|
|
15582
|
+
if (isGraceful || isPlannedMigrationRestart) {
|
|
15583
|
+
if (isPlannedMigrationRestart) {
|
|
15584
|
+
this.consecutiveMigrationRestarts += 1;
|
|
15585
|
+
log.info({
|
|
15586
|
+
runtime: this.options.runtime,
|
|
15587
|
+
code
|
|
15588
|
+
}, "Runtime asked for a restart after its startup migration inputs changed — restarting (planned, not a crash)");
|
|
15589
|
+
} else log.info({
|
|
15560
15590
|
runtime: this.options.runtime,
|
|
15561
15591
|
code
|
|
15562
15592
|
}, "Runtime exited gracefully — restarting");
|
|
@@ -15573,7 +15603,6 @@ var RuntimeProcess = class {
|
|
|
15573
15603
|
spawnError: spawnError?.message,
|
|
15574
15604
|
backoffMs: this.backoffMs
|
|
15575
15605
|
}, "Runtime crashed — scheduling restart with backoff");
|
|
15576
|
-
const uptime = Date.now() - this.lastStartTime;
|
|
15577
15606
|
if (this.detectorFlushTimer) {
|
|
15578
15607
|
clearTimeout(this.detectorFlushTimer);
|
|
15579
15608
|
this.detectorFlushTimer = null;
|
|
@@ -15619,6 +15648,10 @@ var RuntimeProcess = class {
|
|
|
15619
15648
|
stream,
|
|
15620
15649
|
line
|
|
15621
15650
|
});
|
|
15651
|
+
if (!this.sawMigrationInputsChanged) {
|
|
15652
|
+
const lower = line.toLowerCase();
|
|
15653
|
+
if (MIGRATION_RESTART_MARKERS.some((marker) => lower.includes(marker))) this.sawMigrationInputsChanged = true;
|
|
15654
|
+
}
|
|
15622
15655
|
for (const block of this.detector.feed(stream, line)) this.emitErrorBlock(block);
|
|
15623
15656
|
if (this.detectorFlushTimer) {
|
|
15624
15657
|
clearTimeout(this.detectorFlushTimer);
|
|
@@ -15690,6 +15723,14 @@ var RuntimeProcess = class {
|
|
|
15690
15723
|
});
|
|
15691
15724
|
}
|
|
15692
15725
|
/**
|
|
15726
|
+
* Final stop for daemon shutdown: latches so nothing can start the runtime
|
|
15727
|
+
* again (see {@link shutdownLatched}), then stops it.
|
|
15728
|
+
*/
|
|
15729
|
+
async shutdown() {
|
|
15730
|
+
this.shutdownLatched = true;
|
|
15731
|
+
await this.stop();
|
|
15732
|
+
}
|
|
15733
|
+
/**
|
|
15693
15734
|
* Stop the runtime, but first wait for any in-flight chat turn to finish
|
|
15694
15735
|
* (up to a grace deadline). This is the entry point for NON-crash restarts —
|
|
15695
15736
|
* a mid-turn SIGTERM turns a healthy reply into an "unknown error" for the
|
|
@@ -15744,8 +15785,10 @@ var RuntimeProcess = class {
|
|
|
15744
15785
|
* the runtime back after a mutating reconcile branch has finished.
|
|
15745
15786
|
*/
|
|
15746
15787
|
resume() {
|
|
15788
|
+
if (this.shutdownLatched) return;
|
|
15747
15789
|
this.stopped = false;
|
|
15748
15790
|
this.backoffMs = BACKOFF_INITIAL_MS;
|
|
15791
|
+
this.consecutiveMigrationRestarts = 0;
|
|
15749
15792
|
this.start();
|
|
15750
15793
|
}
|
|
15751
15794
|
/**
|
|
@@ -15766,6 +15809,7 @@ var RuntimeProcess = class {
|
|
|
15766
15809
|
else await this.stop();
|
|
15767
15810
|
this.stopped = false;
|
|
15768
15811
|
this.backoffMs = BACKOFF_INITIAL_MS;
|
|
15812
|
+
this.consecutiveMigrationRestarts = 0;
|
|
15769
15813
|
this.start();
|
|
15770
15814
|
}
|
|
15771
15815
|
/**
|
|
@@ -15774,6 +15818,18 @@ var RuntimeProcess = class {
|
|
|
15774
15818
|
get isRunning() {
|
|
15775
15819
|
return this.child !== null && !this.child.killed;
|
|
15776
15820
|
}
|
|
15821
|
+
/**
|
|
15822
|
+
* Whether the runtime is live OR about to be: a child exists, or a restart
|
|
15823
|
+
* timer (crash backoff / graceful / migration restart) is pending and will
|
|
15824
|
+
* spawn one. This — not {@link isRunning} — is what the RuntimeGate must
|
|
15825
|
+
* check before a mutation: during the restart window the child is null but
|
|
15826
|
+
* the pending timer would fire `start()` mid-mutation, putting a second
|
|
15827
|
+
* SQLite writer on ~/.openclaw/state. `stop()` cancels the timer, and the
|
|
15828
|
+
* gate's `resume()` brings the runtime back.
|
|
15829
|
+
*/
|
|
15830
|
+
get isActive() {
|
|
15831
|
+
return !this.stopped && (this.child !== null || this.restartTimer !== null);
|
|
15832
|
+
}
|
|
15777
15833
|
};
|
|
15778
15834
|
//#endregion
|
|
15779
15835
|
export { ErrorLineDetector as a, createLogger as c, PINNED_OPENCLAW_VERSION as d, parseOpenClawVersion as f, __require as h, CaptureThrottle as i, OPENCLAW_MANAGED_NODE_MAJOR as l, __commonJSMin as m, OPENCLAW_EXTERNAL_SUPERVISOR_ENV as n, STRICT_ERROR_CAPTURE_PROFILE as o, supportsOpenClawNode as p, RuntimeProcess as r, toFingerprintKey as s, DRAIN_GRACE_MS as t, OPENCLAW_NODE_REQUIREMENT as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/gateway",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.6",
|
|
4
4
|
"description": "Alfe local gateway daemon — persistent control plane for agent integrations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,7 +27,7 @@
|
|
|
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.
|
|
30
|
+
"@alfe.ai/integrations": "^0.6.12",
|
|
31
31
|
"@alfe.ai/mcp-bundler": "^0.4.2"
|
|
32
32
|
},
|
|
33
33
|
"license": "UNLICENSED",
|