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 = {
|
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;
|
resumeSummary?: string;
|
||||||
resumeSessionId?: string;
|
resumeSessionId?: string;
|
||||||
initialMessages?: ChatMessage[];
|
initialMessages?: ChatMessage[];
|
||||||
@@ -191,7 +200,10 @@ function NewChat(props: NewChatProps) {
|
|||||||
<EmbeddableChat
|
<EmbeddableChat
|
||||||
chat={chat}
|
chat={chat}
|
||||||
sessionId={undefined}
|
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}
|
initialMessage={initialMessage}
|
||||||
defaultInput={locationState?.prefillInput ?? ''}
|
defaultInput={locationState?.prefillInput ?? ''}
|
||||||
cwd={cwd}
|
cwd={cwd}
|
||||||
@@ -241,6 +253,7 @@ export const ChatDetailPanel = () => {
|
|||||||
initialMessages={selected.initialMessages}
|
initialMessages={selected.initialMessages}
|
||||||
total={selected.total}
|
total={selected.total}
|
||||||
initialOffset={selected.initialOffset}
|
initialOffset={selected.initialOffset}
|
||||||
|
model={selected.model}
|
||||||
sessionCwd={selected.cwd}
|
sessionCwd={selected.cwd}
|
||||||
sessionTitle={title}
|
sessionTitle={title}
|
||||||
partCount={selected.partCount}
|
partCount={selected.partCount}
|
||||||
|
|||||||
Reference in New Issue
Block a user