commanders run on Sonnet, not the account default
every new commander came up on whatever the owner last picked in a chat — Fable, currently. that default is chosen for interactive work, and a commander is the opposite: long-running, mostly repeating one settled job, and started by a button rather than by somebody thinking about which model they want. COMMANDER_DEFAULT_MODEL, in both places, because the two ways of starting a commander do not share code — the server's /commanders/new route and the detail component's own first turn. per-surface defaults belong in settings eventually. "commanders default to Sonnet" is the decision; this is where it lives until there is a better place for it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
@@ -47,6 +47,20 @@ import { snapshotClaudeUsage } from './usage-store';
|
|||||||
// Default model when no user preference is set
|
// Default model when no user preference is set
|
||||||
export const DEFAULT_MODEL = 'claude-code';
|
export const DEFAULT_MODEL = 'claude-code';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What a commander runs on, regardless of the account's default.
|
||||||
|
*
|
||||||
|
* A commander is long-running and mostly does the same settled thing over and over, so it is the one
|
||||||
|
* surface where the account default — chosen for interactive work — is the wrong answer. Pinned here
|
||||||
|
* rather than left to the picker so a commander created today does not depend on what the owner last
|
||||||
|
* selected for a chat.
|
||||||
|
*
|
||||||
|
* Temporary in the sense that per-surface defaults belong in settings; permanent in the sense that
|
||||||
|
* "commanders default to Sonnet" is the decision, and this is where it lives until there is a better
|
||||||
|
* place for it.
|
||||||
|
*/
|
||||||
|
export const COMMANDER_DEFAULT_MODEL = 'claude-code/sonnet';
|
||||||
|
|
||||||
// Harness selection: the `claude-code` provider runs through the Claude sidecar; every other provider
|
// Harness selection: the `claude-code` provider runs through the Claude sidecar; every other provider
|
||||||
// (opencode/anthropic/openai/… — all `providerID/modelID` ids) runs through the OpenCode server.
|
// (opencode/anthropic/openai/… — all `providerID/modelID` ids) runs through the OpenCode server.
|
||||||
const isClaudeModel = (model: string): boolean => model.startsWith('claude-code');
|
const isClaudeModel = (model: string): boolean => model.startsWith('claude-code');
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import {
|
|||||||
type ChatIdentity,
|
type ChatIdentity,
|
||||||
type ClaudeSessionSummary,
|
type ClaudeSessionSummary,
|
||||||
} from '../chat/claude-sessions';
|
} from '../chat/claude-sessions';
|
||||||
import { collectRunningTasks, startClaudeTurn, getUserDefaultModel, DEFAULT_MODEL } from '../chat/websocket';
|
import { collectRunningTasks, startClaudeTurn, COMMANDER_DEFAULT_MODEL } from '../chat/websocket';
|
||||||
import {
|
import {
|
||||||
listCommandHqCategories,
|
listCommandHqCategories,
|
||||||
createCommandHqCategory,
|
createCommandHqCategory,
|
||||||
@@ -373,7 +373,8 @@ commandHqRouter.post('/commanders/new', async (ctx) => {
|
|||||||
if (credentialError) console.warn(`[command-hq] ${row.dirName}: no credential minted — ${credentialError}`);
|
if (credentialError) console.warn(`[command-hq] ${row.dirName}: no credential minted — ${credentialError}`);
|
||||||
|
|
||||||
const sessionId = randomUUID();
|
const sessionId = randomUUID();
|
||||||
const model = (await getUserDefaultModel(user.id)) || DEFAULT_MODEL;
|
// Not the account default: a commander runs on Sonnet — see COMMANDER_DEFAULT_MODEL.
|
||||||
|
const model = COMMANDER_DEFAULT_MODEL;
|
||||||
|
|
||||||
const outcome = await new Promise<{ ok: true; transcriptId: string } | { ok: false; error: string }>((resolve) => {
|
const outcome = await new Promise<{ ok: true; transcriptId: string } | { ok: false; error: string }>((resolve) => {
|
||||||
// 30s covers a slow spawn (member login checks, a cold sidecar) without holding an HTTP connection
|
// 30s covers a slow spawn (member login checks, a cold sidecar) without holding an HTTP connection
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { useClient } from 'hooks/useClient';
|
|||||||
import { errorText } from 'helpers/error-text';
|
import { errorText } from 'helpers/error-text';
|
||||||
import type { ClaudeSessionDetail } from 'state/useClaudeSessions';
|
import type { ClaudeSessionDetail } from 'state/useClaudeSessions';
|
||||||
import { useChat, EmbeddableChat } from '../Chat';
|
import { useChat, EmbeddableChat } from '../Chat';
|
||||||
|
import { COMMANDER_DEFAULT_MODEL } from './types';
|
||||||
import { DirPickerModal } from '../ChatHistory/DirPickerModal';
|
import { DirPickerModal } from '../ChatHistory/DirPickerModal';
|
||||||
import type { ChatMessage } from '../Chat/types';
|
import type { ChatMessage } from '../Chat/types';
|
||||||
import { categoryFromParam, commandHqListPath, commandHqSessionPath } from './command-hq-routes';
|
import { categoryFromParam, commandHqListPath, commandHqSessionPath } from './command-hq-routes';
|
||||||
@@ -102,13 +103,15 @@ const startBrief = (name: string, referencePath: string): string =>
|
|||||||
'',
|
'',
|
||||||
'Read the **Commanders** skill before you reply. It is how this works, and it is short.',
|
'Read the **Commanders** skill before you reply. It is how this works, and it is short.',
|
||||||
'',
|
'',
|
||||||
'You watch for the thing I name and you respond when it happens. You never cause it — not while we',
|
'Go and LOOK at that path, then talk to me.',
|
||||||
'are setting you up, and not on a first run. If you find work of that kind already waiting here, it',
|
|
||||||
'is mine to start, not yours to volunteer for.',
|
|
||||||
'',
|
'',
|
||||||
'So: go and LOOK at it first, then talk to me. How many things are in there, what kinds, how they are',
|
'Your first message is SHORT. A sentence or two on what is actually in there — how many, what kind —',
|
||||||
'named, how recently they arrive. Open with what you actually found and one question about it — that',
|
'and then one question. Nothing else. Not how the platform works, not what you could be told to do,',
|
||||||
'is worth ten generic ones, and it tells me straight away whether you are looking at the right thing.',
|
'not what you are declining to do, not what happens next. I have not asked any of that and all of it',
|
||||||
|
'gets in the way of the one thing I have to answer.',
|
||||||
|
'',
|
||||||
|
'Plain words. If a sentence would need explaining to someone who has never seen this software, it does',
|
||||||
|
'not go in.',
|
||||||
'',
|
'',
|
||||||
'The name tells you the subject. It never tells you what to do about it, so do not decide that part on',
|
'The name tells you the subject. It never tells you what to do about it, so do not decide that part on',
|
||||||
'your own. One question, and wait.',
|
'your own. One question, and wait.',
|
||||||
@@ -303,7 +306,10 @@ function ActiveNewCommander({
|
|||||||
const { t, tUi } = useTranslation();
|
const { t, tUi } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const chat = useChat(undefined, undefined, {
|
// Sonnet, not the account's default. A commander is long-running and mostly repeats one settled job, so
|
||||||
|
// whatever the owner last picked for an interactive chat is the wrong answer here. Mirrors
|
||||||
|
// COMMANDER_DEFAULT_MODEL on the server, which the button-created path uses.
|
||||||
|
const chat = useChat(undefined, COMMANDER_DEFAULT_MODEL, {
|
||||||
replaceUrl: false,
|
replaceUrl: false,
|
||||||
context: 'command-hq',
|
context: 'command-hq',
|
||||||
// Known from this component's very first render — see the docstring above for why that timing is the
|
// Known from this component's very first render — see the docstring above for why that timing is the
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* What a commander runs on, regardless of the account's default.
|
||||||
|
*
|
||||||
|
* A commander is long-running and mostly repeats one settled job, so the account default — chosen for
|
||||||
|
* interactive work — is the wrong answer here. Mirrors `COMMANDER_DEFAULT_MODEL` in
|
||||||
|
* `api/chat/websocket.ts`, which the server uses for the button-created path; both exist because the two
|
||||||
|
* ways of starting a commander do not share code.
|
||||||
|
*/
|
||||||
|
export const COMMANDER_DEFAULT_MODEL = 'claude-code/sonnet';
|
||||||
|
|
||||||
// Mirrors the JSON shapes `src/servers/api/command-hq/router.ts` actually returns — hand-kept rather
|
// Mirrors the JSON shapes `src/servers/api/command-hq/router.ts` actually returns — hand-kept rather
|
||||||
// than imported from officerdb, the same way every other frontend API type in this codebase is (dates
|
// than imported from officerdb, the same way every other frontend API type in this codebase is (dates
|
||||||
// arrive as ISO strings over the wire, not `Date`, which is the one thing importing the server's own
|
// arrive as ISO strings over the wire, not `Date`, which is the one thing importing the server's own
|
||||||
|
|||||||
Reference in New Issue
Block a user