visionclaw 0.1.188-beta.3 → 0.1.188-beta.5
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/agent/heartbeat-manager.d.ts +86 -20
- package/dist/agent/heartbeat-manager.d.ts.map +1 -1
- package/dist/agent/heartbeat-manager.js +231 -105
- package/dist/agent/heartbeat-manager.js.map +1 -1
- package/dist/agent/heartbeat-watchdog.d.ts +59 -0
- package/dist/agent/heartbeat-watchdog.d.ts.map +1 -0
- package/dist/agent/heartbeat-watchdog.js +240 -0
- package/dist/agent/heartbeat-watchdog.js.map +1 -0
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +37 -12
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/subscription-gate.d.ts.map +1 -1
- package/dist/agent/subscription-gate.js +21 -8
- package/dist/agent/subscription-gate.js.map +1 -1
- package/dist/onboarding/prepare-mac.d.ts +1 -0
- package/dist/onboarding/prepare-mac.d.ts.map +1 -1
- package/dist/onboarding/prepare-mac.js +88 -0
- package/dist/onboarding/prepare-mac.js.map +1 -1
- package/dist/service/launchd.d.ts +0 -26
- package/dist/service/launchd.d.ts.map +1 -1
- package/dist/service/launchd.js +0 -227
- package/dist/service/launchd.js.map +1 -1
- package/dist/tools/email.d.ts +1 -1
- package/dist-agent/bundle.cjs +465 -357
- package/package.json +1 -1
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HeartbeatManager —
|
|
3
|
-
* the onboarding service: startup validation, per-wake heartbeats, busy
|
|
4
|
-
* keep-alive intervals, and end-of-cycle status updates.
|
|
2
|
+
* HeartbeatManager — owns the always-on heartbeat lifecycle.
|
|
5
3
|
*
|
|
6
|
-
*
|
|
4
|
+
* Heartbeats are intentionally decoupled from the wake/sleep cycle so the
|
|
5
|
+
* onboarding service sees an accurate liveness signal even during long
|
|
6
|
+
* sleeps (notably the 2-hour overnight interval). The runtime loop is a
|
|
7
|
+
* single recursive `setTimeout` whose cadence adapts to `agentState.busy`:
|
|
8
|
+
*
|
|
9
|
+
* - busy === true -> 30s (preserves dashboard precision during work)
|
|
10
|
+
* - busy === false -> 5min (idle keep-alive)
|
|
11
|
+
*
|
|
12
|
+
* Every heartbeat (startup, runtime tick, subscription gate, nudge)
|
|
13
|
+
* funnels through one private `sendHeartbeat()` method that owns the
|
|
14
|
+
* mutex, payload build, and full response handling (subscription,
|
|
15
|
+
* billing, shared keys, credential bundle, pending commands, ack
|
|
16
|
+
* confirm/requeue). This eliminates the previously scattered duplicate
|
|
17
|
+
* response-handling blocks in init/onWake/sendHeartbeatForSubscription.
|
|
18
|
+
*
|
|
19
|
+
* The local on-disk heartbeat file is a separate liveness signal
|
|
20
|
+
* consumed by the in-process child watchdog (see heartbeat-watchdog.ts).
|
|
21
|
+
* It is refreshed at the top of every send, so it stays fresh even when
|
|
22
|
+
* cloud heartbeat credentials are absent.
|
|
7
23
|
*/
|
|
8
24
|
import type { VisionClawConfig } from "../config/types.js";
|
|
9
25
|
import type { ChannelManager } from "../channels/manager.js";
|
|
@@ -27,7 +43,6 @@ export interface HeartbeatManagerOptions {
|
|
|
27
43
|
export declare class HeartbeatManager {
|
|
28
44
|
private client;
|
|
29
45
|
readonly commandDispatcher: CommandDispatcher;
|
|
30
|
-
private busyInterval;
|
|
31
46
|
private readonly config;
|
|
32
47
|
private channelManager;
|
|
33
48
|
private _lastSubscriptionStatus;
|
|
@@ -40,9 +55,41 @@ export declare class HeartbeatManager {
|
|
|
40
55
|
* building the next payload.
|
|
41
56
|
*/
|
|
42
57
|
private readonly credentialBundle;
|
|
58
|
+
/** Recursive setTimeout handle. `null` when the loop is not armed. */
|
|
59
|
+
private runtimeTimer;
|
|
60
|
+
/** Provider for current `agentState.busy`. Set by `startRuntime`. */
|
|
61
|
+
private getBusy;
|
|
62
|
+
/**
|
|
63
|
+
* Mutex tail — every `sendHeartbeat` awaits this then replaces it
|
|
64
|
+
* with the new in-flight promise so concurrent callers serialise.
|
|
65
|
+
* Initialised to a resolved promise so the first send doesn't block.
|
|
66
|
+
*/
|
|
67
|
+
private sendMutex;
|
|
43
68
|
constructor(config: VisionClawConfig, options?: HeartbeatManagerOptions);
|
|
44
69
|
/** Build a base heartbeat payload including owner info for server-side backfill. */
|
|
45
70
|
private buildPayload;
|
|
71
|
+
/**
|
|
72
|
+
* The single place every heartbeat goes through.
|
|
73
|
+
*
|
|
74
|
+
* - Always refreshes the on-disk heartbeat file (cheap, even when no
|
|
75
|
+
* onboarding-service client is configured).
|
|
76
|
+
* - Mutex-serialises concurrent callers (busy ticks, idle ticks,
|
|
77
|
+
* subscription gate polls, nudges) so command acks cannot be
|
|
78
|
+
* double-submitted and response handlers cannot interleave.
|
|
79
|
+
* - In `strict` mode, errors are rethrown after re-queuing acks so
|
|
80
|
+
* `init()` can surface auth failures and disable the client. In
|
|
81
|
+
* non-strict mode errors are caught and acks are requeued for the
|
|
82
|
+
* next attempt, identical to the previous best-effort behaviour.
|
|
83
|
+
* - Does NOT wake the main loop. The `notify` command handler in
|
|
84
|
+
* loop.ts is responsible for waking after it enqueues the message;
|
|
85
|
+
* waking from inside `sendHeartbeat` would race that handler.
|
|
86
|
+
*/
|
|
87
|
+
private sendHeartbeat;
|
|
88
|
+
/**
|
|
89
|
+
* Body of a single heartbeat send, executed under the mutex. Split
|
|
90
|
+
* out from `sendHeartbeat` so the locking shell is a thin wrapper.
|
|
91
|
+
*/
|
|
92
|
+
private runSend;
|
|
46
93
|
/**
|
|
47
94
|
* Initialize the heartbeat client and validate the API key.
|
|
48
95
|
* Should be called once at startup. If validation fails, heartbeats
|
|
@@ -67,30 +114,49 @@ export declare class HeartbeatManager {
|
|
|
67
114
|
currentPeriodEnd: string | null;
|
|
68
115
|
portalUrl?: string;
|
|
69
116
|
} | null>;
|
|
70
|
-
/**
|
|
71
|
-
* Send a heartbeat at the start of each wake cycle.
|
|
72
|
-
* Dispatches any pending commands from the server and applies shared keys.
|
|
73
|
-
*/
|
|
74
|
-
onWake(busy: boolean): Promise<void>;
|
|
75
117
|
/**
|
|
76
118
|
* Send a single heartbeat and return the subscription status from the response.
|
|
77
119
|
* Used by the subscription gate for fast-polling (10s interval).
|
|
78
120
|
*/
|
|
79
121
|
sendHeartbeatForSubscription(): Promise<SubscriptionStatus>;
|
|
80
122
|
/**
|
|
81
|
-
*
|
|
123
|
+
* Arm the always-on runtime heartbeat loop.
|
|
124
|
+
*
|
|
125
|
+
* Called once at process startup, after `agentState` is initialised so
|
|
126
|
+
* the `getBusy` provider can read the live busy flag. Does NOT fire
|
|
127
|
+
* an immediate heartbeat — `init()` already sent the strict startup
|
|
128
|
+
* heartbeat, and an immediate runtime tick would just duplicate it.
|
|
129
|
+
* State-change latency is covered by `nudge()`, not by an immediate
|
|
130
|
+
* fire.
|
|
131
|
+
*
|
|
132
|
+
* Calling `startRuntime` while the loop is already armed is a no-op
|
|
133
|
+
* apart from refreshing the `getBusy` reference; this makes the
|
|
134
|
+
* subscription gate's `stop -> poll -> start` pattern safe even if
|
|
135
|
+
* something inside the gate accidentally calls `startRuntime` again.
|
|
136
|
+
*/
|
|
137
|
+
startRuntime(getBusy: () => boolean): void;
|
|
138
|
+
/**
|
|
139
|
+
* Stop the runtime heartbeat loop and wait for any in-flight send to
|
|
140
|
+
* finish. Used by graceful shutdown and by the subscription gate
|
|
141
|
+
* before it takes over with its own 10s polling cadence.
|
|
82
142
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* -
|
|
87
|
-
*
|
|
143
|
+
* IMPORTANT: clearing the timer is not enough on its own — a tick
|
|
144
|
+
* that is already mid-send would still re-arm itself when it
|
|
145
|
+
* completes (see `runRuntimeTick`). We null `getBusy` so the
|
|
146
|
+
* `runRuntimeTick` re-arm guard short-circuits, and `nudge()`
|
|
147
|
+
* (which keys off `getBusy`) becomes a no-op until `startRuntime`
|
|
148
|
+
* is called again. `startRuntime` is the only caller that brings
|
|
149
|
+
* `getBusy` back, so the lifecycle is start/stop-symmetric.
|
|
88
150
|
*/
|
|
89
|
-
|
|
151
|
+
stopRuntime(): Promise<void>;
|
|
90
152
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
153
|
+
* Fire an out-of-band heartbeat right now via the same mutex path
|
|
154
|
+
* the runtime loop uses. Used by the main loop on busy<->idle
|
|
155
|
+
* transitions to flatten dashboard latency without waiting for the
|
|
156
|
+
* next scheduled tick (up to 5min in the idle case).
|
|
93
157
|
*/
|
|
94
|
-
|
|
158
|
+
nudge(): void;
|
|
159
|
+
private scheduleNextTick;
|
|
160
|
+
private runRuntimeTick;
|
|
95
161
|
}
|
|
96
162
|
//# sourceMappingURL=heartbeat-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"heartbeat-manager.d.ts","sourceRoot":"","sources":["../../src/agent/heartbeat-manager.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"heartbeat-manager.d.ts","sourceRoot":"","sources":["../../src/agent/heartbeat-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAEL,KAAK,oBAAoB,EAGzB,KAAK,kBAAkB,EACxB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,OAAO,EAA4D,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAavH,MAAM,WAAW,uBAAuB;IACtC;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC9C;AA+CD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAwC;IACtD,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAAC;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,uBAAuB,CAA8B;IAC7D,OAAO,CAAC,iBAAiB,CAA8B;IACvD,OAAO,CAAC,mBAAmB,CAAqB;IAChD;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA0B;IAG3D,sEAAsE;IACtE,OAAO,CAAC,YAAY,CAA8C;IAClE,qEAAqE;IACrE,OAAO,CAAC,OAAO,CAAgC;IAC/C;;;;OAIG;IACH,OAAO,CAAC,SAAS,CAAoC;gBAEzC,MAAM,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,uBAAuB;IAYvE,oFAAoF;IACpF,OAAO,CAAC,YAAY;IAoCpB;;;;;;;;;;;;;;;OAeG;YACW,aAAa;IAsB3B;;;OAGG;YACW,OAAO;IAsErB;;;;OAIG;IACG,IAAI,CACR,cAAc,EAAE,cAAc,EAC9B,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,YAAY,EAAE,kBAAkB,GAAG,IAAI,GACtC,OAAO,CAAC,IAAI,CAAC;IAoEhB,4CAA4C;IAC5C,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,uEAAuE;IACvE,IAAI,sBAAsB,IAAI,kBAAkB,CAE/C;IAED,sFAAsF;IACtF,IAAI,gBAAgB,IAAI,oBAAoB,EAAE,CAE7C;IAED,wFAAwF;IACxF,IAAI,kBAAkB,IAAI,MAAM,GAAG,SAAS,CAE3C;IAED;;;OAGG;IACG,gBAAgB,IAAI,OAAO,CAAC;QAChC,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;QAChC,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,IAAI,CAAC;IAKT;;;OAGG;IACG,4BAA4B,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAMjE;;;;;;;;;;;;;;OAcG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,OAAO,GAAG,IAAI;IAY1C;;;;;;;;;;;;OAYG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAYlC;;;;;OAKG;IACH,KAAK,IAAI,IAAI;IAOb,OAAO,CAAC,gBAAgB;YAaV,cAAc;CAY7B"}
|
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HeartbeatManager —
|
|
3
|
-
* the onboarding service: startup validation, per-wake heartbeats, busy
|
|
4
|
-
* keep-alive intervals, and end-of-cycle status updates.
|
|
2
|
+
* HeartbeatManager — owns the always-on heartbeat lifecycle.
|
|
5
3
|
*
|
|
6
|
-
*
|
|
4
|
+
* Heartbeats are intentionally decoupled from the wake/sleep cycle so the
|
|
5
|
+
* onboarding service sees an accurate liveness signal even during long
|
|
6
|
+
* sleeps (notably the 2-hour overnight interval). The runtime loop is a
|
|
7
|
+
* single recursive `setTimeout` whose cadence adapts to `agentState.busy`:
|
|
8
|
+
*
|
|
9
|
+
* - busy === true -> 30s (preserves dashboard precision during work)
|
|
10
|
+
* - busy === false -> 5min (idle keep-alive)
|
|
11
|
+
*
|
|
12
|
+
* Every heartbeat (startup, runtime tick, subscription gate, nudge)
|
|
13
|
+
* funnels through one private `sendHeartbeat()` method that owns the
|
|
14
|
+
* mutex, payload build, and full response handling (subscription,
|
|
15
|
+
* billing, shared keys, credential bundle, pending commands, ack
|
|
16
|
+
* confirm/requeue). This eliminates the previously scattered duplicate
|
|
17
|
+
* response-handling blocks in init/onWake/sendHeartbeatForSubscription.
|
|
18
|
+
*
|
|
19
|
+
* The local on-disk heartbeat file is a separate liveness signal
|
|
20
|
+
* consumed by the in-process child watchdog (see heartbeat-watchdog.ts).
|
|
21
|
+
* It is refreshed at the top of every send, so it stays fresh even when
|
|
22
|
+
* cloud heartbeat credentials are absent.
|
|
7
23
|
*/
|
|
8
24
|
import { OnboardingServiceClient, } from "../onboarding/onboarding-service-client.js";
|
|
9
25
|
import { CommandDispatcher } from "./command-dispatcher.js";
|
|
@@ -20,7 +36,15 @@ import { resetArchiveConfigCache } from "./image-archive.js";
|
|
|
20
36
|
import { CredentialBundleHandler } from "./credential-bundle-handler.js";
|
|
21
37
|
import { writeHeartbeatFile } from "./heartbeat-file.js";
|
|
22
38
|
import { logger } from "../logger.js";
|
|
39
|
+
/** Cadence while the agent is processing a wake cycle. */
|
|
23
40
|
const BUSY_HEARTBEAT_INTERVAL_MS = 30_000;
|
|
41
|
+
/**
|
|
42
|
+
* Cadence while the agent is idle (sleeping between wake cycles or
|
|
43
|
+
* waiting on input). Sized so two missed idle heartbeats + slack stay
|
|
44
|
+
* inside the server's `HEARTBEAT_ONLINE_MS` threshold (10 min) — see
|
|
45
|
+
* `services/onboarding/src/lib/db.ts` for the corresponding constant.
|
|
46
|
+
*/
|
|
47
|
+
const IDLE_HEARTBEAT_INTERVAL_MS = 5 * 60_000;
|
|
24
48
|
function applySharedKeysFromHeartbeat(config, _channelManager, sharedKeys) {
|
|
25
49
|
if (!sharedKeys)
|
|
26
50
|
return false;
|
|
@@ -44,7 +68,6 @@ function applySharedKeysFromHeartbeat(config, _channelManager, sharedKeys) {
|
|
|
44
68
|
export class HeartbeatManager {
|
|
45
69
|
client = null;
|
|
46
70
|
commandDispatcher;
|
|
47
|
-
busyInterval = null;
|
|
48
71
|
config;
|
|
49
72
|
channelManager = null;
|
|
50
73
|
_lastSubscriptionStatus = "skip";
|
|
@@ -57,6 +80,17 @@ export class HeartbeatManager {
|
|
|
57
80
|
* building the next payload.
|
|
58
81
|
*/
|
|
59
82
|
credentialBundle;
|
|
83
|
+
// ── Runtime loop state ──
|
|
84
|
+
/** Recursive setTimeout handle. `null` when the loop is not armed. */
|
|
85
|
+
runtimeTimer = null;
|
|
86
|
+
/** Provider for current `agentState.busy`. Set by `startRuntime`. */
|
|
87
|
+
getBusy = null;
|
|
88
|
+
/**
|
|
89
|
+
* Mutex tail — every `sendHeartbeat` awaits this then replaces it
|
|
90
|
+
* with the new in-flight promise so concurrent callers serialise.
|
|
91
|
+
* Initialised to a resolved promise so the first send doesn't block.
|
|
92
|
+
*/
|
|
93
|
+
sendMutex = Promise.resolve();
|
|
60
94
|
constructor(config, options) {
|
|
61
95
|
this.config = config;
|
|
62
96
|
this.credentialBundle = new CredentialBundleHandler({
|
|
@@ -107,6 +141,109 @@ export class HeartbeatManager {
|
|
|
107
141
|
}
|
|
108
142
|
return payload;
|
|
109
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* The single place every heartbeat goes through.
|
|
146
|
+
*
|
|
147
|
+
* - Always refreshes the on-disk heartbeat file (cheap, even when no
|
|
148
|
+
* onboarding-service client is configured).
|
|
149
|
+
* - Mutex-serialises concurrent callers (busy ticks, idle ticks,
|
|
150
|
+
* subscription gate polls, nudges) so command acks cannot be
|
|
151
|
+
* double-submitted and response handlers cannot interleave.
|
|
152
|
+
* - In `strict` mode, errors are rethrown after re-queuing acks so
|
|
153
|
+
* `init()` can surface auth failures and disable the client. In
|
|
154
|
+
* non-strict mode errors are caught and acks are requeued for the
|
|
155
|
+
* next attempt, identical to the previous best-effort behaviour.
|
|
156
|
+
* - Does NOT wake the main loop. The `notify` command handler in
|
|
157
|
+
* loop.ts is responsible for waking after it enqueues the message;
|
|
158
|
+
* waking from inside `sendHeartbeat` would race that handler.
|
|
159
|
+
*/
|
|
160
|
+
async sendHeartbeat(options) {
|
|
161
|
+
// Acquire the mutex by waiting on the previous tail and installing
|
|
162
|
+
// a new one. Even if `prev` rejected we still want to proceed —
|
|
163
|
+
// `await prev` swallowing nothing here would allow an unhandled
|
|
164
|
+
// rejection from a previous send to propagate out of an unrelated
|
|
165
|
+
// call, so guard with `.catch(() => undefined)`.
|
|
166
|
+
const prev = this.sendMutex;
|
|
167
|
+
let release;
|
|
168
|
+
this.sendMutex = new Promise((resolve) => {
|
|
169
|
+
release = resolve;
|
|
170
|
+
});
|
|
171
|
+
try {
|
|
172
|
+
await prev.catch(() => undefined);
|
|
173
|
+
return await this.runSend(options);
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
release();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Body of a single heartbeat send, executed under the mutex. Split
|
|
181
|
+
* out from `sendHeartbeat` so the locking shell is a thin wrapper.
|
|
182
|
+
*/
|
|
183
|
+
async runSend(options) {
|
|
184
|
+
// Refresh the local liveness file before any network work. This is
|
|
185
|
+
// unconditional and runs even when no client is configured, so the
|
|
186
|
+
// child watchdog (when enabled) sees a heartbeat regardless.
|
|
187
|
+
writeHeartbeatFile();
|
|
188
|
+
if (!this.client)
|
|
189
|
+
return null;
|
|
190
|
+
const acks = this.commandDispatcher.drainAcks();
|
|
191
|
+
const payload = this.buildPayload(options.busy);
|
|
192
|
+
if (acks.length > 0)
|
|
193
|
+
payload.commandAcks = acks;
|
|
194
|
+
let resp;
|
|
195
|
+
try {
|
|
196
|
+
resp = options.strict
|
|
197
|
+
? await this.client.heartbeatStrict(payload)
|
|
198
|
+
: await this.client.heartbeat(payload);
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
// Put undelivered acks back so the next heartbeat can retry them.
|
|
202
|
+
if (acks.length > 0)
|
|
203
|
+
this.commandDispatcher.requeueAcks(acks);
|
|
204
|
+
if (options.strict) {
|
|
205
|
+
// Strict callers (init) want auth failures to bubble up so they
|
|
206
|
+
// can disable the client and notify the owner.
|
|
207
|
+
throw err;
|
|
208
|
+
}
|
|
209
|
+
// Non-strict: best-effort, swallow.
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
// The non-strict client swallows network/HTTP errors and returns
|
|
213
|
+
// `null` instead of throwing, so we must treat a null response as
|
|
214
|
+
// "delivery failed" and requeue acks. Without this, an offline
|
|
215
|
+
// server window silently drops every command ack drained during
|
|
216
|
+
// it. (heartbeatStrict only ever returns null when the server
|
|
217
|
+
// reports a non-auth non-2xx, which is the same delivery-failed
|
|
218
|
+
// case — strict callers care about auth, not delivery.)
|
|
219
|
+
if (resp === null) {
|
|
220
|
+
if (acks.length > 0)
|
|
221
|
+
this.commandDispatcher.requeueAcks(acks);
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
// ── Response side-effects (single source of truth) ──
|
|
225
|
+
// `resp` is non-null past the guard above.
|
|
226
|
+
if (resp.subscriptionStatus) {
|
|
227
|
+
this._lastSubscriptionStatus = resp.subscriptionStatus;
|
|
228
|
+
}
|
|
229
|
+
if (resp.billingPlans) {
|
|
230
|
+
this._lastBillingPlans = resp.billingPlans;
|
|
231
|
+
}
|
|
232
|
+
if (resp.billingMessage !== undefined) {
|
|
233
|
+
this._lastBillingMessage = resp.billingMessage;
|
|
234
|
+
}
|
|
235
|
+
if (applySharedKeysFromHeartbeat(this.config, this.channelManager, resp.sharedKeys)) {
|
|
236
|
+
logger.debug("Shared keys updated from heartbeat");
|
|
237
|
+
}
|
|
238
|
+
this.credentialBundle.handle(resp.credentialBundle);
|
|
239
|
+
// Acks were delivered successfully — clear their completed-set entries.
|
|
240
|
+
if (acks.length > 0)
|
|
241
|
+
this.commandDispatcher.confirmAcksDelivered(acks);
|
|
242
|
+
if (resp.pendingCommands && resp.pendingCommands.length > 0) {
|
|
243
|
+
this.commandDispatcher.dispatch(resp.pendingCommands);
|
|
244
|
+
}
|
|
245
|
+
return resp;
|
|
246
|
+
}
|
|
110
247
|
/**
|
|
111
248
|
* Initialize the heartbeat client and validate the API key.
|
|
112
249
|
* Should be called once at startup. If validation fails, heartbeats
|
|
@@ -126,15 +263,12 @@ export class HeartbeatManager {
|
|
|
126
263
|
logger.system(`Queued auto-ack for upgrade command ${justUpgraded.commandId} (${justUpgraded.fromVersion} → ${justUpgraded.toVersion})`);
|
|
127
264
|
}
|
|
128
265
|
if (!this.config.onboardingServiceUrl || !this.config.visionclawServiceApiKey) {
|
|
266
|
+
// No client to validate; still refresh the local heartbeat file so
|
|
267
|
+
// the watchdog sees a fresh timestamp from process start.
|
|
268
|
+
await this.sendHeartbeat({ busy: false });
|
|
129
269
|
return;
|
|
130
270
|
}
|
|
131
271
|
this.client = new OnboardingServiceClient(this.config.onboardingServiceUrl, this.config.agentName, this.config.visionclawServiceApiKey);
|
|
132
|
-
// The startup heartbeat carries whatever signature we have on disk
|
|
133
|
-
// (or none, on a fresh install). The server returns a
|
|
134
|
-
// `credentialBundle` only on signature mismatch, so the steady-state
|
|
135
|
-
// restart costs nothing extra; first installs and recovery cases
|
|
136
|
-
// get the full bundle automatically.
|
|
137
|
-
const startupPayload = this.buildPayload(false);
|
|
138
272
|
// Register billing handler for bundle purchases.
|
|
139
273
|
// Subscription payment is handled by the subscription gate (subscription-gate.ts),
|
|
140
274
|
// not via a command — the gate reads plans from the heartbeat response directly.
|
|
@@ -142,22 +276,14 @@ export class HeartbeatManager {
|
|
|
142
276
|
const billing = new BillingHandler(channelManager, telegramChatId, this.client);
|
|
143
277
|
this.commandDispatcher.registerHandler("bundle_purchase", billing.handleBundlePurchase);
|
|
144
278
|
}
|
|
279
|
+
// The startup heartbeat carries whatever signature we have on disk
|
|
280
|
+
// (or none, on a fresh install). The server returns a
|
|
281
|
+
// `credentialBundle` only on signature mismatch, so the steady-state
|
|
282
|
+
// restart costs nothing extra; first installs and recovery cases
|
|
283
|
+
// get the full bundle automatically.
|
|
145
284
|
try {
|
|
146
|
-
|
|
285
|
+
await this.sendHeartbeat({ busy: false, strict: true });
|
|
147
286
|
logger.system("Runtime heartbeat: initial key validation succeeded");
|
|
148
|
-
if (startupResp?.subscriptionStatus) {
|
|
149
|
-
this._lastSubscriptionStatus = startupResp.subscriptionStatus;
|
|
150
|
-
}
|
|
151
|
-
if (startupResp?.billingPlans) {
|
|
152
|
-
this._lastBillingPlans = startupResp.billingPlans;
|
|
153
|
-
}
|
|
154
|
-
if (startupResp?.billingMessage !== undefined) {
|
|
155
|
-
this._lastBillingMessage = startupResp.billingMessage;
|
|
156
|
-
}
|
|
157
|
-
if (applySharedKeysFromHeartbeat(this.config, this.channelManager, startupResp?.sharedKeys)) {
|
|
158
|
-
logger.debug("Shared keys applied from startup heartbeat");
|
|
159
|
-
}
|
|
160
|
-
this.credentialBundle.handle(startupResp?.credentialBundle);
|
|
161
287
|
}
|
|
162
288
|
catch (err) {
|
|
163
289
|
logger.err(`Runtime heartbeat: API key validation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -197,45 +323,6 @@ export class HeartbeatManager {
|
|
|
197
323
|
return null;
|
|
198
324
|
return this.client.getBillingPortal();
|
|
199
325
|
}
|
|
200
|
-
/**
|
|
201
|
-
* Send a heartbeat at the start of each wake cycle.
|
|
202
|
-
* Dispatches any pending commands from the server and applies shared keys.
|
|
203
|
-
*/
|
|
204
|
-
async onWake(busy) {
|
|
205
|
-
if (!this.client)
|
|
206
|
-
return;
|
|
207
|
-
const acks = this.commandDispatcher.drainAcks();
|
|
208
|
-
try {
|
|
209
|
-
const payload = this.buildPayload(busy);
|
|
210
|
-
if (acks.length > 0)
|
|
211
|
-
payload.commandAcks = acks;
|
|
212
|
-
const resp = await this.client.heartbeat(payload);
|
|
213
|
-
if (resp?.subscriptionStatus) {
|
|
214
|
-
this._lastSubscriptionStatus = resp.subscriptionStatus;
|
|
215
|
-
}
|
|
216
|
-
if (resp?.billingPlans) {
|
|
217
|
-
this._lastBillingPlans = resp.billingPlans;
|
|
218
|
-
}
|
|
219
|
-
if (resp?.billingMessage !== undefined) {
|
|
220
|
-
this._lastBillingMessage = resp.billingMessage;
|
|
221
|
-
}
|
|
222
|
-
// Acks were delivered successfully — clear their completed-set entries.
|
|
223
|
-
if (acks.length > 0)
|
|
224
|
-
this.commandDispatcher.confirmAcksDelivered(acks);
|
|
225
|
-
if (resp?.pendingCommands && resp.pendingCommands.length > 0) {
|
|
226
|
-
this.commandDispatcher.dispatch(resp.pendingCommands);
|
|
227
|
-
}
|
|
228
|
-
if (applySharedKeysFromHeartbeat(this.config, this.channelManager, resp?.sharedKeys)) {
|
|
229
|
-
logger.debug("Shared keys updated from heartbeat");
|
|
230
|
-
}
|
|
231
|
-
this.credentialBundle.handle(resp?.credentialBundle);
|
|
232
|
-
}
|
|
233
|
-
catch {
|
|
234
|
-
// Put undelivered acks back so the next heartbeat can retry them.
|
|
235
|
-
if (acks.length > 0)
|
|
236
|
-
this.commandDispatcher.requeueAcks(acks);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
326
|
/**
|
|
240
327
|
* Send a single heartbeat and return the subscription status from the response.
|
|
241
328
|
* Used by the subscription gate for fast-polling (10s interval).
|
|
@@ -243,57 +330,96 @@ export class HeartbeatManager {
|
|
|
243
330
|
async sendHeartbeatForSubscription() {
|
|
244
331
|
if (!this.client)
|
|
245
332
|
return this._lastSubscriptionStatus;
|
|
246
|
-
|
|
247
|
-
const resp = await this.client.heartbeat(this.buildPayload(false));
|
|
248
|
-
if (resp?.subscriptionStatus) {
|
|
249
|
-
this._lastSubscriptionStatus = resp.subscriptionStatus;
|
|
250
|
-
}
|
|
251
|
-
if (resp?.billingPlans) {
|
|
252
|
-
this._lastBillingPlans = resp.billingPlans;
|
|
253
|
-
}
|
|
254
|
-
if (resp?.billingMessage !== undefined) {
|
|
255
|
-
this._lastBillingMessage = resp.billingMessage;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
catch {
|
|
259
|
-
// best-effort — keep last known status
|
|
260
|
-
}
|
|
333
|
+
await this.sendHeartbeat({ busy: false });
|
|
261
334
|
return this._lastSubscriptionStatus;
|
|
262
335
|
}
|
|
263
336
|
/**
|
|
264
|
-
*
|
|
337
|
+
* Arm the always-on runtime heartbeat loop.
|
|
265
338
|
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
339
|
+
* Called once at process startup, after `agentState` is initialised so
|
|
340
|
+
* the `getBusy` provider can read the live busy flag. Does NOT fire
|
|
341
|
+
* an immediate heartbeat — `init()` already sent the strict startup
|
|
342
|
+
* heartbeat, and an immediate runtime tick would just duplicate it.
|
|
343
|
+
* State-change latency is covered by `nudge()`, not by an immediate
|
|
344
|
+
* fire.
|
|
345
|
+
*
|
|
346
|
+
* Calling `startRuntime` while the loop is already armed is a no-op
|
|
347
|
+
* apart from refreshing the `getBusy` reference; this makes the
|
|
348
|
+
* subscription gate's `stop -> poll -> start` pattern safe even if
|
|
349
|
+
* something inside the gate accidentally calls `startRuntime` again.
|
|
271
350
|
*/
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}, BUSY_HEARTBEAT_INTERVAL_MS);
|
|
351
|
+
startRuntime(getBusy) {
|
|
352
|
+
this.getBusy = getBusy;
|
|
353
|
+
// Synchronously refresh the local heartbeat file so a freshly armed
|
|
354
|
+
// watchdog (when enabled) sees a current timestamp from the moment
|
|
355
|
+
// the runtime loop is in charge.
|
|
356
|
+
writeHeartbeatFile();
|
|
357
|
+
if (this.runtimeTimer !== null)
|
|
358
|
+
return;
|
|
359
|
+
this.scheduleNextTick();
|
|
282
360
|
}
|
|
283
361
|
/**
|
|
284
|
-
* Stop the
|
|
285
|
-
*
|
|
362
|
+
* Stop the runtime heartbeat loop and wait for any in-flight send to
|
|
363
|
+
* finish. Used by graceful shutdown and by the subscription gate
|
|
364
|
+
* before it takes over with its own 10s polling cadence.
|
|
365
|
+
*
|
|
366
|
+
* IMPORTANT: clearing the timer is not enough on its own — a tick
|
|
367
|
+
* that is already mid-send would still re-arm itself when it
|
|
368
|
+
* completes (see `runRuntimeTick`). We null `getBusy` so the
|
|
369
|
+
* `runRuntimeTick` re-arm guard short-circuits, and `nudge()`
|
|
370
|
+
* (which keys off `getBusy`) becomes a no-op until `startRuntime`
|
|
371
|
+
* is called again. `startRuntime` is the only caller that brings
|
|
372
|
+
* `getBusy` back, so the lifecycle is start/stop-symmetric.
|
|
286
373
|
*/
|
|
287
|
-
|
|
288
|
-
if (this.
|
|
289
|
-
|
|
290
|
-
this.
|
|
374
|
+
async stopRuntime() {
|
|
375
|
+
if (this.runtimeTimer !== null) {
|
|
376
|
+
clearTimeout(this.runtimeTimer);
|
|
377
|
+
this.runtimeTimer = null;
|
|
291
378
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
379
|
+
this.getBusy = null;
|
|
380
|
+
// Drain any in-flight send so the caller can rely on a quiet
|
|
381
|
+
// post-stop state (no surprise heartbeats firing during gate
|
|
382
|
+
// polling or during graceful shutdown).
|
|
383
|
+
await this.sendMutex.catch(() => undefined);
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Fire an out-of-band heartbeat right now via the same mutex path
|
|
387
|
+
* the runtime loop uses. Used by the main loop on busy<->idle
|
|
388
|
+
* transitions to flatten dashboard latency without waiting for the
|
|
389
|
+
* next scheduled tick (up to 5min in the idle case).
|
|
390
|
+
*/
|
|
391
|
+
nudge() {
|
|
392
|
+
if (!this.getBusy)
|
|
393
|
+
return;
|
|
394
|
+
void this.sendHeartbeat({ busy: this.getBusy() }).catch(() => {
|
|
395
|
+
/* best-effort */
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
scheduleNextTick() {
|
|
399
|
+
const interval = this.getBusy?.() === true
|
|
400
|
+
? BUSY_HEARTBEAT_INTERVAL_MS
|
|
401
|
+
: IDLE_HEARTBEAT_INTERVAL_MS;
|
|
402
|
+
this.runtimeTimer = setTimeout(() => {
|
|
403
|
+
// Detach from the timer slot before sending so a re-entrant
|
|
404
|
+
// `startRuntime` (e.g. subscription gate teardown racing the
|
|
405
|
+
// tick fire) sees `runtimeTimer === null` and re-arms cleanly.
|
|
406
|
+
this.runtimeTimer = null;
|
|
407
|
+
void this.runRuntimeTick();
|
|
408
|
+
}, interval);
|
|
409
|
+
}
|
|
410
|
+
async runRuntimeTick() {
|
|
411
|
+
try {
|
|
412
|
+
await this.sendHeartbeat({ busy: this.getBusy?.() === true });
|
|
296
413
|
}
|
|
414
|
+
catch {
|
|
415
|
+
// sendHeartbeat already logs / requeues on error in non-strict mode;
|
|
416
|
+
// belt-and-suspenders catch in case future strict ticks slip in.
|
|
417
|
+
}
|
|
418
|
+
// If the loop was stopped (or about to be stopped) during the
|
|
419
|
+
// send, don't re-arm.
|
|
420
|
+
if (this.getBusy === null)
|
|
421
|
+
return;
|
|
422
|
+
this.scheduleNextTick();
|
|
297
423
|
}
|
|
298
424
|
}
|
|
299
425
|
//# sourceMappingURL=heartbeat-manager.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"heartbeat-manager.js","sourceRoot":"","sources":["../../src/agent/heartbeat-manager.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"heartbeat-manager.js","sourceRoot":"","sources":["../../src/agent/heartbeat-manager.ts"],"names":[],"mappings":"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"}
|