Skip to content

Commit 26dd5b6

Browse files
committed
Merge remote-tracking branch 'refs/remotes/origin/main' into dohooo/team-cloud-sandbox
2 parents 5983254 + 1d52da9 commit 26dd5b6

18 files changed

Lines changed: 309 additions & 82 deletions

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## 0.39.1
4+
5+
### Patch Changes
6+
7+
- [#848](https://github.com/dohooo/helmor/pull/848) [`eac697c`](https://github.com/dohooo/helmor/commit/eac697c62d0bbf434433421db09d63ac11f25893) Thanks [@natllian](https://github.com/natllian)! - Improve Claude model handling:
8+
- The default Claude model is pinned to Opus 4.8 (1M context) so it can't silently switch to a different model when the bundled Claude CLI updates; existing sessions and settings keep the same model.
9+
- Terminal mode is now limited to official Claude models — custom (BYOK) Claude models run in GUI mode instead, since the terminal can't carry their custom provider settings.
10+
311
## 0.39.0
412

513
### Minor Changes

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "helmor",
33
"description": "The local-first IDE for coding agent orchestration.",
44
"private": true,
5-
"version": "0.39.0",
5+
"version": "0.39.1",
66
"packageManager": "bun@1.3.2",
77
"type": "module",
88
"workspaces": [

sidecar/src/model-catalog.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,25 @@ const MODEL_CATALOG: Record<Provider, readonly ProviderModelInfo[]> = {
1111
claude: [
1212
// Fable 5 leads the list as the most capable pick, but it burns limits
1313
// ~2x faster than Opus — `useEnsureDefaultModel` pins the app default
14-
// to the `default` (Opus) entry below, NOT to the first entry. No fast
14+
// to the Opus 4.8 entry below, NOT to the first entry. No fast
1515
// mode (Opus 4.6+ only).
1616
{
1717
id: "claude-fable-5[1m]",
1818
label: "Fable 5 1M",
1919
cliModel: "claude-fable-5[1m]",
2020
effortLevels: ["low", "medium", "high", "xhigh", "max"],
2121
},
22-
// `default` resolves to the newest Opus the bundled claude-code knows
23-
// about — in 2.1.170 that is Opus 4.8 (1M context, adaptive thinking,
24-
// default high effort, fast mode at 2x rate / 2.5x speed). Kept as
25-
// `default` (rather than pinned `claude-opus-4-8`) so it stays the
26-
// auto-latest pick AND remains the app default selection (see
27-
// `useEnsureDefaultModel`, which prefers id == "default").
22+
// App default selection (see `useEnsureDefaultModel`, which pins this
23+
// id). Pinned to the explicit `claude-opus-4-8[1m]` wire id — the `[1m]`
24+
// suffix selects the 1M-context variant, matching the label. We do NOT
25+
// use the CLI's `default` sentinel: it resolves to whatever the bundled
26+
// claude-code decides is "default" (non-deterministic across CLI bumps),
27+
// whereas a pinned id is stable. Bump when a newer Opus ships. MUST stay
28+
// in sync with the Rust catalog (`official_claude_section`).
2829
{
29-
id: "default",
30+
id: "claude-opus-4-8[1m]",
3031
label: "Opus 4.8 1M",
31-
cliModel: "default",
32+
cliModel: "claude-opus-4-8[1m]",
3233
effortLevels: ["low", "medium", "high", "xhigh", "max"],
3334
supportsFastMode: true,
3435
},

sidecar/test/claude-session-manager.test.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -493,8 +493,9 @@ describe("ClaudeSessionManager.sendMessage", () => {
493493
models.map((m) => [m.id, m.supportsFastMode]),
494494
);
495495

496-
// `default` now resolves to Opus 4.8, which supports fast mode.
497-
expect(bySupports.default).toBe(true);
496+
// Opus 4.8 1M supports fast mode.
497+
expect(bySupports["claude-opus-4-8[1m]"]).toBe(true);
498+
expect(bySupports.default).toBeUndefined();
498499
expect(bySupports.sonnet).toBeUndefined();
499500
expect(bySupports["claude-opus-4-7[1m]"]).toBeUndefined();
500501
expect(bySupports["claude-opus-4-6[1m]"]).toBe(true);
@@ -539,7 +540,7 @@ describe("ClaudeSessionManager.sendMessage", () => {
539540
{
540541
sessionId: `helmor-sess-effort-${level}`,
541542
prompt: "test",
542-
model: "default",
543+
model: "claude-opus-4-8[1m]",
543544
cwd: undefined,
544545
resume: undefined,
545546
permissionMode: undefined,
@@ -562,7 +563,7 @@ describe("ClaudeSessionManager.sendMessage", () => {
562563
{
563564
sessionId: "helmor-sess-effort-bogus",
564565
prompt: "test",
565-
model: "default",
566+
model: "claude-opus-4-8[1m]",
566567
cwd: undefined,
567568
resume: undefined,
568569
permissionMode: undefined,
@@ -585,7 +586,7 @@ describe("ClaudeSessionManager.sendMessage", () => {
585586
{
586587
sessionId: "helmor-sess-mcp-blocking",
587588
prompt: "test",
588-
model: "default",
589+
model: "claude-opus-4-8[1m]",
589590
cwd: undefined,
590591
resume: undefined,
591592
permissionMode: undefined,
@@ -633,7 +634,7 @@ describe("ClaudeSessionManager.sendMessage", () => {
633634
{
634635
sessionId: "helmor-sess-fm",
635636
prompt: "hi",
636-
model: "default",
637+
model: "claude-opus-4-8[1m]",
637638
cwd: undefined,
638639
resume: undefined,
639640
permissionMode: undefined,
@@ -675,7 +676,7 @@ describe("ClaudeSessionManager.sendMessage", () => {
675676
{
676677
sessionId: "helmor-sess-fm-init",
677678
prompt: "hi",
678-
model: "default",
679+
model: "claude-opus-4-8[1m]",
679680
cwd: undefined,
680681
resume: undefined,
681682
permissionMode: undefined,
@@ -714,7 +715,7 @@ describe("ClaudeSessionManager.sendMessage", () => {
714715
{
715716
sessionId: "helmor-sess-fm-on",
716717
prompt: "hi",
717-
model: "default",
718+
model: "claude-opus-4-8[1m]",
718719
cwd: undefined,
719720
resume: undefined,
720721
permissionMode: undefined,
@@ -1771,9 +1772,9 @@ describe("ClaudeSessionManager.listModels", () => {
17711772
effortLevels: ["low", "medium", "high", "xhigh", "max"],
17721773
},
17731774
{
1774-
id: "default",
1775+
id: "claude-opus-4-8[1m]",
17751776
label: "Opus 4.8 1M",
1776-
cliModel: "default",
1777+
cliModel: "claude-opus-4-8[1m]",
17771778
effortLevels: ["low", "medium", "high", "xhigh", "max"],
17781779
supportsFastMode: true,
17791780
},

src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "helmor"
3-
version = "0.39.0"
3+
version = "0.39.1"
44
description = "The local-first IDE for coding agent orchestration."
55
authors = ["Caspian Zhao", "Nathan Lian"]
66
edition = "2021"

src-tauri/src/agents.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -821,9 +821,12 @@ mod tests {
821821
#[test]
822822
fn resolve_model_infers_provider() {
823823
let _env = crate::testkit::TestEnv::new("resolve-model-infers-provider");
824-
let claude = resolve_model("default", None);
824+
// Hint-less ids that match no other provider infer to claude and pass
825+
// through verbatim (legacy "default" rows are normalized by the DB
826+
// migration, so resolve_model needs no special case for it).
827+
let claude = resolve_model("claude-opus-4-8[1m]", None);
825828
assert_eq!(claude.provider, "claude");
826-
assert_eq!(claude.cli_model, "default");
829+
assert_eq!(claude.cli_model, "claude-opus-4-8[1m]");
827830

828831
let codex = resolve_model("gpt-5.4", None);
829832
assert_eq!(codex.provider, "codex");

src-tauri/src/agents/catalog.rs

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -277,30 +277,29 @@ fn official_claude_section() -> AgentModelSection {
277277
options: vec![
278278
// Fable 5 leads the list as the most capable pick, but it burns
279279
// limits ~2x faster than Opus — `useEnsureDefaultModel` therefore
280-
// pins the app default to the `default` (Opus) entry below, NOT
280+
// pins the app default to the Opus 4.8 entry below, NOT
281281
// to options[0]. No fast mode (Opus 4.6+ only).
282282
claude_model(
283283
"claude-fable-5[1m]",
284284
"Fable 5 1M",
285285
&["low", "medium", "high", "xhigh", "max"],
286286
false,
287287
),
288-
// `default` resolves to the newest Opus the bundled claude-code
289-
// knows about — 2.1.170 maps it to Opus 4.8 (1M context, adaptive
290-
// thinking, default high effort, fast mode at 2x rate / 2.5x
291-
// speed). Kept as `default` so it stays the auto-latest pick and
292-
// remains the app's default selection (see
293-
// `useEnsureDefaultModel`, which prefers id == "default"). MUST
294-
// stay in sync with `sidecar/src/model-catalog.ts`.
288+
// App default selection (see `useEnsureDefaultModel`, which pins
289+
// this id). Pinned to the explicit `claude-opus-4-8[1m]` wire id —
290+
// the `[1m]` suffix selects the 1M-context variant, matching the
291+
// label. We do NOT use the CLI's `default` sentinel: it resolves to
292+
// whatever the bundled claude-code decides (non-deterministic
293+
// across CLI bumps), whereas a pinned id is stable. Bump when a
294+
// newer Opus ships. MUST stay in sync with
295+
// `sidecar/src/model-catalog.ts`.
295296
claude_model(
296-
"default",
297+
"claude-opus-4-8[1m]",
297298
"Opus 4.8 1M",
298299
&["low", "medium", "high", "xhigh", "max"],
299300
true,
300301
),
301-
// Explicit 4.7 pin — this slot used to BE `default`; now that
302-
// `default` advanced to 4.8 we surface 4.7 as its own selectable
303-
// entry, above 4.6.
302+
// Explicit 4.7 pin, above 4.6.
304303
claude_model(
305304
"claude-opus-4-7[1m]",
306305
"Opus 4.7 1M",
@@ -900,7 +899,7 @@ mod tests {
900899
.collect::<Vec<_>>(),
901900
vec![
902901
"claude-fable-5[1m]",
903-
"default",
902+
"claude-opus-4-8[1m]",
904903
"claude-opus-4-7[1m]",
905904
"claude-opus-4-6[1m]",
906905
"sonnet",
@@ -970,7 +969,7 @@ mod tests {
970969
.collect::<Vec<_>>(),
971970
vec![
972971
"claude-fable-5[1m]",
973-
"default",
972+
"claude-opus-4-8[1m]",
974973
"claude-opus-4-7[1m]",
975974
"claude-opus-4-6[1m]",
976975
"sonnet",
@@ -993,10 +992,11 @@ mod tests {
993992
#[test]
994993
fn resolve_claude_model() {
995994
let _env = crate::testkit::TestEnv::new("resolve-claude-model");
996-
let m = resolve_model("default", None);
995+
// The pinned Opus 4.8 1M id resolves to itself.
996+
let m = resolve_model("claude-opus-4-8[1m]", None);
997997
assert_eq!(m.provider, "claude");
998-
assert_eq!(m.cli_model, "default");
999-
assert_eq!(m.id, "default");
998+
assert_eq!(m.cli_model, "claude-opus-4-8[1m]");
999+
assert_eq!(m.id, "claude-opus-4-8[1m]");
10001000
assert!(m.supports_effort);
10011001
}
10021002

@@ -1441,15 +1441,15 @@ mod tests {
14411441
&ids[..4],
14421442
&[
14431443
"claude-fable-5[1m]",
1444-
"default",
1444+
"claude-opus-4-8[1m]",
14451445
"claude-opus-4-7[1m]",
14461446
"claude-opus-4-6[1m]"
14471447
],
14481448
"Fable 5 must lead, with Opus 4.8 (default) / 4.7 / 4.6 beneath it"
14491449
);
14501450

14511451
// Fable 5: most capable, leads the list, but is NOT the app default
1452-
// (too expensive) — `useEnsureDefaultModel` pins to id == "default".
1452+
// (too expensive) — `useEnsureDefaultModel` pins to the Opus 4.8 id.
14531453
// No fast mode (Opus 4.6+ only); full effort tiers incl. xhigh.
14541454
let fable = &claude.options[0];
14551455
assert_eq!(fable.label, "Fable 5 1M");
@@ -1460,11 +1460,11 @@ mod tests {
14601460
vec!["low", "medium", "high", "xhigh", "max"]
14611461
);
14621462

1463-
// `default` → Opus 4.8: stays the app default selection, supports
1464-
// fast mode, and keeps the xhigh effort tier.
1463+
// Opus 4.8: the app default selection, supports fast mode, and keeps
1464+
// the xhigh effort tier. Pinned to its explicit `[1m]` wire id.
14651465
let default = &claude.options[1];
14661466
assert_eq!(default.label, "Opus 4.8 1M");
1467-
assert_eq!(default.cli_model, "default");
1467+
assert_eq!(default.cli_model, "claude-opus-4-8[1m]");
14681468
assert!(default.supports_fast_mode, "Opus 4.8 supports fast mode");
14691469
assert_eq!(
14701470
default.effort_levels,
@@ -1490,10 +1490,12 @@ mod tests {
14901490
#[test]
14911491
fn claude_default_no_longer_collides_with_cursor_auto() {
14921492
let _env = crate::testkit::TestEnv::new("claude-default-no-longer-collides-with-c");
1493-
// `default` belongs to Claude (Opus 4.8 1M). Cursor's Auto is
1494-
// `cursor-default`. They MUST resolve to different providers
1495-
// even when the picker / persistence flow doesn't pass a hint —
1496-
// this is the regression the namespace prefix exists to prevent.
1493+
// A hint-less bare `default` still infers to claude; cursor's Auto is
1494+
// the namespaced `cursor-default`. They MUST resolve to different
1495+
// providers even without a hint — the regression the namespace prefix
1496+
// exists to prevent. (Claude no longer ships a `default` model id; any
1497+
// legacy occurrence is normalized by the DB migration before it gets
1498+
// here, so resolve_model just passes it through.)
14971499
let claude = resolve_model("default", None);
14981500
assert_eq!(claude.provider, "claude");
14991501
assert_eq!(claude.cli_model, "default");

src-tauri/src/import.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -414,9 +414,11 @@ fn import_workspace_db_records(conn: &Connection, workspace_id: &str) -> Result<
414414
)
415415
.context("Failed to import sessions")?;
416416

417-
// 3b. Remap legacy "opus-1m" model ID (CLI no longer accepts it)
417+
// 3b. Remap legacy "opus-1m" model ID to the pinned Opus 4.8 1M wire id
418+
// (CLI no longer accepts "opus-1m"; claude no longer uses the "default"
419+
// sentinel).
418420
conn.execute(
419-
"UPDATE main.sessions SET model = 'default' WHERE model = 'opus-1m' AND workspace_id = ?1",
421+
"UPDATE main.sessions SET model = 'claude-opus-4-8[1m]' WHERE model = 'opus-1m' AND workspace_id = ?1",
420422
[workspace_id],
421423
)
422424
.ok();

0 commit comments

Comments
 (0)