route a resumed opencode session to opencode
Resuming an OpenCode conversation dispatched it to the Claude CLI: a `ses_…` id handed to
`claude --resume`. Wrong harness, not degraded output.
The whole six-hop chain, confirmed rather than inferred, because the endpoints alone do not show which
hop drops the value:
/chat/:id fetches detail and sets `selected.model` = `opencode/big-pickle` ← the value exists
ChatDetailPanel renders <NewChat …> without a `model` prop ← dropped here
NewChat reads `initialModel={locationState?.model}` ← unrelated source
nothing in the tree ever writes `location.state.model` ← so always undefined
useChat therefore holds no model and the socket sends none
websocket.ts falls back to the user default, `isClaudeModel` is true
So the model was resolved correctly at the top and read from somewhere else at the bottom. `selected`
has carried `model` all along.
`locationState.model` stays as a fallback rather than being deleted: it is declared on
ChatLocationState and costs nothing to keep for a caller that navigates with one deliberately.
docs/opencode-parity.md B2, which flagged this as the one to verify hop by hop. Its account is accurate;
the added detail is that the value is produced and then dropped, not never produced.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -123,6 +123,15 @@ function DetailBar({ sessionTitle, cwd, partCount, isConnected, isGenerating, on
|
||||
}
|
||||
|
||||
type NewChatProps = {
|
||||
/**
|
||||
* The harness that owns this conversation, as `harness/model` — e.g. `opencode/big-pickle`.
|
||||
*
|
||||
* Load-bearing on resume, not cosmetic. The server routes by model: anything starting `claude-code`
|
||||
* goes to the Claude sidecar, everything else to OpenCode. Send nothing and it falls back to the
|
||||
* user's default, which is how a `ses_…` id ended up being handed to `claude --resume` — a
|
||||
* wrong-harness dispatch rather than a degradation.
|
||||
*/
|
||||
model?: string | null;
|
||||
resumeSummary?: string;
|
||||
resumeSessionId?: string;
|
||||
initialMessages?: ChatMessage[];
|
||||
@@ -191,7 +200,10 @@ function NewChat(props: NewChatProps) {
|
||||
<EmbeddableChat
|
||||
chat={chat}
|
||||
sessionId={undefined}
|
||||
initialModel={locationState?.model ?? undefined}
|
||||
// The session's own model wins. `locationState.model` is declared on ChatLocationState and
|
||||
// written by nothing, so it was the only source and always undefined on resume; it stays as a
|
||||
// fallback for a future caller that navigates with one deliberately.
|
||||
initialModel={props.model ?? locationState?.model ?? undefined}
|
||||
initialMessage={initialMessage}
|
||||
defaultInput={locationState?.prefillInput ?? ''}
|
||||
cwd={cwd}
|
||||
@@ -241,6 +253,7 @@ export const ChatDetailPanel = () => {
|
||||
initialMessages={selected.initialMessages}
|
||||
total={selected.total}
|
||||
initialOffset={selected.initialOffset}
|
||||
model={selected.model}
|
||||
sessionCwd={selected.cwd}
|
||||
sessionTitle={title}
|
||||
partCount={selected.partCount}
|
||||
|
||||
Reference in New Issue
Block a user