Skip to content

Plugin iframe postMessage bridge: cli-invoke never reaches cli_run #203

@mayagore

Description

@mayagore

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 truewindow.parent !== window is correct
  • Viewer compiled with --features clicli_run Tauri command is registered
  • API server running on custom port with claude_agent_sdk enabled

What fails

The SDK's viewerApiCallUnaryviewerApiCallChunksinvokeCli(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:

  1. contentWindow of a plugin://-scheme sandboxed iframe (no allow-same-origin) might be null or inaccessible in WKWebView
  2. registerIframe might not have been called by the time the first cli-invoke fires (race condition on tab mount)
  3. 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?

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions