@codexhost/cli-darwin-x64 0.3.2 → 0.3.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.
- package/README.md +3 -1
- package/app/codexhost-distribution.json +1 -1
- package/app/desktop-controller.mjs +468 -16
- package/app/host-runtime.mjs +8806 -1613
- package/app/renderer-extension.js +570 -289
- package/bin/codexhost +0 -0
- package/libexec/codexhost-shim +0 -0
- package/libexec/codexhost-updater +0 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Run Pi and Claude Code as first-class external harnesses inside Codex Desktop.
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install -g @codexhost/cli@0.3.
|
|
10
|
+
npm install -g @codexhost/cli@0.3.4
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
Do not install this package directly. npm selects it through the optional dependencies of `@codexhost/cli`.
|
|
@@ -22,6 +22,8 @@ codexhost --version
|
|
|
22
22
|
codexhost inspect
|
|
23
23
|
codexhost launch
|
|
24
24
|
codexhost remote install
|
|
25
|
+
codexhost remote start
|
|
26
|
+
codexhost remote stop
|
|
25
27
|
codexhost remote status
|
|
26
28
|
codexhost remote uninstall
|
|
27
29
|
```
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"schemaVersion":1,"version":"0.3.
|
|
1
|
+
{"schemaVersion":1,"version":"0.3.4","distribution":"npm","target":"macos-x64"}
|
|
@@ -558,16 +558,376 @@ async function readMainProcessTitlePolicyCounters(inspector) {
|
|
|
558
558
|
}
|
|
559
559
|
|
|
560
560
|
// packages/desktop-control/src/renderer-draft-prewarm-runtime.ts
|
|
561
|
-
function installDraftPrewarmPolicyBridge(bridge, hostId, target, prewarmedThreadManager) {
|
|
561
|
+
function installDraftPrewarmPolicyBridge(manager, bridge, hostId, target, prewarmedThreadManager) {
|
|
562
562
|
const existing = target.__codexhostDraftPrewarmPolicyV1;
|
|
563
|
-
if (existing?.owns?.length ===
|
|
563
|
+
if (existing?.owns?.length === 4 && existing.owns(manager, bridge, hostId, prewarmedThreadManager) === true) {
|
|
564
564
|
return { state: "ready", reason: "owned-request-bridge" };
|
|
565
565
|
}
|
|
566
566
|
existing?.dispose?.();
|
|
567
567
|
const originalSend = bridge.sendRequest;
|
|
568
568
|
const originalPrewarm = bridge.prewarmThreadStart;
|
|
569
|
+
const originalOnNotification = manager.onNotification;
|
|
570
|
+
const originalDispatchAppServerResponse = manager.dispatchAppServerResponse;
|
|
569
571
|
let selectedModel = null;
|
|
570
572
|
const isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
573
|
+
const isRemoteControlHost = hostId.startsWith("remote-control:");
|
|
574
|
+
const knownExternalThreadIds = /* @__PURE__ */ new Set();
|
|
575
|
+
const knownOfficialThreadIds = /* @__PURE__ */ new Set();
|
|
576
|
+
const threadOwnershipResolutions = /* @__PURE__ */ new Map();
|
|
577
|
+
const createBridgeProcessHandle = () => `codexhost-${typeof crypto?.randomUUID === "function" ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(16).slice(2)}`}`;
|
|
578
|
+
let bridgeProcessHandle = createBridgeProcessHandle();
|
|
579
|
+
const bridgeReadyMethod = "codexhost/remote-control-bridge/ready";
|
|
580
|
+
const bridgeRequests = /* @__PURE__ */ new Map();
|
|
581
|
+
const bridgeServerRequestIdPrefix = "codexhost/remote-control-bridge/server-request/";
|
|
582
|
+
const bridgeServerRequests = /* @__PURE__ */ new Map();
|
|
583
|
+
let nextBridgeServerRequestOrdinal = 1;
|
|
584
|
+
let outputDecoders = {
|
|
585
|
+
stdout: new TextDecoder(),
|
|
586
|
+
stderr: new TextDecoder()
|
|
587
|
+
};
|
|
588
|
+
const outputBuffers = { stdout: "", stderr: "" };
|
|
589
|
+
let bridgeState = "idle";
|
|
590
|
+
let bridgeReadyPromise = null;
|
|
591
|
+
let bridgeReadyResolve = null;
|
|
592
|
+
let bridgeReadyReject = null;
|
|
593
|
+
let bridgeReadyTimeout = null;
|
|
594
|
+
let bridgeInitialization = null;
|
|
595
|
+
let writeTail = Promise.resolve();
|
|
596
|
+
const transportError = (message, cause) => {
|
|
597
|
+
const error = new Error(`codexhost Remote Control bridge: ${message}`);
|
|
598
|
+
if (cause !== void 0) Object.assign(error, { cause });
|
|
599
|
+
return error;
|
|
600
|
+
};
|
|
601
|
+
const utf8Base64 = (value) => {
|
|
602
|
+
const bytes = new TextEncoder().encode(value);
|
|
603
|
+
let binary = "";
|
|
604
|
+
for (let index = 0; index < bytes.length; index += 32768) {
|
|
605
|
+
binary += String.fromCharCode(...bytes.subarray(index, index + 32768));
|
|
606
|
+
}
|
|
607
|
+
return btoa(binary);
|
|
608
|
+
};
|
|
609
|
+
const utf16LeBase64 = (value) => {
|
|
610
|
+
let binary = "";
|
|
611
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
612
|
+
const codeUnit = value.charCodeAt(index);
|
|
613
|
+
binary += String.fromCharCode(codeUnit & 255, codeUnit >>> 8);
|
|
614
|
+
}
|
|
615
|
+
return btoa(binary);
|
|
616
|
+
};
|
|
617
|
+
const failBridge = (cause, terminateProcess = true) => {
|
|
618
|
+
if (bridgeState === "failed" || bridgeState === "disposed") return;
|
|
619
|
+
const failedProcessHandle = bridgeProcessHandle;
|
|
620
|
+
const terminate = terminateProcess && (bridgeState === "starting" || bridgeState === "ready");
|
|
621
|
+
bridgeState = "failed";
|
|
622
|
+
if (bridgeReadyTimeout !== null) globalThis.clearTimeout(bridgeReadyTimeout);
|
|
623
|
+
bridgeReadyTimeout = null;
|
|
624
|
+
const stderr = outputBuffers.stderr.trim().slice(-1e3);
|
|
625
|
+
const error = transportError(
|
|
626
|
+
`${cause instanceof Error ? cause.message : String(cause)}${stderr ? `; ${stderr}` : ""}`,
|
|
627
|
+
cause
|
|
628
|
+
);
|
|
629
|
+
bridgeReadyReject?.(error);
|
|
630
|
+
bridgeReadyReject = null;
|
|
631
|
+
bridgeReadyResolve = null;
|
|
632
|
+
for (const requestId of bridgeRequests.keys()) bridge.onError(requestId, error);
|
|
633
|
+
bridgeRequests.clear();
|
|
634
|
+
if (isRemoteControlHost && terminate) {
|
|
635
|
+
void Promise.resolve(
|
|
636
|
+
originalSend.call(bridge, "process/kill", { processHandle: failedProcessHandle })
|
|
637
|
+
).catch(() => void 0);
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
const resetFailedBridge = () => {
|
|
641
|
+
if (bridgeState !== "failed") return;
|
|
642
|
+
bridgeProcessHandle = createBridgeProcessHandle();
|
|
643
|
+
outputDecoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };
|
|
644
|
+
outputBuffers.stdout = "";
|
|
645
|
+
outputBuffers.stderr = "";
|
|
646
|
+
bridgeReadyPromise = null;
|
|
647
|
+
bridgeReadyResolve = null;
|
|
648
|
+
bridgeReadyReject = null;
|
|
649
|
+
bridgeInitialization = null;
|
|
650
|
+
writeTail = Promise.resolve();
|
|
651
|
+
bridgeServerRequests.clear();
|
|
652
|
+
nextBridgeServerRequestOrdinal = 1;
|
|
653
|
+
bridgeState = "idle";
|
|
654
|
+
};
|
|
655
|
+
const rememberExternalThread = (value) => {
|
|
656
|
+
if (!isRecord5(value) || typeof value.id !== "string") return;
|
|
657
|
+
if (value.modelProvider === "codexhost" || value.cliVersion === "codexhost") {
|
|
658
|
+
knownExternalThreadIds.add(value.id);
|
|
659
|
+
knownOfficialThreadIds.delete(value.id);
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
const observeBridgeResult = (request, result) => {
|
|
663
|
+
if (!request || !isRecord5(result)) return;
|
|
664
|
+
if (request.method === "thread/list" && Array.isArray(result.data)) {
|
|
665
|
+
for (const thread of result.data) rememberExternalThread(thread);
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
if (request.method === "thread/start" || request.method === "thread/read" || request.method === "thread/resume") {
|
|
669
|
+
rememberExternalThread(result.thread);
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
if (request.method === "codexhost/thread/inspect" && isRecord5(request.parameters) && typeof request.parameters.threadId === "string") {
|
|
673
|
+
if (result.owner === "external") {
|
|
674
|
+
knownExternalThreadIds.add(request.parameters.threadId);
|
|
675
|
+
knownOfficialThreadIds.delete(request.parameters.threadId);
|
|
676
|
+
} else if (result.owner === "codex") {
|
|
677
|
+
knownOfficialThreadIds.add(request.parameters.threadId);
|
|
678
|
+
knownExternalThreadIds.delete(request.parameters.threadId);
|
|
679
|
+
}
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
if (request.method === "thread/delete" && isRecord5(request.parameters) && typeof request.parameters.threadId === "string") {
|
|
683
|
+
knownExternalThreadIds.delete(request.parameters.threadId);
|
|
684
|
+
knownOfficialThreadIds.delete(request.parameters.threadId);
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
const handleBridgeFrame = (value) => {
|
|
688
|
+
if (!isRecord5(value)) {
|
|
689
|
+
failBridge("received a non-object app-server frame");
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
if (value.method === bridgeReadyMethod && value.id === void 0) {
|
|
693
|
+
if (isRecord5(value.params) && value.params.protocolVersion === 1) {
|
|
694
|
+
bridgeState = "ready";
|
|
695
|
+
if (bridgeReadyTimeout !== null) globalThis.clearTimeout(bridgeReadyTimeout);
|
|
696
|
+
bridgeReadyTimeout = null;
|
|
697
|
+
bridgeReadyResolve?.();
|
|
698
|
+
bridgeReadyResolve = null;
|
|
699
|
+
bridgeReadyReject = null;
|
|
700
|
+
} else {
|
|
701
|
+
failBridge("reported an unsupported protocol version");
|
|
702
|
+
}
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
if (typeof value.method === "string" && value.id === void 0) {
|
|
706
|
+
if (value.method === "thread/started" && isRecord5(value.params)) {
|
|
707
|
+
rememberExternalThread(value.params.thread);
|
|
708
|
+
}
|
|
709
|
+
originalOnNotification.call(manager, value.method, value.params);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if (typeof value.method === "string" && value.id !== void 0) {
|
|
713
|
+
const outerRequestId = `${bridgeServerRequestIdPrefix}${bridgeProcessHandle}/${nextBridgeServerRequestOrdinal}`;
|
|
714
|
+
nextBridgeServerRequestOrdinal += 1;
|
|
715
|
+
bridgeServerRequests.set(outerRequestId, value.id);
|
|
716
|
+
manager.onRequest({ ...value, id: outerRequestId });
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
if (value.id === void 0) {
|
|
720
|
+
failBridge("received an app-server response without an id");
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const request = bridgeRequests.get(value.id);
|
|
724
|
+
bridgeRequests.delete(value.id);
|
|
725
|
+
if ("error" in value) {
|
|
726
|
+
bridge.onError(value.id, value.error, value.metrics);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
observeBridgeResult(request, value.result);
|
|
730
|
+
bridge.onResult(value.id, value.result, value.metrics);
|
|
731
|
+
};
|
|
732
|
+
const consumeBridgeOutput = (stream, base64) => {
|
|
733
|
+
const binary = atob(base64);
|
|
734
|
+
const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
735
|
+
outputBuffers[stream] += outputDecoders[stream].decode(bytes, { stream: true });
|
|
736
|
+
if (stream === "stderr") {
|
|
737
|
+
outputBuffers.stderr = outputBuffers.stderr.slice(-4e3);
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
while (true) {
|
|
741
|
+
const newline = outputBuffers.stdout.indexOf("\n");
|
|
742
|
+
if (newline < 0) break;
|
|
743
|
+
const line = outputBuffers.stdout.slice(0, newline).trim();
|
|
744
|
+
outputBuffers.stdout = outputBuffers.stdout.slice(newline + 1);
|
|
745
|
+
if (!line) continue;
|
|
746
|
+
try {
|
|
747
|
+
handleBridgeFrame(JSON.parse(line));
|
|
748
|
+
} catch (error) {
|
|
749
|
+
failBridge(
|
|
750
|
+
`emitted invalid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
751
|
+
);
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
const handleOuterNotification = (method, parameters) => {
|
|
757
|
+
if (method === "process/exited" && isRecord5(parameters) && parameters.processHandle === bridgeProcessHandle) {
|
|
758
|
+
const exitCode = typeof parameters.exitCode === "number" ? parameters.exitCode : "unknown";
|
|
759
|
+
const stderr = typeof parameters.stderr === "string" ? parameters.stderr.trim() : "";
|
|
760
|
+
failBridge(
|
|
761
|
+
`process exited unexpectedly with code ${exitCode}${stderr ? `: ${stderr}` : ""}`,
|
|
762
|
+
false
|
|
763
|
+
);
|
|
764
|
+
return true;
|
|
765
|
+
}
|
|
766
|
+
if (method !== "process/outputDelta" || !isRecord5(parameters) || parameters.processHandle !== bridgeProcessHandle || parameters.stream !== "stdout" && parameters.stream !== "stderr" || typeof parameters.deltaBase64 !== "string") {
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
if (parameters.capReached === true) {
|
|
770
|
+
failBridge(`${parameters.stream} was truncated`);
|
|
771
|
+
return true;
|
|
772
|
+
}
|
|
773
|
+
consumeBridgeOutput(parameters.stream, parameters.deltaBase64);
|
|
774
|
+
return true;
|
|
775
|
+
};
|
|
776
|
+
const startBridge = () => {
|
|
777
|
+
if (!isRemoteControlHost) return Promise.resolve();
|
|
778
|
+
if (bridgeReadyPromise) return bridgeReadyPromise;
|
|
779
|
+
bridgeState = "starting";
|
|
780
|
+
bridgeReadyPromise = new Promise((resolve, reject) => {
|
|
781
|
+
bridgeReadyResolve = resolve;
|
|
782
|
+
bridgeReadyReject = reject;
|
|
783
|
+
});
|
|
784
|
+
bridgeReadyTimeout = globalThis.setTimeout(
|
|
785
|
+
() => failBridge("startup timed out after 15000ms"),
|
|
786
|
+
15e3
|
|
787
|
+
);
|
|
788
|
+
const powershellScript = "$ErrorActionPreference = 'Stop'; $descriptorPath = Join-Path $env:LOCALAPPDATA 'codexhost\\remote-control-bridge-v1.json'; $descriptor = Get-Content -LiteralPath $descriptorPath -Raw | ConvertFrom-Json; if ($descriptor.schemaVersion -ne 1) { throw 'Unsupported codexhost Remote Control descriptor' }; $nodePath = [string]$descriptor.nodePath; $runtimePath = [string]$descriptor.runtimePath; if (-not [System.IO.Path]::IsPathRooted($nodePath) -or -not [System.IO.Path]::IsPathRooted($runtimePath)) { throw 'Invalid codexhost Remote Control runtime path' }; $owner = Get-Process -Id ([int]$descriptor.ownerPid) -ErrorAction SilentlyContinue; if ($null -eq $owner) { throw 'CodexHost Remote Control runtime is not running' }; $env:CODEXHOST_REMOTE_CONTROL_BRIDGE_PIPE = [string]$descriptor.pipePath; & $nodePath $runtimePath '--codexhost-remote-control-bridge'; exit $LASTEXITCODE";
|
|
789
|
+
const command = [
|
|
790
|
+
"powershell.exe",
|
|
791
|
+
"-NoLogo",
|
|
792
|
+
"-NoProfile",
|
|
793
|
+
"-NonInteractive",
|
|
794
|
+
"-EncodedCommand",
|
|
795
|
+
utf16LeBase64(powershellScript)
|
|
796
|
+
];
|
|
797
|
+
const startedProcessHandle = bridgeProcessHandle;
|
|
798
|
+
try {
|
|
799
|
+
const started = originalSend.call(bridge, "process/spawn", {
|
|
800
|
+
command,
|
|
801
|
+
processHandle: startedProcessHandle,
|
|
802
|
+
cwd: "C:\\",
|
|
803
|
+
streamStdin: true,
|
|
804
|
+
streamStdoutStderr: true,
|
|
805
|
+
outputBytesCap: null,
|
|
806
|
+
timeoutMs: null
|
|
807
|
+
});
|
|
808
|
+
void Promise.resolve(started).catch((error) => {
|
|
809
|
+
if (bridgeProcessHandle === startedProcessHandle) failBridge(error, false);
|
|
810
|
+
});
|
|
811
|
+
} catch (error) {
|
|
812
|
+
failBridge(error);
|
|
813
|
+
}
|
|
814
|
+
return bridgeReadyPromise;
|
|
815
|
+
};
|
|
816
|
+
const writeBridgeFrame = (value) => {
|
|
817
|
+
const operation = async () => {
|
|
818
|
+
await startBridge();
|
|
819
|
+
if (bridgeState !== "ready") throw transportError("is not ready");
|
|
820
|
+
await originalSend.call(bridge, "process/writeStdin", {
|
|
821
|
+
processHandle: bridgeProcessHandle,
|
|
822
|
+
deltaBase64: utf8Base64(`${JSON.stringify(value)}
|
|
823
|
+
`)
|
|
824
|
+
});
|
|
825
|
+
};
|
|
826
|
+
const next = writeTail.then(operation, operation);
|
|
827
|
+
writeTail = next.catch(() => void 0);
|
|
828
|
+
return next;
|
|
829
|
+
};
|
|
830
|
+
const enqueueBridgeRequest = (method, parameters, options) => bridge.enqueueRequest(method, parameters, options, (request) => {
|
|
831
|
+
bridgeRequests.set(request.id, { method, parameters });
|
|
832
|
+
void writeBridgeFrame(request).catch((error) => failBridge(error));
|
|
833
|
+
});
|
|
834
|
+
const initializeBridgeProtocol = () => {
|
|
835
|
+
const initialization = enqueueBridgeRequest("initialize", {
|
|
836
|
+
clientInfo: {
|
|
837
|
+
name: "codexhost_remote_control_bridge",
|
|
838
|
+
title: "codexhost Remote Control bridge",
|
|
839
|
+
version: "1"
|
|
840
|
+
},
|
|
841
|
+
capabilities: {
|
|
842
|
+
experimentalApi: true,
|
|
843
|
+
mcpServerOpenaiFormElicitation: true
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
return new Promise((resolve, reject) => {
|
|
847
|
+
const timeout = globalThis.setTimeout(() => {
|
|
848
|
+
const message = "initialization timed out after 15000ms";
|
|
849
|
+
failBridge(message);
|
|
850
|
+
reject(transportError(message));
|
|
851
|
+
}, 15e3);
|
|
852
|
+
void Promise.resolve(initialization).then(
|
|
853
|
+
(value) => {
|
|
854
|
+
globalThis.clearTimeout(timeout);
|
|
855
|
+
resolve(value);
|
|
856
|
+
},
|
|
857
|
+
(error) => {
|
|
858
|
+
globalThis.clearTimeout(timeout);
|
|
859
|
+
reject(error);
|
|
860
|
+
}
|
|
861
|
+
);
|
|
862
|
+
});
|
|
863
|
+
};
|
|
864
|
+
const initializeBridge = () => {
|
|
865
|
+
resetFailedBridge();
|
|
866
|
+
if (bridgeInitialization) return bridgeInitialization;
|
|
867
|
+
bridgeInitialization = startBridge().then(() => initializeBridgeProtocol()).then(() => writeBridgeFrame({ method: "initialized", params: {} }));
|
|
868
|
+
return bridgeInitialization;
|
|
869
|
+
};
|
|
870
|
+
const threadIdFromParameters = (parameters) => {
|
|
871
|
+
if (!isRecord5(parameters)) return null;
|
|
872
|
+
const value = parameters.threadId ?? parameters.conversationId;
|
|
873
|
+
return typeof value === "string" ? value : null;
|
|
874
|
+
};
|
|
875
|
+
const isThreadScopedMethod = (method) => method.startsWith("thread/") || method.startsWith("turn/") || method.startsWith("review/");
|
|
876
|
+
const resolveThreadOwnership = (threadId) => {
|
|
877
|
+
if (knownExternalThreadIds.has(threadId)) return Promise.resolve("external");
|
|
878
|
+
if (knownOfficialThreadIds.has(threadId)) return Promise.resolve("codex");
|
|
879
|
+
const pending = threadOwnershipResolutions.get(threadId);
|
|
880
|
+
if (pending) return pending;
|
|
881
|
+
const resolution = initializeBridge().then(
|
|
882
|
+
() => enqueueBridgeRequest("codexhost/thread/ownership/list", {
|
|
883
|
+
threadIds: [threadId]
|
|
884
|
+
})
|
|
885
|
+
).then((value) => {
|
|
886
|
+
const ownerships = isRecord5(value) && Array.isArray(value.threads) ? value.threads : null;
|
|
887
|
+
const ownership = ownerships?.[0];
|
|
888
|
+
if (!ownerships || !isRecord5(ownership) || ownerships.length !== 1 || ownership.threadId !== threadId || ownership.owner !== "external" && ownership.owner !== "codex") {
|
|
889
|
+
throw transportError("Thread ownership lookup returned an invalid result");
|
|
890
|
+
}
|
|
891
|
+
if (ownership.owner === "external") {
|
|
892
|
+
knownExternalThreadIds.add(threadId);
|
|
893
|
+
knownOfficialThreadIds.delete(threadId);
|
|
894
|
+
return "external";
|
|
895
|
+
}
|
|
896
|
+
knownOfficialThreadIds.add(threadId);
|
|
897
|
+
knownExternalThreadIds.delete(threadId);
|
|
898
|
+
return "codex";
|
|
899
|
+
});
|
|
900
|
+
threadOwnershipResolutions.set(threadId, resolution);
|
|
901
|
+
const clearResolution = () => {
|
|
902
|
+
if (threadOwnershipResolutions.get(threadId) === resolution) {
|
|
903
|
+
threadOwnershipResolutions.delete(threadId);
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
void resolution.then(clearResolution, clearResolution);
|
|
907
|
+
return resolution;
|
|
908
|
+
};
|
|
909
|
+
const shouldResolveThreadOwnership = (method, parameters) => {
|
|
910
|
+
if (!isRemoteControlHost || method.startsWith("codexhost/") || !isThreadScopedMethod(method)) {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
const threadId = threadIdFromParameters(parameters);
|
|
914
|
+
if (!threadId || knownExternalThreadIds.has(threadId) || knownOfficialThreadIds.has(threadId)) {
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
return threadId;
|
|
918
|
+
};
|
|
919
|
+
const shouldUseBridge = (method, parameters) => {
|
|
920
|
+
if (!isRemoteControlHost) return false;
|
|
921
|
+
if (method.startsWith("codexhost/")) return true;
|
|
922
|
+
if (method === "thread/list") return true;
|
|
923
|
+
if (method === "thread/start") {
|
|
924
|
+
return isRecord5(parameters) && typeof parameters.model === "string" && parameters.model.startsWith("codexhost/");
|
|
925
|
+
}
|
|
926
|
+
const threadId = threadIdFromParameters(parameters);
|
|
927
|
+
if (threadId && knownExternalThreadIds.has(threadId)) return true;
|
|
928
|
+
if (threadId && knownOfficialThreadIds.has(threadId)) return false;
|
|
929
|
+
return selectedModel !== null && (method.startsWith("thread/") || method.startsWith("turn/") || method.startsWith("review/"));
|
|
930
|
+
};
|
|
571
931
|
const routeThreadStart = (parameters) => {
|
|
572
932
|
if (selectedModel === null || !isRecord5(parameters) || parameters.ephemeral === true) {
|
|
573
933
|
return parameters;
|
|
@@ -576,19 +936,62 @@ function installDraftPrewarmPolicyBridge(bridge, hostId, target, prewarmedThread
|
|
|
576
936
|
};
|
|
577
937
|
const routedSend = (method, parameters, options) => {
|
|
578
938
|
const routedParameters = method === "thread/start" ? routeThreadStart(parameters) : parameters;
|
|
579
|
-
|
|
939
|
+
const sendBridged = () => initializeBridge().then(
|
|
940
|
+
() => enqueueBridgeRequest(method, routedParameters, options)
|
|
941
|
+
);
|
|
942
|
+
const sendDirect = () => options === void 0 ? originalSend.call(bridge, method, routedParameters) : originalSend.call(bridge, method, routedParameters, options);
|
|
943
|
+
const unresolvedThreadId = shouldResolveThreadOwnership(method, routedParameters);
|
|
944
|
+
if (unresolvedThreadId) {
|
|
945
|
+
return resolveThreadOwnership(unresolvedThreadId).then(
|
|
946
|
+
(owner) => owner === "external" ? sendBridged() : sendDirect()
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
return shouldUseBridge(method, routedParameters) ? sendBridged() : sendDirect();
|
|
580
950
|
};
|
|
581
951
|
const routedPrewarm = (parameters, options) => {
|
|
582
952
|
const routedParameters = routeThreadStart(parameters);
|
|
953
|
+
if (shouldUseBridge("thread/start", routedParameters)) {
|
|
954
|
+
return routedSend("thread/start", routedParameters, options);
|
|
955
|
+
}
|
|
583
956
|
return options === void 0 ? originalPrewarm.call(bridge, routedParameters) : originalPrewarm.call(bridge, routedParameters, options);
|
|
584
957
|
};
|
|
585
958
|
bridge.sendRequest = routedSend;
|
|
586
959
|
bridge.prewarmThreadStart = routedPrewarm;
|
|
960
|
+
const routedWindowMessage = (event) => {
|
|
961
|
+
const message = event.data;
|
|
962
|
+
if (!isRecord5(message) || message.type !== "mcp-notification" || message.hostId !== hostId || typeof message.method !== "string") {
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
handleOuterNotification(message.method, message.params);
|
|
966
|
+
};
|
|
967
|
+
const observesWindowNotifications = isRemoteControlHost && typeof target.addEventListener === "function" && typeof target.removeEventListener === "function";
|
|
968
|
+
if (observesWindowNotifications) target.addEventListener?.("message", routedWindowMessage);
|
|
969
|
+
const routedOnNotification = (method, parameters) => {
|
|
970
|
+
if (!handleOuterNotification(method, parameters)) {
|
|
971
|
+
originalOnNotification.call(manager, method, parameters);
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
const routedDispatchAppServerResponse = (method, response) => {
|
|
975
|
+
if (isRemoteControlHost && typeof response.id === "string") {
|
|
976
|
+
const innerRequestId = bridgeServerRequests.get(response.id);
|
|
977
|
+
if (innerRequestId !== void 0 || bridgeServerRequests.has(response.id)) {
|
|
978
|
+
bridgeServerRequests.delete(response.id);
|
|
979
|
+
void writeBridgeFrame({ ...response, id: innerRequestId }).catch(
|
|
980
|
+
(error) => failBridge(error)
|
|
981
|
+
);
|
|
982
|
+
return void 0;
|
|
983
|
+
}
|
|
984
|
+
if (response.id.startsWith(bridgeServerRequestIdPrefix)) return void 0;
|
|
985
|
+
}
|
|
986
|
+
return originalDispatchAppServerResponse.call(manager, method, response);
|
|
987
|
+
};
|
|
988
|
+
if (!observesWindowNotifications) manager.onNotification = routedOnNotification;
|
|
989
|
+
manager.dispatchAppServerResponse = routedDispatchAppServerResponse;
|
|
587
990
|
const policy = Object.freeze({
|
|
588
991
|
state: "ready",
|
|
589
992
|
hostId,
|
|
590
|
-
owns(candidate, candidateHostId, candidatePrewarmedThreadManager) {
|
|
591
|
-
return candidate === bridge && candidateHostId === hostId && candidatePrewarmedThreadManager === prewarmedThreadManager;
|
|
993
|
+
owns(candidateManager, candidate, candidateHostId, candidatePrewarmedThreadManager) {
|
|
994
|
+
return candidateManager === manager && candidate === bridge && candidateHostId === hostId && candidatePrewarmedThreadManager === prewarmedThreadManager;
|
|
592
995
|
},
|
|
593
996
|
select(model) {
|
|
594
997
|
if (model !== null && (typeof model !== "string" || !model.startsWith("codexhost/"))) {
|
|
@@ -607,6 +1010,30 @@ function installDraftPrewarmPolicyBridge(bridge, hostId, target, prewarmedThread
|
|
|
607
1010
|
if (bridge.prewarmThreadStart === routedPrewarm) {
|
|
608
1011
|
bridge.prewarmThreadStart = originalPrewarm;
|
|
609
1012
|
}
|
|
1013
|
+
if (observesWindowNotifications) {
|
|
1014
|
+
target.removeEventListener?.("message", routedWindowMessage);
|
|
1015
|
+
} else if (manager.onNotification === routedOnNotification) {
|
|
1016
|
+
manager.onNotification = originalOnNotification;
|
|
1017
|
+
}
|
|
1018
|
+
if (manager.dispatchAppServerResponse === routedDispatchAppServerResponse) {
|
|
1019
|
+
manager.dispatchAppServerResponse = originalDispatchAppServerResponse;
|
|
1020
|
+
}
|
|
1021
|
+
if (bridgeReadyTimeout !== null) globalThis.clearTimeout(bridgeReadyTimeout);
|
|
1022
|
+
bridgeReadyTimeout = null;
|
|
1023
|
+
if (isRemoteControlHost && (bridgeState === "starting" || bridgeState === "ready")) {
|
|
1024
|
+
void Promise.resolve(
|
|
1025
|
+
originalSend.call(bridge, "process/kill", { processHandle: bridgeProcessHandle })
|
|
1026
|
+
).catch(() => void 0);
|
|
1027
|
+
}
|
|
1028
|
+
bridgeState = "disposed";
|
|
1029
|
+
const disposedError = transportError("was disposed");
|
|
1030
|
+
bridgeReadyReject?.(disposedError);
|
|
1031
|
+
for (const requestId of bridgeRequests.keys()) bridge.onError(requestId, disposedError);
|
|
1032
|
+
bridgeRequests.clear();
|
|
1033
|
+
bridgeServerRequests.clear();
|
|
1034
|
+
knownExternalThreadIds.clear();
|
|
1035
|
+
knownOfficialThreadIds.clear();
|
|
1036
|
+
threadOwnershipResolutions.clear();
|
|
610
1037
|
selectedModel = null;
|
|
611
1038
|
}
|
|
612
1039
|
});
|
|
@@ -648,10 +1075,13 @@ async function installDraftPrewarmPolicyInRenderer(contents, findRequestManagerE
|
|
|
648
1075
|
const manager = managerProperties.result?.find(
|
|
649
1076
|
(property) => property.name === "manager"
|
|
650
1077
|
)?.value;
|
|
1078
|
+
const requestClient = managerProperties.result?.find(
|
|
1079
|
+
(property) => property.name === "requestClient"
|
|
1080
|
+
)?.value;
|
|
651
1081
|
const prewarmedThreadManager = managerProperties.result?.find(
|
|
652
1082
|
(property) => property.name === "prewarmedThreadManager"
|
|
653
1083
|
)?.value;
|
|
654
|
-
if (candidateCount !== 1 || typeof hostId !== "string" || hostId.length === 0 || typeof manager?.objectId !== "string") {
|
|
1084
|
+
if (candidateCount !== 1 || typeof hostId !== "string" || hostId.length === 0 || typeof manager?.objectId !== "string" || typeof requestClient?.objectId !== "string") {
|
|
655
1085
|
throw new Error("Renderer request manager is ambiguous");
|
|
656
1086
|
}
|
|
657
1087
|
if (typeof prewarmedThreadManager?.objectId !== "string") {
|
|
@@ -660,7 +1090,11 @@ async function installDraftPrewarmPolicyInRenderer(contents, findRequestManagerE
|
|
|
660
1090
|
const installed = await contents.debugger.sendCommand("Runtime.callFunctionOn", {
|
|
661
1091
|
objectId: manager.objectId,
|
|
662
1092
|
functionDeclaration: installRendererPolicyFunction,
|
|
663
|
-
arguments: [
|
|
1093
|
+
arguments: [
|
|
1094
|
+
{ objectId: requestClient.objectId },
|
|
1095
|
+
{ value: hostId },
|
|
1096
|
+
{ objectId: prewarmedThreadManager.objectId }
|
|
1097
|
+
],
|
|
664
1098
|
awaitPromise: true,
|
|
665
1099
|
returnByValue: true
|
|
666
1100
|
});
|
|
@@ -748,12 +1182,19 @@ var FIND_REQUEST_MANAGER_EXPRESSION = `(() => {
|
|
|
748
1182
|
return {
|
|
749
1183
|
candidateCount: selected == null ? candidates.length : 1,
|
|
750
1184
|
hostId: selected?.hostId ?? null,
|
|
751
|
-
manager: selected?.
|
|
1185
|
+
manager: selected?.manager ?? null,
|
|
1186
|
+
requestClient: selected?.requestClient ?? null,
|
|
752
1187
|
prewarmedThreadManager: selected?.prewarmedThreadManager ?? null,
|
|
753
1188
|
};
|
|
754
1189
|
})()`;
|
|
755
|
-
var INSTALL_RENDERER_POLICY_FUNCTION = `function(hostId, prewarmedThreadManager) {
|
|
756
|
-
return (${installDraftPrewarmPolicyBridge.toString()})(
|
|
1190
|
+
var INSTALL_RENDERER_POLICY_FUNCTION = `function(requestClient, hostId, prewarmedThreadManager) {
|
|
1191
|
+
return (${installDraftPrewarmPolicyBridge.toString()})(
|
|
1192
|
+
this,
|
|
1193
|
+
requestClient,
|
|
1194
|
+
hostId,
|
|
1195
|
+
window,
|
|
1196
|
+
prewarmedThreadManager,
|
|
1197
|
+
);
|
|
757
1198
|
}`;
|
|
758
1199
|
var REQUEST_MANAGER_WAIT_TIMEOUT_MS = 6e4;
|
|
759
1200
|
var REQUEST_MANAGER_POLL_INTERVAL_MS = 25;
|
|
@@ -826,12 +1267,16 @@ function validateBindingStatus(value, expectedAgents) {
|
|
|
826
1267
|
}
|
|
827
1268
|
return value;
|
|
828
1269
|
}
|
|
829
|
-
function selectRendererWebContents(contents) {
|
|
1270
|
+
function selectRendererWebContents(contents, preferredRendererId) {
|
|
830
1271
|
const candidates = contents.filter(
|
|
831
1272
|
(item) => item.type === "window" && item.surface === "primary" && item.runtime.available && item.runtime.elementCount !== null
|
|
832
1273
|
).toSorted(
|
|
833
1274
|
(left, right) => (right.runtime.elementCount ?? 0) - (left.runtime.elementCount ?? 0)
|
|
834
1275
|
);
|
|
1276
|
+
const preferred = candidates.find(
|
|
1277
|
+
(candidate) => candidate.id === preferredRendererId && (candidate.runtime.elementCount ?? 0) > 0
|
|
1278
|
+
);
|
|
1279
|
+
if (preferred) return preferred;
|
|
835
1280
|
return candidates.find((candidate) => (candidate.runtime.elementCount ?? 0) > 0) ?? null;
|
|
836
1281
|
}
|
|
837
1282
|
async function waitForRendererTitlePolicyReady(markReadiness, options = {}) {
|
|
@@ -959,13 +1404,13 @@ var defaultOperations = {
|
|
|
959
1404
|
),
|
|
960
1405
|
readTitlePolicyCounters: readMainProcessTitlePolicyCounters
|
|
961
1406
|
};
|
|
962
|
-
async function waitForRenderer(inspector, operations, timeoutMs, pollIntervalMs) {
|
|
1407
|
+
async function waitForRenderer(inspector, operations, timeoutMs, pollIntervalMs, preferredRendererId) {
|
|
963
1408
|
const deadline = Date.now() + timeoutMs;
|
|
964
1409
|
let candidateCount = 0;
|
|
965
1410
|
while (Date.now() < deadline) {
|
|
966
1411
|
const inventory = await operations.inspect(inspector);
|
|
967
1412
|
candidateCount = inventory.length;
|
|
968
|
-
const renderer = selectRendererWebContents(inventory);
|
|
1413
|
+
const renderer = selectRendererWebContents(inventory, preferredRendererId);
|
|
969
1414
|
if (renderer) return renderer;
|
|
970
1415
|
await sleep(pollIntervalMs);
|
|
971
1416
|
}
|
|
@@ -1016,7 +1461,8 @@ var InstalledRendererControlSession = class {
|
|
|
1016
1461
|
this.inspector,
|
|
1017
1462
|
this.operations,
|
|
1018
1463
|
this.timeoutMs,
|
|
1019
|
-
this.pollIntervalMs
|
|
1464
|
+
this.pollIntervalMs,
|
|
1465
|
+
this.#snapshot.renderer.id
|
|
1020
1466
|
);
|
|
1021
1467
|
const existing = await this.operations.readBinding(this.inspector, selected.id).catch(() => null);
|
|
1022
1468
|
if (existing !== null) {
|
|
@@ -1124,7 +1570,13 @@ async function createRendererControlSession(options) {
|
|
|
1124
1570
|
startupTrace("reloading Renderer");
|
|
1125
1571
|
await operations.reload(options.inspector, initial.id);
|
|
1126
1572
|
startupTrace("waiting for reloaded Renderer");
|
|
1127
|
-
const selected = await waitForRenderer(
|
|
1573
|
+
const selected = await waitForRenderer(
|
|
1574
|
+
options.inspector,
|
|
1575
|
+
operations,
|
|
1576
|
+
timeoutMs,
|
|
1577
|
+
pollIntervalMs,
|
|
1578
|
+
initial.id
|
|
1579
|
+
);
|
|
1128
1580
|
startupTrace("waiting for title policy readiness");
|
|
1129
1581
|
const titlePolicyReadiness = await waitForRendererTitlePolicyReady(
|
|
1130
1582
|
() => operations.markTitlePolicyReady(options.inspector, selected.id),
|
|
@@ -1366,7 +1818,7 @@ async function runDesktopController(options, signal, dependencies = defaultDepen
|
|
|
1366
1818
|
inspectorEndpoint: options.inspectorEndpoint,
|
|
1367
1819
|
rendererSource: `${configuration}
|
|
1368
1820
|
${rendererSource}`,
|
|
1369
|
-
enabledAgents: ["codex", "pi", "claude-code", "deepseek-harness", "grok"],
|
|
1821
|
+
enabledAgents: ["codex", "pi", "claude-code", "deepseek-harness", "grok", "omp"],
|
|
1370
1822
|
timeoutMs: PRODUCTION_INSTALL_TIMEOUT_MS
|
|
1371
1823
|
},
|
|
1372
1824
|
dependencies
|