@parall/daemon 1.35.0 → 1.36.1
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/bundle/manifest.json +13 -9
- package/bundle/parall-browser-pod.js +40737 -0
- package/bundle/parall-claude-agent.js +472 -13
- package/bundle/parall-codex-agent.js +472 -13
- package/bundle/parall-daemon.js +351 -32
- package/dist/browser-pod.d.ts +301 -0
- package/dist/browser-pod.d.ts.map +1 -0
- package/dist/browser-pod.js +835 -0
- package/dist/clip-runtime/browser-profile-manager.d.ts +16 -0
- package/dist/clip-runtime/browser-profile-manager.d.ts.map +1 -1
- package/dist/clip-runtime/browser-profile-manager.js +17 -0
- package/dist/clip-runtime/browser-state-store.d.ts +157 -0
- package/dist/clip-runtime/browser-state-store.d.ts.map +1 -0
- package/dist/clip-runtime/browser-state-store.js +370 -0
- package/dist/clip-runtime/browser-viewer-streamer.d.ts +35 -1
- package/dist/clip-runtime/browser-viewer-streamer.d.ts.map +1 -1
- package/dist/clip-runtime/browser-viewer-streamer.js +97 -1
- package/dist/clip-runtime/clip-provider.d.ts +56 -0
- package/dist/clip-runtime/clip-provider.d.ts.map +1 -1
- package/dist/clip-runtime/clip-provider.js +141 -17
- package/dist/clip-runtime/index.d.ts +1 -0
- package/dist/clip-runtime/index.d.ts.map +1 -1
- package/dist/supervisor.d.ts +11 -1
- package/dist/supervisor.d.ts.map +1 -1
- package/dist/supervisor.js +38 -13
- package/package.json +11 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-viewer-streamer.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/browser-viewer-streamer.ts"],"names":[],"mappings":"AAsCA,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAcD;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE;QAAE,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACpF,gFAAgF;IAChF,kBAAkB,CAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GACtE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACpC,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjD,kBAAkB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC9E,gEAAgE;IAChE,WAAW,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxD;AAED,qBAAa,qBAAqB;
|
|
1
|
+
{"version":3,"file":"browser-viewer-streamer.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/browser-viewer-streamer.ts"],"names":[],"mappings":"AAsCA,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAcD;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE;QAAE,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACpF,gFAAgF;IAChF,kBAAkB,CAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GACtE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACpC,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjD,kBAAkB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC9E,gEAAgE;IAChE,WAAW,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,gBAAgB,EAAE,MAAM,GAAG,SAAS,GACnC,OAAO,CAGT;AAED,qBAAa,qBAAqB;IAiBpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAbjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoC;IAO9D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAI5D,OAAO,CAAC,QAAQ,CAAS;gBAEI,IAAI,EAAE,kBAAkB;IAErD;;;;;OAKG;IACG,mBAAmB,CACvB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,CAAC,EAAE,gBAAgB,GACtB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAuCnC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,UAAU;IAwBlB;;;;OAIG;YACW,iBAAiB;IA+C/B;;;;OAIG;YACW,kBAAkB;IAgBhC;;;;;;;;;;OAUG;YACW,iBAAiB;IAuB/B;;;;;;;;OAQG;YACW,kBAAkB;IAuBhC;;;;;;OAMG;YACW,cAAc;IAiC5B;;;;;;;OAOG;YACW,kBAAkB;IAqBhC;;;;;;;;;;;;;;;;;;;;OAoBG;YACW,gBAAgB;IA6C9B;;;;;;;;;;;;;;;;;;OAkBG;YACW,wBAAwB;IAetC;;;;;OAKG;YACW,yBAAyB;IAevC;;;;;OAKG;YACW,wBAAwB;IAatC;;;OAGG;YACW,kBAAkB;IAehC,yFAAyF;YAC3E,WAAW;IAgBzB;;;;;OAKG;YACW,aAAa;IAiH3B;;;;;;;OAOG;IACH,OAAO,CAAC,SAAS;IAqBjB;;;;OAIG;YACW,eAAe;IA+B7B;;;;;OAKG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAQ5C;;;OAGG;IACH,QAAQ,IAAI,IAAI;CAMjB"}
|
|
@@ -11,12 +11,40 @@ const BB_VIEWER_COMMAND_TIMEOUT_MS = 15_000;
|
|
|
11
11
|
const BB_VIEWER_UNANSWERED_REAP_MS = 90_000;
|
|
12
12
|
// Grace between SIGTERM and the SIGKILL escalation in killChild.
|
|
13
13
|
const BB_VIEWER_TERM_GRACE_MS = 5_000;
|
|
14
|
+
/**
|
|
15
|
+
* bb-viewer's /command control plane is UNAUTHENTICATED, so on the shared agents
|
|
16
|
+
* cluster its bind host MUST be loopback — a 0.0.0.0 / pod-IP / typo'd value would
|
|
17
|
+
* silently re-open it cross-tenant. Only these exact loopback forms are accepted;
|
|
18
|
+
* anything else is rejected (fail fast) at spawn.
|
|
19
|
+
*/
|
|
20
|
+
export function isLoopbackBindHost(host) {
|
|
21
|
+
return host === '127.0.0.1' || host === 'localhost' || host === '::1' || host === '[::1]';
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Whether a kick targets the CURRENT live streamer (so its process is torn down). A
|
|
25
|
+
* kick with no explicit target hits whoever is live; an explicit target tears down the
|
|
26
|
+
* current streamer ONLY when it matches — a stale/replayed session_id must not kill a
|
|
27
|
+
* newer live viewer (it is only recorded for straggler rejection). currentSessionId
|
|
28
|
+
* undefined (no live streamer) → never a current-kill.
|
|
29
|
+
*/
|
|
30
|
+
export function kickHitsCurrentSession(requested, currentSessionId) {
|
|
31
|
+
if (currentSessionId === undefined)
|
|
32
|
+
return false;
|
|
33
|
+
return requested === undefined || requested === currentSessionId;
|
|
34
|
+
}
|
|
14
35
|
export class BrowserViewerStreamer {
|
|
15
36
|
host;
|
|
16
37
|
// Live bb-viewer (WebRTC streamer) subprocess per profile. One per profile;
|
|
17
38
|
// see StreamerState. Cleaned up on stream.close, stopProfile, resetProfile,
|
|
18
39
|
// and stop().
|
|
19
40
|
streamers = new Map();
|
|
41
|
+
// The most-recently server-side-kicked viewer session per profile (design §3.6
|
|
42
|
+
// PR7). After a kick the streamer is gone, so the session-staleness guards
|
|
43
|
+
// (which key off a live streamer) can no longer reject the kicked viewer's
|
|
44
|
+
// straggler nav commands — this map does. Reset whenever the profile's streamer
|
|
45
|
+
// is (re)killed via killProfileStreamer, so a fresh stream.start clears it. One
|
|
46
|
+
// entry per profile (overwritten per kick); bounded, no leak.
|
|
47
|
+
kickedSessions = new Map();
|
|
20
48
|
// Set by shutdown(): an in-flight spawnStreamer has no map entry yet, so a
|
|
21
49
|
// shutdown during its health poll would otherwise let the bb-viewer child
|
|
22
50
|
// survive daemon stop (on BYOC nothing else reaps it).
|
|
@@ -33,6 +61,16 @@ export class BrowserViewerStreamer {
|
|
|
33
61
|
async handleViewerCommand(profileId, sessionId, command, input, turn) {
|
|
34
62
|
if (!profileId)
|
|
35
63
|
throw new Error('browser profile id is required');
|
|
64
|
+
// Reject a kicked viewer's straggler commands (design §3.6 PR7). After a kick
|
|
65
|
+
// there is no streamer, so the per-command staleness guards (which compare
|
|
66
|
+
// against a live streamer's sessionId) would let a kicked viewer keep driving
|
|
67
|
+
// nav. A fresh stream.start re-establishes a session (killProfileStreamer
|
|
68
|
+
// clears the marker), so it is exempt.
|
|
69
|
+
if (command !== 'stream.start' &&
|
|
70
|
+
sessionId &&
|
|
71
|
+
this.kickedSessions.get(profileId) === sessionId) {
|
|
72
|
+
throw new Error('viewer session was terminated');
|
|
73
|
+
}
|
|
36
74
|
switch (command) {
|
|
37
75
|
case 'stream.start':
|
|
38
76
|
return this.viewerStreamStart(profileId, sessionId, turn);
|
|
@@ -42,6 +80,8 @@ export class BrowserViewerStreamer {
|
|
|
42
80
|
return this.viewerStreamClose(profileId, sessionId, input);
|
|
43
81
|
case 'stream.switch':
|
|
44
82
|
return this.viewerStreamSwitch(profileId, sessionId, input);
|
|
83
|
+
case 'kick':
|
|
84
|
+
return this.viewerKick(profileId, sessionId, input);
|
|
45
85
|
case 'close':
|
|
46
86
|
return this.viewerCloseTab(profileId, sessionId, input);
|
|
47
87
|
case 'tab_list':
|
|
@@ -55,6 +95,36 @@ export class BrowserViewerStreamer {
|
|
|
55
95
|
throw new Error(`unknown viewer command: ${command}`);
|
|
56
96
|
}
|
|
57
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* kick — server-side per-viewer disconnect (design §3.6 PR7). Terminates ONE
|
|
100
|
+
* viewer's WebRTC session: stop its bb-viewer streamer (killing the process
|
|
101
|
+
* tears down the peer connection + datachannel) and reject the kicked session's
|
|
102
|
+
* subsequent commands — while bb-browser + Chromium (the agent's live browser)
|
|
103
|
+
* keep running. This is explicitly DISTINCT from stopping the pod / profile,
|
|
104
|
+
* which would kill the agent's browser too; a viewer session is a sub-session of
|
|
105
|
+
* the profile lease, so a kick does NOT release the lease.
|
|
106
|
+
*
|
|
107
|
+
* Targets `input.session_id` if given, else the current streamer's session.
|
|
108
|
+
* Idempotent: kicking with no live streamer still records the kicked session.
|
|
109
|
+
*/
|
|
110
|
+
viewerKick(profileId, sessionId, input) {
|
|
111
|
+
const streamer = this.streamers.get(profileId);
|
|
112
|
+
const requested = typeof input?.session_id === 'string' ? input.session_id : undefined;
|
|
113
|
+
const target = requested || streamer?.sessionId || sessionId;
|
|
114
|
+
// Only tear down the LIVE streamer when the kick targets the current session — a
|
|
115
|
+
// replayed/delayed stale session_id must not kill a newer live viewer. A kick with
|
|
116
|
+
// no explicit target (kick "whoever is live") also tears it down. A stale target is
|
|
117
|
+
// still recorded so its straggler commands are rejected by the staleness guard.
|
|
118
|
+
const killsCurrent = kickHitsCurrentSession(requested, streamer?.sessionId);
|
|
119
|
+
if (killsCurrent) {
|
|
120
|
+
// killProfileStreamer clears the kick marker, so record the target AFTER it.
|
|
121
|
+
this.killProfileStreamer(profileId);
|
|
122
|
+
}
|
|
123
|
+
if (target)
|
|
124
|
+
this.kickedSessions.set(profileId, target);
|
|
125
|
+
this.host.log.info(`[bb-viewer] kicked viewer session ${target || '(none)'} for profile ${profileId}; pod + agent browser keep running`);
|
|
126
|
+
return target ? { ok: true, kicked: true, session_id: target } : { ok: true, kicked: true };
|
|
127
|
+
}
|
|
58
128
|
/**
|
|
59
129
|
* stream.start — spawn a FRESH bb-viewer for this profile (killing any prior
|
|
60
130
|
* one), resolve the profile's account-scoped page-target CDP ws URL, and run
|
|
@@ -394,6 +464,26 @@ export class BrowserViewerStreamer {
|
|
|
394
464
|
const bin = process.env.PRLL_BB_VIEWER_BIN ?? 'bb-viewer';
|
|
395
465
|
const port = await findFreePort();
|
|
396
466
|
const args = ['--api-only', '--port', String(port)];
|
|
467
|
+
// Bind bb-viewer's HTTP control surface (/command + /health) to a specific
|
|
468
|
+
// host when the image opts in via PRLL_BB_VIEWER_HOST (the hosted browser pod
|
|
469
|
+
// sets it to 127.0.0.1; design §3.6 PR7). The streamer ALWAYS reaches
|
|
470
|
+
// bb-viewer over loopback (streamerCommand + the health poll below both use
|
|
471
|
+
// 127.0.0.1), so a loopback bind never breaks the in-pod path — it only
|
|
472
|
+
// removes the 0.0.0.0 exposure that, on the shared agents cluster, would let
|
|
473
|
+
// another tenant's pod re-point this streamer or start its own stream. Left
|
|
474
|
+
// unset (no --host) for BYOC and the current pinned bb-viewer, which has no
|
|
475
|
+
// --host flag — see deploy/browser-docker/AGENTS.md "bb-viewer loopback bind".
|
|
476
|
+
const bindHost = process.env.PRLL_BB_VIEWER_HOST?.trim();
|
|
477
|
+
if (bindHost) {
|
|
478
|
+
// SECURITY: bb-viewer's /command is unauthenticated, so on the shared agents
|
|
479
|
+
// cluster its bind host MUST be loopback — a 0.0.0.0 / pod-IP / typo'd value would
|
|
480
|
+
// silently re-open the cross-tenant control plane this env exists to close. Fail
|
|
481
|
+
// fast on anything non-loopback rather than pass it through to --host.
|
|
482
|
+
if (!isLoopbackBindHost(bindHost)) {
|
|
483
|
+
throw new Error(`PRLL_BB_VIEWER_HOST must be loopback (127.0.0.1, localhost, ::1, [::1]) — refusing to bind bb-viewer's unauthenticated /command to ${bindHost}`);
|
|
484
|
+
}
|
|
485
|
+
args.push('--host', bindHost);
|
|
486
|
+
}
|
|
397
487
|
if (turn?.url) {
|
|
398
488
|
args.push('--turn-url', turn.url, '--turn-user', turn.username ?? '', '--turn-cred', turn.credential ?? '');
|
|
399
489
|
}
|
|
@@ -528,8 +618,14 @@ export class BrowserViewerStreamer {
|
|
|
528
618
|
clearTimeout(timer);
|
|
529
619
|
}
|
|
530
620
|
}
|
|
531
|
-
/**
|
|
621
|
+
/**
|
|
622
|
+
* Kill + remove the profile's bb-viewer streamer, if any. Idempotent. Also
|
|
623
|
+
* clears any kick marker for the profile: a (re)kill establishes a clean
|
|
624
|
+
* streamer slot, so a subsequent stream.start starts un-kicked. viewerKick
|
|
625
|
+
* re-records the kicked session AFTER calling this.
|
|
626
|
+
*/
|
|
532
627
|
killProfileStreamer(profileId) {
|
|
628
|
+
this.kickedSessions.delete(profileId);
|
|
533
629
|
const streamer = this.streamers.get(profileId);
|
|
534
630
|
if (!streamer)
|
|
535
631
|
return;
|
|
@@ -39,6 +39,26 @@ export interface ClipProviderOptions {
|
|
|
39
39
|
* re-trigger machine.hello. Optional.
|
|
40
40
|
*/
|
|
41
41
|
onPersistentFailure?: () => void;
|
|
42
|
+
/**
|
|
43
|
+
* Hosted-browser live-viewer handler. Set ONLY for a hosted browser pod
|
|
44
|
+
* (browser-pod.ts): clip-service relays viewer commands down the
|
|
45
|
+
* ProviderStream and the pod replies over it (design §3.4). Returns the
|
|
46
|
+
* streamer result on success; throws to signal a command-level error (its
|
|
47
|
+
* message is forwarded to the viewer). Absent for ordinary BYOC providers,
|
|
48
|
+
* which never receive ViewerCommands. Callers should serialize their own
|
|
49
|
+
* streamer state if needed — the provider invokes this per inbound command.
|
|
50
|
+
*/
|
|
51
|
+
onViewerCommand?: (cmd: {
|
|
52
|
+
profileId: string;
|
|
53
|
+
sessionId: string;
|
|
54
|
+
command: string;
|
|
55
|
+
input?: Record<string, unknown>;
|
|
56
|
+
turn?: {
|
|
57
|
+
url: string;
|
|
58
|
+
username?: string;
|
|
59
|
+
credential?: string;
|
|
60
|
+
};
|
|
61
|
+
}) => Promise<Record<string, unknown>>;
|
|
42
62
|
}
|
|
43
63
|
export declare class ClipProvider {
|
|
44
64
|
private readonly opts;
|
|
@@ -52,6 +72,7 @@ export declare class ClipProvider {
|
|
|
52
72
|
private statusUnsubscribe;
|
|
53
73
|
private manifestUnsubscribe;
|
|
54
74
|
private needsReregister;
|
|
75
|
+
private lastRegisteredKey;
|
|
55
76
|
private streamGeneration;
|
|
56
77
|
private static RECONNECT_BASE_MS;
|
|
57
78
|
private static RECONNECT_MAX_MS;
|
|
@@ -69,6 +90,31 @@ export declare class ClipProvider {
|
|
|
69
90
|
private clearReconnectTimer;
|
|
70
91
|
private clearHeartbeatTimer;
|
|
71
92
|
private startHeartbeat;
|
|
93
|
+
/**
|
|
94
|
+
* Re-register the current clip set on the OPEN stream — the daemon's response
|
|
95
|
+
* to a manifest/clip-set change. The hub overwrites its stored set from this
|
|
96
|
+
* full register (clip-provider-sharing-design §11), so a clip install / wake /
|
|
97
|
+
* sleep no longer requires tearing down and reopening the ProviderStream,
|
|
98
|
+
* which used to drop any invoke in flight on that stream (in-flight requests
|
|
99
|
+
* are keyed by request_id on the hub and survive a clip-set swap).
|
|
100
|
+
*
|
|
101
|
+
* No-op when the stream is not currently writable; needsReregister stays set
|
|
102
|
+
* so the next heartbeat tick (or the post-connect flush) retries.
|
|
103
|
+
*
|
|
104
|
+
* Against an older hub that still rejects an in-stream duplicate register, the
|
|
105
|
+
* write reaches a stream the hub then resets; the resulting stream error runs
|
|
106
|
+
* the normal reconnect path, which re-registers as the first frame — i.e. it
|
|
107
|
+
* degrades to the previous reconnect-to-re-register behavior, so no capability
|
|
108
|
+
* negotiation is needed (deploy the hub first).
|
|
109
|
+
*/
|
|
110
|
+
private flushReregister;
|
|
111
|
+
/**
|
|
112
|
+
* Re-register the current clip set on the open stream — public entry for the
|
|
113
|
+
* supervisor when machine clip assignments change. Same in-stream path as a
|
|
114
|
+
* manifest update (no reconnect, in-flight invokes survive). Safe when
|
|
115
|
+
* disconnected: the flag is flushed on the next heartbeat / post-connect.
|
|
116
|
+
*/
|
|
117
|
+
reregister(): void;
|
|
72
118
|
private closeStream;
|
|
73
119
|
private closeSession;
|
|
74
120
|
private subscribeStatusChanges;
|
|
@@ -76,6 +122,16 @@ export declare class ClipProvider {
|
|
|
76
122
|
private handleHubMessage;
|
|
77
123
|
private handleRegistered;
|
|
78
124
|
private handleInvokeCommand;
|
|
125
|
+
/**
|
|
126
|
+
* Handle a hosted-browser live-viewer command relayed by clip-service over the
|
|
127
|
+
* stream and reply on the same stream (design §3.4). Always answers the
|
|
128
|
+
* request/reply bridge exactly once — `{result}` on success, `{error:{message}}`
|
|
129
|
+
* on failure — and never throws into the message loop, mirroring the BYOC
|
|
130
|
+
* daemon's handleBrowserProfileViewer (which replies over HTTP instead).
|
|
131
|
+
*/
|
|
132
|
+
private handleViewerCommand;
|
|
133
|
+
/** Send a hosted-viewer reply (base64-encoded JSON body) on the stream. */
|
|
134
|
+
private sendViewerReply;
|
|
79
135
|
private sendRegister;
|
|
80
136
|
private sendProviderMessage;
|
|
81
137
|
private buildClipRegistrations;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clip-provider.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/clip-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"clip-provider.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/clip-provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -28,6 +28,17 @@ function encodeEnvelope(msg) {
|
|
|
28
28
|
header.writeUInt32BE(json.length, 1);
|
|
29
29
|
return Buffer.concat([header, json]);
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Canonical key for a clip-registration set — used by flushReregister to skip a
|
|
33
|
+
* re-register that would not change the hub's view. Sorted by alias so set
|
|
34
|
+
* order is irrelevant; each registration's shape is fixed by
|
|
35
|
+
* clipConfigToRegistration, so its JSON is a stable digest of the hub-facing
|
|
36
|
+
* content (commands, version, dependencies, …).
|
|
37
|
+
*/
|
|
38
|
+
function registrationKey(clips) {
|
|
39
|
+
const sorted = [...clips].sort((a, b) => (a.alias ?? '').localeCompare(b.alias ?? ''));
|
|
40
|
+
return JSON.stringify(sorted);
|
|
41
|
+
}
|
|
31
42
|
/**
|
|
32
43
|
* Incremental envelope decoder. Feed chunks via push(), receive decoded
|
|
33
44
|
* messages from flush(). Handles partial reads across chunk boundaries.
|
|
@@ -66,7 +77,15 @@ export class ClipProvider {
|
|
|
66
77
|
heartbeatTimer = null;
|
|
67
78
|
statusUnsubscribe = null;
|
|
68
79
|
manifestUnsubscribe = null;
|
|
80
|
+
// Set when the clip manifest/set changed and the hub's copy is stale. Drained
|
|
81
|
+
// by flushReregister(), which re-registers IN-STREAM (no reconnect).
|
|
69
82
|
needsReregister = false;
|
|
83
|
+
// Canonical key of the clip-registration set last sent to the hub on the
|
|
84
|
+
// CURRENT stream. flushReregister diff-gates against it so a cold-start that
|
|
85
|
+
// re-derives an identical set (the common case — a clip's command surface is
|
|
86
|
+
// stable across spawns) does not emit a redundant re-register. Reset per
|
|
87
|
+
// stream; the first-frame register in openStream re-seeds it.
|
|
88
|
+
lastRegisteredKey = null;
|
|
70
89
|
// Monotonic stream id, bumped by openStream(). Handlers from a stream that a
|
|
71
90
|
// newer openStream() has superseded bail in handleDisconnect(gen), so their
|
|
72
91
|
// late close/end/error can't reconnect against the healthy replacement.
|
|
@@ -109,6 +128,9 @@ export class ClipProvider {
|
|
|
109
128
|
return;
|
|
110
129
|
// Bind this stream's handlers to a fresh generation; older streams go stale.
|
|
111
130
|
const gen = ++this.streamGeneration;
|
|
131
|
+
// New stream → the hub holds nothing until the first-frame register below
|
|
132
|
+
// re-seeds this; clear so a stale key can't suppress that register.
|
|
133
|
+
this.lastRegisteredKey = null;
|
|
112
134
|
try {
|
|
113
135
|
this.closeStream();
|
|
114
136
|
this.closeSession();
|
|
@@ -218,25 +240,68 @@ export class ClipProvider {
|
|
|
218
240
|
startHeartbeat() {
|
|
219
241
|
this.clearHeartbeatTimer();
|
|
220
242
|
this.heartbeatTimer = setInterval(() => {
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
this.clearHeartbeatTimer();
|
|
228
|
-
this.clearReconnectTimer();
|
|
229
|
-
// openStream() bumps the generation before closing the old stream, so its
|
|
230
|
-
// late close events fail the gen check (closing inline would re-arm the flap).
|
|
231
|
-
void this.openStream();
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
243
|
+
// A pending manifest/clip-set change re-registers IN-STREAM (no
|
|
244
|
+
// reconnect), so in-flight invokes on this stream survive. This is the
|
|
245
|
+
// fallback for when the immediate flush on the change itself could not
|
|
246
|
+
// write (e.g. it fired mid-(re)connect); the common case re-registers the
|
|
247
|
+
// instant the manifest changes via the manifest listener.
|
|
248
|
+
this.flushReregister();
|
|
234
249
|
this.sendProviderMessage({ ping: { sentAtUnixMs: Date.now() } }).catch((err) => {
|
|
235
250
|
this.opts.log.warn(`[clip-provider] heartbeat send failed: ${String(err)}`);
|
|
236
251
|
});
|
|
237
252
|
}, ClipProvider.HEARTBEAT_INTERVAL_MS);
|
|
238
253
|
this.heartbeatTimer.unref?.();
|
|
239
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Re-register the current clip set on the OPEN stream — the daemon's response
|
|
257
|
+
* to a manifest/clip-set change. The hub overwrites its stored set from this
|
|
258
|
+
* full register (clip-provider-sharing-design §11), so a clip install / wake /
|
|
259
|
+
* sleep no longer requires tearing down and reopening the ProviderStream,
|
|
260
|
+
* which used to drop any invoke in flight on that stream (in-flight requests
|
|
261
|
+
* are keyed by request_id on the hub and survive a clip-set swap).
|
|
262
|
+
*
|
|
263
|
+
* No-op when the stream is not currently writable; needsReregister stays set
|
|
264
|
+
* so the next heartbeat tick (or the post-connect flush) retries.
|
|
265
|
+
*
|
|
266
|
+
* Against an older hub that still rejects an in-stream duplicate register, the
|
|
267
|
+
* write reaches a stream the hub then resets; the resulting stream error runs
|
|
268
|
+
* the normal reconnect path, which re-registers as the first frame — i.e. it
|
|
269
|
+
* degrades to the previous reconnect-to-re-register behavior, so no capability
|
|
270
|
+
* negotiation is needed (deploy the hub first).
|
|
271
|
+
*/
|
|
272
|
+
flushReregister() {
|
|
273
|
+
if (!this.needsReregister)
|
|
274
|
+
return;
|
|
275
|
+
if (!this.connected || !this.stream || this.stream.closed || this.stream.destroyed)
|
|
276
|
+
return;
|
|
277
|
+
// Diff-gate: a cold-start re-derives the clip set on every wake, but the
|
|
278
|
+
// hub-facing registration is usually identical (a clip's command surface is
|
|
279
|
+
// stable across spawns). Skip the redundant re-register so the hot path
|
|
280
|
+
// (invoke → wake clip → manifest re-read) does no hub work when nothing
|
|
281
|
+
// actually changed. Compare the canonical registration set against what this
|
|
282
|
+
// stream last sent.
|
|
283
|
+
const clips = this.buildClipRegistrations();
|
|
284
|
+
if (registrationKey(clips) === this.lastRegisteredKey) {
|
|
285
|
+
this.needsReregister = false;
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
this.needsReregister = false;
|
|
289
|
+
this.opts.log.info('[clip-provider] re-registering clips in-stream');
|
|
290
|
+
this.sendRegister(clips).catch((err) => {
|
|
291
|
+
this.opts.log.warn(`[clip-provider] in-stream re-register failed: ${String(err)}`);
|
|
292
|
+
this.needsReregister = true; // retry on the next heartbeat tick
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Re-register the current clip set on the open stream — public entry for the
|
|
297
|
+
* supervisor when machine clip assignments change. Same in-stream path as a
|
|
298
|
+
* manifest update (no reconnect, in-flight invokes survive). Safe when
|
|
299
|
+
* disconnected: the flag is flushed on the next heartbeat / post-connect.
|
|
300
|
+
*/
|
|
301
|
+
reregister() {
|
|
302
|
+
this.needsReregister = true;
|
|
303
|
+
this.flushReregister();
|
|
304
|
+
}
|
|
240
305
|
closeStream() {
|
|
241
306
|
if (this.stream) {
|
|
242
307
|
try {
|
|
@@ -279,9 +344,11 @@ export class ClipProvider {
|
|
|
279
344
|
this.opts.log.warn(`[clip-provider] status change send failed: ${String(err)}`);
|
|
280
345
|
});
|
|
281
346
|
});
|
|
282
|
-
// Listen for manifest updates so we can re-register with populated commands
|
|
347
|
+
// Listen for manifest updates so we can re-register with populated commands.
|
|
348
|
+
// Re-register immediately on the open stream; the heartbeat retries if the
|
|
349
|
+
// stream is momentarily not writable.
|
|
283
350
|
this.manifestUnsubscribe = mgr.addManifestListener(() => {
|
|
284
|
-
this.
|
|
351
|
+
this.reregister();
|
|
285
352
|
});
|
|
286
353
|
}
|
|
287
354
|
unsubscribeStatusChanges() {
|
|
@@ -300,6 +367,9 @@ export class ClipProvider {
|
|
|
300
367
|
else if (msg.invokeCommand) {
|
|
301
368
|
void this.handleInvokeCommand(msg.invokeCommand);
|
|
302
369
|
}
|
|
370
|
+
else if (msg.viewerCommand) {
|
|
371
|
+
void this.handleViewerCommand(msg.viewerCommand);
|
|
372
|
+
}
|
|
303
373
|
else if (msg.pong !== undefined) {
|
|
304
374
|
// Pong is the hub's keepalive ack. Do NOT echo a ping here — the
|
|
305
375
|
// heartbeat timer (startHeartbeat) is the sole ping driver. Replying to
|
|
@@ -313,6 +383,9 @@ export class ClipProvider {
|
|
|
313
383
|
this.connected = true;
|
|
314
384
|
this.reconnectAttempt = 0;
|
|
315
385
|
this.startHeartbeat();
|
|
386
|
+
// A manifest change that landed during the connect handshake (after the
|
|
387
|
+
// initial register was sent) is flushed now that the stream is writable.
|
|
388
|
+
this.flushReregister();
|
|
316
389
|
}
|
|
317
390
|
else {
|
|
318
391
|
this.opts.log.error(`[clip-provider] registration rejected: ${reg.message}`);
|
|
@@ -387,17 +460,68 @@ export class ClipProvider {
|
|
|
387
460
|
}).catch(() => { }); // best-effort error response
|
|
388
461
|
}
|
|
389
462
|
}
|
|
463
|
+
/**
|
|
464
|
+
* Handle a hosted-browser live-viewer command relayed by clip-service over the
|
|
465
|
+
* stream and reply on the same stream (design §3.4). Always answers the
|
|
466
|
+
* request/reply bridge exactly once — `{result}` on success, `{error:{message}}`
|
|
467
|
+
* on failure — and never throws into the message loop, mirroring the BYOC
|
|
468
|
+
* daemon's handleBrowserProfileViewer (which replies over HTTP instead).
|
|
469
|
+
*/
|
|
470
|
+
async handleViewerCommand(cmd) {
|
|
471
|
+
const { requestId } = cmd;
|
|
472
|
+
if (!this.opts.onViewerCommand) {
|
|
473
|
+
// A provider with no live-viewer handler received a viewer command (should
|
|
474
|
+
// not happen — only hosted pods are sent these). Fail the bridge fast.
|
|
475
|
+
await this.sendViewerReply(requestId, {
|
|
476
|
+
error: { message: 'live viewer is not supported by this provider' },
|
|
477
|
+
}).catch(() => { });
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
try {
|
|
481
|
+
let input;
|
|
482
|
+
if (cmd.input) {
|
|
483
|
+
const decoded = Buffer.from(cmd.input, 'base64').toString('utf-8');
|
|
484
|
+
try {
|
|
485
|
+
input = JSON.parse(decoded);
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
input = undefined;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
const result = await this.opts.onViewerCommand({
|
|
492
|
+
profileId: cmd.profileId,
|
|
493
|
+
sessionId: cmd.sessionId,
|
|
494
|
+
command: cmd.command,
|
|
495
|
+
input,
|
|
496
|
+
turn: cmd.turn,
|
|
497
|
+
});
|
|
498
|
+
await this.sendViewerReply(requestId, { result });
|
|
499
|
+
}
|
|
500
|
+
catch (err) {
|
|
501
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
502
|
+
this.opts.log.warn(`[clip-provider] viewer command failed (profile=${cmd.profileId}, command=${cmd.command}): ${message}`);
|
|
503
|
+
await this.sendViewerReply(requestId, { error: { message } }).catch(() => { });
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
/** Send a hosted-viewer reply (base64-encoded JSON body) on the stream. */
|
|
507
|
+
async sendViewerReply(requestId, reply) {
|
|
508
|
+
const payload = Buffer.from(JSON.stringify(reply), 'utf-8').toString('base64');
|
|
509
|
+
await this.sendProviderMessage({ viewerResult: { requestId, payload } });
|
|
510
|
+
}
|
|
390
511
|
// ---------------------------------------------------------------------------
|
|
391
512
|
// Sending
|
|
392
513
|
// ---------------------------------------------------------------------------
|
|
393
|
-
async sendRegister() {
|
|
394
|
-
const clips = this.buildClipRegistrations();
|
|
514
|
+
async sendRegister(prebuilt) {
|
|
515
|
+
const clips = prebuilt ?? this.buildClipRegistrations();
|
|
395
516
|
await this.sendProviderMessage({
|
|
396
517
|
register: {
|
|
397
518
|
providerName: this.opts.providerName,
|
|
398
519
|
clips,
|
|
399
520
|
},
|
|
400
521
|
});
|
|
522
|
+
// Record what the hub now holds on this stream so flushReregister can
|
|
523
|
+
// diff-gate subsequent cold-start re-registers.
|
|
524
|
+
this.lastRegisteredKey = registrationKey(clips);
|
|
401
525
|
}
|
|
402
526
|
async sendProviderMessage(msg) {
|
|
403
527
|
if (!this.stream || this.stream.closed || this.stream.destroyed) {
|
|
@@ -5,5 +5,6 @@ export { type ClipConfig, type ManifestCache, type CommandDetail, type ClipJson,
|
|
|
5
5
|
export { ClipProvider, type ClipProviderOptions } from './clip-provider.js';
|
|
6
6
|
export { HubClient, HubCommandError, type HubClientOptions, type ClipBinding, } from './hub-client.js';
|
|
7
7
|
export { BrowserProfileManager } from './browser-profile-manager.js';
|
|
8
|
+
export type { BrowserProxyConfig } from './browser-profile-manager.js';
|
|
8
9
|
export { installClip, removeClip, parseSource, type InstallOptions, type InstallResult, } from './clip-installer.js';
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,KAAK,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AAC1F,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,QAAQ,EACb,WAAW,EACX,YAAY,EACZ,YAAY,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,QAAQ,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EACL,SAAS,EACT,eAAe,EACf,KAAK,gBAAgB,EACrB,KAAK,WAAW,GACjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EACL,WAAW,EACX,UAAU,EACV,WAAW,EACX,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/clip-runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,KAAK,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AAC1F,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,QAAQ,EACb,WAAW,EACX,YAAY,EACZ,YAAY,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,QAAQ,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EACL,SAAS,EACT,eAAe,EACf,KAAK,gBAAgB,EACrB,KAAK,WAAW,GACjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,YAAY,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EACL,WAAW,EACX,UAAU,EACV,WAAW,EACX,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,qBAAqB,CAAC"}
|
package/dist/supervisor.d.ts
CHANGED
|
@@ -57,6 +57,17 @@ export declare class DaemonSupervisor {
|
|
|
57
57
|
private bootstrapWithRetry;
|
|
58
58
|
private fullReconcile;
|
|
59
59
|
private reconcileBrowserProfiles;
|
|
60
|
+
/**
|
|
61
|
+
* Resolve a profile's outbound proxy for bb-browser account creation (BYOC).
|
|
62
|
+
* Fetched fresh — account creation is rare (once per profile until reset), so a
|
|
63
|
+
* proxy edit applies on the next account_create. The mck_-authed machine list is
|
|
64
|
+
* the only path that carries proxy_password (for replay to bb-browser).
|
|
65
|
+
*
|
|
66
|
+
* Fails closed: a fetch failure or a vanished profile THROWS, so the manager never
|
|
67
|
+
* creates an account that would egress from the host's real IP for a
|
|
68
|
+
* proxy-configured profile (the caller reports `error` and retries next tick).
|
|
69
|
+
*/
|
|
70
|
+
private resolveBrowserProfileProxy;
|
|
60
71
|
/**
|
|
61
72
|
* Detects a legacy flat state layout (no agents/ subdir) and migrates it
|
|
62
73
|
* into the per-agent directory for the owning agent. Ownership is determined
|
|
@@ -89,7 +100,6 @@ export declare class DaemonSupervisor {
|
|
|
89
100
|
private clipConfigSignature;
|
|
90
101
|
private ensureClipInstalled;
|
|
91
102
|
private readInstalledClipVersion;
|
|
92
|
-
private reconnectClipProvider;
|
|
93
103
|
private handleWorkspaceSetupRequested;
|
|
94
104
|
private scheduleWorkspaceSetupRetry;
|
|
95
105
|
private clearWorkspaceSetupRetry;
|
package/dist/supervisor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"AAGA,OAAO,EAA8B,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACpF,OAAO,EAeL,KAAK,YAAY,EAElB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"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"}
|
package/dist/supervisor.js
CHANGED
|
@@ -133,6 +133,7 @@ export class DaemonSupervisor {
|
|
|
133
133
|
.reportBrowserProfileStatus(profileId, status, errorMsg)
|
|
134
134
|
.catch((err) => this.log.warn(`browser profile status report failed: ${String(err)}`));
|
|
135
135
|
},
|
|
136
|
+
resolveProxy: (profileId) => this.resolveBrowserProfileProxy(profileId),
|
|
136
137
|
});
|
|
137
138
|
this.clipManager = new ClipProcessManager({
|
|
138
139
|
clipsDir: path.join(this.config.rootStateDir, 'clips'),
|
|
@@ -400,10 +401,16 @@ export class DaemonSupervisor {
|
|
|
400
401
|
for (const profile of profiles) {
|
|
401
402
|
if (profile.status !== 'running' && profile.status !== 'pending')
|
|
402
403
|
continue;
|
|
404
|
+
// The daemon supervises only machine-bound (byoc) profiles; hosted
|
|
405
|
+
// profiles (null machine_id) run on the platform pool and are never owned
|
|
406
|
+
// by a daemon machine.
|
|
407
|
+
if (!profile.machine_id)
|
|
408
|
+
continue;
|
|
409
|
+
const machineId = profile.machine_id;
|
|
403
410
|
try {
|
|
404
411
|
if (profile.status === 'pending') {
|
|
405
412
|
await this.enqueueBrowserProfileLifecycle({
|
|
406
|
-
machine_id:
|
|
413
|
+
machine_id: machineId,
|
|
407
414
|
profile_id: profile.id,
|
|
408
415
|
action: 'open',
|
|
409
416
|
});
|
|
@@ -417,6 +424,30 @@ export class DaemonSupervisor {
|
|
|
417
424
|
}
|
|
418
425
|
}
|
|
419
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* Resolve a profile's outbound proxy for bb-browser account creation (BYOC).
|
|
429
|
+
* Fetched fresh — account creation is rare (once per profile until reset), so a
|
|
430
|
+
* proxy edit applies on the next account_create. The mck_-authed machine list is
|
|
431
|
+
* the only path that carries proxy_password (for replay to bb-browser).
|
|
432
|
+
*
|
|
433
|
+
* Fails closed: a fetch failure or a vanished profile THROWS, so the manager never
|
|
434
|
+
* creates an account that would egress from the host's real IP for a
|
|
435
|
+
* proxy-configured profile (the caller reports `error` and retries next tick).
|
|
436
|
+
*/
|
|
437
|
+
async resolveBrowserProfileProxy(profileId) {
|
|
438
|
+
const profiles = await this.client.listMachineBrowserProfiles();
|
|
439
|
+
const profile = profiles.find((p) => p.id === profileId);
|
|
440
|
+
if (!profile) {
|
|
441
|
+
throw new Error(`browser profile ${profileId} not found; refusing to create a bb-browser account without its proxy config`);
|
|
442
|
+
}
|
|
443
|
+
if (!profile.proxy_server)
|
|
444
|
+
return null;
|
|
445
|
+
return {
|
|
446
|
+
server: profile.proxy_server,
|
|
447
|
+
username: profile.proxy_username ?? undefined,
|
|
448
|
+
password: profile.proxy_password ?? undefined,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
420
451
|
// ---- Flat layout migration (self-hosted → daemon) ----
|
|
421
452
|
/**
|
|
422
453
|
* Detects a legacy flat state layout (no agents/ subdir) and migrates it
|
|
@@ -681,7 +712,12 @@ export class DaemonSupervisor {
|
|
|
681
712
|
}
|
|
682
713
|
if (changed) {
|
|
683
714
|
this.log.info(`machine clips synced — count=${desired.length}`);
|
|
684
|
-
|
|
715
|
+
// Re-register the new clip set IN-STREAM rather than reconnecting the
|
|
716
|
+
// provider — a clip assign/unassign must not tear down the stream and drop
|
|
717
|
+
// in-flight invokes (the hub overwrites its set from the register; see
|
|
718
|
+
// clip-provider-sharing-design §11). Endpoint changes still rebuild via
|
|
719
|
+
// applyClipProviderState.
|
|
720
|
+
this.clipProvider?.reregister();
|
|
685
721
|
}
|
|
686
722
|
}
|
|
687
723
|
startClipReconcileTimer() {
|
|
@@ -816,17 +852,6 @@ export class DaemonSupervisor {
|
|
|
816
852
|
}
|
|
817
853
|
return null;
|
|
818
854
|
}
|
|
819
|
-
async reconnectClipProvider() {
|
|
820
|
-
if (!this.clipProvider)
|
|
821
|
-
return;
|
|
822
|
-
const provider = this.clipProvider;
|
|
823
|
-
this.clipProvider = null;
|
|
824
|
-
this.connectedClipServiceUrl = null;
|
|
825
|
-
await provider
|
|
826
|
-
.disconnect()
|
|
827
|
-
.catch((err) => this.log.warn(`clip provider reconnect disconnect failed: ${String(err)}`));
|
|
828
|
-
await this.applyClipProviderState();
|
|
829
|
-
}
|
|
830
855
|
async handleWorkspaceSetupRequested(agentId) {
|
|
831
856
|
if (this.spawningAgents.has(agentId)) {
|
|
832
857
|
this.log.info(`agent ${agentId}: workspace setup already in progress; queueing one restart`);
|