feat(opencode): add plan mode, reasoning effort, and status telemetry (#688)

* feat(opencode): support plan mode

* feat(opencode): support reasoning effort

* feat(opencode): surface context usage in web

Bridge OpenCode ACP usage updates into the existing token-count pipeline so the web status bar can show live context and cache information without a separate UI path.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(opencode): restrict plan mode to remote, rollback reasoning effort on failure

- Block local OpenCode plan startup (tools not enforced in local path)
- Allow remote OpenCode plan only (ACP permission handler denies tools)
- Guard web /permission-mode endpoint for local OpenCode plan sessions
- Rollback session reasoning effort when OpenCode rejects set_config_option
- Wire rollback callback through opencodeLoop to runOpencode closure
- Add tests: local plan rejected, remote plan allowed, web guard, effort rollback

* fix(web): auto-retry OpenCode models query to populate model selector without refresh

- Retry early failures (RPC may still be registering on new sessions)
- Poll briefly until availableModels is non-empty
- Stop polling once model options are discovered
- Add tests for retry/poll/stop policy

* fix(opencode): cap model discovery polling

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
SSU-WEI HUANG
2026-05-27 11:17:24 +08:00
committed by GitHub
co-authored by Cursor
parent d5a67b717c
commit 6f2bb7d32b
43 changed files with 1263 additions and 68 deletions
+2 -2
View File
@@ -623,7 +623,7 @@ export function SessionChat(props: {
collaborationMode={codexCollaborationModeSupported ? props.session.collaborationMode : undefined}
threadGoal={reduced.latestGoal}
model={props.session.model}
modelReasoningEffort={agentFlavor === 'codex' ? props.session.modelReasoningEffort : undefined}
modelReasoningEffort={agentFlavor === 'codex' || agentFlavor === 'opencode' ? props.session.modelReasoningEffort : undefined}
effort={props.session.effort}
agentFlavor={agentFlavor}
availableModelOptions={
@@ -658,7 +658,7 @@ export function SessionChat(props: {
: handleModelChange
}
onModelReasoningEffortChange={
agentFlavor === 'codex' && props.session.active && !controlledByUser
(agentFlavor === 'codex' || agentFlavor === 'opencode') && props.session.active && !controlledByUser
? handleModelReasoningEffortChange
: undefined
}