diff --git a/assets/hero.png b/assets/hero.png index d469b65..2dd12af 100644 Binary files a/assets/hero.png and b/assets/hero.png differ diff --git a/assets/hero.svg b/assets/hero.svg index 0ede983..6258e35 100644 --- a/assets/hero.svg +++ b/assets/hero.svg @@ -14,10 +14,12 @@ - + - - + + + + diff --git a/assets/social.png b/assets/social.png index 00c6894..f62d8c9 100644 Binary files a/assets/social.png and b/assets/social.png differ diff --git a/assets/social.svg b/assets/social.svg index b7cc3d2..2136248 100644 --- a/assets/social.svg +++ b/assets/social.svg @@ -16,8 +16,10 @@ - - + + + + swisscode diff --git a/src/adapters/agents/kilo/index.ts b/src/adapters/agents/kilo/index.ts index 44c4960..8cdf2b7 100644 --- a/src/adapters/agents/kilo/index.ts +++ b/src/adapters/agents/kilo/index.ts @@ -19,6 +19,7 @@ import { ambientUnset, anthropicOptions, collapsedTierWarning, + compatIgnoredWarning, sessionUnavailableWarning, extendedContextWarning, modelRef, @@ -57,7 +58,7 @@ export const kilo = { }, binary, translate(input: TranslateInput): Translation { - const { intent, passthrough } = input + const { intent, passthrough, profile } = input const primary = intent.models.opus const config: Record = { @@ -82,6 +83,8 @@ export const kilo = { if (collapse) warnings.push(collapse) const ext = extendedContextWarning(intent, primary, 'Kilo') if (ext) warnings.push(ext) + const compat = compatIgnoredWarning(profile.compat, 'Kilo') + if (compat) warnings.push(compat) return { plan: { set, unset: ambientUnset(intent) }, args: [...passthrough], warnings } }, diff --git a/src/adapters/agents/opencode/index.ts b/src/adapters/agents/opencode/index.ts index 17336f0..0b9976b 100644 --- a/src/adapters/agents/opencode/index.ts +++ b/src/adapters/agents/opencode/index.ts @@ -16,6 +16,7 @@ import { ambientUnset, anthropicOptions, collapsedTierWarning, + compatIgnoredWarning, sessionUnavailableWarning, extendedContextWarning, modelRef, @@ -56,7 +57,7 @@ export const opencode = { }, binary, translate(input: TranslateInput): Translation { - const { intent, passthrough } = input + const { intent, passthrough, profile } = input const primary = intent.models.opus const small = intent.models.haiku @@ -87,6 +88,8 @@ export const opencode = { if (collapse) warnings.push(collapse) const ext = extendedContextWarning(intent, primary, 'OpenCode') if (ext) warnings.push(ext) + const compat = compatIgnoredWarning(profile.compat, 'OpenCode') + if (compat) warnings.push(compat) return { plan: { set, unset: ambientUnset(intent) }, args, warnings } }, diff --git a/src/adapters/agents/shared.ts b/src/adapters/agents/shared.ts index a41da85..593dffb 100644 --- a/src/adapters/agents/shared.ts +++ b/src/adapters/agents/shared.ts @@ -103,6 +103,35 @@ export function collapsedTierWarning( } } +/** + * Warn when a profile carries gateway compatibility flags that this agent does + * not consume. + * + * The compat mechanism is Claude-Code-only (capabilities.compatFlags). A flag + * toggled on for a Kilo or OpenCode setup changes nothing and, until this + * existed, said so NOWHERE — not at edit, not at launch, not in the doctor. + * That is the one capability mismatch with no net, and the + * capability-gap-never-silently-dropped rule the tier and session warnings + * already follow says it should have one. The flags are kept, not stripped, so + * the setup still works when it runs Claude Code again. + */ +export function compatIgnoredWarning( + compat: Record | undefined, + agentLabel: string, +): EnvWarning | null { + const active = Object.entries(compat ?? {}) + .filter(([, on]) => on) + .map(([id]) => id) + if (active.length === 0) return null + return { + severity: 'medium', + code: 'compat-ignored', + message: + `${agentLabel} does not use gateway compatibility flags; these are set but ignored: ` + + `${active.join(', ')}.`, + } +} + /** * Warn when a 1M-capable provider is reached without Claude Code's `[1m]` * signal, which only Claude Code sends. The model still runs; its window is diff --git a/test/adapters/agents/kilo.test.ts b/test/adapters/agents/kilo.test.ts index 1ddba9a..5e9eb9f 100644 --- a/test/adapters/agents/kilo.test.ts +++ b/test/adapters/agents/kilo.test.ts @@ -68,3 +68,35 @@ test('capabilities describe a single model slot', () => { assert.equal(kilo.binary.name, 'kilo') assert.equal(kilo.binary.overrideEnv, 'SWISSCODE_KILO_BIN') }) + +test('a gateway compat flag on a Kilo profile is flagged as ignored — the safety net', () => { + // The UI now hides the compat section for Kilo, but a hand-edited or + // agent-switched config can still carry a flag. Compat is Claude-Code-only, + // so Kilo warns rather than silently doing nothing — the one mismatch that + // previously had no net at edit, launch, or doctor. + const input: TranslateInput = { + intent: intent(), + profile: makeProfile({ provider: 'zai', compat: { disableAdaptiveThinking: true } }), + provider: null, + passthrough: [], + ambient: {}, + } + const w = kilo.translate(input).warnings.find((x) => x.code === 'compat-ignored') + assert.ok(w, 'Kilo should warn that a compat flag is ignored') + assert.match(w.message, /disableAdaptiveThinking/) + assert.match(w.message, /Kilo/) +}) + +test('a Kilo profile with no compat flags emits no compat-ignored warning', () => { + const input: TranslateInput = { + intent: intent(), + profile: makeProfile({ provider: 'zai' }), + provider: null, + passthrough: [], + ambient: {}, + } + assert.equal( + kilo.translate(input).warnings.find((x) => x.code === 'compat-ignored'), + undefined, + ) +}) diff --git a/web/index.html b/web/index.html index 4b1a57d..b0fb415 100644 --- a/web/index.html +++ b/web/index.html @@ -4,6 +4,15 @@ swisscode + + + + +