visionclaw 0.1.185-beta.7 → 0.1.185-beta.9
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/applied-credential-signature.d.ts +53 -0
- package/dist/agent/applied-credential-signature.d.ts.map +1 -0
- package/dist/agent/applied-credential-signature.js +137 -0
- package/dist/agent/applied-credential-signature.js.map +1 -0
- package/dist/agent/credential-bundle-handler.d.ts +98 -0
- package/dist/agent/credential-bundle-handler.d.ts.map +1 -0
- package/dist/agent/credential-bundle-handler.js +161 -0
- package/dist/agent/credential-bundle-handler.js.map +1 -0
- package/dist/agent/heartbeat-manager.d.ts +20 -14
- package/dist/agent/heartbeat-manager.d.ts.map +1 -1
- package/dist/agent/heartbeat-manager.js +28 -52
- package/dist/agent/heartbeat-manager.js.map +1 -1
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +7 -1
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/runtime-credentials.d.ts +21 -7
- package/dist/agent/runtime-credentials.d.ts.map +1 -1
- package/dist/agent/runtime-credentials.js +142 -35
- package/dist/agent/runtime-credentials.js.map +1 -1
- package/dist/agent/tunnel-credential-handler.d.ts +90 -0
- package/dist/agent/tunnel-credential-handler.d.ts.map +1 -0
- package/dist/agent/tunnel-credential-handler.js +162 -0
- package/dist/agent/tunnel-credential-handler.js.map +1 -0
- package/dist/builtin-skills/cloudflare-tunnel/SKILL.md +38 -0
- package/dist/builtin-skills/visionclaw-manual/SKILL.md +1 -1
- package/dist/obs/legacy-tunnel-adoption.d.ts +1 -1
- package/dist/obs/legacy-tunnel-adoption.js +1 -1
- package/dist/obs/server.d.ts.map +1 -1
- package/dist/obs/server.js +18 -1
- package/dist/obs/server.js.map +1 -1
- package/dist/obs/tunnel.d.ts +1 -1
- package/dist/onboarding/index.js +1 -1
- package/dist/onboarding/onboarding-service-client.d.ts +32 -12
- package/dist/onboarding/onboarding-service-client.d.ts.map +1 -1
- package/dist/onboarding/onboarding-service-client.js.map +1 -1
- package/dist-agent/bundle.cjs +213 -65
- package/package.json +1 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applied-credential signature manifest.
|
|
3
|
+
*
|
|
4
|
+
* Persisted alongside materialised tunnel credentials at
|
|
5
|
+
* `<profileTunnelDir>/.applied-signature.json`. Records the signature
|
|
6
|
+
* the server handed us with the last bundle we successfully applied.
|
|
7
|
+
*
|
|
8
|
+
* The manifest is the *only* piece of state the agent has to remember
|
|
9
|
+
* across process restarts about credential bundles — everything else
|
|
10
|
+
* (creds JSON, config.yml) is reconstructable from a fresh server
|
|
11
|
+
* delivery. The signature is opaque to us; we just echo it back on the
|
|
12
|
+
* next heartbeat so the server can decide whether to re-ship the body.
|
|
13
|
+
*
|
|
14
|
+
* Self-healing properties (intentional):
|
|
15
|
+
* - Manifest missing on disk → no signature sent → server sends
|
|
16
|
+
* bundle → we re-apply and rewrite the manifest. Recovers any
|
|
17
|
+
* install where the manifest was deleted but credentials weren't,
|
|
18
|
+
* or vice versa.
|
|
19
|
+
* - Manifest present but probe says no live tunnel files → we treat
|
|
20
|
+
* the manifest as unusable (return null from `loadIfValid`), the
|
|
21
|
+
* next heartbeat goes signature-free, the server re-ships the
|
|
22
|
+
* bundle, and we land back in a coherent state.
|
|
23
|
+
* - Process restart with everything intact → manifest signature is
|
|
24
|
+
* sent on the startup heartbeat, server replies with no
|
|
25
|
+
* `credentialBundle`, no disk writes, no tunnel restart.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Read the persisted signature, if any. Returns `null` when the file
|
|
29
|
+
* does not exist, fails to parse, has the wrong shape, or refers to a
|
|
30
|
+
* tunnel state that no longer matches what's on disk.
|
|
31
|
+
*
|
|
32
|
+
* The on-disk-coherence check (`probeLocalTunnelCredential()`) is what
|
|
33
|
+
* gives us self-healing: a stranded manifest from a wiped tunnel dir
|
|
34
|
+
* gets ignored, forcing a fresh bundle on the next heartbeat.
|
|
35
|
+
*/
|
|
36
|
+
export declare function loadAppliedSignatureIfValid(): string | null;
|
|
37
|
+
/**
|
|
38
|
+
* Persist the signature for the most recently applied bundle. Best-
|
|
39
|
+
* effort: a write failure is logged but does not throw, since the
|
|
40
|
+
* worst case is just an extra full-bundle delivery on the next
|
|
41
|
+
* heartbeat.
|
|
42
|
+
*
|
|
43
|
+
* The file is written with 0o600 to match the credentials sitting next
|
|
44
|
+
* to it; the directory itself is already 0o700 from
|
|
45
|
+
* `runtime-credentials.ts`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function writeAppliedSignature(signature: string): void;
|
|
48
|
+
/**
|
|
49
|
+
* Forget the persisted signature. Used by tests; production code never
|
|
50
|
+
* needs to delete it explicitly because writes overwrite in place.
|
|
51
|
+
*/
|
|
52
|
+
export declare function __clearAppliedSignatureForTests(): void;
|
|
53
|
+
//# sourceMappingURL=applied-credential-signature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"applied-credential-signature.d.ts","sourceRoot":"","sources":["../../src/agent/applied-credential-signature.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAwBH;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,GAAG,IAAI,CA2C3D;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAgC7D;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,IAAI,IAAI,CAMtD"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applied-credential signature manifest.
|
|
3
|
+
*
|
|
4
|
+
* Persisted alongside materialised tunnel credentials at
|
|
5
|
+
* `<profileTunnelDir>/.applied-signature.json`. Records the signature
|
|
6
|
+
* the server handed us with the last bundle we successfully applied.
|
|
7
|
+
*
|
|
8
|
+
* The manifest is the *only* piece of state the agent has to remember
|
|
9
|
+
* across process restarts about credential bundles — everything else
|
|
10
|
+
* (creds JSON, config.yml) is reconstructable from a fresh server
|
|
11
|
+
* delivery. The signature is opaque to us; we just echo it back on the
|
|
12
|
+
* next heartbeat so the server can decide whether to re-ship the body.
|
|
13
|
+
*
|
|
14
|
+
* Self-healing properties (intentional):
|
|
15
|
+
* - Manifest missing on disk → no signature sent → server sends
|
|
16
|
+
* bundle → we re-apply and rewrite the manifest. Recovers any
|
|
17
|
+
* install where the manifest was deleted but credentials weren't,
|
|
18
|
+
* or vice versa.
|
|
19
|
+
* - Manifest present but probe says no live tunnel files → we treat
|
|
20
|
+
* the manifest as unusable (return null from `loadIfValid`), the
|
|
21
|
+
* next heartbeat goes signature-free, the server re-ships the
|
|
22
|
+
* bundle, and we land back in a coherent state.
|
|
23
|
+
* - Process restart with everything intact → manifest signature is
|
|
24
|
+
* sent on the startup heartbeat, server replies with no
|
|
25
|
+
* `credentialBundle`, no disk writes, no tunnel restart.
|
|
26
|
+
*/
|
|
27
|
+
import fs from "node:fs";
|
|
28
|
+
import path from "node:path";
|
|
29
|
+
import { getProfileTunnelDir } from "../config/index.js";
|
|
30
|
+
import { probeLocalTunnelCredential } from "./runtime-credentials.js";
|
|
31
|
+
import { logger } from "../logger.js";
|
|
32
|
+
const MANIFEST_FILENAME = ".applied-signature.json";
|
|
33
|
+
const MANIFEST_VERSION = 1;
|
|
34
|
+
function getManifestPath() {
|
|
35
|
+
return path.join(getProfileTunnelDir(), MANIFEST_FILENAME);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Read the persisted signature, if any. Returns `null` when the file
|
|
39
|
+
* does not exist, fails to parse, has the wrong shape, or refers to a
|
|
40
|
+
* tunnel state that no longer matches what's on disk.
|
|
41
|
+
*
|
|
42
|
+
* The on-disk-coherence check (`probeLocalTunnelCredential()`) is what
|
|
43
|
+
* gives us self-healing: a stranded manifest from a wiped tunnel dir
|
|
44
|
+
* gets ignored, forcing a fresh bundle on the next heartbeat.
|
|
45
|
+
*/
|
|
46
|
+
export function loadAppliedSignatureIfValid() {
|
|
47
|
+
const manifestPath = getManifestPath();
|
|
48
|
+
if (!fs.existsSync(manifestPath))
|
|
49
|
+
return null;
|
|
50
|
+
let raw;
|
|
51
|
+
try {
|
|
52
|
+
raw = fs.readFileSync(manifestPath, "utf-8");
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
logger.warn(`Applied-signature manifest unreadable: ${err instanceof Error ? err.message : String(err)}`);
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
let parsed;
|
|
59
|
+
try {
|
|
60
|
+
parsed = JSON.parse(raw);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
logger.warn(`Applied-signature manifest malformed; ignoring`);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (!parsed ||
|
|
67
|
+
typeof parsed !== "object" ||
|
|
68
|
+
!("signature" in parsed) ||
|
|
69
|
+
typeof parsed.signature !== "string" ||
|
|
70
|
+
parsed.signature.trim() === "") {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const signature = parsed.signature.trim();
|
|
74
|
+
// Coherence: a manifest is only meaningful when the credentials it
|
|
75
|
+
// refers to actually still exist on disk. If the tunnel files were
|
|
76
|
+
// wiped (e.g. operator deleted `~/.visionclaw/profiles/default/tunnel`)
|
|
77
|
+
// the stored signature lies about our state, so pretend we have none
|
|
78
|
+
// and let the server reissue the bundle.
|
|
79
|
+
if (probeLocalTunnelCredential() === null) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return signature;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Persist the signature for the most recently applied bundle. Best-
|
|
86
|
+
* effort: a write failure is logged but does not throw, since the
|
|
87
|
+
* worst case is just an extra full-bundle delivery on the next
|
|
88
|
+
* heartbeat.
|
|
89
|
+
*
|
|
90
|
+
* The file is written with 0o600 to match the credentials sitting next
|
|
91
|
+
* to it; the directory itself is already 0o700 from
|
|
92
|
+
* `runtime-credentials.ts`.
|
|
93
|
+
*/
|
|
94
|
+
export function writeAppliedSignature(signature) {
|
|
95
|
+
if (typeof signature !== "string" || signature.trim() === "")
|
|
96
|
+
return;
|
|
97
|
+
const manifestPath = getManifestPath();
|
|
98
|
+
const dir = path.dirname(manifestPath);
|
|
99
|
+
try {
|
|
100
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// best-effort; the credential apply path will have created it
|
|
104
|
+
}
|
|
105
|
+
const payload = {
|
|
106
|
+
version: MANIFEST_VERSION,
|
|
107
|
+
signature: signature.trim(),
|
|
108
|
+
appliedAt: new Date().toISOString(),
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
fs.writeFileSync(manifestPath, JSON.stringify(payload, null, 2), "utf-8");
|
|
112
|
+
if (process.platform !== "win32") {
|
|
113
|
+
try {
|
|
114
|
+
fs.chmodSync(manifestPath, 0o600);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// best-effort; directory perms still protect
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
logger.warn(`Failed to persist applied-signature manifest: ${err instanceof Error ? err.message : String(err)}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Forget the persisted signature. Used by tests; production code never
|
|
127
|
+
* needs to delete it explicitly because writes overwrite in place.
|
|
128
|
+
*/
|
|
129
|
+
export function __clearAppliedSignatureForTests() {
|
|
130
|
+
try {
|
|
131
|
+
fs.rmSync(getManifestPath(), { force: true });
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// ignore
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=applied-credential-signature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"applied-credential-signature.js","sourceRoot":"","sources":["../../src/agent/applied-credential-signature.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential bundle coordinator.
|
|
3
|
+
*
|
|
4
|
+
* Owns every concern around server-issued `credentialBundle` deliveries
|
|
5
|
+
* over the heartbeat:
|
|
6
|
+
*
|
|
7
|
+
* - exposing the locally-applied signature so the heartbeat layer can
|
|
8
|
+
* send it back to the server (which decides whether to re-ship)
|
|
9
|
+
* - materialising delivered bundles to disk (via `runtime-credentials.ts`)
|
|
10
|
+
* - bouncing cloudflared when a tunnel rotation lands, with restart
|
|
11
|
+
* coalescing so concurrent rotations don't race
|
|
12
|
+
*
|
|
13
|
+
* Replaces the old `TunnelCredentialHandler` + `bootstrapCredentials`
|
|
14
|
+
* flag heuristic. The signature path is server-authoritative and
|
|
15
|
+
* forward-compatible: when a future credential kind is added to
|
|
16
|
+
* `CredentialBundleBody`, the same signature mechanism naturally
|
|
17
|
+
* detects + delivers it without any new wire fields.
|
|
18
|
+
*
|
|
19
|
+
* Persistence model — deliberately none. The applied signature lives
|
|
20
|
+
* only in memory, which means **every fresh process gets a full bundle
|
|
21
|
+
* delivery on its first heartbeat**. That's the recovery path for any
|
|
22
|
+
* disk drift we can't enumerate in advance: a corrupted creds JSON, a
|
|
23
|
+
* stale config.yml, a wiped profile dir, an operator-edited manifest —
|
|
24
|
+
* doesn't matter. Restart the agent, the server hands us the current
|
|
25
|
+
* bundle, we re-apply, we're back in sync. The cost is one bundle
|
|
26
|
+
* download per process lifetime (a few KB), which is negligible.
|
|
27
|
+
*/
|
|
28
|
+
import type { CredentialBundle } from "../onboarding/onboarding-service-client.js";
|
|
29
|
+
export interface CredentialBundleHandlerOptions {
|
|
30
|
+
/** Canonical agent name used to validate tunnel-name drift. */
|
|
31
|
+
agentName: string;
|
|
32
|
+
/**
|
|
33
|
+
* Callback that bounces cloudflared (typically the `restartTunnel`
|
|
34
|
+
* returned from `startObsServer`). Invoked when a newly materialised
|
|
35
|
+
* tunnel credential differs from what's on disk — otherwise
|
|
36
|
+
* cloudflared keeps running against the UUID it was spawned with
|
|
37
|
+
* (it does not hot-reload `config.yml`).
|
|
38
|
+
*
|
|
39
|
+
* Optional so test harnesses and non-obs builds can skip the restart
|
|
40
|
+
* side-effect entirely.
|
|
41
|
+
*/
|
|
42
|
+
restartTunnel?: () => Promise<string | null>;
|
|
43
|
+
}
|
|
44
|
+
export declare class CredentialBundleHandler {
|
|
45
|
+
private readonly agentName;
|
|
46
|
+
private readonly restartTunnel?;
|
|
47
|
+
/**
|
|
48
|
+
* The signature the agent will send to the server on the next
|
|
49
|
+
* heartbeat. `undefined` means "I have no opinion, please send me
|
|
50
|
+
* the bundle" — which is the state every fresh process starts in,
|
|
51
|
+
* by design. Updated only after a successful apply.
|
|
52
|
+
*/
|
|
53
|
+
private _appliedSignature;
|
|
54
|
+
private _restartInFlight;
|
|
55
|
+
/**
|
|
56
|
+
* Set whenever a `tunnelChanged` apply lands while a previous restart
|
|
57
|
+
* is still in flight. The in-flight restart already snapshotted disk
|
|
58
|
+
* state at its start (`resolveTunnelOpts()` in `obs/server.ts`), so
|
|
59
|
+
* the second rotation needs its own bounce — we re-kick from the
|
|
60
|
+
* restart's `finally` block when this flag is set.
|
|
61
|
+
*
|
|
62
|
+
* Stored as the latest `ApplyRuntimeCredentialsResult` so the
|
|
63
|
+
* rotation label in the follow-up log line still reflects the
|
|
64
|
+
* *latest* tunnel delivery, not the first one already restarted.
|
|
65
|
+
*/
|
|
66
|
+
private _pendingRestart;
|
|
67
|
+
constructor(options: CredentialBundleHandlerOptions);
|
|
68
|
+
/**
|
|
69
|
+
* The signature to attach to the next heartbeat as
|
|
70
|
+
* `appliedCredentialSignature`. `undefined` until the first
|
|
71
|
+
* successful apply in this process — which forces the server to
|
|
72
|
+
* ship the bundle on the startup heartbeat (the recovery path).
|
|
73
|
+
*/
|
|
74
|
+
get appliedSignature(): string | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Handle the optional `credentialBundle` from a heartbeat response.
|
|
77
|
+
*
|
|
78
|
+
* Defensive by contract: never throws — a failed materialisation must
|
|
79
|
+
* not block heartbeats. A missing/undefined bundle means "server
|
|
80
|
+
* agrees with our applied signature": no work to do.
|
|
81
|
+
*/
|
|
82
|
+
handle(bundle: CredentialBundle | undefined): void;
|
|
83
|
+
/**
|
|
84
|
+
* Kick `restartTunnel` in the background, coalescing concurrent
|
|
85
|
+
* invocations. Logged at `system` level because a restart is a
|
|
86
|
+
* visible event for operators tailing pm2 logs.
|
|
87
|
+
*
|
|
88
|
+
* If a restart is already in flight when we're called, we don't drop
|
|
89
|
+
* the trigger — the in-flight restart sampled disk before this newer
|
|
90
|
+
* credential was materialised and would launch cloudflared against
|
|
91
|
+
* the stale UUID. Instead we record the latest result in
|
|
92
|
+
* `_pendingRestart` and re-kick from the in-flight restart's
|
|
93
|
+
* `finally` block, which guarantees one extra restart that picks up
|
|
94
|
+
* whatever's now on disk.
|
|
95
|
+
*/
|
|
96
|
+
private triggerRestart;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=credential-bundle-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-bundle-handler.d.ts","sourceRoot":"","sources":["../../src/agent/credential-bundle-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EACV,gBAAgB,EACjB,MAAM,4CAA4C,CAAC;AAOpD,MAAM,WAAW,8BAA8B;IAC7C,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC9C;AAED,qBAAa,uBAAuB;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAA+B;IAE9D;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB,CAAiC;IAE1D,OAAO,CAAC,gBAAgB,CAA8B;IACtD;;;;;;;;;;OAUG;IACH,OAAO,CAAC,eAAe,CAA8C;gBAEzD,OAAO,EAAE,8BAA8B;IAKnD;;;;;OAKG;IACH,IAAI,gBAAgB,IAAI,MAAM,GAAG,SAAS,CAEzC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,GAAG,IAAI;IAuClD;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,cAAc;CA6CvB"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential bundle coordinator.
|
|
3
|
+
*
|
|
4
|
+
* Owns every concern around server-issued `credentialBundle` deliveries
|
|
5
|
+
* over the heartbeat:
|
|
6
|
+
*
|
|
7
|
+
* - exposing the locally-applied signature so the heartbeat layer can
|
|
8
|
+
* send it back to the server (which decides whether to re-ship)
|
|
9
|
+
* - materialising delivered bundles to disk (via `runtime-credentials.ts`)
|
|
10
|
+
* - bouncing cloudflared when a tunnel rotation lands, with restart
|
|
11
|
+
* coalescing so concurrent rotations don't race
|
|
12
|
+
*
|
|
13
|
+
* Replaces the old `TunnelCredentialHandler` + `bootstrapCredentials`
|
|
14
|
+
* flag heuristic. The signature path is server-authoritative and
|
|
15
|
+
* forward-compatible: when a future credential kind is added to
|
|
16
|
+
* `CredentialBundleBody`, the same signature mechanism naturally
|
|
17
|
+
* detects + delivers it without any new wire fields.
|
|
18
|
+
*
|
|
19
|
+
* Persistence model — deliberately none. The applied signature lives
|
|
20
|
+
* only in memory, which means **every fresh process gets a full bundle
|
|
21
|
+
* delivery on its first heartbeat**. That's the recovery path for any
|
|
22
|
+
* disk drift we can't enumerate in advance: a corrupted creds JSON, a
|
|
23
|
+
* stale config.yml, a wiped profile dir, an operator-edited manifest —
|
|
24
|
+
* doesn't matter. Restart the agent, the server hands us the current
|
|
25
|
+
* bundle, we re-apply, we're back in sync. The cost is one bundle
|
|
26
|
+
* download per process lifetime (a few KB), which is negligible.
|
|
27
|
+
*/
|
|
28
|
+
import { applyRuntimeCredentials, } from "./runtime-credentials.js";
|
|
29
|
+
import { logger } from "../logger.js";
|
|
30
|
+
export class CredentialBundleHandler {
|
|
31
|
+
agentName;
|
|
32
|
+
restartTunnel;
|
|
33
|
+
/**
|
|
34
|
+
* The signature the agent will send to the server on the next
|
|
35
|
+
* heartbeat. `undefined` means "I have no opinion, please send me
|
|
36
|
+
* the bundle" — which is the state every fresh process starts in,
|
|
37
|
+
* by design. Updated only after a successful apply.
|
|
38
|
+
*/
|
|
39
|
+
_appliedSignature = undefined;
|
|
40
|
+
_restartInFlight = null;
|
|
41
|
+
/**
|
|
42
|
+
* Set whenever a `tunnelChanged` apply lands while a previous restart
|
|
43
|
+
* is still in flight. The in-flight restart already snapshotted disk
|
|
44
|
+
* state at its start (`resolveTunnelOpts()` in `obs/server.ts`), so
|
|
45
|
+
* the second rotation needs its own bounce — we re-kick from the
|
|
46
|
+
* restart's `finally` block when this flag is set.
|
|
47
|
+
*
|
|
48
|
+
* Stored as the latest `ApplyRuntimeCredentialsResult` so the
|
|
49
|
+
* rotation label in the follow-up log line still reflects the
|
|
50
|
+
* *latest* tunnel delivery, not the first one already restarted.
|
|
51
|
+
*/
|
|
52
|
+
_pendingRestart = null;
|
|
53
|
+
constructor(options) {
|
|
54
|
+
this.agentName = options.agentName;
|
|
55
|
+
this.restartTunnel = options.restartTunnel;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The signature to attach to the next heartbeat as
|
|
59
|
+
* `appliedCredentialSignature`. `undefined` until the first
|
|
60
|
+
* successful apply in this process — which forces the server to
|
|
61
|
+
* ship the bundle on the startup heartbeat (the recovery path).
|
|
62
|
+
*/
|
|
63
|
+
get appliedSignature() {
|
|
64
|
+
return this._appliedSignature;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Handle the optional `credentialBundle` from a heartbeat response.
|
|
68
|
+
*
|
|
69
|
+
* Defensive by contract: never throws — a failed materialisation must
|
|
70
|
+
* not block heartbeats. A missing/undefined bundle means "server
|
|
71
|
+
* agrees with our applied signature": no work to do.
|
|
72
|
+
*/
|
|
73
|
+
handle(bundle) {
|
|
74
|
+
if (!bundle)
|
|
75
|
+
return;
|
|
76
|
+
if (typeof bundle.signature !== "string" || bundle.signature.trim() === "") {
|
|
77
|
+
logger.warn(`Credential bundle delivery missing signature; ignoring`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
let result = null;
|
|
81
|
+
try {
|
|
82
|
+
result = applyRuntimeCredentials(bundle.body, { agentName: this.agentName });
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
logger.warn(`Credential bundle apply failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
86
|
+
// Deliberately do NOT update `_appliedSignature` on failure: the
|
|
87
|
+
// server should keep retrying the bundle until we report success.
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
// Remember the new signature even when the bundle body was empty
|
|
91
|
+
// (e.g. server has no credentials provisioned yet). This is an
|
|
92
|
+
// intentional policy choice: empty bundle means "no new server-
|
|
93
|
+
// issued credential to apply", NOT "tear down any local tunnel
|
|
94
|
+
// currently on disk". The agent therefore keeps its local tunnel
|
|
95
|
+
// materialization as-is and simply records agreement on the empty
|
|
96
|
+
// server state for the rest of this process lifetime.
|
|
97
|
+
this._appliedSignature = bundle.signature.trim();
|
|
98
|
+
// If the apply rotated cloudflared's UUID, bounce it. Without this
|
|
99
|
+
// obs/server.ts spawned cloudflared against whatever was on disk at
|
|
100
|
+
// startup, and a fresh UUID would sit unused until the next
|
|
101
|
+
// process restart.
|
|
102
|
+
if (result.tunnelApplied && result.tunnelChanged && result.tunnel) {
|
|
103
|
+
this.triggerRestart(result);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Kick `restartTunnel` in the background, coalescing concurrent
|
|
108
|
+
* invocations. Logged at `system` level because a restart is a
|
|
109
|
+
* visible event for operators tailing pm2 logs.
|
|
110
|
+
*
|
|
111
|
+
* If a restart is already in flight when we're called, we don't drop
|
|
112
|
+
* the trigger — the in-flight restart sampled disk before this newer
|
|
113
|
+
* credential was materialised and would launch cloudflared against
|
|
114
|
+
* the stale UUID. Instead we record the latest result in
|
|
115
|
+
* `_pendingRestart` and re-kick from the in-flight restart's
|
|
116
|
+
* `finally` block, which guarantees one extra restart that picks up
|
|
117
|
+
* whatever's now on disk.
|
|
118
|
+
*/
|
|
119
|
+
triggerRestart(result) {
|
|
120
|
+
if (!this.restartTunnel || !result.tunnel)
|
|
121
|
+
return;
|
|
122
|
+
if (this._restartInFlight) {
|
|
123
|
+
// Always overwrite — only the most recent credential matters; any
|
|
124
|
+
// intermediate bundles will be on disk by the time the follow-up
|
|
125
|
+
// restart re-probes via `obs/server.ts:resolveTunnelOpts()`.
|
|
126
|
+
this._pendingRestart = result;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const newTunnelId = result.tunnel.tunnelId;
|
|
130
|
+
const previousTunnelId = result.previousTunnelId;
|
|
131
|
+
const rotationLabel = previousTunnelId === null
|
|
132
|
+
? "initial"
|
|
133
|
+
: previousTunnelId === newTunnelId
|
|
134
|
+
? "refreshed"
|
|
135
|
+
: `rotated (${previousTunnelId} → ${newTunnelId})`;
|
|
136
|
+
logger.system(`Runtime credential: tunnel ${rotationLabel}; restarting cloudflared`);
|
|
137
|
+
const restart = this.restartTunnel;
|
|
138
|
+
this._restartInFlight = (async () => {
|
|
139
|
+
try {
|
|
140
|
+
await restart();
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
logger.warn(`Failed to restart tunnel after credential update: ${err instanceof Error ? err.message : String(err)}`);
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
this._restartInFlight = null;
|
|
147
|
+
// Drain any rotation that landed mid-flight. We re-enter
|
|
148
|
+
// triggerRestart synchronously (not via setImmediate) so the
|
|
149
|
+
// follow-up restart starts as soon as the previous Promise
|
|
150
|
+
// resolves, but `_restartInFlight` is already null above so the
|
|
151
|
+
// recursive call goes down the spawn path, not the coalesce path.
|
|
152
|
+
const pending = this._pendingRestart;
|
|
153
|
+
if (pending) {
|
|
154
|
+
this._pendingRestart = null;
|
|
155
|
+
this.triggerRestart(pending);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
})();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=credential-bundle-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-bundle-handler.js","sourceRoot":"","sources":["../../src/agent/credential-bundle-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,6 +10,20 @@ import type { ChannelManager } from "../channels/manager.js";
|
|
|
10
10
|
import { type HeartbeatBillingPlan, type SubscriptionStatus } from "../onboarding/onboarding-service-client.js";
|
|
11
11
|
import { CommandDispatcher } from "./command-dispatcher.js";
|
|
12
12
|
import { type PendingUpgradeInfo } from "../config/index.js";
|
|
13
|
+
export interface HeartbeatManagerOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Callback that bounces the cloudflared tunnel (typically the
|
|
16
|
+
* `restartTunnel` returned from `startObsServer`). Forwarded verbatim
|
|
17
|
+
* to the tunnel credential handler — HeartbeatManager itself has no
|
|
18
|
+
* tunnel knowledge beyond passing this through.
|
|
19
|
+
*
|
|
20
|
+
* HeartbeatManager is constructed before `agentState` is initialised
|
|
21
|
+
* (the heartbeat fires during `init` while the agent state is still
|
|
22
|
+
* being built), so we take the callback directly rather than reaching
|
|
23
|
+
* into `getAgentState().restartTunnel`.
|
|
24
|
+
*/
|
|
25
|
+
restartTunnel?: () => Promise<string | null>;
|
|
26
|
+
}
|
|
13
27
|
export declare class HeartbeatManager {
|
|
14
28
|
private client;
|
|
15
29
|
readonly commandDispatcher: CommandDispatcher;
|
|
@@ -20,23 +34,15 @@ export declare class HeartbeatManager {
|
|
|
20
34
|
private _lastBillingPlans;
|
|
21
35
|
private _lastBillingMessage;
|
|
22
36
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
37
|
+
* Owns all server-issued credential orchestration: signature tracking,
|
|
38
|
+
* bundle materialisation, restart coalescing. HeartbeatManager hands
|
|
39
|
+
* it each heartbeat response and reads its `appliedSignature` when
|
|
40
|
+
* building the next payload.
|
|
26
41
|
*/
|
|
27
|
-
private
|
|
28
|
-
constructor(config: VisionClawConfig);
|
|
42
|
+
private readonly credentialBundle;
|
|
43
|
+
constructor(config: VisionClawConfig, options?: HeartbeatManagerOptions);
|
|
29
44
|
/** Build a base heartbeat payload including owner info for server-side backfill. */
|
|
30
45
|
private buildPayload;
|
|
31
|
-
/**
|
|
32
|
-
* Apply any `runtimeCredentials` delivered by the server.
|
|
33
|
-
*
|
|
34
|
-
* Runs inside a try/catch so a failed materialization never breaks the
|
|
35
|
-
* heartbeat critical path. If the server omitted `runtimeCredentials` but
|
|
36
|
-
* the agent has no local tunnel credential either, we arm the next
|
|
37
|
-
* heartbeat to request a bootstrap refresh.
|
|
38
|
-
*/
|
|
39
|
-
private handleRuntimeCredentialsFromResponse;
|
|
40
46
|
/**
|
|
41
47
|
* Initialize the heartbeat client and validate the API key.
|
|
42
48
|
* Should be called once at startup. If validation fails, heartbeats
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"heartbeat-manager.d.ts","sourceRoot":"","sources":["../../src/agent/heartbeat-manager.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAEL,KAAK,oBAAoB,
|
|
1
|
+
{"version":3,"file":"heartbeat-manager.d.ts","sourceRoot":"","sources":["../../src/agent/heartbeat-manager.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAEL,KAAK,oBAAoB,EAEzB,KAAK,kBAAkB,EACxB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,OAAO,EAA4D,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAYvH,MAAM,WAAW,uBAAuB;IACtC;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC9C;AAsCD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAwC;IACtD,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAAC;IAC9C,OAAO,CAAC,YAAY,CAA+C;IACnE,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;gBAE/C,MAAM,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,uBAAuB;IAYvE,oFAAoF;IACpF,OAAO,CAAC,YAAY;IA+BpB;;;;OAIG;IACG,IAAI,CACR,cAAc,EAAE,cAAc,EAC9B,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,YAAY,EAAE,kBAAkB,GAAG,IAAI,GACtC,OAAO,CAAC,IAAI,CAAC;IAgFhB,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,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAgC1C;;;OAGG;IACG,4BAA4B,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAoBjE;;;OAGG;IACH,iBAAiB,IAAI,IAAI;IAWzB;;;OAGG;IACH,gBAAgB,IAAI,IAAI;CAYzB"}
|