Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions src/codex-micro-renderer-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ export type AgentDispatchPlan =
| { kind: "native"; slot: number; threadKey: string }
| { kind: "direct"; threadKey: string };

export function localThreadKeyVariants(threadKey: string): string[] {
return threadKey.startsWith("local:")
? [threadKey, threadKey.slice("local:".length)]
: [threadKey, `local:${threadKey}`];
}

export function resolveAgentDispatch(
snapshot: MicroSnapshot,
requestedSlot: number,
Expand Down Expand Up @@ -352,8 +358,10 @@ export class CodexMicroRendererBridge {
}

private async ensureThreadActivated(threadKey: string): Promise<void> {
const threadKeyVariants = localThreadKeyVariants(threadKey);
const result = await this.evaluate<"active" | "opened" | "missing" | "failed">(`(async () => {
const threadKey = ${JSON.stringify(threadKey)};
const threadKeyVariants = ${JSON.stringify(threadKeyVariants)};
const activeThreadKey = () => document.querySelector('[data-above-composer-conversation-id]')
?.getAttribute('data-above-composer-conversation-id')
?? document.querySelector('[data-app-action-sidebar-thread-id][data-app-action-sidebar-thread-active="true"]')
Expand All @@ -364,14 +372,14 @@ export class CodexMicroRendererBridge {
const waitForActive = async (duration) => {
const deadline = Date.now() + duration;
while (Date.now() < deadline) {
if (activeThreadKey() === threadKey) return true;
if (threadKeyVariants.includes(activeThreadKey())) return true;
await new Promise((resolve) => setTimeout(resolve, 25));
}
return activeThreadKey() === threadKey;
return threadKeyVariants.includes(activeThreadKey());
};
if (await waitForActive(250)) return 'active';
const item = [...document.querySelectorAll('[data-app-action-sidebar-thread-id]')]
.find((element) => element.getAttribute('data-app-action-sidebar-thread-id') === threadKey);
.find((element) => threadKeyVariants.includes(element.getAttribute('data-app-action-sidebar-thread-id')));
if (!item) return 'missing';
const selector = 'button, a, [role="button"], [role="link"]';
const clickable = item.matches(selector) ? item : item.querySelector(selector) ?? item.closest(selector) ?? item;
Expand Down
8 changes: 7 additions & 1 deletion test/micro-bridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
import {
REASONING_ENCODER_KEYS, resolveAgentDispatch, retainEvaluationPromise, selectCodexMainTarget
localThreadKeyVariants, REASONING_ENCODER_KEYS, resolveAgentDispatch, retainEvaluationPromise, selectCodexMainTarget
} from "../src/codex-micro-renderer-bridge.js";
import { ADDITIONAL_KEYCAPS, OFFICIAL_KEYCAP_IDS } from "../src/keycaps.js";
import { visualStatusFromMicro } from "../src/status.js";
Expand Down Expand Up @@ -77,6 +77,12 @@ test("renderer evaluations retain their awaited promise until CDP has collected
assert.match(namespaced, /codex-deck-bridge-a-1/);
});

test("renderer activation accepts local-prefixed and bare forms of the same thread key", () => {
const threadId = "019fc4e4-4ecc-7f20-b7f5-855c11da7b37";
assert.deepEqual(localThreadKeyVariants(`local:${threadId}`), [`local:${threadId}`, threadId]);
assert.deepEqual(localThreadKeyVariants(threadId), [threadId, `local:${threadId}`]);
});

test("agent routing follows the stable thread identity when a cross-host slot is stale", () => {
const snapshot = {
slots: Array.from({ length: 6 }, (_, id) => ({
Expand Down