visionclaw 0.1.216-beta.1 → 0.1.216-beta.2

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.
Files changed (102) hide show
  1. package/dist/agent/engine-fallback.d.ts +175 -0
  2. package/dist/agent/engine-fallback.d.ts.map +1 -0
  3. package/dist/agent/engine-fallback.js +454 -0
  4. package/dist/agent/engine-fallback.js.map +1 -0
  5. package/dist/agent/engine-probe.d.ts +47 -0
  6. package/dist/agent/engine-probe.d.ts.map +1 -0
  7. package/dist/agent/engine-probe.js +99 -0
  8. package/dist/agent/engine-probe.js.map +1 -0
  9. package/dist/agent/engine-switch-target.d.ts.map +1 -1
  10. package/dist/agent/engine-switch-target.js +23 -5
  11. package/dist/agent/engine-switch-target.js.map +1 -1
  12. package/dist/agent/engine-switch.d.ts +21 -4
  13. package/dist/agent/engine-switch.d.ts.map +1 -1
  14. package/dist/agent/engine-switch.js +71 -22
  15. package/dist/agent/engine-switch.js.map +1 -1
  16. package/dist/agent/engines/pi/tools.js +2 -2
  17. package/dist/agent/engines/pi/tools.js.map +1 -1
  18. package/dist/agent/heartbeat-manager.d.ts +10 -6
  19. package/dist/agent/heartbeat-manager.d.ts.map +1 -1
  20. package/dist/agent/heartbeat-manager.js +30 -24
  21. package/dist/agent/heartbeat-manager.js.map +1 -1
  22. package/dist/agent/interrupt-handler.d.ts.map +1 -1
  23. package/dist/agent/interrupt-handler.js +2 -1
  24. package/dist/agent/interrupt-handler.js.map +1 -1
  25. package/dist/agent/loop.d.ts.map +1 -1
  26. package/dist/agent/loop.js +80 -15
  27. package/dist/agent/loop.js.map +1 -1
  28. package/dist/agent/model-provider.d.ts +63 -12
  29. package/dist/agent/model-provider.d.ts.map +1 -1
  30. package/dist/agent/model-provider.js +97 -34
  31. package/dist/agent/model-provider.js.map +1 -1
  32. package/dist/agent/nightly-maintenance/memory-dreaming.js +4 -4
  33. package/dist/agent/nightly-maintenance/memory-dreaming.js.map +1 -1
  34. package/dist/agent/nightly-maintenance/vocabulary-processor.d.ts +2 -11
  35. package/dist/agent/nightly-maintenance/vocabulary-processor.d.ts.map +1 -1
  36. package/dist/agent/nightly-maintenance/vocabulary-processor.js +36 -70
  37. package/dist/agent/nightly-maintenance/vocabulary-processor.js.map +1 -1
  38. package/dist/agent/onboarding-gate.d.ts.map +1 -1
  39. package/dist/agent/onboarding-gate.js +2 -0
  40. package/dist/agent/onboarding-gate.js.map +1 -1
  41. package/dist/agent/status.d.ts.map +1 -1
  42. package/dist/agent/status.js +27 -22
  43. package/dist/agent/status.js.map +1 -1
  44. package/dist/agent/transcript-summarizer.d.ts +2 -0
  45. package/dist/agent/transcript-summarizer.d.ts.map +1 -1
  46. package/dist/agent/transcript-summarizer.js +60 -7
  47. package/dist/agent/transcript-summarizer.js.map +1 -1
  48. package/dist/agent/vocabulary-evidence.d.ts +11 -0
  49. package/dist/agent/vocabulary-evidence.d.ts.map +1 -0
  50. package/dist/agent/vocabulary-evidence.js +17 -0
  51. package/dist/agent/vocabulary-evidence.js.map +1 -0
  52. package/dist/agent/vocabulary-glossary.d.ts +12 -3
  53. package/dist/agent/vocabulary-glossary.d.ts.map +1 -1
  54. package/dist/agent/vocabulary-glossary.js +69 -22
  55. package/dist/agent/vocabulary-glossary.js.map +1 -1
  56. package/dist/agent/vocabulary-injection.d.ts +8 -5
  57. package/dist/agent/vocabulary-injection.d.ts.map +1 -1
  58. package/dist/agent/vocabulary-injection.js +10 -12
  59. package/dist/agent/vocabulary-injection.js.map +1 -1
  60. package/dist/agent/wake-cycle-runner.d.ts +61 -3
  61. package/dist/agent/wake-cycle-runner.d.ts.map +1 -1
  62. package/dist/agent/wake-cycle-runner.js +304 -64
  63. package/dist/agent/wake-cycle-runner.js.map +1 -1
  64. package/dist/channels/finapp.d.ts.map +1 -1
  65. package/dist/channels/finapp.js +3 -1
  66. package/dist/channels/finapp.js.map +1 -1
  67. package/dist/channels/telegram.d.ts.map +1 -1
  68. package/dist/channels/telegram.js +3 -1
  69. package/dist/channels/telegram.js.map +1 -1
  70. package/dist/config/types.d.ts +3 -0
  71. package/dist/config/types.d.ts.map +1 -1
  72. package/dist/config/types.js +2 -0
  73. package/dist/config/types.js.map +1 -1
  74. package/dist/onboarding/onboarding-service-client.d.ts +15 -6
  75. package/dist/onboarding/onboarding-service-client.d.ts.map +1 -1
  76. package/dist/onboarding/onboarding-service-client.js.map +1 -1
  77. package/dist/onboarding/prepare-linux.d.ts.map +1 -1
  78. package/dist/onboarding/prepare-linux.js +23 -3
  79. package/dist/onboarding/prepare-linux.js.map +1 -1
  80. package/dist/stt-client.d.ts.map +1 -1
  81. package/dist/stt-client.js +20 -14
  82. package/dist/stt-client.js.map +1 -1
  83. package/dist/tools/index.js +2 -2
  84. package/dist/tools/index.js.map +1 -1
  85. package/dist/tools/transcribe-audio.d.ts +4 -9
  86. package/dist/tools/transcribe-audio.d.ts.map +1 -1
  87. package/dist/tools/transcribe-audio.js +22 -33
  88. package/dist/tools/transcribe-audio.js.map +1 -1
  89. package/dist/transcription-context.d.ts +9 -0
  90. package/dist/transcription-context.d.ts.map +1 -0
  91. package/dist/transcription-context.js +12 -0
  92. package/dist/transcription-context.js.map +1 -0
  93. package/dist/utils/transcribe.d.ts +3 -2
  94. package/dist/utils/transcribe.d.ts.map +1 -1
  95. package/dist/utils/transcribe.js +5 -4
  96. package/dist/utils/transcribe.js.map +1 -1
  97. package/dist-agent/bundle.cjs +2 -2
  98. package/package.json +1 -1
  99. package/dist/agent/provider-fallback.d.ts +0 -69
  100. package/dist/agent/provider-fallback.d.ts.map +0 -1
  101. package/dist/agent/provider-fallback.js +0 -169
  102. package/dist/agent/provider-fallback.js.map +0 -1
@@ -0,0 +1,175 @@
1
+ /**
2
+ * EngineFallbackManager — health, cooldowns and failover across engines.
3
+ *
4
+ * Failover happens between **engines** (`grok` / `gpt` / `claude`), all served
5
+ * through the single runtime provider, LLM Gateway. The provider only chooses
6
+ * the route, credentials and protocol; it takes no part in health tracking.
7
+ * Failures that belong to the shared gateway — 401/402/403, the managed key's
8
+ * token budget — are handled by the caller and never cool an engine down,
9
+ * because every engine would fail them identically. See issue #2519.
10
+ *
11
+ * ## What is and is not persisted
12
+ *
13
+ * The selected failover engine lives in `model-provider`'s module state, not
14
+ * in `config.json`: `engineOverride` is the *user's* preference and must
15
+ * survive a failover untouched. But degrading to Claude changes the agent
16
+ * runtime and therefore requires a process restart — so the cooldown table is
17
+ * mirrored to `<configDir>/engine-health.json`. Without that, the replacement
18
+ * process would come up with an empty table, re-select the dead engine, and
19
+ * restart again in a loop.
20
+ *
21
+ * ## Priority
22
+ *
23
+ * The candidate order is the user's preference first, then the remaining
24
+ * engines in priority order. Putting the preference first is what makes
25
+ * recovery automatic: as soon as its cooldown lapses it is picked again, with
26
+ * no separate "switch back" code path.
27
+ *
28
+ * The priority comes from the heartbeat's `llmEnginePriority` when the server
29
+ * sends it, and from {@link ENGINE_PRIORITY} otherwise — so an older server, or
30
+ * one that never sets the field, leaves failover fully working.
31
+ *
32
+ * Cooldowns:
33
+ * - 5 min, doubling per consecutive failure, capped at 30 min
34
+ * - reset to the base on the first success
35
+ */
36
+ import { type Engine, type VisionClawConfig } from "../config/types.js";
37
+ /** Engine failover order used when the server does not send one. */
38
+ export declare const ENGINE_PRIORITY: readonly Engine[];
39
+ /**
40
+ * Why an engine was taken out of rotation. There is deliberately no auth
41
+ * reason: a gateway auth or key-budget failure is shared by every engine and
42
+ * never cools one down.
43
+ */
44
+ export type EngineFailReason = "network" | "silence_exhausted"
45
+ /** The engine's model is gone or out of quota — deterministic, not transient. */
46
+ | "model_unavailable";
47
+ /** An engine currently out of rotation, as reported on the heartbeat. */
48
+ export interface DisabledEngineInfo {
49
+ engine: Engine;
50
+ reason: EngineFailReason;
51
+ disabledUntil: string;
52
+ }
53
+ /** Outcome of an attempted failover, for logging and caller branching. */
54
+ export type EngineSwitchOutcome = {
55
+ kind: "hot";
56
+ from: Engine;
57
+ to: Engine;
58
+ } | {
59
+ kind: "restart";
60
+ from: Engine;
61
+ to: Engine;
62
+ } | {
63
+ kind: "none";
64
+ why: "no-candidate" | "probe-failed" | "persist-failed" | "worker-cross-runtime";
65
+ };
66
+ export declare function setActiveEngineFallback(manager: EngineFallbackManager | null): void;
67
+ export declare function getActiveEngineFallback(): EngineFallbackManager | null;
68
+ export declare class EngineFallbackManager {
69
+ private readonly config;
70
+ private priorityList;
71
+ private states;
72
+ /**
73
+ * Cross-runtime failover is blocked while a restart is already pending, so
74
+ * a second failure inside the shutdown window cannot schedule a second one.
75
+ */
76
+ private restartScheduled;
77
+ constructor(config: VisionClawConfig);
78
+ /**
79
+ * Adopt the server's `llmEnginePriority`. Unknown and duplicate entries are
80
+ * dropped; a missing or unusable list falls back to {@link ENGINE_PRIORITY}.
81
+ * Never let a bad list disable failover — that is exactly the silent no-op
82
+ * this class used to have.
83
+ */
84
+ setPriorityList(serverPriority: string[] | null | undefined): void;
85
+ /** Engine priority currently in force. */
86
+ get enginePriority(): Engine[];
87
+ private healthFilePath;
88
+ /**
89
+ * Load the persisted cooldown table and re-apply a still-valid failover
90
+ * selection. Must run before `prepareEngineSwitchBoundary()` so the session
91
+ * boundary is evaluated against the engine we will actually start on.
92
+ *
93
+ * A missing, corrupt or version-mismatched file is treated as "no state":
94
+ * startup must never be blocked by it.
95
+ */
96
+ initEngineHealth(): void;
97
+ /**
98
+ * Atomically write the cooldown table. Returns false when it did not land.
99
+ *
100
+ * `activeFallback` is passed explicitly rather than read from module state:
101
+ * a cross-runtime switch records the engine the *next* process should start
102
+ * on while this process deliberately keeps serving the old one until the
103
+ * restart lands.
104
+ */
105
+ private persistEngineHealth;
106
+ /** The engine the user/server selected, ignoring any active failover. */
107
+ private preferredEngine;
108
+ /** Candidate order: preference first, then the remaining priority entries. */
109
+ private candidateOrder;
110
+ private isCooling;
111
+ private getOrCreateState;
112
+ /** The engine currently serving traffic. */
113
+ get activeEngine(): Engine | null;
114
+ /** Engines currently in cooldown, reported on the heartbeat as `disabledEngines`. */
115
+ get disabledEngines(): DisabledEngineInfo[];
116
+ /**
117
+ * Record a retryable network failure against the active engine.
118
+ * Returns true once the threshold is reached and it should be cooled down.
119
+ */
120
+ recordNetworkFailure(): boolean;
121
+ /**
122
+ * A turn completed on `engine`: clear its backoff so a later, unrelated
123
+ * failure starts from the 5 minute base again.
124
+ */
125
+ noteEngineSuccess(): void;
126
+ /**
127
+ * Cool down the active engine and move to the next usable candidate.
128
+ *
129
+ * `probeTarget` is injected so the manager stays free of network concerns
130
+ * and unit-testable; it is only consulted before a cross-runtime switch,
131
+ * where being wrong costs a restart plus a session collapse. A failing probe
132
+ * there means the target is unreachable too, i.e. a shared-cause failure
133
+ * rather than a per-engine one, so nothing is switched.
134
+ */
135
+ switchAwayFromActive(reason: EngineFailReason, probeTarget?: (engine: Engine) => Promise<boolean>): Promise<EngineSwitchOutcome>;
136
+ private runtimeOf;
137
+ /**
138
+ * Optimistic recovery: when a better candidate (normally the user's
139
+ * preference) is out of cooldown again, take it.
140
+ *
141
+ * Only same-runtime moves happen here — a cross-runtime move back costs a
142
+ * restart, so it waits for the idle gate (see
143
+ * {@link pendingCrossRuntimeRecovery}). Either way the caller must rotate the
144
+ * session: a model change never reuses the previous history.
145
+ */
146
+ maybeRecoverEngine(): {
147
+ recovered: Engine;
148
+ } | null;
149
+ /**
150
+ * Whether a cross-runtime move back to a recovered engine is pending.
151
+ * The caller restarts only while idle so a live conversation is never cut.
152
+ */
153
+ pendingCrossRuntimeRecovery(): {
154
+ target: Engine;
155
+ } | null;
156
+ /**
157
+ * A cross-runtime failover or recovery has armed a restart of this process.
158
+ * Work claimed by the current wake must then stay in flight so the
159
+ * replacement process replays it on the new engine.
160
+ */
161
+ isRestartPending(): boolean;
162
+ /** Arm a cross-runtime recovery: persist first, then let the caller restart. */
163
+ commitCrossRuntimeRecovery(target: Engine): boolean;
164
+ /**
165
+ * Forget every cooldown and the failover selection because the owner chose
166
+ * an engine by hand (`/engine`, `switch_engine`).
167
+ *
168
+ * Only valid for callers that restart immediately afterwards: dropping a
169
+ * cross-runtime selection (e.g. back from Claude) without a restart would
170
+ * flip the effective engine while the live `SessionManager` still runs the
171
+ * old SDK. Every manual-switch caller restarts, and nothing else resets.
172
+ */
173
+ clearForManualSwitch(): void;
174
+ }
175
+ //# sourceMappingURL=engine-fallback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine-fallback.d.ts","sourceRoot":"","sources":["../../src/agent/engine-fallback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAOH,OAAO,EACL,KAAK,MAAM,EACX,KAAK,gBAAgB,EAEtB,MAAM,oBAAoB,CAAC;AAU5B,oEAAoE;AACpE,eAAO,MAAM,eAAe,EAAE,SAAS,MAAM,EAA8B,CAAC;AAQ5E;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,mBAAmB;AACrB,iFAAiF;GAC/E,mBAAmB,CAAC;AAaxB,yEAAyE;AACzE,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,gBAAgB,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,0EAA0E;AAC1E,MAAM,MAAM,mBAAmB,GAC3B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,cAAc,GAAG,cAAc,GAAG,gBAAgB,GAAG,sBAAsB,CAAA;CAAE,CAAC;AAiDvG,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI,GAAG,IAAI,CAEnF;AAED,wBAAgB,uBAAuB,IAAI,qBAAqB,GAAG,IAAI,CAEtE;AAED,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,MAAM,CAAkC;IAChD;;;OAGG;IACH,OAAO,CAAC,gBAAgB,CAAS;gBAErB,MAAM,EAAE,gBAAgB;IAMpC;;;;;OAKG;IACH,eAAe,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IAclE,0CAA0C;IAC1C,IAAI,cAAc,IAAI,MAAM,EAAE,CAE7B;IAID,OAAO,CAAC,cAAc;IAItB;;;;;;;OAOG;IACH,gBAAgB,IAAI,IAAI;IA+CxB;;;;;;;OAOG;IACH,OAAO,CAAC,mBAAmB;IAmC3B,yEAAyE;IACzE,OAAO,CAAC,eAAe;IAMvB,8EAA8E;IAC9E,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,gBAAgB;IASxB,4CAA4C;IAC5C,IAAI,YAAY,IAAI,MAAM,GAAG,IAAI,CAEhC;IAED,qFAAqF;IACrF,IAAI,eAAe,IAAI,kBAAkB,EAAE,CAa1C;IAID;;;OAGG;IACH,oBAAoB,IAAI,OAAO;IAQ/B;;;OAGG;IACH,iBAAiB,IAAI,IAAI;IAazB;;;;;;;;OAQG;IACG,oBAAoB,CACxB,MAAM,EAAE,gBAAgB,EACxB,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GACjD,OAAO,CAAC,mBAAmB,CAAC;IA0F/B,OAAO,CAAC,SAAS;IAIjB;;;;;;;;OAQG;IACH,kBAAkB,IAAI;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAiBlD;;;OAGG;IACH,2BAA2B,IAAI;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAWxD;;;;OAIG;IACH,gBAAgB,IAAI,OAAO;IAI3B,gFAAgF;IAChF,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAenD;;;;;;;;OAQG;IACH,oBAAoB,IAAI,IAAI;CAK7B"}
@@ -0,0 +1,454 @@
1
+ /**
2
+ * EngineFallbackManager — health, cooldowns and failover across engines.
3
+ *
4
+ * Failover happens between **engines** (`grok` / `gpt` / `claude`), all served
5
+ * through the single runtime provider, LLM Gateway. The provider only chooses
6
+ * the route, credentials and protocol; it takes no part in health tracking.
7
+ * Failures that belong to the shared gateway — 401/402/403, the managed key's
8
+ * token budget — are handled by the caller and never cool an engine down,
9
+ * because every engine would fail them identically. See issue #2519.
10
+ *
11
+ * ## What is and is not persisted
12
+ *
13
+ * The selected failover engine lives in `model-provider`'s module state, not
14
+ * in `config.json`: `engineOverride` is the *user's* preference and must
15
+ * survive a failover untouched. But degrading to Claude changes the agent
16
+ * runtime and therefore requires a process restart — so the cooldown table is
17
+ * mirrored to `<configDir>/engine-health.json`. Without that, the replacement
18
+ * process would come up with an empty table, re-select the dead engine, and
19
+ * restart again in a loop.
20
+ *
21
+ * ## Priority
22
+ *
23
+ * The candidate order is the user's preference first, then the remaining
24
+ * engines in priority order. Putting the preference first is what makes
25
+ * recovery automatic: as soon as its cooldown lapses it is picked again, with
26
+ * no separate "switch back" code path.
27
+ *
28
+ * The priority comes from the heartbeat's `llmEnginePriority` when the server
29
+ * sends it, and from {@link ENGINE_PRIORITY} otherwise — so an older server, or
30
+ * one that never sets the field, leaves failover fully working.
31
+ *
32
+ * Cooldowns:
33
+ * - 5 min, doubling per consecutive failure, capped at 30 min
34
+ * - reset to the base on the first success
35
+ */
36
+ import fs from "node:fs";
37
+ import { z } from "zod";
38
+ import path from "node:path";
39
+ import { getConfigDir, isWorkerThreadContext } from "../config/index.js";
40
+ import { EngineSchema, } from "../config/types.js";
41
+ import { canUseEngine, getEffectiveEngine, getEffectiveRoute, getFallbackEngine, setFallbackEngine, } from "./model-provider.js";
42
+ import { logger } from "../logger.js";
43
+ /** Engine failover order used when the server does not send one. */
44
+ export const ENGINE_PRIORITY = ["grok", "gpt", "claude"];
45
+ const ENGINE_COOLDOWN_BASE_MS = 5 * 60_000; // 5 minutes
46
+ const ENGINE_COOLDOWN_MAX_MS = 30 * 60_000; // 30 minutes
47
+ const NETWORK_FAILURE_THRESHOLD = 3;
48
+ const ENGINE_HEALTH_FILE = "engine-health.json";
49
+ const ENGINE_HEALTH_VERSION = 1;
50
+ const ENGINE_FAIL_REASONS = ["network", "silence_exhausted", "model_unavailable"];
51
+ /**
52
+ * Validates `engine-health.json`. Anything that does not match — including an
53
+ * older `version` — is discarded rather than trusted, because this file drives
54
+ * a restart decision.
55
+ */
56
+ const EngineHealthFileSchema = z.object({
57
+ version: z.literal(ENGINE_HEALTH_VERSION),
58
+ activeFallback: EngineSchema.nullish().transform((v) => v ?? null),
59
+ engines: z.record(z.string(), z.object({
60
+ until: z.number(),
61
+ consecutiveFailures: z.number().int().nonnegative().catch(1),
62
+ // An unknown reason (e.g. from a newer build) drops the reason, not the
63
+ // whole cooldown entry.
64
+ reason: z.enum(ENGINE_FAIL_REASONS).optional().catch(undefined),
65
+ })).default({}),
66
+ });
67
+ function emptyState() {
68
+ return {
69
+ disabledUntil: 0,
70
+ consecutiveFailures: 0,
71
+ consecutiveNetworkFailures: 0,
72
+ disableReason: null,
73
+ };
74
+ }
75
+ /**
76
+ * The manager owned by the running agent loop.
77
+ *
78
+ * `/engine` and the `switch_engine` remote command both land in
79
+ * `applyEngineSwitchTarget()`, which is a pure config mutation with no access
80
+ * to the loop's wiring. It needs to drop the failover state so a manual choice
81
+ * is not immediately overridden — hence this small registry rather than
82
+ * threading the manager through every command handler.
83
+ */
84
+ let activeManager = null;
85
+ export function setActiveEngineFallback(manager) {
86
+ activeManager = manager;
87
+ }
88
+ export function getActiveEngineFallback() {
89
+ return activeManager;
90
+ }
91
+ export class EngineFallbackManager {
92
+ config;
93
+ priorityList = [];
94
+ states = new Map();
95
+ /**
96
+ * Cross-runtime failover is blocked while a restart is already pending, so
97
+ * a second failure inside the shutdown window cannot schedule a second one.
98
+ */
99
+ restartScheduled = false;
100
+ constructor(config) {
101
+ this.config = config;
102
+ }
103
+ // ---------------------------------------------------------------- priority
104
+ /**
105
+ * Adopt the server's `llmEnginePriority`. Unknown and duplicate entries are
106
+ * dropped; a missing or unusable list falls back to {@link ENGINE_PRIORITY}.
107
+ * Never let a bad list disable failover — that is exactly the silent no-op
108
+ * this class used to have.
109
+ */
110
+ setPriorityList(serverPriority) {
111
+ const engines = [];
112
+ for (const candidate of serverPriority ?? []) {
113
+ const parsed = EngineSchema.safeParse(candidate);
114
+ if (parsed.success && !engines.includes(parsed.data))
115
+ engines.push(parsed.data);
116
+ }
117
+ const next = engines.length > 0 ? engines : [...ENGINE_PRIORITY];
118
+ const changed = next.join(",") !== this.priorityList.join(",");
119
+ this.priorityList = next;
120
+ if (changed && engines.length > 0) {
121
+ logger.system(`[engine-fallback] Engine priority from server: [${next.join(", ")}]`);
122
+ }
123
+ }
124
+ /** Engine priority currently in force. */
125
+ get enginePriority() {
126
+ return this.priorityList.length > 0 ? this.priorityList : [...ENGINE_PRIORITY];
127
+ }
128
+ // ------------------------------------------------------------- persistence
129
+ healthFilePath() {
130
+ return path.join(getConfigDir(), ENGINE_HEALTH_FILE);
131
+ }
132
+ /**
133
+ * Load the persisted cooldown table and re-apply a still-valid failover
134
+ * selection. Must run before `prepareEngineSwitchBoundary()` so the session
135
+ * boundary is evaluated against the engine we will actually start on.
136
+ *
137
+ * A missing, corrupt or version-mismatched file is treated as "no state":
138
+ * startup must never be blocked by it.
139
+ */
140
+ initEngineHealth() {
141
+ const filePath = this.healthFilePath();
142
+ let parsed;
143
+ try {
144
+ if (!fs.existsSync(filePath))
145
+ return;
146
+ parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
147
+ }
148
+ catch (err) {
149
+ logger.warn(`[engine-fallback] Ignoring unreadable engine-health.json: ${err instanceof Error ? err.message : String(err)}`);
150
+ return;
151
+ }
152
+ const snapshot = EngineHealthFileSchema.safeParse(parsed);
153
+ if (!snapshot.success) {
154
+ logger.warn("[engine-fallback] Ignoring malformed or version-mismatched engine-health.json");
155
+ return;
156
+ }
157
+ const now = Date.now();
158
+ for (const [name, entry] of Object.entries(snapshot.data.engines)) {
159
+ const engine = EngineSchema.safeParse(name);
160
+ if (!engine.success)
161
+ continue;
162
+ if (entry.until <= now)
163
+ continue; // expired — drop it
164
+ const state = this.getOrCreateState(engine.data);
165
+ state.disabledUntil = entry.until;
166
+ state.consecutiveFailures = entry.consecutiveFailures;
167
+ state.disableReason = entry.reason ?? null;
168
+ }
169
+ const restored = snapshot.data.activeFallback;
170
+ if (restored) {
171
+ // Only honour a restored selection that is still usable; otherwise the
172
+ // normal selection path runs on the next failure.
173
+ if (!this.isCooling(restored) && canUseEngine(this.config, restored)) {
174
+ setFallbackEngine(restored);
175
+ logger.system(`[engine-fallback] Restored failover engine "${restored}" from disk`);
176
+ }
177
+ }
178
+ const cooling = this.disabledEngines;
179
+ if (cooling.length > 0) {
180
+ logger.system(`[engine-fallback] Restored cooldowns: ${cooling.map((c) => `${c.engine}→${c.disabledUntil}`).join(", ")}`);
181
+ }
182
+ }
183
+ /**
184
+ * Atomically write the cooldown table. Returns false when it did not land.
185
+ *
186
+ * `activeFallback` is passed explicitly rather than read from module state:
187
+ * a cross-runtime switch records the engine the *next* process should start
188
+ * on while this process deliberately keeps serving the old one until the
189
+ * restart lands.
190
+ */
191
+ persistEngineHealth(activeFallback = getFallbackEngine()) {
192
+ const filePath = this.healthFilePath();
193
+ const tempPath = `${filePath}.tmp-${process.pid}`;
194
+ const engines = {};
195
+ const now = Date.now();
196
+ for (const [engine, state] of this.states) {
197
+ if (state.disabledUntil <= now)
198
+ continue;
199
+ engines[engine] = {
200
+ until: state.disabledUntil,
201
+ consecutiveFailures: state.consecutiveFailures,
202
+ ...(state.disableReason ? { reason: state.disableReason } : {}),
203
+ };
204
+ }
205
+ const payload = {
206
+ version: ENGINE_HEALTH_VERSION,
207
+ updatedAt: new Date().toISOString(),
208
+ activeFallback,
209
+ engines,
210
+ };
211
+ try {
212
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
213
+ fs.writeFileSync(tempPath, JSON.stringify(payload, null, 2), "utf-8");
214
+ fs.renameSync(tempPath, filePath);
215
+ return true;
216
+ }
217
+ catch (err) {
218
+ try {
219
+ fs.unlinkSync(tempPath);
220
+ }
221
+ catch { /* best-effort */ }
222
+ logger.warn(`[engine-fallback] Failed to persist engine health: ${err instanceof Error ? err.message : String(err)}`);
223
+ return false;
224
+ }
225
+ }
226
+ // ------------------------------------------------------------------ state
227
+ /** The engine the user/server selected, ignoring any active failover. */
228
+ preferredEngine() {
229
+ return this.config.engineOverride
230
+ ?? getEffectiveRoute(this.config, { ignoreFallback: true })?.engine
231
+ ?? null;
232
+ }
233
+ /** Candidate order: preference first, then the remaining priority entries. */
234
+ candidateOrder() {
235
+ const preference = this.preferredEngine();
236
+ const rest = this.enginePriority.filter((e) => e !== preference);
237
+ return preference ? [preference, ...rest] : rest;
238
+ }
239
+ isCooling(engine) {
240
+ const state = this.states.get(engine);
241
+ return !!state && state.disabledUntil > Date.now();
242
+ }
243
+ getOrCreateState(engine) {
244
+ let state = this.states.get(engine);
245
+ if (!state) {
246
+ state = emptyState();
247
+ this.states.set(engine, state);
248
+ }
249
+ return state;
250
+ }
251
+ /** The engine currently serving traffic. */
252
+ get activeEngine() {
253
+ return getEffectiveEngine(this.config);
254
+ }
255
+ /** Engines currently in cooldown, reported on the heartbeat as `disabledEngines`. */
256
+ get disabledEngines() {
257
+ const now = Date.now();
258
+ const result = [];
259
+ for (const [engine, state] of this.states) {
260
+ if (state.disabledUntil > now) {
261
+ result.push({
262
+ engine,
263
+ reason: state.disableReason ?? "network",
264
+ disabledUntil: new Date(state.disabledUntil).toISOString(),
265
+ });
266
+ }
267
+ }
268
+ return result;
269
+ }
270
+ // ------------------------------------------------------------- failure I/O
271
+ /**
272
+ * Record a retryable network failure against the active engine.
273
+ * Returns true once the threshold is reached and it should be cooled down.
274
+ */
275
+ recordNetworkFailure() {
276
+ const engine = this.activeEngine;
277
+ if (!engine)
278
+ return false;
279
+ const state = this.getOrCreateState(engine);
280
+ state.consecutiveNetworkFailures++;
281
+ return state.consecutiveNetworkFailures >= NETWORK_FAILURE_THRESHOLD;
282
+ }
283
+ /**
284
+ * A turn completed on `engine`: clear its backoff so a later, unrelated
285
+ * failure starts from the 5 minute base again.
286
+ */
287
+ noteEngineSuccess() {
288
+ const engine = this.activeEngine;
289
+ if (!engine)
290
+ return;
291
+ const state = this.states.get(engine);
292
+ if (!state)
293
+ return;
294
+ state.consecutiveNetworkFailures = 0;
295
+ state.consecutiveFailures = 0;
296
+ state.disabledUntil = 0;
297
+ state.disableReason = null;
298
+ }
299
+ // ---------------------------------------------------------------- failover
300
+ /**
301
+ * Cool down the active engine and move to the next usable candidate.
302
+ *
303
+ * `probeTarget` is injected so the manager stays free of network concerns
304
+ * and unit-testable; it is only consulted before a cross-runtime switch,
305
+ * where being wrong costs a restart plus a session collapse. A failing probe
306
+ * there means the target is unreachable too, i.e. a shared-cause failure
307
+ * rather than a per-engine one, so nothing is switched.
308
+ */
309
+ async switchAwayFromActive(reason, probeTarget) {
310
+ const current = this.activeEngine;
311
+ if (!current)
312
+ return { kind: "none", why: "no-candidate" };
313
+ if (this.restartScheduled) {
314
+ // A restart is already armed and the replacement process will pick its
315
+ // engine from `engine-health.json`. Rerouting now would only diverge
316
+ // from what was persisted.
317
+ return { kind: "none", why: "no-candidate" };
318
+ }
319
+ const state = this.getOrCreateState(current);
320
+ const cooldownMs = Math.min(ENGINE_COOLDOWN_BASE_MS * 2 ** state.consecutiveFailures, ENGINE_COOLDOWN_MAX_MS);
321
+ const previousUntil = state.disabledUntil;
322
+ const previousFailures = state.consecutiveFailures;
323
+ state.disabledUntil = Date.now() + cooldownMs;
324
+ state.consecutiveFailures += 1;
325
+ state.consecutiveNetworkFailures = 0;
326
+ state.disableReason = reason;
327
+ const rollback = () => {
328
+ state.disabledUntil = previousUntil;
329
+ state.consecutiveFailures = previousFailures;
330
+ };
331
+ const target = this.candidateOrder().find((engine) => engine !== current && !this.isCooling(engine) && canUseEngine(this.config, engine));
332
+ if (!target) {
333
+ logger.system(`[engine-fallback] ${current} cooled for ${cooldownMs / 60_000}min (${reason}); ` +
334
+ "no usable alternate engine — staying put");
335
+ this.persistEngineHealth();
336
+ return { kind: "none", why: "no-candidate" };
337
+ }
338
+ const crossRuntime = this.runtimeOf(current) !== this.runtimeOf(target);
339
+ if (!crossRuntime) {
340
+ setFallbackEngine(target === this.preferredEngine() ? null : target);
341
+ this.persistEngineHealth();
342
+ logger.system(`[engine-fallback] ${current} → ${target} (${reason}, cooled ${cooldownMs / 60_000}min) — same runtime, no restart (the caller rotates the session)`);
343
+ return { kind: "hot", from: current, to: target };
344
+ }
345
+ // Cross-runtime: the agent SDK and transcript store both change, so this
346
+ // only takes effect after a restart.
347
+ if (isWorkerThreadContext()) {
348
+ // A worker is a forked child; `scheduleRestart()` would either bounce the
349
+ // whole app via its process manager or exit just this worker. Neither is
350
+ // a behaviour we want to trigger from a thread, so stay put.
351
+ rollback();
352
+ logger.system(`[engine-fallback] ${current} → ${target} needs a cross-runtime restart; skipped inside a thread worker`);
353
+ return { kind: "none", why: "worker-cross-runtime" };
354
+ }
355
+ if (probeTarget && !(await probeTarget(target))) {
356
+ rollback();
357
+ logger.system(`[engine-fallback] ${target} failed its probe as well — treating as a shared-cause failure, not restarting`);
358
+ return { kind: "none", why: "probe-failed" };
359
+ }
360
+ // Deliberately not `setFallbackEngine(target)`: this process keeps serving
361
+ // the old runtime until the restart lands, and flipping the effective
362
+ // engine now would make `SessionManager` (built for the old runtime)
363
+ // resolve models for the new one.
364
+ if (!this.persistEngineHealth(target === this.preferredEngine() ? null : target)) {
365
+ // Restarting without a durable cooldown table would re-select the dead
366
+ // engine and restart again. Abort the switch instead.
367
+ rollback();
368
+ logger.err(`[engine-fallback] Refusing cross-runtime switch to ${target}: engine health could not be persisted`);
369
+ return { kind: "none", why: "persist-failed" };
370
+ }
371
+ this.restartScheduled = true;
372
+ logger.system(`[engine-fallback] ${current} → ${target} (${reason}) — cross-runtime, restarting`);
373
+ return { kind: "restart", from: current, to: target };
374
+ }
375
+ runtimeOf(engine) {
376
+ return getEffectiveRoute(this.config, { engine })?.runtime ?? null;
377
+ }
378
+ /**
379
+ * Optimistic recovery: when a better candidate (normally the user's
380
+ * preference) is out of cooldown again, take it.
381
+ *
382
+ * Only same-runtime moves happen here — a cross-runtime move back costs a
383
+ * restart, so it waits for the idle gate (see
384
+ * {@link pendingCrossRuntimeRecovery}). Either way the caller must rotate the
385
+ * session: a model change never reuses the previous history.
386
+ */
387
+ maybeRecoverEngine() {
388
+ const current = this.activeEngine;
389
+ // A restart is already armed and the next process will pick its own engine
390
+ // from disk; swapping now would only diverge from what was persisted.
391
+ if (!current || this.restartScheduled)
392
+ return null;
393
+ const target = this.candidateOrder().find((engine) => !this.isCooling(engine) && canUseEngine(this.config, engine));
394
+ if (!target || target === current)
395
+ return null;
396
+ if (this.runtimeOf(target) !== this.runtimeOf(current))
397
+ return null;
398
+ setFallbackEngine(target === this.preferredEngine() ? null : target);
399
+ this.persistEngineHealth();
400
+ logger.system(`[engine-fallback] ${current} → ${target} — recovered, same runtime`);
401
+ return { recovered: target };
402
+ }
403
+ /**
404
+ * Whether a cross-runtime move back to a recovered engine is pending.
405
+ * The caller restarts only while idle so a live conversation is never cut.
406
+ */
407
+ pendingCrossRuntimeRecovery() {
408
+ const current = this.activeEngine;
409
+ if (!current || this.restartScheduled || isWorkerThreadContext())
410
+ return null;
411
+ const target = this.candidateOrder().find((engine) => !this.isCooling(engine) && canUseEngine(this.config, engine));
412
+ if (!target || target === current)
413
+ return null;
414
+ if (this.runtimeOf(target) === this.runtimeOf(current))
415
+ return null;
416
+ return { target };
417
+ }
418
+ /**
419
+ * A cross-runtime failover or recovery has armed a restart of this process.
420
+ * Work claimed by the current wake must then stay in flight so the
421
+ * replacement process replays it on the new engine.
422
+ */
423
+ isRestartPending() {
424
+ return this.restartScheduled;
425
+ }
426
+ /** Arm a cross-runtime recovery: persist first, then let the caller restart. */
427
+ commitCrossRuntimeRecovery(target) {
428
+ if (this.restartScheduled)
429
+ return false;
430
+ // As above: persist the next process's selection without activating it here.
431
+ if (!this.persistEngineHealth(target === this.preferredEngine() ? null : target)) {
432
+ logger.err(`[engine-fallback] Refusing cross-runtime recovery to ${target}: engine health could not be persisted`);
433
+ return false;
434
+ }
435
+ this.restartScheduled = true;
436
+ return true;
437
+ }
438
+ // --------------------------------------------------------------- lifecycle
439
+ /**
440
+ * Forget every cooldown and the failover selection because the owner chose
441
+ * an engine by hand (`/engine`, `switch_engine`).
442
+ *
443
+ * Only valid for callers that restart immediately afterwards: dropping a
444
+ * cross-runtime selection (e.g. back from Claude) without a restart would
445
+ * flip the effective engine while the live `SessionManager` still runs the
446
+ * old SDK. Every manual-switch caller restarts, and nothing else resets.
447
+ */
448
+ clearForManualSwitch() {
449
+ this.states.clear();
450
+ setFallbackEngine(null);
451
+ this.persistEngineHealth(null);
452
+ }
453
+ }
454
+ //# sourceMappingURL=engine-fallback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine-fallback.js","sourceRoot":"","sources":["../../src/agent/engine-fallback.ts"],"names":[],"mappings":"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"}