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'],