Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ product overview.

## Unreleased

- Update Claude Code to 2.1.306 and offer Sonnet 5.5 in the model picker, with its
native 1M context window and verified external-usage cache rates. Existing saved models remain valid.

- Find specialist channel agents with a live directory of purposes, examples and shared skills.
Managers can describe/hide their channel's agent. Metadata refreshes at startup and nightly;
recommendations respect the requester and reply audience and never transfer work automatically.
Expand Down
3 changes: 2 additions & 1 deletion FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -1179,7 +1179,8 @@ A categorized catalog of what's shipped. Cross-linked to `TEST-PLAN.md` checks.
change. Discovery is bounded and cached for six hours; a failed refresh retains the last good
snapshot, or the bundled fallback on a cold start.
- **Claude's model choices state exact versions:** the picker offers Opus 5.5, Fable 5.1,
Sonnet 5, and Haiku 4.5 under their full model IDs. It omits duplicate aliases and special
Sonnet 5.5, and Haiku 4.5 under their full model IDs. Sonnet 5.5 uses its native 1M
context window and verified cache-aware pricing for external transcript usage. It omits duplicate aliases and special
modes from the normal choices. Previously saved Claude aliases and full IDs remain valid;
the Slack picker shows the current saved value, and the Admin UI round-trips it. The curated
list is reviewed when Anthropic releases a new model. Provider CLI/package upgrades are
Expand Down
12 changes: 11 additions & 1 deletion TEST-PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -5000,10 +5000,20 @@ exercise the `qwen-eu` adapter itself, in a scratch runtime root (no production
is what exposes a new model, and `~/.codex/models_cache.json` carries `client_version` so a
mixed-version probe cannot leave a stale catalog behind.
- [x] Automated: Claude's picker and browser fallback show only the exact Opus 5.5, Fable 5.1,
Sonnet 5, and Haiku 4.5 IDs; a previously saved same-engine alias remains selected in
Sonnet 5.5, and Haiku 4.5 IDs; a previously saved same-engine alias remains selected in
Slack, and the Admin UI consumes the registry's model/effort manifests and keeps a valid
saved same-engine custom ID available (`test/model-options.test.js`,
`test/model-wizard-buttons.test.js`).
- [x] Automated: Sonnet 5.5 retains a native 1M context window without runtime metadata;
external transcripts use $2 input, $10 output, $2.50/$4 cache writes and $0.10 cache reads
per million tokens, while saved Sonnet 5 remains selectable and priced independently
(`test/model-info.test.js`, `test/model-options.test.js`, `test/external-usage.test.js`).
- [ ] Live Claude/Codex: in private admin fixtures, open `/model` from a thread on each engine,
select Claude → Sonnet 5.5 → medium, and ask "Reply with exactly SONNET55_OK".
Pass: picker submits `claude-sonnet-5-5`, next Claude turn reports that provider model,
replies with the sentinel and shows a 1M window. Reopen a thread saved to Sonnet 5;
pass: it remains selected until explicitly changed. Verify the Admin UI model selector
offers Sonnet 5.5 and retains a preexisting Sonnet 5 value.
- [x] Automated: the `/model` wizard's **This channel** scope applies to the thread it was clicked
in. Same-harness: the thread's stale engine/model/effort overrides are cleared and the next
turn resolves the channel's new model/effort. Cross-harness: the thread's Claude session gets
Expand Down
2 changes: 1 addition & 1 deletion containers/versions.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"comment": "Pinned toolchain for the channel image. Keep `claude` and `codex` at the versions the gateway host runs, so a channel behaves identically on either runtime backend; bump deliberately, rebuild, and let the image-id fingerprint recreate the containers. `imageSpecVersion` is the image CONTRACT (paths, PATH, baked-in packages), not the pins: bump it when the image gains or moves something the daemon relies on, keep it equal to IMAGE_SPEC_VERSION in src/runtimes/container/image-paths.js (a test pins them together), and the daemon will tell an operator at boot when the built image is older.",
"base": "node:22-bookworm-slim",
"npm": {
"@anthropic-ai/claude-code": "2.1.300",
"@anthropic-ai/claude-code": "2.1.306",
"@openai/codex": "0.162.1",
"mcp-remote": "0.14.3",
"vercel": "62.4.0",
Expand Down
2 changes: 1 addition & 1 deletion docs/COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ definitions are in `TEST-PLAN.md` and `docs/RELEASE-ACCEPTANCE.md`.
| --- | --- | --- |
| Operating system | Linux (systemd) with rootless Podman; Ubuntu 24.04 tested | CI on Ubuntu |
| Node.js | 22.13 minimum; 24 LTS. SQLite FTS5 (channel-memory search index) is present from the later 22.x builds and 24; on a build without it the daemon boots and memory search uses a plain scan | Full matrix CI |
| Claude Code | Pinned nightly target `2.1.300` (official installer) | Real CLI surface + stub transport |
| Claude Code | Pinned nightly target `2.1.306` (official installer) | Real CLI surface + stub transport |
| Codex CLI | Pinned nightly target `0.162.1`. The container login relay relies on three behaviors verified on 0.156.1 (2026-09-26 spike, `TEST-PLAN.md` P4): a turn runs on an `auth.json` with an access token and an empty refresh token; a valid access token is not refreshed because of `last_refresh` age; `login status` and a turn accept a JWT whose signature segment is a placeholder. `codex exec` needs a closed stdin. Re-run the spike on a Codex bump | Real CLI surface + stub transport; the relay spike before promotion |
| SQLite | Built-in `node:sqlite` | Migrations + backup/restore quick-check |
| VS Code (editor attach) | Any client. Servers for `1.139.0` and `1.138.0` are shared in the channel image (image spec 1.5.0); another version downloads its own into the channel volume as before | Live image test (`npm run test:live-container`) + a real Remote-SSH / attach connection before promotion |
Expand Down
2 changes: 1 addition & 1 deletion public/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ const MODEL_OPTIONS = {
claude: [
["claude-opus-5-5", "Opus 5.5"],
["claude-fable-5-1", "Fable 5.1"],
["claude-sonnet-5", "Sonnet 5"],
["claude-sonnet-5-5", "Sonnet 5.5"],
["claude-haiku-4-5", "Haiku 4.5"],
],
codex: [
Expand Down
4 changes: 3 additions & 1 deletion src/config/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,7 @@ export function getCodexModelRates() {
// launch (../gateway/external-usage.js), where the only record is a transcript's token counts.
// Verified against platform.claude.com/docs/en/about-claude/pricing on 2026-09-23. `cacheWrite5m` /
// `cacheWrite1h` are the two cache-write TTLs and `cacheRead` the hit rate; the multipliers differ
// per family (Fable 5.1 reads at 0.025x, Opus 5.5 at 0.05x, everything else at 0.1x), which is why
// per family (Fable 5.1 reads at 0.025x, Opus/Sonnet 5.5 at 0.05x, others at 0.1x), which is why
// the rate is stored outright instead of derived. The 1M context window carries no premium on
// 4.6-and-later models, so there is no long-context multiplier here.
export const DEFAULT_CLAUDE_RATES = {
Expand All @@ -690,6 +690,8 @@ export const DEFAULT_CLAUDE_RATES = {
"claude-opus-4-6": { input: 5, cacheWrite5m: 6.25, cacheWrite1h: 10, cacheRead: 0.5, output: 25 },
"claude-opus-4-5": { input: 5, cacheWrite5m: 6.25, cacheWrite1h: 10, cacheRead: 0.5, output: 25 },
"claude-opus-4-1": { input: 15, cacheWrite5m: 18.75, cacheWrite1h: 30, cacheRead: 1.5, output: 75 },
// Sonnet 5.5: platform.claude.com/docs/en/about-claude/pricing, verified 2026-10-21.
"claude-sonnet-5-5": { input: 2, cacheWrite5m: 2.5, cacheWrite1h: 4, cacheRead: 0.1, output: 10 },
"claude-sonnet-5": { input: 2, cacheWrite5m: 2.5, cacheWrite1h: 4, cacheRead: 0.2, output: 10 },
"claude-sonnet-4-6": { input: 3, cacheWrite5m: 3.75, cacheWrite1h: 6, cacheRead: 0.3, output: 15 },
"claude-sonnet-4-5": { input: 3, cacheWrite5m: 3.75, cacheWrite1h: 6, cacheRead: 0.3, output: 15 },
Expand Down
2 changes: 1 addition & 1 deletion src/engines/adapters.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const claude = validateEngineAdapter({
efforts: ["low", "medium", "high", "xhigh"], models: [
{ label: "Opus 5.5", value: "claude-opus-5-5", description: "Current Opus model for most coding and knowledge work." },
{ label: "Fable 5.1", value: "claude-fable-5-1", description: "Current Fable model for demanding long-running work." },
{ label: "Sonnet 5", value: "claude-sonnet-5", description: "Current Sonnet model for everyday work." },
{ label: "Sonnet 5.5", value: "claude-sonnet-5-5", description: "Current Sonnet model for everyday work." },
{ label: "Haiku 4.5", value: "claude-haiku-4-5", description: "Current Haiku model for fast, low-cost work." },
], mintsOwnSessionId: false,
// Provider-failure kinds (stream.js claudeProviderError) the orchestrator may replay IN PLACE on
Expand Down
2 changes: 1 addition & 1 deletion src/gateway/model-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export function contextWindowFor(result = {}) {
// for a 1M run and would otherwise understate the window fivefold.
if (/\[1m\]/i.test(raw) || /\[1m\]/i.test(String(result.model || ""))) return 1_000_000;
// These current models have 1M by default; their explicit IDs need no [1m] suffix.
if (/^claude-(?:opus-5-5|fable-5-1|sonnet-5)$/i.test(raw)) return 1_000_000;
if (/^claude-(?:opus-5-5|fable-5-1|sonnet-5(?:-5)?)$/i.test(raw)) return 1_000_000;
const runtimeWindow = modelUsageContextWindow(result.raw, raw);
if (runtimeWindow) return runtimeWindow;
const requestWindow = Array.isArray(result.usageRequests)
Expand Down
9 changes: 9 additions & 0 deletions test/external-usage.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -442,3 +442,12 @@ test("a pass reads all history by default and reports how it attributed what it
assert.ok(Array.isArray(summary.errors));
assert.ok(summary.scopes.some((s) => s.scope === "host"));
});

// Sonnet 5.5 halves the cache-read rate; old Sonnet transcripts keep their own price.
test("Sonnet 5.5 external usage prices both cache TTLs without repricing Sonnet 5", async () => {
const { getClaudeModelRates } = await import("../src/config/settings.js");
const rates = getClaudeModelRates();
const usage = { input_tokens: 1e6, cached_input_tokens: 1e6, cache_write_5m_tokens: 1e6, cache_write_1h_tokens: 1e6, output_tokens: 1e6 };
assert.equal(priceClaudeTokens(usage, "claude-sonnet-5-5", rates), 18.6);
assert.equal(priceClaudeTokens(usage, "claude-sonnet-5", rates), 18.7);
});
2 changes: 1 addition & 1 deletion test/model-info.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ test("a configured 1M model keeps its window even though the CLI reports the pla
});

test("current versioned Claude IDs retain their default 1M window", () => {
for (const model of ["claude-opus-5-5", "claude-fable-5-1", "claude-sonnet-5"]) {
for (const model of ["claude-opus-5-5", "claude-fable-5-1", "claude-sonnet-5", "claude-sonnet-5-5"]) {
assert.equal(contextWindowFor({ engine: "claude", model, raw: { model } }), 1_000_000);
}
assert.equal(contextWindowFor({ engine: "claude", model: "claude-haiku-4-5", raw: { model: "claude-haiku-4-5" } }), 200_000);
Expand Down
4 changes: 3 additions & 1 deletion test/model-options.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ test("Slack offers a short Claude list with exact versioned IDs", () => {
const claudeValues = modelOptionsForEngine("claude").map(({ value }) => value);
const codexValues = modelOptionsForEngine("codex").map(({ value }) => value);

assert.deepEqual(claudeValues, ["__default__", "claude-opus-5-5", "claude-fable-5-1", "claude-sonnet-5", "claude-haiku-4-5"]);
assert.deepEqual(claudeValues, ["__default__", "claude-opus-5-5", "claude-fable-5-1", "claude-sonnet-5-5", "claude-haiku-4-5"]);
assert.ok(!codexValues.some((value) => /fable/i.test(value)));
assert.equal(modelOptionsForEngine("claude", "claude-sonnet-5").at(-1).value, "claude-sonnet-5");
assert.equal(modelOptionsForEngine("claude", "fable").at(-1).label, "Current: fable");
assert.equal(modelOptionsForEngine("claude", "claude-opus-5-5").length, claudeValues.length);
});
Expand All @@ -28,6 +29,7 @@ test("admin UI consumes server model manifests and keeps exact versions as its o
assert.match(source, /current && modelMatchesEngine\(current, eng\).*options\.push\(\[current, current\]\)/);
assert.match(claudeList, /\["claude-opus-5-5", "Opus 5\.5"\]/);
assert.match(claudeList, /\["claude-fable-5-1", "Fable 5\.1"\]/);
assert.match(claudeList, /\["claude-sonnet-5-5", "Sonnet 5\.5"\]/);
assert.doesNotMatch(claudeList, /\["(?:best|fable|opusplan)",/);
assert.doesNotMatch(codexList, /fable/i);
});
Loading