Skip to content

feat(opencode): bridge permission_asked events for verification cards - #1613

Open
baizhu945 wants to merge 10 commits into
chenhg5:mainfrom
baizhu945:feat/opencode-permission-bridge
Open

baizhu945 wants to merge 10 commits into
chenhg5:mainfrom
baizhu945:feat/opencode-permission-bridge

Conversation

@baizhu945

@baizhu945 baizhu945 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Two related contributions to permission handling and the Reasonix agent:

  1. opencode: bridge permission_asked events for verification cards — OpenCode's --format json mode emits permission_asked NDJSON events, but cc-connect had no handler and RespondPermission was a no-op, so tool calls were silently auto-rejected with no user notification. Fixes [Bug] 使用 Opencode 无法发送验证卡片 #1420.

  2. reasonix: permission cards, live mode switching, context injection, readable tool args — fixes and enhancements for the Reasonix agent adapter so it works well with cc-connect's interactive permission cards and platform tooling.

Part 1 — opencode permission bridge (agent/opencode/session.go)

How permission bridging works

  1. cc-connect launches OpenCode with prompt as positional arg (keeps stdin free)
  2. OpenCode emits permission_asked JSON event → cc-connect converts to EventPermissionRequest
  3. Engine builds permission verification card → sends to Telegram/Feishu
  4. User responds (Allow/Deny) → RespondPermission writes JSON reply to stdin
  5. OpenCode reads reply and continues

Changes

  • stdin pipe: Replaces strings.NewReader(prompt) with io.Pipe() — prompt goes as positional arg, stdin stays open for permission replies
  • permission_asked handler: Parses the permission event and emits EventPermissionRequest with tool name and input summary
  • RespondPermission: Maps engine behavior ("allow"/"deny") to OpenCode reply format ("once"/"reject"), writes JSON line to stdin

Part 2 — reasonix agent fixes (agent/reasonix/)

approval_request → permission card shows the command

The engine renders permission cards from Event.ToolInput, but the reasonix adapter only set Content, so the command field was blank even though approval still executed. Now both Content and ToolInput carry the approval subject.

Live mode switching (/mode yolo|default|plan)

reasonixSession implements core.LiveModeSwitcher: /mode yolo (and aliases auto/force) POSTs /auto-approve-tools {"on": true} to reasonix serve so it stops emitting approval_request events; default/plan disables it. The configured mode is also applied when a session starts. Without this, switching to fully-automatic mode in chat had no effect and permission cards kept appearing.

cc-connect capabilities prompt injection

reasonix serve is a separate process with its own workspace context; it never reads cc-connect's memory files. The adapter now implements PlatformPromptInjector + SystemPromptSupporter and prepends core.AgentSystemPrompt() (cc-connect send / cron / timer usage) to every submitted turn, so the model knows how to send images/files/voice back through cc-connect.

Human-readable tool args on cards

reasonix serve emits tool_dispatch args as JSON (e.g. {"command":"ls -la"}); the adapter passed it through verbatim, so Feishu/Telegram cards showed raw JSON. humanizeToolArgs now extracts the actual command for bash, a description/path for other tools, and falls back to compact key=value. Also applied defensively to approval_request subjects.

Dependencies

Testing

  • go test ./agent/reasonix/ — all pass (new tests: SetLiveMode auto-approve, platform prompt prepended to submit, humanizeToolArgs formats, ToolInput carried on approval_request and tool_dispatch)
  • go test ./agent/opencode/ — all pass
  • End-to-end tested against a local reasonix serve + Feishu.

Enables the OpenCode agent to handle permission_asked NDJSON events
emitted by OpenCode in --format json mode and relay permission
verification cards to messaging platforms via the cc-connect engine.

Changes:
- Pass prompt as positional argument instead of stdin, keeping stdin
  open via io.Pipe for permission reply writes
- Add permission_asked event handler that emits EventPermissionRequest
  with tool name and input summary
- Implement RespondPermission() to write JSON reply back to OpenCode
  stdin, mapping engine behavior ("allow"/"deny") to OpenCode reply
  format ("once"/"reject")
- Add stdin io.WriteCloser field to session struct

Fixes: chenhg5#1420
@baizhu945
baizhu945 requested a review from chenhg5 as a code owner July 29, 2026 03:51
…nal arg

The prior commit changed buildRunArgs to pass the prompt as a trailing
positional argument (keeping stdin free for permission replies instead
of piping). Update the test expectation to match.
…show the command

The engine renders permission card content from Event.ToolInput; the
reasonix adapter only set Content (approval subject), leaving the card's
command field empty even though approval still executed. Mirror the
opencode permission bridge which populates ToolInput.
…ction

- reasonixSession.SetLiveMode: /mode yolo|auto|force now POSTs
  /auto-approve-tools {"on":true} to reasonix serve so it stops
  emitting approval_request cards; default/plan disables it. Implements
  core.LiveModeSwitcher so the engine's applyLiveModeChange works.
- Agent.StartSession applies the persisted mode to serve on session start.
- PlatformPromptInjector + SystemPromptSupporter: inject core.AgentSystemPrompt
  (cc-connect send/cron/timer instructions) into every submitted turn,
  since serve keeps its own workspace context and never reads cc-connect's
  memory files. Fixes the model not knowing how to send images/files/voice.
- Tests: SetLiveMode posts auto-approve, platform prompt prepended.
…e command

The engine renders tool-call cards from Event.ToolInput; the reasonix
adapter only set Content on tool_dispatch, so executed commands appeared
blank in yolo/auto mode (ask mode was unaffected because the permission
card path already used ToolInput). Mirror the approval_request fix.
reasonix serve emits tool_dispatch args as JSON (e.g. bash
{"command":"ls -la"}); the adapter passed it through verbatim, so
Feishu/Telegram cards showed raw JSON. Add humanizeToolArgs which
extracts the actual command for bash, a description/path for other
tools, and falls back to compact key=value. Also applies defensively
to approval_request subject. Unit tests cover all formats.
@baizhu945

Copy link
Copy Markdown
Contributor Author

@chenhg5

AvailableModels only read enabledModels from settings.json, which is
empty by default. /model in Feishu then renders an empty select with
only the current model, making model switching impossible.

Fall back to pi's own model catalog (~/.pi/agent/models-store.json),
which pi maintains as providers are added, when enabledModels is not
configured.
The pi permission-gate extension runs inside the pi CLI and has no way
to learn cc-connect's permission mode. When a user switches to yolo
(auto-approve) mode in Feishu, the engine restarts the session, but the
new pi process is still spawned without any mode hint, so the extension
keeps emitting permission cards.

Inject CC_PERMISSION_MODE=<mode> into the spawned pi process environment
in StartSession. The extension reads it and auto-approves all tool calls
in yolo mode. Mode switches already recreate the session (pi does not
implement LiveModeSwitcher), so the new process picks up the new value.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] 使用 Opencode 无法发送验证卡片

1 participant