Summary
Building phosphene, a viewer plugin that calls agentCompletionsCreateAgentCompletion via new ObjectiveAI({ viewer: true }). The plugin renders in the viewer tab, but API calls hang — the cli-invoke postMessage from the iframe never triggers cli_run on the Rust side.
What works
- CLI → API → Claude works end-to-end. Tested directly:
objectiveai api agent completions post --body-inline '{"agent":{"upstream":"claude_agent_sdk",...},...}'
→ {"type":"notification","value":{...,"content":"Hey!",...}}
- Plugin renders in the viewer via
plugin:// scheme (single-file build with viteSingleFile() to avoid CORS issues with the sandboxed iframe's opaque origin)
isViewerMode() returns true — window.parent !== window is correct
- Viewer compiled with
--features cli — cli_run Tauri command is registered
- API server running on custom port with
claude_agent_sdk enabled
What fails
The SDK's viewerApiCallUnary → viewerApiCallChunks → invokeCli(args) → window.parent.postMessage({ kind: "cli-invoke", args }, "*") fires from the iframe, but:
- No
cli_run activity appears in the viewer's stdout
- No API requests reach the API server
- The async iterator from
invokeCli() never yields — the promise hangs until our 15s timeout catches it
After timeout, the plugin falls back to mock data (which is why it still shows directions/scores).
Suspected cause
plugin-bridge.ts:onIframeMessage receives the postMessage and calls findPluginByWindow(event.source). This checks handle.iframe.contentWindow === event.source. Possible failure modes:
contentWindow of a plugin://-scheme sandboxed iframe (no allow-same-origin) might be null or inaccessible in WKWebView
registerIframe might not have been called by the time the first cli-invoke fires (race condition on tab mount)
- The
event.source identity check might not work across the custom URI scheme boundary in WKWebView
Environment
- macOS (aarch64), Tauri debug build via
pnpm tauri dev -- --features cli
@objectiveai/sdk v2.0.11
- Plugin manifest:
viewer_zip (served via plugin:// scheme)
- Viewer binary compiled with
--features cli (688 crates including objectiveai-cli)
- API server on port 5002 (port 5000 held by macOS ControlCenter)
config.json: api.mode: "local", api.port: 5002, api.local.claude_agent_sdk: true
To reproduce
# 1. Clone phosphene, install, build
cd phosphene-viewer && pnpm install && pnpm build
# 2. Symlink plugin (or run install-dev.sh)
ln -sfn /path/to/phosphene/objectiveai.json ~/.objectiveai/plugins/phosphene.json
mkdir -p ~/.objectiveai/plugins/phosphene
ln -sfn /path/to/phosphene/phosphene-viewer/dist ~/.objectiveai/plugins/phosphene/viewer
# 3. Start API server
cd objectiveai-api && source .env && export CLAUDE_AGENT_SDK
PORT=5002 cargo run -p objectiveai-api --no-default-features
# 4. Start viewer
pnpm --filter objectiveai-viewer run tauri dev -- --features cli
# 5. Click phosphene tab → type intent → "generate directions"
# → HUD shows "bridge: direction generation: no response after 15s"
Question
Is there a known constraint or setup step for getting cli-invoke postMessages from a plugin://-scheme sandboxed iframe to reach onIframeMessage in the viewer host? Is findPluginByWindow expected to work with plugin:// custom scheme iframes in WKWebView?
Summary
Building phosphene, a viewer plugin that calls
agentCompletionsCreateAgentCompletionvianew ObjectiveAI({ viewer: true }). The plugin renders in the viewer tab, but API calls hang — thecli-invokepostMessage from the iframe never triggerscli_runon the Rust side.What works
plugin://scheme (single-file build withviteSingleFile()to avoid CORS issues with the sandboxed iframe's opaque origin)isViewerMode()returns true —window.parent !== windowis correct--features cli—cli_runTauri command is registeredclaude_agent_sdkenabledWhat fails
The SDK's
viewerApiCallUnary→viewerApiCallChunks→invokeCli(args)→window.parent.postMessage({ kind: "cli-invoke", args }, "*")fires from the iframe, but:cli_runactivity appears in the viewer's stdoutinvokeCli()never yields — the promise hangs until our 15s timeout catches itAfter timeout, the plugin falls back to mock data (which is why it still shows directions/scores).
Suspected cause
plugin-bridge.ts:onIframeMessagereceives the postMessage and callsfindPluginByWindow(event.source). This checkshandle.iframe.contentWindow === event.source. Possible failure modes:contentWindowof aplugin://-scheme sandboxed iframe (noallow-same-origin) might benullor inaccessible in WKWebViewregisterIframemight not have been called by the time the firstcli-invokefires (race condition on tab mount)event.sourceidentity check might not work across the custom URI scheme boundary in WKWebViewEnvironment
pnpm tauri dev -- --features cli@objectiveai/sdkv2.0.11viewer_zip(served viaplugin://scheme)--features cli(688 crates includingobjectiveai-cli)config.json:api.mode: "local",api.port: 5002,api.local.claude_agent_sdk: trueTo reproduce
Question
Is there a known constraint or setup step for getting
cli-invokepostMessages from aplugin://-scheme sandboxed iframe to reachonIframeMessagein the viewer host? IsfindPluginByWindowexpected to work withplugin://custom scheme iframes in WKWebView?