@codexhost/cli-darwin-arm64 0.1.0-test.2 → 0.1.0

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.
@@ -48,14 +48,27 @@ async function startControllerAttachmentServer(options) {
48
48
  if (newline < 0) return;
49
49
  handled = true;
50
50
  const line = request.slice(0, newline).replace(/\r$/, "");
51
- if (line !== `ATTACH ${options.nonce}`) {
52
- respond(socket, "rejected");
51
+ if (line === `ATTACH ${options.nonce}`) {
52
+ void options.attach().then(
53
+ () => respond(socket, "ready"),
54
+ () => respond(socket, "failed")
55
+ );
53
56
  return;
54
57
  }
55
- void options.attach().then(
56
- () => respond(socket, "ready"),
57
- () => respond(socket, "failed")
58
- );
58
+ if (line === `COMPATIBILITY_UPDATE ${options.nonce}`) {
59
+ socket.setTimeout(2e4);
60
+ void options.compatibilityUpdate().then(
61
+ (outcome) => respond(socket, outcome),
62
+ () => respond(socket, "failed")
63
+ );
64
+ return;
65
+ }
66
+ if (line === `SHUTDOWN ${options.nonce}`) {
67
+ respond(socket, "ready");
68
+ queueMicrotask(() => void options.shutdown().catch(() => void 0));
69
+ return;
70
+ }
71
+ respond(socket, "rejected");
59
72
  });
60
73
  });
61
74
  await new Promise((resolve, reject) => {
@@ -321,32 +334,18 @@ var ELECTRON_MODULE_EXPRESSION = `(() => {
321
334
  return createRequire(process.execPath)('electron');
322
335
  })()`;
323
336
  var CONNECT_APP_HOST_CHANNEL = "codex_desktop:connect-app-host";
337
+ var REVIEWED_TITLE_SERVICE_IDENTITIES = ["Dhe", "Nye", "wbe", "nxe"];
324
338
  var POLICY_STATE_SYMBOL = "codexhost.main-process-title-policy.v1";
325
339
  var SERVICE_OWNER_SYMBOL = "codexhost.main-process-title-policy.owner.v1";
326
340
  var RENDERER_READY_EXPRESSION = "(() => { Object.defineProperty(window, '__codexhostMainProcessTitlePolicyV1', { configurable: true, value: { state: 'ready' } }); return 'ready'; })()";
327
- var INSTALL_POLICY_FUNCTION = `async function () {
341
+ var INSTALL_POLICY_FUNCTION = `async function (rendererWebContentsId) {
328
342
  const mainModule = process.mainModule;
329
343
  const electron = mainModule != null && typeof mainModule.require === 'function'
330
344
  ? mainModule.require('electron')
331
345
  : process.getBuiltinModule('module').createRequire(process.execPath)('electron');
332
- let selected = null;
333
- let largestElementCount = 0;
334
- for (const contents of electron.webContents.getAllWebContents()) {
335
- if (contents.isDestroyed() || contents.getType() !== 'window') continue;
336
- let elementCount = 0;
337
- try {
338
- elementCount = await contents.executeJavaScript(
339
- "document.querySelectorAll('*').length",
340
- true,
341
- );
342
- } catch {}
343
- if (Number.isInteger(elementCount) && elementCount > largestElementCount) {
344
- selected = contents;
345
- largestElementCount = elementCount;
346
- }
347
- }
348
- if (selected == null || largestElementCount < 50) {
349
- throw new Error('Populated Renderer unavailable for title policy');
346
+ const selected = electron.webContents.fromId(rendererWebContentsId);
347
+ if (selected == null || selected.isDestroyed() || selected.getType() !== 'window') {
348
+ throw new Error('Owned Renderer unavailable for title policy');
350
349
  }
351
350
 
352
351
  const context = this(selected);
@@ -364,12 +363,22 @@ var INSTALL_POLICY_FUNCTION = `async function () {
364
363
  const originalGenerateTitle = servicePrototype?.generateTitle;
365
364
  if (
366
365
  servicePrototype == null ||
367
- !['Dhe', 'Nye'].includes(sampleService?.constructor?.name) ||
368
366
  typeof originalGenerateTitle !== 'function' ||
369
367
  !Function.prototype.toString.call(originalGenerateTitle).includes('Failed to generate thread title')
370
368
  ) {
371
369
  throw new Error('ThreadMetadataGenerationService signature mismatch');
372
370
  }
371
+ const rawServiceClass = sampleService?.constructor?.name;
372
+ const serviceClass = typeof rawServiceClass === 'string' && /^[A-Za-z_$][A-Za-z0-9_$]{0,63}$/.test(rawServiceClass)
373
+ ? rawServiceClass
374
+ : 'unknown';
375
+ const warnings = ${JSON.stringify(REVIEWED_TITLE_SERVICE_IDENTITIES)}.includes(serviceClass)
376
+ ? []
377
+ : [{
378
+ capability: 'title-isolation',
379
+ reason: 'unreviewed-title-service-identity',
380
+ observedIdentity: serviceClass,
381
+ }];
373
382
 
374
383
  const counters = {
375
384
  codexTitleCalls: 0,
@@ -443,12 +452,14 @@ var INSTALL_POLICY_FUNCTION = `async function () {
443
452
  return {
444
453
  state: 'ready',
445
454
  reason: 'ready',
446
- contextClass: context.constructor.name,
447
- serviceClass: sampleService.constructor.name,
448
455
  requiresRendererReload: true,
456
+ warnings,
449
457
  };
450
458
  }`;
451
- async function installMainProcessTitlePolicy(inspector) {
459
+ async function installMainProcessTitlePolicy(inspector, rendererWebContentsId) {
460
+ if (!Number.isInteger(rendererWebContentsId) || rendererWebContentsId <= 0) {
461
+ throw new Error("Renderer webContents ID must be a positive integer");
462
+ }
452
463
  const listenerResponse = resultRecord(
453
464
  await inspector.command("Runtime.evaluate", {
454
465
  expression: `(${ELECTRON_MODULE_EXPRESSION}).ipcMain.listeners(${JSON.stringify(
@@ -497,7 +508,8 @@ async function installMainProcessTitlePolicy(inspector) {
497
508
  const installPromise = resultRecord(
498
509
  await inspector.command("Runtime.callFunctionOn", {
499
510
  objectId: getContextId,
500
- functionDeclaration: INSTALL_POLICY_FUNCTION
511
+ functionDeclaration: INSTALL_POLICY_FUNCTION,
512
+ arguments: [{ value: rendererWebContentsId }]
501
513
  }),
502
514
  "Runtime.callFunctionOn"
503
515
  );
@@ -514,12 +526,17 @@ async function installMainProcessTitlePolicy(inspector) {
514
526
  );
515
527
  const remoteResult = installResponse.result;
516
528
  const value = isRecord2(remoteResult) ? remoteResult.value : null;
517
- if (!isRecord2(value) || value.state !== "ready" || value.reason !== "ready" || typeof value.contextClass !== "string" || typeof value.serviceClass !== "string" || value.requiresRendererReload !== true) {
529
+ if (!isRecord2(value) || value.state !== "ready" || value.reason !== "ready" || value.requiresRendererReload !== true || !Array.isArray(value.warnings) || value.warnings.length > 1 || value.warnings.some(
530
+ (warning) => !isRecord2(warning) || warning.capability !== "title-isolation" || warning.reason !== "unreviewed-title-service-identity" || typeof warning.observedIdentity !== "string" || !/^[A-Za-z_$][A-Za-z0-9_$]{0,63}$/.test(warning.observedIdentity) || Object.keys(warning).length !== 3
531
+ )) {
518
532
  throw new Error("Main-process title policy returned an invalid status");
519
533
  }
520
534
  return value;
521
535
  }
522
- async function markRendererTitlePolicyReady(inspector) {
536
+ async function markRendererTitlePolicyReady(inspector, rendererWebContentsId) {
537
+ if (!Number.isInteger(rendererWebContentsId) || rendererWebContentsId <= 0) {
538
+ throw new Error("Renderer webContents ID must be a positive integer");
539
+ }
523
540
  const value = await inspector.evaluate(`(async () => {
524
541
  const state = globalThis[Symbol.for(${JSON.stringify(POLICY_STATE_SYMBOL)})];
525
542
  if (state == null) throw new Error('Main-process title policy is unavailable');
@@ -527,28 +544,9 @@ async function markRendererTitlePolicyReady(inspector) {
527
544
  const electron = mainModule != null && typeof mainModule.require === 'function'
528
545
  ? mainModule.require('electron')
529
546
  : process.getBuiltinModule('module').createRequire(process.execPath)('electron');
530
- let selected = null;
531
- let largestElementCount = 0;
532
- for (const contents of electron.webContents.getAllWebContents()) {
533
- if (contents.isDestroyed() || contents.getType() !== 'window') continue;
534
- let elementCount = 0;
535
- try {
536
- const evaluation = contents.executeJavaScript(
537
- "document.querySelectorAll('*').length",
538
- true,
539
- );
540
- const timeout = new Promise((_, reject) => {
541
- setTimeout(() => reject(new Error('Renderer readiness inspection timed out')), 2_000);
542
- });
543
- elementCount = await Promise.race([evaluation, timeout]);
544
- } catch {}
545
- if (Number.isInteger(elementCount) && elementCount > largestElementCount) {
546
- selected = contents;
547
- largestElementCount = elementCount;
548
- }
549
- }
550
- if (selected == null || largestElementCount < 50) {
551
- throw new Error('Populated Renderer unavailable for title policy readiness');
547
+ const selected = electron.webContents.fromId(${rendererWebContentsId});
548
+ if (selected == null || selected.isDestroyed() || selected.getType() !== 'window') {
549
+ throw new Error('Owned Renderer unavailable for title policy readiness');
552
550
  }
553
551
  if (!state.ownedWebContentsIds.has(selected.id)) {
554
552
  throw new Error('Renderer metadata service ownership is unavailable');
@@ -626,55 +624,78 @@ async function installDraftPrewarmPolicyInRenderer(contents, findRequestManagerE
626
624
  (property) => property.name === "candidateCount"
627
625
  )?.value?.value;
628
626
  const hostId = managerProperties.result?.find((property) => property.name === "hostId")?.value?.value;
629
- const sendRequest = managerProperties.result?.find(
630
- (property) => property.name === "sendRequest"
627
+ const manager = managerProperties.result?.find(
628
+ (property) => property.name === "manager"
631
629
  )?.value;
632
- if (candidateCount !== 1 || hostId !== "local" || typeof sendRequest?.objectId !== "string") {
630
+ if (candidateCount !== 1 || hostId !== "local" || typeof manager?.objectId !== "string") {
633
631
  throw new Error("Renderer request manager is ambiguous");
634
632
  }
633
+ const managerFunctions = await contents.debugger.sendCommand("Runtime.getProperties", {
634
+ objectId: manager.objectId
635
+ });
636
+ let managerSendRequestId = managerFunctions.result?.find(
637
+ (property) => property.name === "sendRequest"
638
+ )?.value?.objectId;
639
+ const managerPrototypeId = managerFunctions.internalProperties?.find(
640
+ (property) => property.name === "[[Prototype]]"
641
+ )?.value?.objectId;
642
+ if (typeof managerSendRequestId !== "string" && typeof managerPrototypeId === "string") {
643
+ const prototypeFunctions = await contents.debugger.sendCommand("Runtime.getProperties", {
644
+ objectId: managerPrototypeId,
645
+ ownProperties: true
646
+ });
647
+ managerSendRequestId = prototypeFunctions.result?.find(
648
+ (property) => property.name === "sendRequest"
649
+ )?.value?.objectId;
650
+ }
651
+ if (typeof managerSendRequestId !== "string") {
652
+ throw new Error("Renderer request manager sendRequest is unavailable");
653
+ }
635
654
  const functionProperties = await contents.debugger.sendCommand("Runtime.getProperties", {
636
- objectId: sendRequest.objectId
655
+ objectId: managerSendRequestId
637
656
  });
638
657
  const scopesId = functionProperties.internalProperties?.find(
639
658
  (property) => property.name === "[[Scopes]]"
640
659
  )?.value?.objectId;
641
- if (typeof scopesId !== "string") {
642
- throw new Error("Renderer request bridge scopes are unavailable");
643
- }
644
- const scopes = await contents.debugger.sendCommand("Runtime.getProperties", {
645
- objectId: scopesId,
646
- ownProperties: true
647
- });
648
- const bridgeCandidates = [];
649
- for (const scope of scopes.result ?? []) {
650
- const scopeId = scope.value?.objectId;
651
- if (typeof scopeId !== "string") continue;
652
- const scopeProperties = await contents.debugger.sendCommand("Runtime.getProperties", {
653
- objectId: scopeId,
660
+ const hostBridgeCandidates = [];
661
+ if (typeof scopesId === "string") {
662
+ const scopes = await contents.debugger.sendCommand("Runtime.getProperties", {
663
+ objectId: scopesId,
654
664
  ownProperties: true
655
665
  });
656
- const bridge2 = scopeProperties.result?.find(
657
- (property) => (property.name === "Rf" || property.name === "rp") && property.value?.type === "function"
658
- )?.value;
659
- if (typeof bridge2?.objectId === "string") {
660
- bridgeCandidates.push({ objectId: bridge2.objectId });
666
+ for (const scope of scopes.result ?? []) {
667
+ if (typeof scope.value?.objectId !== "string") continue;
668
+ const scopeProperties = await contents.debugger.sendCommand("Runtime.getProperties", {
669
+ objectId: scope.value.objectId,
670
+ ownProperties: true
671
+ });
672
+ for (const property of scopeProperties.result ?? []) {
673
+ if (property.value?.type !== "object" || typeof property.value.objectId !== "string") {
674
+ continue;
675
+ }
676
+ const candidateSignature = await contents.debugger.sendCommand(
677
+ "Runtime.callFunctionOn",
678
+ {
679
+ objectId: property.value.objectId,
680
+ functionDeclaration: "function(){const send=this.sendRequest;return typeof send==='function'&&Function.prototype.toString.call(send).includes('messageHandler')}",
681
+ returnByValue: true
682
+ }
683
+ );
684
+ if (candidateSignature.result?.value === true) {
685
+ hostBridgeCandidates.push({
686
+ name: String(property.name),
687
+ objectId: property.value.objectId
688
+ });
689
+ }
690
+ }
661
691
  }
662
692
  }
663
- const bridge = bridgeCandidates[0];
664
- if (bridgeCandidates.length !== 1 || !bridge) {
665
- throw new Error("Renderer request bridge is ambiguous");
666
- }
667
- const signature = await contents.debugger.sendCommand("Runtime.callFunctionOn", {
668
- objectId: bridge.objectId,
669
- functionDeclaration: "function(){return {arity:this.length,source:Function.prototype.toString.call(this)}}",
670
- returnByValue: true
671
- });
672
- const signatureValue = signature.result?.value;
673
- if (signatureValue?.arity !== 2 || typeof signatureValue.source !== "string" || !signatureValue.source.includes(".sendRequest")) {
674
- throw new Error("Renderer request bridge signature mismatch");
693
+ const hostBridge = hostBridgeCandidates[0];
694
+ if (hostBridgeCandidates.length !== 1 || !hostBridge) {
695
+ throw new Error("Renderer Host request bridge is ambiguous");
675
696
  }
676
697
  const installed = await contents.debugger.sendCommand("Runtime.callFunctionOn", {
677
- objectId: bridge.objectId,
698
+ objectId: hostBridge.objectId,
678
699
  functionDeclaration: installRendererPolicyFunction,
679
700
  arguments: [{ value: hostId }],
680
701
  awaitPromise: true,
@@ -732,11 +753,13 @@ var FIND_REQUEST_MANAGER_EXPRESSION = `(() => {
732
753
  return {
733
754
  candidateCount: managers.size,
734
755
  hostId: manager?.getHostId?.() ?? null,
735
- sendRequest: manager?.sendRequest ?? null,
756
+ manager,
736
757
  };
737
758
  })()`;
738
759
  var INSTALL_RENDERER_POLICY_FUNCTION = `function(hostId) {
739
- return (${installDraftPrewarmPolicyBridge.toString()})(this, hostId, window);
760
+ const bridge = this;
761
+ const send = (method, parameters) => bridge.sendRequest(method, parameters);
762
+ return (${installDraftPrewarmPolicyBridge.toString()})(send, hostId, window);
740
763
  }`;
741
764
  function mainProcessInstaller(rendererWebContentsId) {
742
765
  return `async function () {
@@ -765,21 +788,30 @@ async function installRendererDraftPrewarmPolicy(inspector, rendererWebContentsI
765
788
  }
766
789
 
767
790
  // packages/desktop-control/src/renderer-control-session.ts
791
+ var RendererCompatibilityError = class extends Error {
792
+ constructor(capability, reason, cause) {
793
+ super(`Renderer compatibility boundary unavailable: ${capability}`, { cause });
794
+ this.capability = capability;
795
+ this.reason = reason;
796
+ this.name = "RendererCompatibilityError";
797
+ }
798
+ capability;
799
+ reason;
800
+ };
768
801
  function isRecord4(value) {
769
802
  return typeof value === "object" && value !== null && !Array.isArray(value);
770
803
  }
771
- function safeTargetUrl(value) {
772
- if (typeof value !== "string" || value.length === 0) return "unknown";
773
- try {
774
- const url = new URL(value);
775
- return url.protocol === "app:" ? `${url.protocol}//${url.host}${url.pathname}` : url.protocol;
776
- } catch {
777
- return "unknown";
778
- }
779
- }
780
804
  function sleep(milliseconds) {
781
805
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
782
806
  }
807
+ async function requireCompatibilityBoundary(capability, reason, operation) {
808
+ try {
809
+ return await operation();
810
+ } catch (error) {
811
+ if (error instanceof RendererCompatibilityError) throw error;
812
+ throw new RendererCompatibilityError(capability, reason, error);
813
+ }
814
+ }
783
815
  function sameAgents(actual, expected) {
784
816
  return actual.length === expected.length && actual.every((agent, index) => agent === expected[index]);
785
817
  }
@@ -810,8 +842,7 @@ function selectRendererWebContents(contents) {
810
842
  ).toSorted(
811
843
  (left, right) => (right.runtime.elementCount ?? 0) - (left.runtime.elementCount ?? 0)
812
844
  );
813
- const selected = candidates[0];
814
- return selected && (selected.runtime.elementCount ?? 0) >= 50 ? selected : null;
845
+ return candidates.find((candidate) => (candidate.runtime.elementCount ?? 0) > 0) ?? null;
815
846
  }
816
847
  async function waitForRendererTitlePolicyReady(markReadiness, options = {}) {
817
848
  const timeoutMs = options.timeoutMs ?? 3e4;
@@ -858,17 +889,13 @@ var electronModuleExpression = `(() => {
858
889
  const { createRequire } = process.getBuiltinModule('module');
859
890
  return createRequire(process.execPath)('electron');
860
891
  })()`;
861
- var webContentsRuntimeExpression = `(() => ({
862
- elementCount: document.querySelectorAll('*').length,
863
- editorCandidates: document.querySelectorAll('textarea, [contenteditable="true"], [role="textbox"]').length,
864
- sendButtonCandidates: [...document.querySelectorAll('button')].filter((button) => button.type === 'submit').length
865
- }))()`;
892
+ var webContentsRuntimeExpression = "(() => ({ elementCount: document.querySelectorAll('*').length }))()";
866
893
  async function inspectElectronWebContents(inspector) {
867
894
  const value = await inspector.evaluate(`(async () => {
868
895
  const { webContents } = ${electronModuleExpression};
869
896
  const result = [];
870
897
  for (const contents of webContents.getAllWebContents()) {
871
- let runtime = { available: false, elementCount: null, editorCandidates: null, sendButtonCandidates: null };
898
+ let runtime = { available: false, elementCount: null };
872
899
  try {
873
900
  const evaluation = contents.executeJavaScript(${JSON.stringify(webContentsRuntimeExpression)}, true);
874
901
  const timeout = new Promise((_, reject) => {
@@ -880,7 +907,6 @@ async function inspectElectronWebContents(inspector) {
880
907
  id: contents.id,
881
908
  type: contents.getType(),
882
909
  surface: contents.getURL().includes('avatar-overlay') ? 'overlay' : 'primary',
883
- url: contents.getURL(),
884
910
  runtime,
885
911
  });
886
912
  }
@@ -895,12 +921,9 @@ async function inspectElectronWebContents(inspector) {
895
921
  id: item.id,
896
922
  type: item.type,
897
923
  surface: item.surface,
898
- url: safeTargetUrl(item.url),
899
924
  runtime: {
900
925
  available: item.runtime.available === true,
901
- elementCount: Number.isInteger(item.runtime.elementCount) ? item.runtime.elementCount : null,
902
- editorCandidates: Number.isInteger(item.runtime.editorCandidates) ? item.runtime.editorCandidates : null,
903
- sendButtonCandidates: Number.isInteger(item.runtime.sendButtonCandidates) ? item.runtime.sendButtonCandidates : null
926
+ elementCount: Number.isInteger(item.runtime.elementCount) ? item.runtime.elementCount : null
904
927
  }
905
928
  };
906
929
  });
@@ -921,6 +944,14 @@ async function activateElectronDesktop(inspector) {
921
944
  }
922
945
  return value;
923
946
  }
947
+ async function quitElectronDesktop(inspector) {
948
+ const accepted = await inspector.evaluate(`(() => {
949
+ const { app } = ${electronModuleExpression};
950
+ setTimeout(() => app.quit(), 100);
951
+ return true;
952
+ })()`);
953
+ if (accepted !== true) throw new Error("Electron Desktop did not accept the quit request");
954
+ }
924
955
  async function executeInWebContents(inspector, rendererWebContentsId, source) {
925
956
  return inspector.evaluate(`(async () => {
926
957
  const { webContents } = ${electronModuleExpression};
@@ -945,20 +976,20 @@ var defaultOperations = {
945
976
  rendererWebContentsId,
946
977
  "window.__codexhostRendererBindingProbeV1?.status() ?? null"
947
978
  ),
948
- readTitlePolicyCounters: readMainProcessTitlePolicyCounters
979
+ readTitlePolicyCounters: readMainProcessTitlePolicyCounters,
980
+ quitDesktop: quitElectronDesktop
949
981
  };
950
982
  async function waitForRenderer(inspector, operations, timeoutMs, pollIntervalMs) {
951
983
  const deadline = Date.now() + timeoutMs;
952
- let inventory = [];
984
+ let candidateCount = 0;
953
985
  while (Date.now() < deadline) {
954
- inventory = await operations.inspect(inspector);
986
+ const inventory = await operations.inspect(inspector);
987
+ candidateCount = inventory.length;
955
988
  const renderer = selectRendererWebContents(inventory);
956
- if (renderer) return { inventory, renderer };
989
+ if (renderer) return renderer;
957
990
  await sleep(pollIntervalMs);
958
991
  }
959
- throw new Error(
960
- `Inspector did not find a populated Electron Renderer (${inventory.length} seen)`
961
- );
992
+ throw new Error(`Inspector did not find a live Electron Renderer (${candidateCount} seen)`);
962
993
  }
963
994
  async function waitForBinding(inspector, operations, rendererWebContentsId, enabledAgents, timeoutMs, pollIntervalMs) {
964
995
  const deadline = Date.now() + timeoutMs;
@@ -1007,32 +1038,45 @@ var InstalledRendererControlSession = class {
1007
1038
  this.timeoutMs,
1008
1039
  this.pollIntervalMs
1009
1040
  );
1010
- const existing = await this.operations.readBinding(this.inspector, selected.renderer.id).catch(() => null);
1041
+ const existing = await this.operations.readBinding(this.inspector, selected.id).catch(() => null);
1011
1042
  if (existing !== null) {
1012
- const binding2 = validateBindingStatus(existing, this.enabledAgents);
1013
- this.#snapshot = { ...this.#snapshot, ...selected, binding: binding2 };
1043
+ const binding2 = await requireCompatibilityBoundary(
1044
+ "agent-routing",
1045
+ "agent-routing-structure-unavailable",
1046
+ () => validateBindingStatus(existing, this.enabledAgents)
1047
+ );
1048
+ this.#snapshot = { ...this.#snapshot, renderer: selected, binding: binding2 };
1014
1049
  return this.#snapshot;
1015
1050
  }
1016
- const titlePolicyReadiness = await waitForRendererTitlePolicyReady(
1017
- () => this.operations.markTitlePolicyReady(this.inspector),
1018
- { timeoutMs: this.timeoutMs, pollIntervalMs: this.pollIntervalMs }
1051
+ const titlePolicyReadiness = await requireCompatibilityBoundary(
1052
+ "title-isolation",
1053
+ "title-isolation-structure-unavailable",
1054
+ () => waitForRendererTitlePolicyReady(
1055
+ () => this.operations.markTitlePolicyReady(this.inspector, selected.id),
1056
+ { timeoutMs: this.timeoutMs, pollIntervalMs: this.pollIntervalMs }
1057
+ )
1019
1058
  );
1020
- const draftPrewarmPolicy = await this.operations.installDraftPrewarmPolicy(
1021
- this.inspector,
1022
- selected.renderer.id
1059
+ await this.operations.execute(this.inspector, selected.id, this.rendererSource);
1060
+ const binding = await requireCompatibilityBoundary(
1061
+ "agent-routing",
1062
+ "agent-routing-structure-unavailable",
1063
+ () => waitForBinding(
1064
+ this.inspector,
1065
+ this.operations,
1066
+ selected.id,
1067
+ this.enabledAgents,
1068
+ this.timeoutMs,
1069
+ this.pollIntervalMs
1070
+ )
1023
1071
  );
1024
- await this.operations.execute(this.inspector, selected.renderer.id, this.rendererSource);
1025
- const binding = await waitForBinding(
1026
- this.inspector,
1027
- this.operations,
1028
- selected.renderer.id,
1029
- this.enabledAgents,
1030
- this.timeoutMs,
1031
- this.pollIntervalMs
1072
+ const draftPrewarmPolicy = await requireCompatibilityBoundary(
1073
+ "draft-routing",
1074
+ "draft-routing-structure-unavailable",
1075
+ () => this.operations.installDraftPrewarmPolicy(this.inspector, selected.id)
1032
1076
  );
1033
1077
  this.#snapshot = {
1034
1078
  ...this.#snapshot,
1035
- ...selected,
1079
+ renderer: selected,
1036
1080
  titlePolicyReadiness,
1037
1081
  draftPrewarmPolicy,
1038
1082
  binding
@@ -1043,6 +1087,19 @@ var InstalledRendererControlSession = class {
1043
1087
  if (this.#closed) return Promise.reject(new Error("Renderer Control Session is closed"));
1044
1088
  return activateElectronDesktop(this.inspector);
1045
1089
  }
1090
+ quitDesktop() {
1091
+ if (this.#closed) return Promise.reject(new Error("Renderer Control Session is closed"));
1092
+ return this.operations.quitDesktop(this.inspector);
1093
+ }
1094
+ async requestCompatibilityUpdate() {
1095
+ const value = await this.executeRenderer(
1096
+ "window.__codexhostRendererBindingProbeV1?.requestCompatibilityUpdate?.() ?? 'unavailable'"
1097
+ );
1098
+ if (value !== "update-started" && value !== "current" && value !== "unavailable") {
1099
+ throw new Error("Renderer returned an invalid compatibility update outcome");
1100
+ }
1101
+ return value;
1102
+ }
1046
1103
  executeRenderer(expression) {
1047
1104
  if (this.#closed) return Promise.reject(new Error("Renderer Control Session is closed"));
1048
1105
  return this.operations.execute(
@@ -1067,25 +1124,41 @@ async function createRendererControlSession(options) {
1067
1124
  const pollIntervalMs = options.pollIntervalMs ?? 250;
1068
1125
  const operations = options.operations ?? defaultOperations;
1069
1126
  const initial = await waitForRenderer(options.inspector, operations, timeoutMs, pollIntervalMs);
1070
- const titlePolicy = await operations.installTitlePolicy(options.inspector);
1071
- await operations.reload(options.inspector, initial.renderer.id);
1127
+ const titlePolicy = await requireCompatibilityBoundary(
1128
+ "title-isolation",
1129
+ "title-isolation-structure-unavailable",
1130
+ () => operations.installTitlePolicy(options.inspector, initial.id)
1131
+ );
1132
+ await operations.reload(options.inspector, initial.id);
1072
1133
  const selected = await waitForRenderer(options.inspector, operations, timeoutMs, pollIntervalMs);
1073
- const titlePolicyReadiness = await waitForRendererTitlePolicyReady(
1074
- () => operations.markTitlePolicyReady(options.inspector),
1075
- { timeoutMs, pollIntervalMs }
1134
+ const titlePolicyReadiness = await requireCompatibilityBoundary(
1135
+ "title-isolation",
1136
+ "title-isolation-structure-unavailable",
1137
+ () => waitForRendererTitlePolicyReady(
1138
+ () => operations.markTitlePolicyReady(options.inspector, selected.id),
1139
+ {
1140
+ timeoutMs,
1141
+ pollIntervalMs
1142
+ }
1143
+ )
1076
1144
  );
1077
- const draftPrewarmPolicy = await operations.installDraftPrewarmPolicy(
1078
- options.inspector,
1079
- selected.renderer.id
1145
+ await operations.execute(options.inspector, selected.id, options.rendererSource);
1146
+ const binding = await requireCompatibilityBoundary(
1147
+ "agent-routing",
1148
+ "agent-routing-structure-unavailable",
1149
+ () => waitForBinding(
1150
+ options.inspector,
1151
+ operations,
1152
+ selected.id,
1153
+ enabledAgents,
1154
+ timeoutMs,
1155
+ pollIntervalMs
1156
+ )
1080
1157
  );
1081
- await operations.execute(options.inspector, selected.renderer.id, options.rendererSource);
1082
- const binding = await waitForBinding(
1083
- options.inspector,
1084
- operations,
1085
- selected.renderer.id,
1086
- enabledAgents,
1087
- timeoutMs,
1088
- pollIntervalMs
1158
+ const draftPrewarmPolicy = await requireCompatibilityBoundary(
1159
+ "draft-routing",
1160
+ "draft-routing-structure-unavailable",
1161
+ () => operations.installDraftPrewarmPolicy(options.inspector, selected.id)
1089
1162
  );
1090
1163
  return new InstalledRendererControlSession(
1091
1164
  options.inspector,
@@ -1095,7 +1168,7 @@ async function createRendererControlSession(options) {
1095
1168
  pollIntervalMs,
1096
1169
  operations,
1097
1170
  {
1098
- ...selected,
1171
+ renderer: selected,
1099
1172
  titlePolicy,
1100
1173
  titlePolicyReadiness,
1101
1174
  draftPrewarmPolicy,
@@ -1127,14 +1200,56 @@ async function installRendererControlSession(options) {
1127
1200
 
1128
1201
  // packages/desktop-control/src/production-controller.ts
1129
1202
  var PRODUCTION_INSTALL_TIMEOUT_MS = 9e4;
1203
+ var DESKTOP_CONTROLLER_READINESS_MAX_BYTES = 512;
1130
1204
  var TRANSIENT_INSTALL_ATTEMPTS = 3;
1131
1205
  var TRANSIENT_INSTALL_RETRY_MS = 250;
1206
+ function validCompatibilityIssue(state, issue) {
1207
+ const keys = Object.keys(issue);
1208
+ if (state === "compatible-with-warning") {
1209
+ return issue.capability === "title-isolation" && issue.reason === "unreviewed-title-service-identity" && typeof issue.observedIdentity === "string" && /^[A-Za-z_$][A-Za-z0-9_$]{0,63}$/.test(issue.observedIdentity) && keys.length === 3;
1210
+ }
1211
+ if (state === "degraded") {
1212
+ return [
1213
+ "permission-control",
1214
+ "sidebar-decoration",
1215
+ "fork-control",
1216
+ "usage-surface",
1217
+ "settings-surface"
1218
+ ].includes(issue.capability) && issue.reason === "capability-unavailable" && issue.observedIdentity === void 0 && keys.length === 2;
1219
+ }
1220
+ if (state === "incompatible") {
1221
+ const pair = `${issue.capability}:${issue.reason}`;
1222
+ return [
1223
+ "title-isolation:title-isolation-structure-unavailable",
1224
+ "draft-routing:draft-routing-structure-unavailable",
1225
+ "agent-routing:agent-routing-structure-unavailable"
1226
+ ].includes(pair) && issue.observedIdentity === void 0 && keys.length === 2;
1227
+ }
1228
+ return state === "detection-failed" && issue.capability === "compatibility-detection" && issue.reason === "inspection-failed" && issue.observedIdentity === void 0 && keys.length === 2;
1229
+ }
1230
+ function serializeDesktopControllerReadiness(readiness) {
1231
+ if (readiness.schemaVersion !== 2 || ![
1232
+ "compatible",
1233
+ "compatible-with-warning",
1234
+ "degraded",
1235
+ "incompatible",
1236
+ "detection-failed"
1237
+ ].includes(readiness.state) || !Array.isArray(readiness.issues) || readiness.issues.length > 1 || readiness.state === "compatible" && readiness.issues.length !== 0 || readiness.state !== "compatible" && (readiness.issues.length !== 1 || !readiness.issues[0] || !validCompatibilityIssue(readiness.state, readiness.issues[0])) || Object.keys(readiness).length !== 3) {
1238
+ throw new Error("Desktop Controller readiness is invalid");
1239
+ }
1240
+ const line = JSON.stringify(readiness);
1241
+ if (Buffer.byteLength(line, "utf8") > DESKTOP_CONTROLLER_READINESS_MAX_BYTES) {
1242
+ throw new Error("Desktop Controller readiness exceeds its size limit");
1243
+ }
1244
+ return line;
1245
+ }
1132
1246
  var defaultDependencies = {
1133
1247
  readRenderer: (filePath) => readFile(filePath, "utf8"),
1134
1248
  install: installRendererControlSession,
1135
1249
  startAttachmentServer: startControllerAttachmentServer,
1136
- ready: () => {
1137
- process.stdout.write("ready\n");
1250
+ ready: (readiness) => {
1251
+ process.stdout.write(`${serializeDesktopControllerReadiness(readiness)}
1252
+ `);
1138
1253
  },
1139
1254
  sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
1140
1255
  monitorIntervalMs: 500
@@ -1218,8 +1333,16 @@ function parseDesktopControllerArguments(arguments_) {
1218
1333
  };
1219
1334
  }
1220
1335
  function isTransientElectronInstallError(error) {
1221
- const message = error instanceof Error ? error.message : String(error);
1222
- return message.includes("Uncaught (in promise)") || message.includes("Execution context was destroyed") || message.includes("Promise was collected");
1336
+ let current = error;
1337
+ for (let depth = 0; depth < 4; depth += 1) {
1338
+ const message = current instanceof Error ? current.message : String(current);
1339
+ if (message.includes("Execution context was destroyed") || message.includes("Promise was collected")) {
1340
+ return true;
1341
+ }
1342
+ current = current instanceof Error ? current.cause : void 0;
1343
+ if (current === void 0) break;
1344
+ }
1345
+ return false;
1223
1346
  }
1224
1347
  async function installProductionSession(options, dependencies) {
1225
1348
  for (let attempt = 1; attempt <= TRANSIENT_INSTALL_ATTEMPTS; attempt += 1) {
@@ -1235,19 +1358,35 @@ async function installProductionSession(options, dependencies) {
1235
1358
  throw new Error("Desktop Controller exhausted Renderer installation attempts");
1236
1359
  }
1237
1360
  async function runDesktopController(options, signal, dependencies = defaultDependencies) {
1238
- const rendererSource = await dependencies.readRenderer(options.rendererPath);
1239
- if (rendererSource.trim().length === 0) throw new Error("production Renderer Bundle is empty");
1240
- const configuration = `Object.defineProperty(window, "__codexhostProductionConfigV1", { configurable: true, value: { defaultAgent: ${JSON.stringify(options.defaultAgent)} } });`;
1241
- const session = await installProductionSession(
1242
- {
1243
- inspectorEndpoint: options.inspectorEndpoint,
1244
- rendererSource: `${configuration}
1361
+ let session;
1362
+ try {
1363
+ const rendererSource = await dependencies.readRenderer(options.rendererPath);
1364
+ if (rendererSource.trim().length === 0) throw new Error("production Renderer Bundle is empty");
1365
+ const configuration = `Object.defineProperty(window, "__codexhostProductionConfigV1", { configurable: true, value: { defaultAgent: ${JSON.stringify(options.defaultAgent)} } });`;
1366
+ session = await installProductionSession(
1367
+ {
1368
+ inspectorEndpoint: options.inspectorEndpoint,
1369
+ rendererSource: `${configuration}
1245
1370
  ${rendererSource}`,
1246
- enabledAgents: ["codex", "pi", "claude-code"],
1247
- timeoutMs: PRODUCTION_INSTALL_TIMEOUT_MS
1248
- },
1249
- dependencies
1250
- );
1371
+ enabledAgents: ["codex", "pi", "claude-code"],
1372
+ timeoutMs: PRODUCTION_INSTALL_TIMEOUT_MS
1373
+ },
1374
+ dependencies
1375
+ );
1376
+ } catch (error) {
1377
+ dependencies.ready(
1378
+ error instanceof RendererCompatibilityError && !isTransientElectronInstallError(error) ? {
1379
+ schemaVersion: 2,
1380
+ state: "incompatible",
1381
+ issues: [{ capability: error.capability, reason: error.reason }]
1382
+ } : {
1383
+ schemaVersion: 2,
1384
+ state: "detection-failed",
1385
+ issues: [{ capability: "compatibility-detection", reason: "inspection-failed" }]
1386
+ }
1387
+ );
1388
+ return;
1389
+ }
1251
1390
  let operation = Promise.resolve(void 0);
1252
1391
  const useSession = (callback) => {
1253
1392
  const next = operation.then(callback, callback);
@@ -1265,9 +1404,19 @@ ${rendererSource}`,
1265
1404
  attach: () => useSession(async () => {
1266
1405
  await session.ensureInstalled();
1267
1406
  await session.activateDesktop();
1268
- })
1407
+ }),
1408
+ compatibilityUpdate: () => useSession(async () => {
1409
+ await session.ensureInstalled();
1410
+ return session.requestCompatibilityUpdate();
1411
+ }),
1412
+ shutdown: () => useSession(() => session.quitDesktop())
1413
+ });
1414
+ const issues = session.snapshot.titlePolicy.warnings;
1415
+ dependencies.ready({
1416
+ schemaVersion: 2,
1417
+ state: issues.length === 0 ? "compatible" : "compatible-with-warning",
1418
+ issues
1269
1419
  });
1270
- dependencies.ready();
1271
1420
  while (!signal.aborted) {
1272
1421
  await dependencies.sleep(dependencies.monitorIntervalMs);
1273
1422
  if (!signal.aborted) await useSession(() => session.ensureInstalled());