@harness-mix/cli 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +469 -467
  3. package/output/native-build/desktop-controller.mjs +1 -1
  4. package/output/native-build/renderer-extension.js +23 -4
  5. package/package.json +11 -9
  6. package/scripts/antigravity-adapter-test.cjs +647 -626
  7. package/scripts/codex-adapter-test.cjs +162 -127
  8. package/scripts/collaboration-test.cjs +274 -262
  9. package/scripts/jsonl-stdin-test.cjs +40 -31
  10. package/scripts/kiro-cursor-adapters-test.cjs +124 -100
  11. package/scripts/native-acp-depth-test.cjs +30 -5
  12. package/scripts/native-update-apply-test.cjs +269 -215
  13. package/scripts/native-update.cjs +78 -0
  14. package/scripts/native-vendor-adapters-test.cjs +196 -154
  15. package/scripts/salvage-rollout-writes.cjs +72 -0
  16. package/scripts/zcode-adapter-test.cjs +329 -0
  17. package/scripts/zcode-live-probe.cjs +66 -0
  18. package/src/main/adapters/antigravity.js +1428 -1418
  19. package/src/main/adapters/codex.js +656 -649
  20. package/src/main/adapters/native-acp-command.js +51 -48
  21. package/src/main/adapters/native-acp.js +47 -12
  22. package/src/main/adapters/qoder.js +12 -8
  23. package/src/main/adapters/zcode.js +921 -10
  24. package/src/main/host/collaboration.js +723 -715
  25. package/src/main/host/jsonl.js +130 -120
  26. package/src/main/native/config.js +9 -9
  27. package/src/main/native/launcher.js +252 -237
  28. package/src/main/native/process-utils.js +157 -57
  29. package/src/main/native/protocol.js +1221 -1187
  30. package/src/main/native/update-state.js +123 -110
  31. package/src/main/native/updater.js +460 -394
  32. package/src/native-ui/desktop-control/src/renderer-cdp-control-session.ts +358 -358
  33. package/src/native-ui/renderer-extension/src/renderer-binding-probe.ts +3211 -3181
  34. package/src/native-ui/renderer-extension/src/settings/connections-page.ts +2 -2
@@ -1,358 +1,358 @@
1
- import {
2
- CdpClient,
3
- listCdpTargets,
4
- type CdpClientOptions,
5
- type CdpFetch,
6
- type CdpTarget,
7
- } from "./cdp-client.js";
8
- import {
9
- installRendererDraftPrewarmPolicyDirect,
10
- type RendererDraftPrewarmPolicyStatus,
11
- } from "./renderer-draft-prewarm-policy.js";
12
-
13
- export interface ProductionRendererStatus {
14
- version: 2;
15
- enabledAgents: string[];
16
- adapter: {
17
- state: "ready";
18
- reason: string;
19
- };
20
- }
21
-
22
- export interface RendererCdpControlSnapshot {
23
- target: CdpTarget;
24
- draftPrewarmPolicy: RendererDraftPrewarmPolicyStatus;
25
- binding: ProductionRendererStatus;
26
- }
27
-
28
- interface RendererCdpClient {
29
- command(method: string, params?: Record<string, unknown>): Promise<unknown>;
30
- evaluate<T>(expression: string): Promise<T>;
31
- close(): void;
32
- }
33
-
34
- interface RendererCdpControlOperations {
35
- listTargets(endpoint: string): Promise<CdpTarget[]>;
36
- connect(webSocketDebuggerUrl: string): Promise<RendererCdpClient>;
37
- installDraftPrewarmPolicy(renderer: RendererCdpClient): Promise<RendererDraftPrewarmPolicyStatus>;
38
- }
39
-
40
- export interface RendererCdpControlSession {
41
- readonly snapshot: RendererCdpControlSnapshot;
42
- ensureInstalled(): Promise<RendererCdpControlSnapshot>;
43
- activateDesktop(): Promise<number>;
44
- executeRenderer<T>(expression: string): Promise<T>;
45
- close(): void;
46
- }
47
-
48
- export interface InstallRendererCdpControlOptions {
49
- rendererCdpEndpoint: string;
50
- rendererSource: string;
51
- enabledAgents?: readonly string[];
52
- pollIntervalMs?: number;
53
- timeoutMs?: number;
54
- }
55
-
56
- interface CreateRendererCdpControlOptions extends InstallRendererCdpControlOptions {
57
- operations?: RendererCdpControlOperations;
58
- }
59
-
60
- function isRecord(value: unknown): value is Record<string, unknown> {
61
- return typeof value === "object" && value !== null && !Array.isArray(value);
62
- }
63
-
64
- function sleep(milliseconds: number): Promise<void> {
65
- return new Promise((resolve) => setTimeout(resolve, milliseconds));
66
- }
67
-
68
- function sameAgents(actual: readonly string[], expected: readonly string[]): boolean {
69
- return (
70
- actual.length === expected.length && actual.every((agent, index) => agent === expected[index])
71
- );
72
- }
73
-
74
- function isPrimaryRendererUrl(value: string): boolean {
75
- try {
76
- const url = new URL(value);
77
- return (
78
- url.protocol === "app:" &&
79
- url.hostname === "-" &&
80
- url.pathname === "/index.html" &&
81
- url.search === "" &&
82
- url.hash === ""
83
- );
84
- } catch {
85
- return false;
86
- }
87
- }
88
-
89
- export function selectPrimaryRendererTarget(
90
- targets: readonly CdpTarget[],
91
- preferredTargetId?: string,
92
- ): CdpTarget | null {
93
- const candidates = targets.filter(
94
- (target) => target.type === "page" && isPrimaryRendererUrl(target.url),
95
- );
96
- return candidates.find((target) => target.id === preferredTargetId) ?? candidates.at(0) ?? null;
97
- }
98
-
99
- class RendererAdapterReadinessError extends Error {
100
- constructor(
101
- readonly state: string,
102
- readonly reason: string,
103
- ) {
104
- super(`Production Renderer Adapter is ${state}: ${reason}`);
105
- this.name = "RendererAdapterReadinessError";
106
- }
107
- }
108
-
109
- function validateBindingStatus(
110
- value: unknown,
111
- expectedAgents: readonly string[],
112
- ): ProductionRendererStatus {
113
- if (
114
- !isRecord(value) ||
115
- value.version !== 2 ||
116
- !Array.isArray(value.enabledAgents) ||
117
- value.enabledAgents.some((agent) => typeof agent !== "string") ||
118
- !sameAgents(value.enabledAgents as string[], expectedAgents) ||
119
- !isRecord(value.adapter)
120
- ) {
121
- throw new Error("Production Renderer binding returned an invalid status");
122
- }
123
- if (value.adapter.state !== "ready" || typeof value.adapter.reason !== "string") {
124
- const state = typeof value.adapter.state === "string" ? value.adapter.state : "invalid";
125
- const reason = typeof value.adapter.reason === "string" ? value.adapter.reason : "unknown";
126
- throw new RendererAdapterReadinessError(state, reason);
127
- }
128
- return value as unknown as ProductionRendererStatus;
129
- }
130
-
131
- async function waitForPrimaryTarget(
132
- endpoint: string,
133
- operations: RendererCdpControlOperations,
134
- timeoutMs: number,
135
- pollIntervalMs: number,
136
- preferredTargetId?: string,
137
- ): Promise<CdpTarget> {
138
- const deadline = Date.now() + timeoutMs;
139
- let lastError: unknown;
140
- while (Date.now() < deadline) {
141
- try {
142
- const target = selectPrimaryRendererTarget(
143
- await operations.listTargets(endpoint),
144
- preferredTargetId,
145
- );
146
- if (target) return target;
147
- lastError = new Error("Renderer CDP has no primary app://-/index.html page target");
148
- } catch (error) {
149
- lastError = error;
150
- }
151
- await sleep(pollIntervalMs);
152
- }
153
- const detail = lastError instanceof Error ? `: ${lastError.message}` : "";
154
- throw new Error(`Primary Codex Renderer CDP target did not become ready${detail}`);
155
- }
156
-
157
- async function evaluateSource(renderer: RendererCdpClient, source: string): Promise<void> {
158
- const response = await renderer.command("Runtime.evaluate", {
159
- expression: source,
160
- awaitPromise: true,
161
- });
162
- if (!isRecord(response)) throw new Error("Renderer source evaluation returned an invalid result");
163
- if (isRecord(response.exceptionDetails)) {
164
- const text =
165
- typeof response.exceptionDetails.text === "string"
166
- ? response.exceptionDetails.text
167
- : "Renderer source evaluation failed";
168
- throw new Error(text);
169
- }
170
- }
171
-
172
- async function readBinding(renderer: RendererCdpClient): Promise<unknown> {
173
- return renderer.evaluate<unknown>("window.__harnessmixRendererBindingProbeV1?.status() ?? null");
174
- }
175
-
176
- async function waitForBinding(
177
- renderer: RendererCdpClient,
178
- enabledAgents: readonly string[],
179
- timeoutMs: number,
180
- pollIntervalMs: number,
181
- ): Promise<ProductionRendererStatus> {
182
- const deadline = Date.now() + timeoutMs;
183
- let lastError: unknown;
184
- while (Date.now() < deadline) {
185
- try {
186
- const value = await readBinding(renderer);
187
- if (value !== null) return validateBindingStatus(value, enabledAgents);
188
- } catch (error) {
189
- lastError = error;
190
- if (error instanceof RendererAdapterReadinessError && error.state !== "installing") {
191
- throw error;
192
- }
193
- }
194
- await sleep(pollIntervalMs);
195
- }
196
- const detail = lastError instanceof Error ? `: ${lastError.message}` : "";
197
- throw new Error(`Production Renderer binding did not become ready${detail}`);
198
- }
199
-
200
- async function installTarget(
201
- target: CdpTarget,
202
- rendererSource: string,
203
- enabledAgents: readonly string[],
204
- timeoutMs: number,
205
- pollIntervalMs: number,
206
- operations: RendererCdpControlOperations,
207
- ): Promise<{ renderer: RendererCdpClient; snapshot: RendererCdpControlSnapshot }> {
208
- const renderer = await operations.connect(target.webSocketDebuggerUrl);
209
- try {
210
- await renderer.command("Runtime.enable");
211
- await renderer.command("Page.enable");
212
- await renderer.command("Page.addScriptToEvaluateOnNewDocument", { source: rendererSource });
213
- await evaluateSource(renderer, rendererSource);
214
- const draftPrewarmPolicy = await operations.installDraftPrewarmPolicy(renderer);
215
- const binding = await waitForBinding(renderer, enabledAgents, timeoutMs, pollIntervalMs);
216
- return { renderer, snapshot: { target, draftPrewarmPolicy, binding } };
217
- } catch (error) {
218
- renderer.close();
219
- throw error;
220
- }
221
- }
222
-
223
- class InstalledRendererCdpControlSession implements RendererCdpControlSession {
224
- #closed = false;
225
-
226
- constructor(
227
- private renderer: RendererCdpClient,
228
- private readonly rendererCdpEndpoint: string,
229
- private readonly rendererSource: string,
230
- private readonly enabledAgents: readonly string[],
231
- private readonly timeoutMs: number,
232
- private readonly pollIntervalMs: number,
233
- private readonly operations: RendererCdpControlOperations,
234
- private currentSnapshot: RendererCdpControlSnapshot,
235
- ) {}
236
-
237
- get snapshot(): RendererCdpControlSnapshot {
238
- return this.currentSnapshot;
239
- }
240
-
241
- async ensureInstalled(): Promise<RendererCdpControlSnapshot> {
242
- if (this.#closed) throw new Error("Renderer CDP Control Session is closed");
243
- const target = await waitForPrimaryTarget(
244
- this.rendererCdpEndpoint,
245
- this.operations,
246
- this.timeoutMs,
247
- this.pollIntervalMs,
248
- this.currentSnapshot.target.id,
249
- );
250
- if (target.id !== this.currentSnapshot.target.id) {
251
- const replacement = await installTarget(
252
- target,
253
- this.rendererSource,
254
- this.enabledAgents,
255
- this.timeoutMs,
256
- this.pollIntervalMs,
257
- this.operations,
258
- );
259
- this.renderer.close();
260
- this.renderer = replacement.renderer;
261
- this.currentSnapshot = replacement.snapshot;
262
- return this.currentSnapshot;
263
- }
264
-
265
- try {
266
- const existing = await readBinding(this.renderer);
267
- if (existing === null) await evaluateSource(this.renderer, this.rendererSource);
268
- else validateBindingStatus(existing, this.enabledAgents);
269
- const draftPrewarmPolicy = await this.operations.installDraftPrewarmPolicy(this.renderer);
270
- const binding = await waitForBinding(
271
- this.renderer,
272
- this.enabledAgents,
273
- this.timeoutMs,
274
- this.pollIntervalMs,
275
- );
276
- this.currentSnapshot = { target, draftPrewarmPolicy, binding };
277
- return this.currentSnapshot;
278
- } catch {
279
- const replacement = await installTarget(
280
- target,
281
- this.rendererSource,
282
- this.enabledAgents,
283
- this.timeoutMs,
284
- this.pollIntervalMs,
285
- this.operations,
286
- );
287
- this.renderer.close();
288
- this.renderer = replacement.renderer;
289
- this.currentSnapshot = replacement.snapshot;
290
- return this.currentSnapshot;
291
- }
292
- }
293
-
294
- async activateDesktop(): Promise<number> {
295
- if (this.#closed) throw new Error("Renderer CDP Control Session is closed");
296
- await this.renderer.command("Page.bringToFront");
297
- return 1;
298
- }
299
-
300
- executeRenderer<T>(expression: string): Promise<T> {
301
- if (this.#closed) return Promise.reject(new Error("Renderer CDP Control Session is closed"));
302
- return this.renderer.evaluate<T>(expression);
303
- }
304
-
305
- close(): void {
306
- if (this.#closed) return;
307
- this.#closed = true;
308
- this.renderer.close();
309
- }
310
- }
311
-
312
- const defaultOperations: RendererCdpControlOperations = {
313
- listTargets: (endpoint) => listCdpTargets(endpoint),
314
- connect: (webSocketDebuggerUrl) => CdpClient.connect(webSocketDebuggerUrl),
315
- installDraftPrewarmPolicy: installRendererDraftPrewarmPolicyDirect,
316
- };
317
-
318
- export async function createRendererCdpControlSession(
319
- options: CreateRendererCdpControlOptions,
320
- ): Promise<RendererCdpControlSession> {
321
- const enabledAgents = options.enabledAgents ?? ["codex", "pi"];
322
- const timeoutMs = options.timeoutMs ?? 30_000;
323
- const pollIntervalMs = options.pollIntervalMs ?? 250;
324
- const operations = options.operations ?? defaultOperations;
325
- const target = await waitForPrimaryTarget(
326
- options.rendererCdpEndpoint,
327
- operations,
328
- timeoutMs,
329
- pollIntervalMs,
330
- );
331
- const installed = await installTarget(
332
- target,
333
- options.rendererSource,
334
- enabledAgents,
335
- timeoutMs,
336
- pollIntervalMs,
337
- operations,
338
- );
339
- return new InstalledRendererCdpControlSession(
340
- installed.renderer,
341
- options.rendererCdpEndpoint,
342
- options.rendererSource,
343
- enabledAgents,
344
- timeoutMs,
345
- pollIntervalMs,
346
- operations,
347
- installed.snapshot,
348
- );
349
- }
350
-
351
- export function installRendererCdpControlSession(
352
- options: InstallRendererCdpControlOptions,
353
- ): Promise<RendererCdpControlSession> {
354
- return createRendererCdpControlSession(options);
355
- }
356
-
357
- export type RendererCdpFetch = CdpFetch;
358
- export type RendererCdpClientOptions = CdpClientOptions;
1
+ import {
2
+ CdpClient,
3
+ listCdpTargets,
4
+ type CdpClientOptions,
5
+ type CdpFetch,
6
+ type CdpTarget,
7
+ } from "./cdp-client.js";
8
+ import {
9
+ installRendererDraftPrewarmPolicyDirect,
10
+ type RendererDraftPrewarmPolicyStatus,
11
+ } from "./renderer-draft-prewarm-policy.js";
12
+
13
+ export interface ProductionRendererStatus {
14
+ version: 2;
15
+ enabledAgents: string[];
16
+ adapter: {
17
+ state: "ready";
18
+ reason: string;
19
+ };
20
+ }
21
+
22
+ export interface RendererCdpControlSnapshot {
23
+ target: CdpTarget;
24
+ draftPrewarmPolicy: RendererDraftPrewarmPolicyStatus;
25
+ binding: ProductionRendererStatus;
26
+ }
27
+
28
+ interface RendererCdpClient {
29
+ command(method: string, params?: Record<string, unknown>): Promise<unknown>;
30
+ evaluate<T>(expression: string): Promise<T>;
31
+ close(): void;
32
+ }
33
+
34
+ interface RendererCdpControlOperations {
35
+ listTargets(endpoint: string): Promise<CdpTarget[]>;
36
+ connect(webSocketDebuggerUrl: string): Promise<RendererCdpClient>;
37
+ installDraftPrewarmPolicy(renderer: RendererCdpClient): Promise<RendererDraftPrewarmPolicyStatus>;
38
+ }
39
+
40
+ export interface RendererCdpControlSession {
41
+ readonly snapshot: RendererCdpControlSnapshot;
42
+ ensureInstalled(): Promise<RendererCdpControlSnapshot>;
43
+ activateDesktop(): Promise<number>;
44
+ executeRenderer<T>(expression: string): Promise<T>;
45
+ close(): void;
46
+ }
47
+
48
+ export interface InstallRendererCdpControlOptions {
49
+ rendererCdpEndpoint: string;
50
+ rendererSource: string;
51
+ enabledAgents?: readonly string[];
52
+ pollIntervalMs?: number;
53
+ timeoutMs?: number;
54
+ }
55
+
56
+ interface CreateRendererCdpControlOptions extends InstallRendererCdpControlOptions {
57
+ operations?: RendererCdpControlOperations;
58
+ }
59
+
60
+ function isRecord(value: unknown): value is Record<string, unknown> {
61
+ return typeof value === "object" && value !== null && !Array.isArray(value);
62
+ }
63
+
64
+ function sleep(milliseconds: number): Promise<void> {
65
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
66
+ }
67
+
68
+ function sameAgents(actual: readonly string[], expected: readonly string[]): boolean {
69
+ // The renderer bundle and the controller keep independently ordered agent
70
+ // catalogs; only the enabled set is contractual.
71
+ return actual.length === expected.length && expected.every((agent) => actual.includes(agent));
72
+ }
73
+
74
+ function isPrimaryRendererUrl(value: string): boolean {
75
+ try {
76
+ const url = new URL(value);
77
+ return (
78
+ url.protocol === "app:" &&
79
+ url.hostname === "-" &&
80
+ url.pathname === "/index.html" &&
81
+ url.search === "" &&
82
+ url.hash === ""
83
+ );
84
+ } catch {
85
+ return false;
86
+ }
87
+ }
88
+
89
+ export function selectPrimaryRendererTarget(
90
+ targets: readonly CdpTarget[],
91
+ preferredTargetId?: string,
92
+ ): CdpTarget | null {
93
+ const candidates = targets.filter(
94
+ (target) => target.type === "page" && isPrimaryRendererUrl(target.url),
95
+ );
96
+ return candidates.find((target) => target.id === preferredTargetId) ?? candidates.at(0) ?? null;
97
+ }
98
+
99
+ class RendererAdapterReadinessError extends Error {
100
+ constructor(
101
+ readonly state: string,
102
+ readonly reason: string,
103
+ ) {
104
+ super(`Production Renderer Adapter is ${state}: ${reason}`);
105
+ this.name = "RendererAdapterReadinessError";
106
+ }
107
+ }
108
+
109
+ function validateBindingStatus(
110
+ value: unknown,
111
+ expectedAgents: readonly string[],
112
+ ): ProductionRendererStatus {
113
+ if (
114
+ !isRecord(value) ||
115
+ value.version !== 2 ||
116
+ !Array.isArray(value.enabledAgents) ||
117
+ value.enabledAgents.some((agent) => typeof agent !== "string") ||
118
+ !sameAgents(value.enabledAgents as string[], expectedAgents) ||
119
+ !isRecord(value.adapter)
120
+ ) {
121
+ throw new Error("Production Renderer binding returned an invalid status");
122
+ }
123
+ if (value.adapter.state !== "ready" || typeof value.adapter.reason !== "string") {
124
+ const state = typeof value.adapter.state === "string" ? value.adapter.state : "invalid";
125
+ const reason = typeof value.adapter.reason === "string" ? value.adapter.reason : "unknown";
126
+ throw new RendererAdapterReadinessError(state, reason);
127
+ }
128
+ return value as unknown as ProductionRendererStatus;
129
+ }
130
+
131
+ async function waitForPrimaryTarget(
132
+ endpoint: string,
133
+ operations: RendererCdpControlOperations,
134
+ timeoutMs: number,
135
+ pollIntervalMs: number,
136
+ preferredTargetId?: string,
137
+ ): Promise<CdpTarget> {
138
+ const deadline = Date.now() + timeoutMs;
139
+ let lastError: unknown;
140
+ while (Date.now() < deadline) {
141
+ try {
142
+ const target = selectPrimaryRendererTarget(
143
+ await operations.listTargets(endpoint),
144
+ preferredTargetId,
145
+ );
146
+ if (target) return target;
147
+ lastError = new Error("Renderer CDP has no primary app://-/index.html page target");
148
+ } catch (error) {
149
+ lastError = error;
150
+ }
151
+ await sleep(pollIntervalMs);
152
+ }
153
+ const detail = lastError instanceof Error ? `: ${lastError.message}` : "";
154
+ throw new Error(`Primary Codex Renderer CDP target did not become ready${detail}`);
155
+ }
156
+
157
+ async function evaluateSource(renderer: RendererCdpClient, source: string): Promise<void> {
158
+ const response = await renderer.command("Runtime.evaluate", {
159
+ expression: source,
160
+ awaitPromise: true,
161
+ });
162
+ if (!isRecord(response)) throw new Error("Renderer source evaluation returned an invalid result");
163
+ if (isRecord(response.exceptionDetails)) {
164
+ const text =
165
+ typeof response.exceptionDetails.text === "string"
166
+ ? response.exceptionDetails.text
167
+ : "Renderer source evaluation failed";
168
+ throw new Error(text);
169
+ }
170
+ }
171
+
172
+ async function readBinding(renderer: RendererCdpClient): Promise<unknown> {
173
+ return renderer.evaluate<unknown>("window.__harnessmixRendererBindingProbeV1?.status() ?? null");
174
+ }
175
+
176
+ async function waitForBinding(
177
+ renderer: RendererCdpClient,
178
+ enabledAgents: readonly string[],
179
+ timeoutMs: number,
180
+ pollIntervalMs: number,
181
+ ): Promise<ProductionRendererStatus> {
182
+ const deadline = Date.now() + timeoutMs;
183
+ let lastError: unknown;
184
+ while (Date.now() < deadline) {
185
+ try {
186
+ const value = await readBinding(renderer);
187
+ if (value !== null) return validateBindingStatus(value, enabledAgents);
188
+ } catch (error) {
189
+ lastError = error;
190
+ if (error instanceof RendererAdapterReadinessError && error.state !== "installing") {
191
+ throw error;
192
+ }
193
+ }
194
+ await sleep(pollIntervalMs);
195
+ }
196
+ const detail = lastError instanceof Error ? `: ${lastError.message}` : "";
197
+ throw new Error(`Production Renderer binding did not become ready${detail}`);
198
+ }
199
+
200
+ async function installTarget(
201
+ target: CdpTarget,
202
+ rendererSource: string,
203
+ enabledAgents: readonly string[],
204
+ timeoutMs: number,
205
+ pollIntervalMs: number,
206
+ operations: RendererCdpControlOperations,
207
+ ): Promise<{ renderer: RendererCdpClient; snapshot: RendererCdpControlSnapshot }> {
208
+ const renderer = await operations.connect(target.webSocketDebuggerUrl);
209
+ try {
210
+ await renderer.command("Runtime.enable");
211
+ await renderer.command("Page.enable");
212
+ await renderer.command("Page.addScriptToEvaluateOnNewDocument", { source: rendererSource });
213
+ await evaluateSource(renderer, rendererSource);
214
+ const draftPrewarmPolicy = await operations.installDraftPrewarmPolicy(renderer);
215
+ const binding = await waitForBinding(renderer, enabledAgents, timeoutMs, pollIntervalMs);
216
+ return { renderer, snapshot: { target, draftPrewarmPolicy, binding } };
217
+ } catch (error) {
218
+ renderer.close();
219
+ throw error;
220
+ }
221
+ }
222
+
223
+ class InstalledRendererCdpControlSession implements RendererCdpControlSession {
224
+ #closed = false;
225
+
226
+ constructor(
227
+ private renderer: RendererCdpClient,
228
+ private readonly rendererCdpEndpoint: string,
229
+ private readonly rendererSource: string,
230
+ private readonly enabledAgents: readonly string[],
231
+ private readonly timeoutMs: number,
232
+ private readonly pollIntervalMs: number,
233
+ private readonly operations: RendererCdpControlOperations,
234
+ private currentSnapshot: RendererCdpControlSnapshot,
235
+ ) {}
236
+
237
+ get snapshot(): RendererCdpControlSnapshot {
238
+ return this.currentSnapshot;
239
+ }
240
+
241
+ async ensureInstalled(): Promise<RendererCdpControlSnapshot> {
242
+ if (this.#closed) throw new Error("Renderer CDP Control Session is closed");
243
+ const target = await waitForPrimaryTarget(
244
+ this.rendererCdpEndpoint,
245
+ this.operations,
246
+ this.timeoutMs,
247
+ this.pollIntervalMs,
248
+ this.currentSnapshot.target.id,
249
+ );
250
+ if (target.id !== this.currentSnapshot.target.id) {
251
+ const replacement = await installTarget(
252
+ target,
253
+ this.rendererSource,
254
+ this.enabledAgents,
255
+ this.timeoutMs,
256
+ this.pollIntervalMs,
257
+ this.operations,
258
+ );
259
+ this.renderer.close();
260
+ this.renderer = replacement.renderer;
261
+ this.currentSnapshot = replacement.snapshot;
262
+ return this.currentSnapshot;
263
+ }
264
+
265
+ try {
266
+ const existing = await readBinding(this.renderer);
267
+ if (existing === null) await evaluateSource(this.renderer, this.rendererSource);
268
+ else validateBindingStatus(existing, this.enabledAgents);
269
+ const draftPrewarmPolicy = await this.operations.installDraftPrewarmPolicy(this.renderer);
270
+ const binding = await waitForBinding(
271
+ this.renderer,
272
+ this.enabledAgents,
273
+ this.timeoutMs,
274
+ this.pollIntervalMs,
275
+ );
276
+ this.currentSnapshot = { target, draftPrewarmPolicy, binding };
277
+ return this.currentSnapshot;
278
+ } catch {
279
+ const replacement = await installTarget(
280
+ target,
281
+ this.rendererSource,
282
+ this.enabledAgents,
283
+ this.timeoutMs,
284
+ this.pollIntervalMs,
285
+ this.operations,
286
+ );
287
+ this.renderer.close();
288
+ this.renderer = replacement.renderer;
289
+ this.currentSnapshot = replacement.snapshot;
290
+ return this.currentSnapshot;
291
+ }
292
+ }
293
+
294
+ async activateDesktop(): Promise<number> {
295
+ if (this.#closed) throw new Error("Renderer CDP Control Session is closed");
296
+ await this.renderer.command("Page.bringToFront");
297
+ return 1;
298
+ }
299
+
300
+ executeRenderer<T>(expression: string): Promise<T> {
301
+ if (this.#closed) return Promise.reject(new Error("Renderer CDP Control Session is closed"));
302
+ return this.renderer.evaluate<T>(expression);
303
+ }
304
+
305
+ close(): void {
306
+ if (this.#closed) return;
307
+ this.#closed = true;
308
+ this.renderer.close();
309
+ }
310
+ }
311
+
312
+ const defaultOperations: RendererCdpControlOperations = {
313
+ listTargets: (endpoint) => listCdpTargets(endpoint),
314
+ connect: (webSocketDebuggerUrl) => CdpClient.connect(webSocketDebuggerUrl),
315
+ installDraftPrewarmPolicy: installRendererDraftPrewarmPolicyDirect,
316
+ };
317
+
318
+ export async function createRendererCdpControlSession(
319
+ options: CreateRendererCdpControlOptions,
320
+ ): Promise<RendererCdpControlSession> {
321
+ const enabledAgents = options.enabledAgents ?? ["codex", "pi"];
322
+ const timeoutMs = options.timeoutMs ?? 30_000;
323
+ const pollIntervalMs = options.pollIntervalMs ?? 250;
324
+ const operations = options.operations ?? defaultOperations;
325
+ const target = await waitForPrimaryTarget(
326
+ options.rendererCdpEndpoint,
327
+ operations,
328
+ timeoutMs,
329
+ pollIntervalMs,
330
+ );
331
+ const installed = await installTarget(
332
+ target,
333
+ options.rendererSource,
334
+ enabledAgents,
335
+ timeoutMs,
336
+ pollIntervalMs,
337
+ operations,
338
+ );
339
+ return new InstalledRendererCdpControlSession(
340
+ installed.renderer,
341
+ options.rendererCdpEndpoint,
342
+ options.rendererSource,
343
+ enabledAgents,
344
+ timeoutMs,
345
+ pollIntervalMs,
346
+ operations,
347
+ installed.snapshot,
348
+ );
349
+ }
350
+
351
+ export function installRendererCdpControlSession(
352
+ options: InstallRendererCdpControlOptions,
353
+ ): Promise<RendererCdpControlSession> {
354
+ return createRendererCdpControlSession(options);
355
+ }
356
+
357
+ export type RendererCdpFetch = CdpFetch;
358
+ export type RendererCdpClientOptions = CdpClientOptions;