diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1abebff8..09ed008e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -52,7 +52,7 @@ updates: patterns: ["*"] update-types: ["minor", "patch"] ignore: - # The Claude Agent SDK is deliberately exact-pinned (0.3.190) because its + # The Claude Agent SDK is deliberately exact-pinned (0.3.282) because its # pre-1.0 API drifts. Below 1.0 semver gives NO stability guarantee for # patch releases either — a 0.3.x → 0.3.y bump of this fast-moving agent # SDK can carry breaking changes, and the engine is the load-bearing diff --git a/apps/desktop/src-tauri/src/contracts/fixtures.json b/apps/desktop/src-tauri/src/contracts/fixtures.json index d5c745ba..a8c880a2 100644 --- a/apps/desktop/src-tauri/src/contracts/fixtures.json +++ b/apps/desktop/src-tauri/src/contracts/fixtures.json @@ -4,7 +4,7 @@ "type": "start-session", "prompt": "do the thing", "providerId": "claude", - "model": "claude-opus-4-8", + "model": "claude-opus-5-5", "effort": "high", "autonomy": "auto-accept", "cwd": "/tmp/work", @@ -73,7 +73,7 @@ "set-model": { "sessionId": 3, "type": "set-model", - "model": "claude-sonnet-4-6" + "model": "claude-sonnet-5" }, "set-autonomy": { "sessionId": 4, @@ -113,7 +113,7 @@ "bugs", "security" ], - "model": "claude-opus-4-8", + "model": "claude-opus-5-5", "effort": "high", "maxConcurrency": 3, "maxTurnsPerCategory": 40, @@ -137,7 +137,7 @@ "folder-structure", "imports-boundaries" ], - "model": "claude-opus-4-8", + "model": "claude-opus-5-5", "effort": "high", "maxConcurrency": 3, "maxTurnsPerCategory": 40, @@ -161,7 +161,7 @@ "tests", "security" ], - "model": "claude-opus-4-8", + "model": "claude-opus-5-5", "effort": "high", "maxConcurrency": 3, "maxTurnsPerDimension": 40, @@ -188,7 +188,7 @@ "tests", "contracts" ], - "model": "claude-opus-4-8", + "model": "claude-opus-5-5", "effort": "high", "maxConcurrency": 3, "deep": { @@ -229,7 +229,7 @@ "diff": "diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx\n@@ -1,3 +1,5 @@\n+ if (!project) return ;\n" } ], - "model": "claude-opus-4-8", + "model": "claude-opus-5-5", "effort": "high", "maxTurns": 40, "maxBudgetUsd": 2 @@ -318,7 +318,7 @@ ], "maxTurns": 12, "maxBudgetUsd": 2, - "model": "claude-opus-4-8" + "model": "claude-opus-5-5" } }, "events": { @@ -326,14 +326,14 @@ "sessionId": 0, "type": "session-started", "prompt": "hi", - "model": "claude-opus-4-8", + "model": "claude-opus-5-5", "permissionMode": "default" }, "session-ready": { "sessionId": 0, "type": "session-ready", "sdkSessionId": "sdk-uuid", - "model": "claude-opus-4-8", + "model": "claude-opus-5-5", "tools": [ "Read", "Bash" @@ -458,7 +458,7 @@ "bugs", "security" ], - "model": "claude-opus-4-8" + "model": "claude-opus-5-5" }, "analysis-category-started": { "type": "analysis-category-started", @@ -586,7 +586,7 @@ "folder-structure", "imports-boundaries" ], - "model": "claude-opus-4-8" + "model": "claude-opus-5-5" }, "harness-profile-ready": { "type": "harness-profile-ready", @@ -877,7 +877,7 @@ "tests", "security" ], - "model": "claude-opus-4-8" + "model": "claude-opus-5-5" }, "scorecard-dimension-started": { "type": "scorecard-dimension-started", @@ -979,7 +979,7 @@ "tests", "contracts" ], - "model": "claude-opus-4-8" + "model": "claude-opus-5-5" }, "pr-review-lens-started": { "type": "pr-review-lens-started", @@ -1081,7 +1081,7 @@ "type": "issue-validation-started", "runId": "run-iv1", "issueNumber": 128, - "model": "claude-opus-4-8" + "model": "claude-opus-5-5" }, "issue-validation-progress": { "type": "issue-validation-progress", @@ -1204,15 +1204,15 @@ } ] }, - "model": "claude-opus-4-8", + "model": "claude-opus-5-5", "permissionMode": "acceptEdits", "outputStyle": "default", "extrasStatus": "supported" }, "models": [ { - "value": "claude-opus-4-8", - "displayName": "Claude Opus 4.8", + "value": "claude-opus-5-5", + "displayName": "Opus 5.5", "description": "Most capable; deep reasoning and long-horizon work.", "supportsEffort": true, "supportedEffortLevels": [ diff --git a/apps/desktop/src-tauri/src/contracts/generated.rs b/apps/desktop/src-tauri/src/contracts/generated.rs index 966e8f9a..c9e8fd0c 100644 --- a/apps/desktop/src-tauri/src/contracts/generated.rs +++ b/apps/desktop/src-tauri/src/contracts/generated.rs @@ -1153,14 +1153,14 @@ pub enum IssueVerdict { #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum KnownModel { - #[serde(rename = "claude-opus-4-8")] - ClaudeOpus48, - #[serde(rename = "claude-sonnet-4-6")] - ClaudeSonnet46, + #[serde(rename = "claude-opus-5-5")] + ClaudeOpus55, + #[serde(rename = "claude-sonnet-5")] + ClaudeSonnet5, #[serde(rename = "claude-haiku-4-5")] ClaudeHaiku45, - #[serde(rename = "claude-fable-5")] - ClaudeFable5, + #[serde(rename = "claude-fable-5-1")] + ClaudeFable51, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/apps/desktop/src-tauri/src/sidecar/models.rs b/apps/desktop/src-tauri/src/sidecar/models.rs index fc783b9b..a17d1a46 100644 --- a/apps/desktop/src-tauri/src/sidecar/models.rs +++ b/apps/desktop/src-tauri/src/sidecar/models.rs @@ -274,7 +274,7 @@ mod tests { let key = ModelCacheKey::new(CLAUDE_PROVIDER_ID, "n/a"); let models = fallback(CLAUDE_PROVIDER_ID, &key, &cache).expect("claude never errors"); assert_eq!(models.len(), 4); - assert_eq!(models[0].value, "claude-opus-4-8"); + assert_eq!(models[0].value, "claude-opus-5-5"); } #[test] diff --git a/apps/desktop/src-tauri/src/store/model_cache.rs b/apps/desktop/src-tauri/src/store/model_cache.rs index 860dbe7f..7248370f 100644 --- a/apps/desktop/src-tauri/src/store/model_cache.rs +++ b/apps/desktop/src-tauri/src/store/model_cache.rs @@ -170,10 +170,10 @@ impl ModelCache { /// `Record`. pub fn claude_static_catalog() -> Vec { const CATALOG: [KnownModel; 4] = [ - KnownModel::ClaudeOpus48, - KnownModel::ClaudeSonnet46, + KnownModel::ClaudeOpus55, + KnownModel::ClaudeSonnet5, KnownModel::ClaudeHaiku45, - KnownModel::ClaudeFable5, + KnownModel::ClaudeFable51, ]; CATALOG .into_iter() @@ -186,8 +186,8 @@ pub fn claude_static_catalog() -> Vec { display_name: display_name.to_string(), description: description.to_string(), // Every shipped Claude model honors the effort option; the SDK silently - // downgrades any level a model can't serve, so an over-generous premium - // set (xhigh/max on Opus/Fable) is safe. + // downgrades any level a model can't serve, so an over-generous set + // (xhigh/max on Opus/Sonnet/Fable, any level on Haiku) is safe. supports_effort: true, supported_effort_levels, } @@ -201,24 +201,24 @@ pub fn claude_static_catalog() -> Vec { fn claude_model_meta(model: KnownModel) -> (&'static str, &'static str, Vec) { use EffortLevel::{High, Low, Max, Medium, Xhigh}; match model { - KnownModel::ClaudeOpus48 => ( - "Claude Opus 4.8", - "Most capable — adaptive reasoning across long-horizon work.", + KnownModel::ClaudeOpus55 => ( + "Opus 5.5", + "Best for everyday, complex tasks.", vec![Low, Medium, High, Xhigh, Max], ), - KnownModel::ClaudeSonnet46 => ( - "Claude Sonnet 4.6", - "Balanced speed and depth.", - vec![Low, Medium, High], + KnownModel::ClaudeSonnet5 => ( + "Sonnet 5", + "Efficient for routine tasks.", + vec![Low, Medium, High, Xhigh, Max], ), KnownModel::ClaudeHaiku45 => ( - "Claude Haiku 4.5", - "Fastest and most lightweight.", + "Haiku 4.5", + "Fastest for quick answers.", vec![Low, Medium, High], ), - KnownModel::ClaudeFable5 => ( - "Claude Fable 5", - "Creative generalist.", + KnownModel::ClaudeFable51 => ( + "Fable 5.1", + "Most capable for your hardest and longest-running tasks.", vec![Low, Medium, High, Xhigh, Max], ), } @@ -314,21 +314,24 @@ mod tests { assert_eq!( ids, vec![ - "claude-opus-4-8", - "claude-sonnet-4-6", + "claude-opus-5-5", + "claude-sonnet-5", "claude-haiku-4-5", - "claude-fable-5", + "claude-fable-5-1", ] ); - // Every fallback descriptor is effort-capable with a non-empty level set, and the - // premium models unlock the higher levels. + // Every fallback descriptor is effort-capable with a non-empty level set; only + // Haiku stays on the base levels. assert!(catalog.iter().all(|m| m.supports_effort)); assert!(catalog .iter() .all(|m| !m.supported_effort_levels.is_empty())); let opus = &catalog[0]; assert!(opus.supported_effort_levels.contains(&EffortLevel::Max)); - assert!(!catalog[1] + assert!(catalog[1] + .supported_effort_levels + .contains(&EffortLevel::Max)); + assert!(!catalog[2] .supported_effort_levels .contains(&EffortLevel::Max)); } diff --git a/apps/desktop/src-tauri/src/store/settings/helpers.rs b/apps/desktop/src-tauri/src/store/settings/helpers.rs index d76fe5b5..3648eec8 100644 --- a/apps/desktop/src-tauri/src/store/settings/helpers.rs +++ b/apps/desktop/src-tauri/src/store/settings/helpers.rs @@ -52,7 +52,7 @@ pub fn known_model_id(model: KnownModel) -> String { Ok(serde_json::Value::String(s)) => s, // Unreachable for a string-valued enum; a defensive fallback keeps the // resolver total rather than panicking in the settings hot path. - _ => "claude-opus-4-8".to_string(), + _ => "claude-opus-5-5".to_string(), } } @@ -60,7 +60,7 @@ pub fn known_model_id(model: KnownModel) -> String { /// so a non-Claude provider never silently falls through to a Claude model: /// /// - `claude` → the first [`KnownModel`], single-sourced from the contract -/// (`claude-opus-4-8`). This is also the arm any UNKNOWN id lands in, because the +/// (`claude-opus-5-5`). This is also the arm any UNKNOWN id lands in, because the /// provider factory treats an unrecognized id as Claude (it falls back to the /// Claude backend with a loud warning — see `provider::factory`), so a Claude /// model is the correct default for it. @@ -78,7 +78,7 @@ pub fn default_model_id(provider: &str) -> String { "codex" => "gpt-5-codex".to_string(), // `claude` and any unrecognized id (the factory falls back to the Claude // backend) → the contract's first `KnownModel`. - _ => known_model_id(KnownModel::ClaudeOpus48), + _ => known_model_id(KnownModel::ClaudeOpus55), } } @@ -88,7 +88,8 @@ pub fn default_model_id(provider: &str) -> String { /// by family to the matching codegen'd [`KnownModel`] so legacy config still resolves /// to a valid model. An already-canonical (`claude-…`) or unrecognized id passes /// through unchanged (the SDK accepts any model string; a custom id is the user's own -/// choice). +/// choice). That includes a canonical id from an earlier model generation +/// (`claude-opus-4-8`): stored canonical ids are never rewritten. /// /// Only the FAMILY tokens are matched here; the long ids come from the contract via /// [`known_model_id`], so the catalog itself is single-sourced (this only fires for @@ -105,13 +106,13 @@ pub fn canonical_model_id(raw: &str) -> String { return raw.to_string(); } let known = if lower.contains("opus") { - KnownModel::ClaudeOpus48 + KnownModel::ClaudeOpus55 } else if lower.contains("sonnet") { - KnownModel::ClaudeSonnet46 + KnownModel::ClaudeSonnet5 } else if lower.contains("haiku") { KnownModel::ClaudeHaiku45 } else if lower.contains("fable") { - KnownModel::ClaudeFable5 + KnownModel::ClaudeFable51 } else { return raw.to_string(); }; diff --git a/apps/desktop/src-tauri/src/store/settings/mod.rs b/apps/desktop/src-tauri/src/store/settings/mod.rs index 01e7a5da..9fae3dd6 100644 --- a/apps/desktop/src-tauri/src/store/settings/mod.rs +++ b/apps/desktop/src-tauri/src/store/settings/mod.rs @@ -69,7 +69,7 @@ mod tests { fn defaults_are_the_contract_values() { let s = Settings::default(); // P0: defaults persist SDK long ids so a new task runs with a valid model. - assert_eq!(s.default_model, "claude-opus-4-8"); + assert_eq!(s.default_model, "claude-opus-5-5"); assert_eq!(s.max_concurrency, 3); // M4.7 §A1: bypass is the studio default. assert_eq!(s.permission_mode, "bypass"); @@ -118,10 +118,12 @@ mod tests { #[test] fn canonical_model_id_maps_legacy_short_ids() { // P0: a pre-P0 settings file holds short ids; they resolve to SDK long ids. - assert_eq!(canonical_model_id("opus-4.8"), "claude-opus-4-8"); - assert_eq!(canonical_model_id("sonnet-4.6"), "claude-sonnet-4-6"); + assert_eq!(canonical_model_id("opus-4.8"), "claude-opus-5-5"); + assert_eq!(canonical_model_id("sonnet-4.6"), "claude-sonnet-5"); assert_eq!(canonical_model_id("haiku-4.5"), "claude-haiku-4-5"); - // Already-canonical ids pass through unchanged. + // Already-canonical ids pass through unchanged, including one from an earlier + // model generation (stored canonical ids are never rewritten). + assert_eq!(canonical_model_id("claude-opus-5-5"), "claude-opus-5-5"); assert_eq!(canonical_model_id("claude-opus-4-8"), "claude-opus-4-8"); assert_eq!( canonical_model_id("claude-haiku-4-5-20251001"), @@ -138,20 +140,20 @@ mod tests { // `KnownModelSchema`. These assertions pin the codegen'd serde renames (a // contract rename reds this drift guard) and prove `canonical_model_id` / // `Settings::default` derive their long ids from the enum, not literals. - assert_eq!(known_model_id(KnownModel::ClaudeOpus48), "claude-opus-4-8"); - assert_eq!( - known_model_id(KnownModel::ClaudeSonnet46), - "claude-sonnet-4-6" - ); + assert_eq!(known_model_id(KnownModel::ClaudeOpus55), "claude-opus-5-5"); + assert_eq!(known_model_id(KnownModel::ClaudeSonnet5), "claude-sonnet-5"); assert_eq!( known_model_id(KnownModel::ClaudeHaiku45), "claude-haiku-4-5" ); - assert_eq!(known_model_id(KnownModel::ClaudeFable5), "claude-fable-5"); + assert_eq!( + known_model_id(KnownModel::ClaudeFable51), + "claude-fable-5-1" + ); // The Claude default is the first known model, single-sourced (not a // duplicated literal). `Settings::default` (a `claude`-provider install) // derives its `default_model` from the SAME provider-aware resolver. - assert_eq!(default_model_id("claude"), "claude-opus-4-8"); + assert_eq!(default_model_id("claude"), "claude-opus-5-5"); assert_eq!( Settings::default().default_model, default_model_id("claude") @@ -163,7 +165,7 @@ mod tests { // B2 (issue #79/#80): a non-Claude provider must NOT fall through to a Claude // model. Claude (and any unrecognized id, which the factory runs on the Claude // backend) → the contract default; Codex → the Codex SDK default. - assert_eq!(default_model_id("claude"), "claude-opus-4-8"); + assert_eq!(default_model_id("claude"), "claude-opus-5-5"); assert_eq!( default_model_id("codex"), "gpt-5-codex", @@ -171,7 +173,7 @@ mod tests { ); // An unknown provider is treated as Claude by the factory (fallback), so a // Claude default is correct for it. - assert_eq!(default_model_id("gemini"), "claude-opus-4-8"); + assert_eq!(default_model_id("gemini"), "claude-opus-5-5"); } #[test] @@ -188,7 +190,7 @@ mod tests { fn default_model_resolves_project_then_global_as_long_id() { let (store, _tmp) = temp_store(); // Global default is already a long id. - assert_eq!(store.default_model(None), "claude-opus-4-8"); + assert_eq!(store.default_model(None), "claude-opus-5-5"); assert_eq!(store.default_effort(None), "medium"); // A per-project override wins for that project; effort falls back to global. @@ -199,7 +201,7 @@ mod tests { ) .expect("update"); assert_eq!(store.default_model(Some("p1")), "claude-sonnet-4-6"); - assert_eq!(store.default_model(Some("other")), "claude-opus-4-8"); + assert_eq!(store.default_model(Some("other")), "claude-opus-5-5"); assert_eq!(store.default_effort(Some("p1")), "medium"); } @@ -216,7 +218,7 @@ mod tests { std::fs::write(dir.join("settings.json"), legacy).unwrap(); let store = SettingsStore::load_from(dir); - assert_eq!(store.default_model(None), "claude-opus-4-8"); + assert_eq!(store.default_model(None), "claude-opus-5-5"); } #[test] @@ -244,7 +246,7 @@ mod tests { let merged = store.update(patch).expect("update"); // Global default is unchanged; the override carries the project-scoped value. - assert_eq!(merged.default_model, "claude-opus-4-8"); + assert_eq!(merged.default_model, "claude-opus-5-5"); let ov = merged .project_overrides .get("proj-1") diff --git a/apps/desktop/src-tauri/src/store/task/create.rs b/apps/desktop/src-tauri/src/store/task/create.rs index c079c373..50ade82d 100644 --- a/apps/desktop/src-tauri/src/store/task/create.rs +++ b/apps/desktop/src-tauri/src/store/task/create.rs @@ -331,7 +331,7 @@ mod tests { assert!(task.max_turns.is_none()); assert!(task.max_budget_usd.is_none()); // The P0 model/effort defaults are still stamped concretely. - assert_eq!(task.model.as_deref(), Some("claude-opus-4-8")); + assert_eq!(task.model.as_deref(), Some("claude-opus-5-5")); assert_eq!(task.effort.as_deref(), Some("medium")); } diff --git a/apps/sidecar/src/wire-protocol.fixture.ts b/apps/sidecar/src/wire-protocol.fixture.ts index a50af3a0..cc94f6e7 100644 --- a/apps/sidecar/src/wire-protocol.fixture.ts +++ b/apps/sidecar/src/wire-protocol.fixture.ts @@ -63,7 +63,7 @@ class ScriptedManager implements SidecarManager { } const sessionId = this.nextSessionId++; - const model = command.model ?? 'claude-sonnet-4-6'; + const model = command.model ?? 'claude-sonnet-5'; // The wire command now carries the neutral `autonomy` vocabulary; the // `session-started` event still reports the resolved SDK permission mode, and // this scripted harness doesn't run a real provider, so it reports the studio's diff --git a/apps/web/src/components/board/NewTaskForm/NewTaskForm.stories.tsx b/apps/web/src/components/board/NewTaskForm/NewTaskForm.stories.tsx index 881e5ffa..503551af 100644 --- a/apps/web/src/components/board/NewTaskForm/NewTaskForm.stories.tsx +++ b/apps/web/src/components/board/NewTaskForm/NewTaskForm.stories.tsx @@ -118,7 +118,7 @@ export const CreatesWithOverrides: Story = { expect(args.onCreate).toHaveBeenCalledWith('Apply a migration', '', 'build', 'main', { permissionMode: 'plan', planFirst: true, - model: 'claude-sonnet-4-6', + model: 'claude-sonnet-5', providerId: 'claude', effort: 'high', maxTurns: null, diff --git a/apps/web/src/components/board/TaskCard/TaskCard.test.tsx b/apps/web/src/components/board/TaskCard/TaskCard.test.tsx index 17d0ee51..acac5885 100644 --- a/apps/web/src/components/board/TaskCard/TaskCard.test.tsx +++ b/apps/web/src/components/board/TaskCard/TaskCard.test.tsx @@ -54,7 +54,7 @@ test('resolves the model id to its display name', async () => { const screen = render(); // T13 badge honesty: `sonnet-4.6` resolves to the real catalog label (was the // hardcoded-wrong "Sonnet 4.8" before the honest resolver landed). - await expect.element(screen.getByText('Sonnet 4.6')).toBeInTheDocument(); + await expect.element(screen.getByText('Sonnet 5')).toBeInTheDocument(); }); test('disables the run action and shows a Blocked label when blocked', async () => { diff --git a/apps/web/src/components/settings/SettingsView/SettingsView.test.tsx b/apps/web/src/components/settings/SettingsView/SettingsView.test.tsx index 71452b38..9eb7b37a 100644 --- a/apps/web/src/components/settings/SettingsView/SettingsView.test.tsx +++ b/apps/web/src/components/settings/SettingsView/SettingsView.test.tsx @@ -15,7 +15,7 @@ test('updates a global setting with the SDK long id when scope is Global', async (model.element() as HTMLElement).focus(); await userEvent.keyboard('{ArrowDown}{ArrowDown}{Enter}'); // The persisted value is the SDK long id, not the short label. - expect(onUpdate).toHaveBeenCalledWith({ defaultModel: 'claude-sonnet-4-6' }); + expect(onUpdate).toHaveBeenCalledWith({ defaultModel: 'claude-sonnet-5' }); }); test('commits a Max-turns ceiling as a global guardrail patch', async () => { @@ -46,7 +46,7 @@ test('routes the patch to a project override under the project scope', async () await screen.getByRole('combobox', { name: 'Default model' }).click(); await screen.getByRole('option', { name: /Opus/ }).click(); expect(onUpdate).toHaveBeenCalledWith({ - defaultModel: 'claude-opus-4-8', + defaultModel: 'claude-opus-5-5', projectId: 'nightcore', }); }); diff --git a/apps/web/src/components/settings/settings-run-controls.tsx b/apps/web/src/components/settings/settings-run-controls.tsx index 4a82e2c9..6731f582 100644 --- a/apps/web/src/components/settings/settings-run-controls.tsx +++ b/apps/web/src/components/settings/settings-run-controls.tsx @@ -14,7 +14,7 @@ export const PROVIDERS: [value: string, label: string][] = [ ]; export function defaultModelForProvider(provider: string): string { - return provider === 'codex' ? 'gpt-5-codex' : 'claude-opus-4-8'; + return provider === 'codex' ? 'gpt-5-codex' : 'claude-opus-5-5'; } export function effortChoices(model: string): [value: string, label: string][] { diff --git a/apps/web/src/components/ui/ModelEffortPicker/ModelEffortPicker.hooks.ts b/apps/web/src/components/ui/ModelEffortPicker/ModelEffortPicker.hooks.ts index f512ddc8..dab8b8a3 100644 --- a/apps/web/src/components/ui/ModelEffortPicker/ModelEffortPicker.hooks.ts +++ b/apps/web/src/components/ui/ModelEffortPicker/ModelEffortPicker.hooks.ts @@ -2,7 +2,7 @@ import { modelOptionFor } from '@/lib/models'; /** Resolve which model option a stored value selects. The store may hold a - * canonical id (`claude-opus-4-8`) or a legacy short id (`opus-4.8`); both match + * canonical id (`claude-opus-5-5`) or a legacy short id (`opus-4.8`); both match * the option by family so the picker highlights the right chip. Returns `null` * (Inherit) when the value is null or unrecognized. Delegates the resolution to * the shared `modelOptionFor` so the family-match rule lives in one place. Pure. */ diff --git a/apps/web/src/components/ui/ModelEffortPicker/ModelEffortPicker.stories.tsx b/apps/web/src/components/ui/ModelEffortPicker/ModelEffortPicker.stories.tsx index 033ae4aa..2c314547 100644 --- a/apps/web/src/components/ui/ModelEffortPicker/ModelEffortPicker.stories.tsx +++ b/apps/web/src/components/ui/ModelEffortPicker/ModelEffortPicker.stories.tsx @@ -29,7 +29,7 @@ type Story = StoryObj; export const Inherit: Story = {}; -export const OpusHigh: Story = { args: { model: 'claude-opus-4-8', effort: 'high' } }; +export const OpusHigh: Story = { args: { model: 'claude-opus-5-5', effort: 'high' } }; export const Disabled: Story = { args: { model: 'claude-haiku-4-5', disabled: true } }; @@ -40,14 +40,14 @@ export const PicksModel: Story = { const canvas = within(canvasElement); await userEvent.click(canvas.getByRole('combobox', { name: /model/i })); await userEvent.click(canvas.getByRole('option', { name: /sonnet/i })); - await expect(args.onChangeModel).toHaveBeenCalledWith('claude-sonnet-4-6'); + await expect(args.onChangeModel).toHaveBeenCalledWith('claude-sonnet-5'); }, }; /** Play test: switching from Opus (effort=max) to Haiku — which can't honor `max` — * reconciles the pinned effort back to Inherit, delegated via `onChangeEffort`. */ export const SwitchingModelResetsUnsupportedEffort: Story = { - args: { model: 'claude-opus-4-8', effort: 'max' }, + args: { model: 'claude-opus-5-5', effort: 'max' }, play: async ({ args, canvasElement }) => { const canvas = within(canvasElement); await userEvent.click(canvas.getByRole('combobox', { name: /model/i })); diff --git a/apps/web/src/components/ui/ModelEffortPicker/ModelEffortPicker.test.tsx b/apps/web/src/components/ui/ModelEffortPicker/ModelEffortPicker.test.tsx index e73eaeaa..e01a3d72 100644 --- a/apps/web/src/components/ui/ModelEffortPicker/ModelEffortPicker.test.tsx +++ b/apps/web/src/components/ui/ModelEffortPicker/ModelEffortPicker.test.tsx @@ -31,7 +31,7 @@ test('delegates a model pick to onChangeModel with the canonical id', async () = ); await screen.getByRole('combobox', { name: /model/i }).click(); await screen.getByRole('option', { name: /sonnet/i }).click(); - expect(onChangeModel).toHaveBeenCalledWith('claude-sonnet-4-6'); + expect(onChangeModel).toHaveBeenCalledWith('claude-sonnet-5'); // The effort stayed Inherit, so the adapter fires only the changed callback. expect(onChangeEffort).not.toHaveBeenCalled(); }); @@ -41,7 +41,7 @@ test('switching to a model that cannot honor the pinned effort resets it to Inhe const onChangeEffort = vi.fn(); const screen = render( { expect(activeModelId(null)).toBeNull(); - expect(activeModelId('claude-opus-4-8')).toBe('claude-opus-4-8'); - expect(activeModelId('sonnet-4.6')).toBe('claude-sonnet-4-6'); + expect(activeModelId('claude-opus-5-5')).toBe('claude-opus-5-5'); + expect(activeModelId('sonnet-4.6')).toBe('claude-sonnet-5'); expect(activeModelId('haiku-4.5')).toBe('claude-haiku-4-5'); expect(activeModelId('gpt-9')).toBeNull(); }); diff --git a/apps/web/src/components/ui/ModelSelect/ModelSelect.stories.tsx b/apps/web/src/components/ui/ModelSelect/ModelSelect.stories.tsx index 0f5daeea..16a5baf4 100644 --- a/apps/web/src/components/ui/ModelSelect/ModelSelect.stories.tsx +++ b/apps/web/src/components/ui/ModelSelect/ModelSelect.stories.tsx @@ -104,7 +104,7 @@ export const InterleavedProviders: Story = { /** Opus is the premium tier — it unlocks the higher effort levels (Extra high / * Max) and shows the adaptive-reasoning hint. */ export const OpusSelected: Story = { - args: { value: { model: 'claude-opus-4-8', effort: 'high', providerId: 'claude' } }, + args: { value: { model: 'claude-opus-5-5', effort: 'high', providerId: 'claude' } }, }; /** The async seam is still resolving. */ @@ -137,7 +137,7 @@ export const PicksModel: Story = { await userEvent.click(canvas.getByRole('combobox', { name: /model/i })); await userEvent.click(canvas.getByRole('option', { name: /sonnet/i })); await expect(args.onChange).toHaveBeenCalledWith( - expect.objectContaining({ model: 'claude-sonnet-4-6' }), + expect.objectContaining({ model: 'claude-sonnet-5' }), ); }, }; @@ -155,7 +155,7 @@ export const PicksEffort: Story = { /** Play test: switching from Opus (effort=max) to Haiku — which can't honor `max` — * reconciles the pinned effort back to Inherit (max → null). */ export const ReconcilesEffortOnModelSwitch: Story = { - args: { value: { model: 'claude-opus-4-8', effort: 'max', providerId: 'claude' } }, + args: { value: { model: 'claude-opus-5-5', effort: 'max', providerId: 'claude' } }, play: async ({ args, canvasElement }) => { const canvas = within(canvasElement); await userEvent.click(canvas.getByRole('combobox', { name: /model/i })); diff --git a/apps/web/src/components/ui/ModelSelect/ModelSelect.test.tsx b/apps/web/src/components/ui/ModelSelect/ModelSelect.test.tsx index 902c6c32..6fdd940f 100644 --- a/apps/web/src/components/ui/ModelSelect/ModelSelect.test.tsx +++ b/apps/web/src/components/ui/ModelSelect/ModelSelect.test.tsx @@ -74,7 +74,7 @@ test('clicking a model fires onChange with the model in the value object', async await screen.getByRole('combobox', { name: /model/i }).click(); await screen.getByRole('option', { name: /sonnet/i }).click(); expect(onChange).toHaveBeenCalledWith( - expect.objectContaining({ model: 'claude-sonnet-4-6', providerId: 'claude' }), + expect.objectContaining({ model: 'claude-sonnet-5', providerId: 'claude' }), ); }); @@ -82,7 +82,7 @@ test('switching to a model that cannot honor the pinned effort resets it to Inhe const onChange = vi.fn(); const screen = render( , @@ -98,7 +98,7 @@ test('switching between models that both support the effort leaves it untouched' const onChange = vi.fn(); const screen = render( , @@ -106,7 +106,7 @@ test('switching between models that both support the effort leaves it untouched' await screen.getByRole('combobox', { name: /model/i }).click(); await screen.getByRole('option', { name: /sonnet/i }).click(); expect(onChange).toHaveBeenCalledWith( - expect.objectContaining({ model: 'claude-sonnet-4-6', effort: 'high' }), + expect.objectContaining({ model: 'claude-sonnet-5', effort: 'high' }), ); }); @@ -117,7 +117,7 @@ test('arrow-down + Enter picks the highlighted model with the keyboard', async ( ); await screen.getByRole('combobox', { name: /model/i }).click(); await userEvent.keyboard('{ArrowDown}{Enter}'); - expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ model: 'claude-opus-4-8' })); + expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ model: 'claude-opus-5-5' })); }); test('grouped keyboard-nav stays in sync across an interleaved provider boundary', async () => { @@ -143,7 +143,7 @@ test('grouped keyboard-nav stays in sync across an interleaved provider boundary // …and Enter picks that same row, not the source-order neighbor. await userEvent.keyboard('{Enter}'); expect(onChange).toHaveBeenCalledWith( - expect.objectContaining({ model: 'claude-sonnet-4-6', providerId: 'claude' }), + expect.objectContaining({ model: 'claude-sonnet-5', providerId: 'claude' }), ); }); @@ -160,7 +160,7 @@ test('Escape closes the listbox', async () => { test('the effort row surfaces the premium levels for Opus + the adaptive hint', async () => { const screen = render( , @@ -202,7 +202,7 @@ test('switching to a dynamic Codex model preserves a supported xhigh effort', as const onChange = vi.fn(); const screen = render( , @@ -218,7 +218,7 @@ test('picking an effort keeps the current model in the value object', async () = const onChange = vi.fn(); const screen = render( , @@ -226,7 +226,7 @@ test('picking an effort keeps the current model in the value object', async () = const efforts = screen.getByRole('radiogroup', { name: /reasoning effort/i }); await efforts.getByRole('radio', { name: /^high$/i }).click(); expect(onChange).toHaveBeenCalledWith( - expect.objectContaining({ model: 'claude-sonnet-4-6', effort: 'high' }), + expect.objectContaining({ model: 'claude-sonnet-5', effort: 'high' }), ); }); @@ -234,7 +234,7 @@ test('the effort radiogroup is a single tab stop that roves + selects with arrow const onChange = vi.fn(); const screen = render( , diff --git a/apps/web/src/components/ui/ModelSelectField/ModelSelectField.test.tsx b/apps/web/src/components/ui/ModelSelectField/ModelSelectField.test.tsx index a142d5d6..50953146 100644 --- a/apps/web/src/components/ui/ModelSelectField/ModelSelectField.test.tsx +++ b/apps/web/src/components/ui/ModelSelectField/ModelSelectField.test.tsx @@ -38,6 +38,6 @@ test('picking a model fires onChange with the model + resolved provider', async await screen.getByRole('combobox', { name: /model/i }).click(); await screen.getByRole('option', { name: /sonnet/i }).click(); expect(onChange).toHaveBeenCalledWith( - expect.objectContaining({ model: 'claude-sonnet-4-6', providerId: 'claude' }), + expect.objectContaining({ model: 'claude-sonnet-5', providerId: 'claude' }), ); }); diff --git a/apps/web/src/lib/models.test.tsx b/apps/web/src/lib/models.test.tsx index 8f2c8010..3752e5d1 100644 --- a/apps/web/src/lib/models.test.tsx +++ b/apps/web/src/lib/models.test.tsx @@ -12,24 +12,33 @@ import { test('MODEL_OPTIONS carry tier + effort metadata for every surfaced model', () => { expect(MODEL_OPTIONS.map((m) => m.id)).toEqual([ - 'claude-opus-4-8', - 'claude-sonnet-4-6', + 'claude-opus-5-5', + 'claude-sonnet-5', 'claude-haiku-4-5', ]); - const opus = MODEL_OPTIONS.find((m) => m.id === 'claude-opus-4-8'); + const opus = MODEL_OPTIONS.find((m) => m.id === 'claude-opus-5-5'); expect(opus?.tier).toBe('Premium'); expect(opus?.adaptive).toBe(true); }); test('modelOptionFor resolves canonical, legacy, inherit, and unknown ids', () => { expect(modelOptionFor(null)).toBeNull(); - expect(modelOptionFor('claude-opus-4-8')?.id).toBe('claude-opus-4-8'); + expect(modelOptionFor('claude-opus-5-5')?.id).toBe('claude-opus-5-5'); expect(modelOptionFor('gpt-5-codex')?.id).toBe('gpt-5-codex'); - expect(modelOptionFor('sonnet-4.6')?.id).toBe('claude-sonnet-4-6'); + expect(modelOptionFor('sonnet-4.6')?.id).toBe('claude-sonnet-5'); expect(modelOptionFor('haiku-4.5')?.id).toBe('claude-haiku-4-5'); expect(modelOptionFor('gpt-9')).toBeNull(); }); +test('modelOptionFor resolves an earlier-generation canonical id by family', () => { + // A stored `claude-opus-4-8` is never rewritten, so it must still land on the + // current option of its family. + expect(modelOptionFor('claude-opus-4-8')?.id).toBe('claude-opus-5-5'); + expect(modelOptionFor('claude-sonnet-4-6')?.id).toBe('claude-sonnet-5'); + expect(modelOptionFor('opus-4.8')?.id).toBe('claude-opus-5-5'); + expect(modelLabel('claude-opus-4-8')).toBe('Opus 5.5'); +}); + test('codexStaticModelDescriptors exposes the SDK-backed Codex default', () => { expect(codexStaticModelDescriptors()).toEqual([ expect.objectContaining({ @@ -40,10 +49,13 @@ test('codexStaticModelDescriptors exposes the SDK-backed Codex default', () => { ]); }); -test('effortOptionsForModel unlocks xhigh/max only for the premium model', () => { - const opus = effortOptionsForModel('claude-opus-4-8').map((o) => o.id); +test('effortOptionsForModel unlocks xhigh/max for every model but Haiku', () => { + const opus = effortOptionsForModel('claude-opus-5-5').map((o) => o.id); expect(opus).toEqual(['low', 'medium', 'high', 'xhigh', 'max', 'none']); + const sonnet = effortOptionsForModel('claude-sonnet-5').map((o) => o.id); + expect(sonnet).toEqual(['low', 'medium', 'high', 'xhigh', 'max', 'none']); + const haiku = effortOptionsForModel('claude-haiku-4-5').map((o) => o.id); expect(haiku).toEqual(['low', 'medium', 'high', 'none']); @@ -57,14 +69,14 @@ test('effortOptionsForModel falls back to the base set for Inherit / unknown', ( }); test('every effort option carries a description', () => { - for (const option of effortOptionsForModel('claude-opus-4-8')) { + for (const option of effortOptionsForModel('claude-opus-5-5')) { expect(option.description.length).toBeGreaterThan(0); } }); test('isAdaptiveModel is true only for the adaptive models', () => { - expect(isAdaptiveModel('claude-opus-4-8')).toBe(true); - expect(isAdaptiveModel('claude-sonnet-4-6')).toBe(false); + expect(isAdaptiveModel('claude-opus-5-5')).toBe(true); + expect(isAdaptiveModel('claude-sonnet-5')).toBe(true); expect(isAdaptiveModel('claude-haiku-4-5')).toBe(false); expect(isAdaptiveModel(null)).toBe(false); }); @@ -75,7 +87,7 @@ test('isEffortSupported treats Inherit and none as always valid', () => { }); test('isEffortSupported rejects a premium-only effort once the model changes', () => { - expect(isEffortSupported('claude-opus-4-8', 'max')).toBe(true); + expect(isEffortSupported('claude-opus-5-5', 'max')).toBe(true); // 'max' is not offered by Haiku — switching models must reconcile it. expect(isEffortSupported('claude-haiku-4-5', 'max')).toBe(false); expect(isEffortSupported('claude-haiku-4-5', 'high')).toBe(true); @@ -83,7 +95,7 @@ test('isEffortSupported rejects a premium-only effort once the model changes', ( test('modelLabel resolves Inherit, known, Codex, and unknown ids', () => { expect(modelLabel(null)).toBe('the inherited model'); - expect(modelLabel('claude-opus-4-8')).toBe('Opus 4.8'); + expect(modelLabel('claude-opus-5-5')).toBe('Opus 5.5'); expect(modelLabel('gpt-5-codex')).toBe('GPT-5 Codex'); expect(modelLabel('some-unknown-model')).toBe('some-unknown-model'); }); diff --git a/apps/web/src/lib/models.ts b/apps/web/src/lib/models.ts index 6b29652a..a29f7413 100644 --- a/apps/web/src/lib/models.ts +++ b/apps/web/src/lib/models.ts @@ -22,7 +22,7 @@ export type ModelTier = 'Speed' | 'Balanced' | 'Premium'; /** A selectable model in the per-task picker / Settings default. */ export interface ModelOption { - /** The model id sent on the wire (a contract `KnownModel`, e.g. `claude-opus-4-8`). */ + /** The model id sent on the wire (a contract `KnownModel`, e.g. `claude-opus-5-5`). */ id: string; label: string; /** Capability/cost tier badge. */ @@ -36,7 +36,7 @@ export interface ModelOption { * `EffortLevel`; mirrors `ModelDescriptor.supportedEffortLevels`). */ supportedEfforts: EffortLevel[]; /** Whether the model decides its reasoning budget adaptively when no effort is - * pinned (Inherit). True for Opus 4.8 / Fable 5. */ + * pinned (Inherit). True for Opus 5.5 / Sonnet 5 / Fable 5.1. */ adaptive: boolean; } @@ -52,38 +52,38 @@ interface ModelMeta { /** Display + capability metadata for the known Claude models, keyed off the * contract `KnownModelSchema` so the *value* can't drift — only the metadata. * Exhaustive over `KnownModel`: adding a model to the contract enum without an - * entry here is a compile error. The premium tier unlocks the higher effort - * levels (`xhigh`/`max`); the SDK silently downgrades any level a model can't - * honor, so an over-generous set is safe. */ + * entry here is a compile error. Every model except Haiku surfaces the higher + * effort levels (`xhigh`/`max`); the SDK silently downgrades any level a model + * can't honor, so an over-generous set is safe. */ const MODEL_META: Record = { - 'claude-opus-4-8': { - label: 'Opus 4.8', + 'claude-opus-5-5': { + label: 'Opus 5.5', tier: 'Premium', - description: 'Most capable — adaptive reasoning', + description: 'Best for everyday, complex tasks', supportsEffort: true, supportedEfforts: ['low', 'medium', 'high', 'xhigh', 'max'], adaptive: true, }, - 'claude-sonnet-4-6': { - label: 'Sonnet 4.6', + 'claude-sonnet-5': { + label: 'Sonnet 5', tier: 'Balanced', - description: 'Balanced speed and depth', + description: 'Efficient for routine tasks', supportsEffort: true, - supportedEfforts: ['low', 'medium', 'high'], - adaptive: false, + supportedEfforts: ['low', 'medium', 'high', 'xhigh', 'max'], + adaptive: true, }, 'claude-haiku-4-5': { label: 'Haiku 4.5', tier: 'Speed', - description: 'Fastest, lightweight', + description: 'Fastest for quick answers', supportsEffort: true, supportedEfforts: ['low', 'medium', 'high'], adaptive: false, }, - 'claude-fable-5': { - label: 'Fable 5', + 'claude-fable-5-1': { + label: 'Fable 5.1', tier: 'Premium', - description: 'Creative generalist', + description: 'Most capable for your hardest and longest-running tasks', supportsEffort: true, supportedEfforts: ['low', 'medium', 'high', 'xhigh', 'max'], adaptive: true, @@ -93,8 +93,8 @@ const MODEL_META: Record = { /** The known Claude models for browser-preview fallback, in display order. The * desktop picker uses the live `listModels()` bridge instead. */ const WEB_MODELS: readonly KnownModel[] = [ - 'claude-opus-4-8', - 'claude-sonnet-4-6', + 'claude-opus-5-5', + 'claude-sonnet-5', 'claude-haiku-4-5', ]; @@ -166,8 +166,8 @@ const EFFORT_META: Record = const NONE_EFFORT: EffortOption = { id: 'none', label: 'None', description: 'Skip extended thinking' }; /** The effort levels surfaced when no specific model context is known (Inherit, or - * an unrecognized model). The premium higher levels (`xhigh`/`max`) only appear - * for models that support them. */ + * an unrecognized model). The higher levels (`xhigh`/`max`) only appear for + * models that support them. */ const BASE_EFFORTS: readonly EffortLevel[] = ['low', 'medium', 'high']; /** The default effort levels the picker surfaces (Inherit context), plus the @@ -178,8 +178,9 @@ export const EFFORT_OPTIONS: EffortOption[] = [ NONE_EFFORT, ]; -/** Resolve a stored model value to its `ModelOption`. The value may be a canonical - * id (`claude-opus-4-8`) or a legacy short id (`opus-4.8`); both match by family. +/** Resolve a stored model value to its `ModelOption`. The value may be a current + * canonical id (`claude-opus-5-5`), a canonical id from an earlier generation + * (`claude-opus-4-8`) or a legacy short id (`opus-4.8`); all match by family. * Returns `null` for Inherit (`null`) or an unrecognized id. Pure. */ export function modelOptionFor(model: string | null): ModelOption | null { if (model === null) return null; @@ -208,7 +209,7 @@ export function effortOptionsForLevels(levels: readonly EffortLevel[]): EffortOp } /** Whether selecting `model` leaves reasoning adaptive when effort is Inherit - * (true for Opus 4.8 / Fable 5). Pure. */ + * (true for Opus 5.5 / Sonnet 5 / Fable 5.1). Pure. */ export function isAdaptiveModel(model: string | null): boolean { return modelOptionFor(model)?.adaptive ?? false; } diff --git a/bun.lock b/bun.lock index a94be4ac..6f25bf5e 100644 --- a/bun.lock +++ b/bun.lock @@ -116,7 +116,7 @@ "name": "@nightcore/engine", "version": "0.0.0", "dependencies": { - "@anthropic-ai/claude-agent-sdk": "0.3.190", + "@anthropic-ai/claude-agent-sdk": "0.3.282", "@nightcore/contracts": "workspace:*", "@nightcore/shared": "workspace:*", "@nightcore/storage": "workspace:*", @@ -197,23 +197,23 @@ "packages": { "@adobe/css-tools": ["@adobe/css-tools@4.5.0", "", {}, "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q=="], - "@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.3.190", "", { "optionalDependencies": { "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.190", "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.190", "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.190", "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.190", "@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.190", "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.190", "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.190", "@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.190" }, "peerDependencies": { "@anthropic-ai/sdk": ">=0.93.0", "@modelcontextprotocol/sdk": "^1.29.0", "zod": "^4.0.0" } }, "sha512-d4morVtH9eBUsk5BXmhcX2tnEums/RwbM9LrhZB3VCDAYNiDTEW4IejY5o572//7/0qN111l6fmPr5b5cmrjsQ=="], + "@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.3.282", "", { "optionalDependencies": { "@anthropic-ai/claude-agent-sdk-darwin-arm64": "0.3.282", "@anthropic-ai/claude-agent-sdk-darwin-x64": "0.3.282", "@anthropic-ai/claude-agent-sdk-linux-arm64": "0.3.282", "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": "0.3.282", "@anthropic-ai/claude-agent-sdk-linux-x64": "0.3.282", "@anthropic-ai/claude-agent-sdk-linux-x64-musl": "0.3.282", "@anthropic-ai/claude-agent-sdk-win32-arm64": "0.3.282", "@anthropic-ai/claude-agent-sdk-win32-x64": "0.3.282" }, "peerDependencies": { "@anthropic-ai/sdk": ">=0.93.0", "@modelcontextprotocol/sdk": "^1.29.0", "zod": "^4.0.0" } }, "sha512-6UAerS1udzndLEx+0XW3gQWiICgfu/a+2fx/aLY3gUy+1JUQESbwYkhR40+D6d+yjueCslkLmvkPlZQFZDph6A=="], - "@anthropic-ai/claude-agent-sdk-darwin-arm64": ["@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.190", "", { "os": "darwin", "cpu": "arm64" }, "sha512-131dhlg7qGXgVsnKi0pTPMK4lQOunF4Ewx9AT4sP24LXc0vxYZ8r7d6Vy5ba/jCp6NqkJUNsMNYwNfvIaGdv4Q=="], + "@anthropic-ai/claude-agent-sdk-darwin-arm64": ["@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.282", "", { "os": "darwin", "cpu": "arm64" }, "sha512-NwvIIpEOORp16K7NgjWE2lt2Y+VmddvkkRoOpyKXz2H2bRixa5ToV+byp2gUaKVsCixBuAiQ2lL9e79gy5lbNQ=="], - "@anthropic-ai/claude-agent-sdk-darwin-x64": ["@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.190", "", { "os": "darwin", "cpu": "x64" }, "sha512-IU8RDROU69Hd81TgH8VAjXMq/pvTreuT0Fqtjut2u5p9zYB7m0q1uWZyEWJfte6aCTA6aQLcFp2l1RRpRfOYxg=="], + "@anthropic-ai/claude-agent-sdk-darwin-x64": ["@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.282", "", { "os": "darwin", "cpu": "x64" }, "sha512-MjVmeojCOIAdqt90j1B9ENfCwLIzMhSyzQJd6+rBn8KDKklduPLbs//rVhvIwR+MJBF67PDkczOb4VXiYPJRxA=="], - "@anthropic-ai/claude-agent-sdk-linux-arm64": ["@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.190", "", { "os": "linux", "cpu": "arm64" }, "sha512-MhBNdygHatPnfw2zpfx70A535pDk2mZQwkFeL0kSfiF0pAgJWyuyYCAI7NUH61NtT3R2kO1kxGGWxgAuh0Ncag=="], + "@anthropic-ai/claude-agent-sdk-linux-arm64": ["@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.282", "", { "os": "linux", "cpu": "arm64" }, "sha512-hL/G7pCQnS9m8RIqxCpljaQsvITKRYdqoNeDsJGIqaFpd79redTwlovRUBtFMeDAtASwtha1wLyh24v+gUb58A=="], - "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": ["@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.190", "", { "os": "linux", "cpu": "arm64" }, "sha512-/fHLhroUkjgqjbU4vQtql9nVNf9qyYLDuXAUViA3VCCYY78mgfQPj0k3p0dVre38Cr5AuzWVS8tLxJm3Yg1o4g=="], + "@anthropic-ai/claude-agent-sdk-linux-arm64-musl": ["@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.282", "", { "os": "linux", "cpu": "arm64" }, "sha512-2/3EtJKfNQfjn1MtTRh7EpcqlAI/UCEaiHSdlPH+AZPDnQXiHLW6JITJNdsc3MMuwjnbsiL4NJBnDr35qkIb9A=="], - "@anthropic-ai/claude-agent-sdk-linux-x64": ["@anthropic-ai/claude-agent-sdk-linux-x64@0.3.190", "", { "os": "linux", "cpu": "x64" }, "sha512-iLT6Z2ivtCSu722H51wBwEjTfRrymrVZjV6puhkBHAFQsW7U8ya0gKzELlun/nj8gYLT38eoUanIhdSDdyWoAA=="], + "@anthropic-ai/claude-agent-sdk-linux-x64": ["@anthropic-ai/claude-agent-sdk-linux-x64@0.3.282", "", { "os": "linux", "cpu": "x64" }, "sha512-NxinW4k1Xh3xTCPKpsw+XXV+O3zwLtLQeJnFucc/LKQQ3p4pXJ7/hW/DIiYdmhgIIaMFkP+2xP8G5+Y8swxB2w=="], - "@anthropic-ai/claude-agent-sdk-linux-x64-musl": ["@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.190", "", { "os": "linux", "cpu": "x64" }, "sha512-ixcKEYuAaK6Fx2a9hjNRlCqTu9uc2SgZZr/f5Uzy4xFSHZNVyc+xn0c+JlCV6ZwiVs8FRlxGuFGoOD0yN3X6Zg=="], + "@anthropic-ai/claude-agent-sdk-linux-x64-musl": ["@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.282", "", { "os": "linux", "cpu": "x64" }, "sha512-Q96hallUSHCCvmsMC08aO3YtivTbRIzzrgfdVjJAcOrQ+6tSwN0Ni4yMEwhWRnq3DVZlNDYIssd1/k7H4lS7Og=="], - "@anthropic-ai/claude-agent-sdk-win32-arm64": ["@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.190", "", { "os": "win32", "cpu": "arm64" }, "sha512-CcMt72vqTWBV3y+E0szfcOcUHcY8qAG+SnjUwYMHNMlzBrUL4cpiiGO6qKYxo4kkdoSWaHZVxVkPtOldrPiz4A=="], + "@anthropic-ai/claude-agent-sdk-win32-arm64": ["@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.282", "", { "os": "win32", "cpu": "arm64" }, "sha512-4v9guW9NSDJj+cTP+aND3Wz6gAFV5OU/VP4CFjc79KgdwPpAtwm2fw5BKhneAuoUc21KU9gHWBtLLOdBvONoZw=="], - "@anthropic-ai/claude-agent-sdk-win32-x64": ["@anthropic-ai/claude-agent-sdk-win32-x64@0.3.190", "", { "os": "win32", "cpu": "x64" }, "sha512-3tkc+zizxgl+q+keZ52OJ3flUq2i4TUUgfkXJaozbylHglW94qW/j0axmEp+cREGiiV5jtJGxLMziHpr/dS6TQ=="], + "@anthropic-ai/claude-agent-sdk-win32-x64": ["@anthropic-ai/claude-agent-sdk-win32-x64@0.3.282", "", { "os": "win32", "cpu": "x64" }, "sha512-oOMNLTcLGqPBHvrTVRXNL6q8LK7DL3blkuBBrAgsmkgr+pxi0E74eQEk7XQ5dYwhETa2/ZfNIwNevcYB7LAVtg=="], "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.131.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1", "standardwebhooks": "^1.0.0" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-9+PepoU7qVMmM70jSahdnfcMHAvAnzhMOLv2W9WjRLO+OG/MAiBETkwKZqv7KIJ1Cj5h8eZ019bHzZaq+Mbepw=="], diff --git a/packages/config/src/index.test.ts b/packages/config/src/index.test.ts index 93da7a19..f5d5862d 100644 --- a/packages/config/src/index.test.ts +++ b/packages/config/src/index.test.ts @@ -40,7 +40,7 @@ afterEach(() => { describe('resolveConfig precedence', () => { test('falls back to defaults when no config files exist', () => { const config = resolveConfig({ home, cwd: project }); - expect(config.model).toBe('claude-opus-4-8'); + expect(config.model).toBe('claude-opus-5-5'); expect(config.logLevel).toBe('info'); expect(config.permissions).toEqual({ allow: [], deny: [], mode: 'default' }); expect(config.paths.home).toBe(home); @@ -201,7 +201,7 @@ describe('resolveConfig degrades, does not throw', () => { test('ignores a malformed (non-JSON) config file', () => { writeProjectConfig(project, '{ this is not json'); const config = resolveConfig({ home, cwd: project }); - expect(config.model).toBe('claude-opus-4-8'); + expect(config.model).toBe('claude-opus-5-5'); }); test('ignores a structurally invalid config file', () => { diff --git a/packages/contracts/src/config.test.ts b/packages/contracts/src/config.test.ts index e9af8632..5969dbd7 100644 --- a/packages/contracts/src/config.test.ts +++ b/packages/contracts/src/config.test.ts @@ -27,7 +27,7 @@ describe('ConfigSchema defaults', () => { const parsed = ConfigSchema.parse({ paths: { home: '/home/.nightcore', sessions: '/home/.nightcore/sessions' }, }); - expect(parsed.model).toBe('claude-opus-4-8'); + expect(parsed.model).toBe('claude-opus-5-5'); expect(parsed.logLevel).toBe('info'); expect(parsed.permissions).toEqual({ allow: [], diff --git a/packages/contracts/src/config.ts b/packages/contracts/src/config.ts index 0af257a0..bfa302cc 100644 --- a/packages/contracts/src/config.ts +++ b/packages/contracts/src/config.ts @@ -46,10 +46,10 @@ export type TaskKind = z.infer; * harness offers these as the curated default set. */ export const KnownModelSchema = z.enum([ - 'claude-opus-4-8', - 'claude-sonnet-4-6', + 'claude-opus-5-5', + 'claude-sonnet-5', 'claude-haiku-4-5', - 'claude-fable-5', + 'claude-fable-5-1', ]); export type KnownModel = z.infer; @@ -130,7 +130,7 @@ export const ConfigSchema = z.object({ * override, so this stays the single engine-side selection input. */ provider: ProviderIdSchema.default('claude'), /** Default model for new sessions. Free string to allow any SDK-supported id. */ - model: z.string().default('claude-opus-4-8'), + model: z.string().default('claude-opus-5-5'), /** Default reasoning effort for new sessions. Omitted = let the model decide * (adaptive). The SDK downgrades silently if the model can't honor it. */ effort: EffortLevelSchema.optional(), diff --git a/packages/contracts/src/models.ts b/packages/contracts/src/models.ts index 5716430a..59554495 100644 --- a/packages/contracts/src/models.ts +++ b/packages/contracts/src/models.ts @@ -14,7 +14,7 @@ import { EffortLevelSchema } from './config.js'; export const ModelDescriptorSchema = z.object({ /** Provider that owns this model (`claude`, `codex`, …). */ providerId: z.string().optional(), - /** Model id passed to `setModel()` / the SDK (e.g. `claude-opus-4-8`). */ + /** Model id passed to `setModel()` / the SDK (e.g. `claude-opus-5-5`). */ value: z.string(), /** Human-readable name for the picker. */ displayName: z.string(), diff --git a/packages/engine/package.json b/packages/engine/package.json index 1ba627a8..82cef7f1 100644 --- a/packages/engine/package.json +++ b/packages/engine/package.json @@ -12,7 +12,7 @@ "main": "./dist/index.js", "types": "./dist/index.d.ts", "dependencies": { - "@anthropic-ai/claude-agent-sdk": "0.3.190", + "@anthropic-ai/claude-agent-sdk": "0.3.282", "@nightcore/contracts": "workspace:*", "@nightcore/shared": "workspace:*", "@nightcore/storage": "workspace:*", diff --git a/packages/engine/src/providers/claude/probe-retry.ts b/packages/engine/src/providers/claude/probe-retry.ts index 78f79894..a4e54859 100644 --- a/packages/engine/src/providers/claude/probe-retry.ts +++ b/packages/engine/src/providers/claude/probe-retry.ts @@ -38,9 +38,10 @@ const PROBE_BACKOFF: BackoffOptions = { jitter: true, }; -/** The minimum a transient probe handle must expose for teardown. */ +/** The minimum a transient probe handle must expose for teardown. The result is + * never read: the SDK's `interrupt()` resolves to a control response. */ export interface ProbeHandle { - interrupt(): Promise; + interrupt(): Promise; } export interface TransientProbeContext { diff --git a/tools/codegen/gen-rust-contracts.ts b/tools/codegen/gen-rust-contracts.ts index 7184cc2a..4f079584 100644 --- a/tools/codegen/gen-rust-contracts.ts +++ b/tools/codegen/gen-rust-contracts.ts @@ -485,7 +485,7 @@ const ENUM_NAMES: Record = { // reachable — it is force-emitted below so the Rust settings layer consumes the // codegen'd `KnownModel` (the canonical long ids + the default) instead of // re-listing the family strings, single-sourcing the catalog to the contract. - 'claude-opus-4-8|claude-sonnet-4-6|claude-haiku-4-5|claude-fable-5': 'KnownModel', + 'claude-opus-5-5|claude-sonnet-5|claude-haiku-4-5|claude-fable-5-1': 'KnownModel', }; /** Guard the {@link ENUM_NAMES} registry: it must be an INJECTION — each canonical @@ -1143,7 +1143,7 @@ const COMMAND_INPUTS: Record = { type: 'start-session', prompt: 'do the thing', providerId: 'claude', - model: 'claude-opus-4-8', + model: 'claude-opus-5-5', effort: 'high', autonomy: 'auto-accept', cwd: '/tmp/work', @@ -1189,7 +1189,7 @@ const COMMAND_INPUTS: Record = { }, 'send-input': { type: 'send-input', sessionId: 1, text: 'more input' }, interrupt: { type: 'interrupt', sessionId: 2 }, - 'set-model': { type: 'set-model', sessionId: 3, model: 'claude-sonnet-4-6' }, + 'set-model': { type: 'set-model', sessionId: 3, model: 'claude-sonnet-5' }, 'set-autonomy': { type: 'set-autonomy', sessionId: 4, @@ -1217,7 +1217,7 @@ const COMMAND_INPUTS: Record = { scope: 'diff', changedFiles: ['src/handler.ts', 'src/store.ts'], categories: ['architecture', 'bugs', 'security'], - model: 'claude-opus-4-8', + model: 'claude-opus-5-5', effort: 'high', maxConcurrency: 3, maxTurnsPerCategory: 40, @@ -1232,7 +1232,7 @@ const COMMAND_INPUTS: Record = { runId: 'run-h1', projectPath: '/proj', categories: ['architecture', 'folder-structure', 'imports-boundaries'], - model: 'claude-opus-4-8', + model: 'claude-opus-5-5', effort: 'high', maxConcurrency: 3, maxTurnsPerCategory: 40, @@ -1247,7 +1247,7 @@ const COMMAND_INPUTS: Record = { runId: 'run-s1', projectPath: '/proj', dimensions: ['architecture', 'tests', 'security'], - model: 'claude-opus-4-8', + model: 'claude-opus-5-5', effort: 'high', maxConcurrency: 3, maxTurnsPerDimension: 40, @@ -1262,7 +1262,7 @@ const COMMAND_INPUTS: Record = { diff: 'diff --git a/src/handler.ts b/src/handler.ts\n@@ -1,3 +1,4 @@\n-old\n+new line\n', changedFiles: ['src/handler.ts', 'src/store.ts'], lenses: ['security', 'logic', 'structure', 'tests', 'contracts'], - model: 'claude-opus-4-8', + model: 'claude-opus-5-5', effort: 'high', maxConcurrency: 3, // `{}` exercises the zod defaults so the emitted fixture carries all three deep @@ -1296,7 +1296,7 @@ const COMMAND_INPUTS: Record = { diff: 'diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx\n@@ -1,3 +1,5 @@\n+ if (!project) return ;\n', }, ], - model: 'claude-opus-4-8', + model: 'claude-opus-5-5', effort: 'high', maxTurns: 40, maxBudgetUsd: 2, @@ -1383,7 +1383,7 @@ const QUERY_INPUTS: Record = { ], maxTurns: 12, maxBudgetUsd: 2, - model: 'claude-opus-4-8', + model: 'claude-opus-5-5', }, }; @@ -1393,14 +1393,14 @@ const EVENT_INPUTS: Record = { type: 'session-started', sessionId: 0, prompt: 'hi', - model: 'claude-opus-4-8', + model: 'claude-opus-5-5', permissionMode: 'default', }, 'session-ready': { type: 'session-ready', sessionId: 0, sdkSessionId: 'sdk-uuid', - model: 'claude-opus-4-8', + model: 'claude-opus-5-5', tools: ['Read', 'Bash'], slashCommands: ['/init'], skills: ['add-feature'], @@ -1502,7 +1502,7 @@ const EVENT_INPUTS: Record = { runId: 'run-1', scope: 'repo', categories: ['architecture', 'bugs', 'security'], - model: 'claude-opus-4-8', + model: 'claude-opus-5-5', }, 'analysis-category-started': { type: 'analysis-category-started', @@ -1604,7 +1604,7 @@ const EVENT_INPUTS: Record = { type: 'harness-scan-started', runId: 'run-h1', categories: ['architecture', 'folder-structure', 'imports-boundaries'], - model: 'claude-opus-4-8', + model: 'claude-opus-5-5', }, 'harness-profile-ready': { type: 'harness-profile-ready', @@ -1846,7 +1846,7 @@ const EVENT_INPUTS: Record = { type: 'scorecard-started', runId: 'run-s1', dimensions: ['architecture', 'tests', 'security'], - model: 'claude-opus-4-8', + model: 'claude-opus-5-5', }, 'scorecard-dimension-started': { type: 'scorecard-dimension-started', @@ -1923,7 +1923,7 @@ const EVENT_INPUTS: Record = { type: 'pr-review-started', runId: 'run-pr1', lenses: ['security', 'logic', 'structure', 'tests', 'contracts'], - model: 'claude-opus-4-8', + model: 'claude-opus-5-5', }, 'pr-review-lens-started': { type: 'pr-review-lens-started', @@ -2022,7 +2022,7 @@ const EVENT_INPUTS: Record = { type: 'issue-validation-started', runId: 'run-iv1', issueNumber: 128, - model: 'claude-opus-4-8', + model: 'claude-opus-5-5', }, 'issue-validation-progress': { type: 'issue-validation-progress', @@ -2131,15 +2131,15 @@ const EVENT_INPUTS: Record = { status: 'supported', subagents: [{ name: 'Explore', description: 'read-only search' }], }, - model: 'claude-opus-4-8', + model: 'claude-opus-5-5', permissionMode: 'acceptEdits', outputStyle: 'default', extrasStatus: 'supported', }, models: [ { - value: 'claude-opus-4-8', - displayName: 'Claude Opus 4.8', + value: 'claude-opus-5-5', + displayName: 'Opus 5.5', description: 'Most capable; deep reasoning and long-horizon work.', supportsEffort: true, supportedEffortLevels: ['low', 'medium', 'high'],