-
Notifications
You must be signed in to change notification settings - Fork 134
chore(release): v0.12.3 #1362
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore(release): v0.12.3 #1362
Changes from all commits
f8cea3a
f58c388
3d27365
b582d08
8073937
4fc06cb
954de70
dc4efc2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file. | |
| The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), | ||
| and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||
|
|
||
| ## [0.12.3] - 2026-09-23 | ||
|
|
||
| **Heads-up before upgrading (every user):** | ||
|
|
||
| - **Altimate Base now registers automatically, with no dialog to accept.** Since 2026-09-17, OpenCode's own free tier (Zen) has rejected keyless requests from Altimate Code ("OpenCode's free tier can only be used from within OpenCode"), so every install that had silently fallen back to it lost its free model. A fresh install — or one with no other usable model configured — now registers the free, no-signup Altimate Base automatically at startup and shows a one-time notice instead of a confirmation dialog; only the confirmation step is gone, not the disclosure. Opt out with `ALTIMATE_BASE_AUTO_REGISTER=0`, `altimate providers logout altimate-base`, or `disabled_providers` in config. (#1361) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: These opt-outs have different scopes: (Based on your team's feedback about Base logout suppression.) Prompt for AI agents |
||
|
|
||
| ### Changed | ||
|
|
||
| - **Altimate Base replaces keyless Zen as the automatic fallback model** when nothing else is configured. See heads-up above. (#1361) | ||
|
|
||
| ### Fixed | ||
|
|
||
| - **`altimate agent create` and `altimate review` no longer fail on a fresh install with no model configured.** Neither command registered Altimate Base before resolving a provider, unlike every other entrypoint (`run`, `tui`, `serve`, `acp`, `web`): `agent create` leaked a raw upstream error mentioning "OpenCode", a brand the user has never seen, with no remediation; `review`'s AI lane silently produced zero findings with no visible signal. Found in this release's review. | ||
| - **The TUI's startup auto-register wait no longer reads as a hang on a fresh install.** A "Connecting to Altimate Base…" status line appears if registration takes more than 300ms; the common already-registered path is unaffected. Found in this release's review. | ||
| - **The pinned-workspace routing section follows the pinned workspace, not the project's own link.** (#1357) | ||
|
|
||
| ## [0.12.2] - 2026-09-22 | ||
|
|
||
| Promotes [0.12.2-beta.1] to `latest` — the six bug fixes below — plus four small fixes from this release's review and one workspace-pilot fix that landed alongside (#1353). No new features. The beta was published earlier the same day and did not soak before promotion; the review below is what stood in for that. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -162,6 +162,17 @@ const AgentCreateCommand = effectCmd({ | |
| description = query | ||
| } | ||
|
|
||
| // altimate_change start — auto-register Altimate Base before provider state is first built, | ||
| // mirroring cli/cmd/run.ts. Without this, a fresh install's first `agent create` resolves | ||
| // the default model before any provider is registered and fails with a raw upstream error. | ||
| { | ||
| const { FreeTier } = await import("../../altimate/free/client") | ||
| const { FreeTierConsent } = await import("../../altimate/free/consent") | ||
| const result = await FreeTier.autoRegisterWithin(undefined, () => void FreeTierConsent.printDisclosureOnceForHeadless(true)) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift 🔎 Supported by static analysis🏁 Script executed: set -eu
printf '%s\n' '--- diff ---'
git diff --no-ext-diff --unified=30 c9f4223f7f4269dd430642004eb554aaa3ee2983 8073937b14c4e39d4d848eb3d6166f6b1212b0fd -- packages/opencode/src/cli/cmd/agent.ts packages/opencode/src/cli/cmd/review.ts
printf '%s\n' '--- candidate files ---'
git ls-files 'packages/opencode/src/**' | rg '(free[-_]?tier|FreeTier|agent|review|provider)' | head -80
printf '%s\n' '--- bindings and usages ---'
rg -n --glob '*.ts' 'autoRegisterWithin|printDisclosureOnceForHeadless|Agent\.Service\.generate|Service\.generate|resolve.*provider|provider.*resolve' packages/opencode/src | head -240Repository: AltimateAI/altimate-code Length of output: 14508 🏁 Script executed: set -eu
git diff --no-ext-diff --unified=30 c9f4223f7f4269dd430642004eb554aaa3ee2983 8073937b14c4e39d4d848eb3d6166f6b1212b0fd -- packages/opencode/src/cli/cmd/agent.ts packages/opencode/src/cli/cmd/review.ts
rg -n --glob '*.ts' 'autoRegisterWithin|printDisclosureOnceForHeadless|Agent\.Service\.generate|Service\.generate' packages/opencode/srcRepository: AltimateAI/altimate-code Length of output: 10547 🏁 Script executed: printf '%s\n' '--- agent/review diff ---'; git diff --no-ext-diff --unified=20 c9f4223f7f4269dd430642004eb554aaa3ee2983 8073937b14c4e39d4d848eb3d6166f6b1212b0fd -- packages/opencode/src/cli/cmd/agent.ts packages/opencode/src/cli/cmd/review.ts; printf '%s\n' '--- relevant symbols ---'; rg -n --glob '*.ts' 'autoRegisterWithin|printDisclosureOnceForHeadless|Agent\.Service\.generate|FreeTier' packages/opencode/src/cli packages/opencode/src | head -300Repository: AltimateAI/altimate-code Length of output: 27537 🏁 Script executed: set -eu
printf '%s\n' '--- FreeTier client ---'
sed -n '500,820p' packages/opencode/src/altimate/free/client.ts
printf '%s\n' '--- consent ---'
sed -n '1,155p' packages/opencode/src/altimate/free/consent.ts
printf '%s\n' '--- agent service generate ---'
rg -n -A35 -B20 'generate\s*[:=]|export.*generate|function generate|class Service' packages/opencode/src/agent/agent.ts
printf '%s\n' '--- review AI and orchestration ---'
rg -n -A45 -B25 'Provider\.defaultModel|defaultModel|aiReview|reviewPullRequest|noAi' packages/opencode/src/altimate/review packages/opencode/src/provider packages/opencode/src/agent/agent.ts | head -500Repository: AltimateAI/altimate-code Length of output: 42259 🏁 Script executed: set -eu
printf '%s\n' '--- client numbered ---'
sed -n '700,785p' packages/opencode/src/altimate/free/client.ts | cat -n
printf '%s\n' '--- agent generate numbered ---'
sed -n '620,735p' packages/opencode/src/agent/agent.ts | cat -n
printf '%s\n' '--- provider default model numbered ---'
rg -n -A70 -B25 'export.*defaultModel|function defaultModel|defaultModel\s*=' packages/opencode/src/provider/provider.ts
printf '%s\n' '--- AI review numbered ---'
sed -n '1,240p' packages/opencode/src/altimate/review/ai-review.ts | cat -n
printf '%s\n' '--- review wiring numbered ---'
sed -n '330,360p' packages/opencode/src/altimate/review/run.ts | cat -n
sed -n '1385,1418p' packages/opencode/src/altimate/review/orchestrate.ts | cat -nRepository: AltimateAI/altimate-code Length of output: 32287 🏁 Script executed: set -eu
printf '%s\n' '--- provider default tail ---'
sed -n '2278,2322p' packages/opencode/src/provider/provider.ts | cat -n
printf '%s\n' '--- provider getModel and managed provider ---'
rg -n -A55 -B25 'async function getModel|export async function getModel|credentialsForLoad|MANAGED_API_KEY_PLACEHOLDER|FreeTier\.PROVIDER_ID' packages/opencode/src/provider/provider.ts | head -360
printf '%s\n' '--- provider list/state definitions ---'
rg -n -A80 -B25 'export async function list|async function list|function state|ProviderState|const state' packages/opencode/src/provider/provider.ts | head -360Repository: AltimateAI/altimate-code Length of output: 28318 Handle pending registration before the first model lookup.
Both commands continue after a Handle 📍 Affects 2 files
🤖 Prompt for AI Agents |
||
| await FreeTierConsent.printDisclosureOnceForHeadless(result.status === "registered") | ||
| } | ||
| // altimate_change end | ||
|
|
||
| // Generate agent | ||
| const spinner = prompts.spinner() | ||
| spinner.start("Generating agent configuration...") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -170,7 +170,16 @@ export const TuiThreadCommand = cmd({ | |
| // altimate_change start — auto-register Altimate Base before the worker is spawned. The | ||
| // worker starts loading instance/provider state as soon as it boots (worker.ts's | ||
| // `traceReady` chain), so this has to land on the parent thread first. | ||
| await FreeTier.autoRegisterWithin() | ||
| // | ||
| // A fresh install's first launch can take up to the 3s wait with zero terminal output, | ||
| // which reads as a hang. Gate the status line behind a short delay so the common | ||
| // already-registered path (near-instant) never flashes it. | ||
| const registerFeedback = setTimeout(() => UI.println("Connecting to Altimate Base…"), 300) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WARNING: Gate startup feedback on an interactive stderr stream
Reply with |
||
| try { | ||
| await FreeTier.autoRegisterWithin() | ||
| } finally { | ||
| clearTimeout(registerFeedback) | ||
| } | ||
| // altimate_change end | ||
| // altimate_change start — hand the launch correlation id to the worker explicitly. A Bun | ||
| // Worker does not see runtime mutations to process.env, so without this the worker mints its | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,15 +44,18 @@ function autoRegisterWithinArgs(source: string): string | null { | |
| const REAL_CALLBACK = /\(\)\s*=>[\s\S]{0,80}?printDisclosureOnceForHeadless\(true\)/ | ||
|
|
||
| describe("autoRegisterWithin() late-notice callback wiring per entrypoint", () => { | ||
| test.each(["run.ts", "acp.ts", "web.ts"])("%s always passes a real onLateRegistration callback", (file) => { | ||
| const source = read(file) | ||
| const args = autoRegisterWithinArgs(source) | ||
| expect(args, `${file} must call autoRegisterWithin()`).not.toBeNull() | ||
| expect(args, `${file}'s autoRegisterWithin() call`).toMatch(REAL_CALLBACK) | ||
| // Guards against a regression that passes the callback conditionally (that's serve.ts's job, | ||
| // not these three) — none of them may reference ALTIMATE_CLI_CLIENT or ternary out. | ||
| expect(args, `${file} must not gate its callback like serve.ts does`).not.toMatch(/\?\s*\(\)\s*=>/) | ||
| }) | ||
| test.each(["run.ts", "acp.ts", "web.ts", "agent.ts", "review.ts"])( | ||
| "%s always passes a real onLateRegistration callback", | ||
| (file) => { | ||
| const source = read(file) | ||
| const args = autoRegisterWithinArgs(source) | ||
| expect(args, `${file} must call autoRegisterWithin()`).not.toBeNull() | ||
| expect(args, `${file}'s autoRegisterWithin() call`).toMatch(REAL_CALLBACK) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WARNING: Verify the callback is the second argument This match only proves that an arrow appears somewhere in the argument list. Reply with |
||
| // Guards against a regression that passes the callback conditionally (that's serve.ts's | ||
| // job, not these five) — none of them may reference ALTIMATE_CLI_CLIENT or ternary out. | ||
| expect(args, `${file} must not gate its callback like serve.ts does`).not.toMatch(/\?\s*\(\)\s*=>/) | ||
| }, | ||
| ) | ||
|
|
||
| test("serve.ts passes a real callback when NOT serving the datamates (VS Code) client", () => { | ||
| const source = read("serve.ts") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| /** | ||
| * Adversarial coverage for the v0.12.3 release payload (v0.12.2..HEAD): the Altimate Base | ||
| * no-consent-gate PR (#1361, 429/49 tests of its own — see the entrypoint-late-notice-wiring | ||
| * pin) plus the routing-pin fix, PLUS the two P0s and P1 found by this release's own | ||
| * multi-persona review and fixed here: | ||
| * | ||
| * - `altimate agent create` and `altimate review` never called `FreeTier.autoRegisterWithin()` | ||
| * before resolving a provider (Chaos Gremlin/Support Engineer persona, both P0): a fresh | ||
| * install's first `agent create` or `review` run failed with a raw upstream error / silently | ||
| * produced zero AI findings, because no provider had ever been registered. | ||
| * - `tui.ts`'s up-to-3s auto-register wait gave zero terminal feedback on a fresh install | ||
| * (End User persona, P1): reads as a hang on the very first launch. | ||
| * | ||
| * This file does NOT re-test PR #1361's own extensive coverage (stale-zen-cycle, | ||
| * altimate-base-auto-register, altimate-base-headless-disclosure, routing-pin, etc. — all | ||
| * re-run and confirmed green during this release's Step 5/5c). It targets only the NEW code | ||
| * from this release's fixes, following the source-assertion pattern already established in | ||
| * test/altimate/entrypoint-late-notice-wiring.test.ts for these same Effect-based / heavy CLI | ||
| * command files. | ||
| */ | ||
| import { describe, test, expect } from "bun:test" | ||
| import { readFileSync } from "fs" | ||
| import { join, resolve } from "path" | ||
|
|
||
| const cmdDir = resolve(import.meta.dir, "..", "..", "src", "cli", "cmd") | ||
|
|
||
| function read(file: string): string { | ||
| return readFileSync(join(cmdDir, file), "utf-8") | ||
| } | ||
|
|
||
| describe("v0.12.3: agent.ts / review.ts auto-register ordering", () => { | ||
| test("agent.ts registers Altimate Base BEFORE resolving/calling Agent.generate", () => { | ||
| const source = read("agent.ts") | ||
| const registerIdx = source.indexOf("autoRegisterWithin(") | ||
| const generateIdx = source.indexOf("agentSvc.generate(") | ||
| expect(registerIdx, "agent.ts must call autoRegisterWithin()").toBeGreaterThan(-1) | ||
| expect(generateIdx, "agent.ts must call agentSvc.generate()").toBeGreaterThan(-1) | ||
| // Registering AFTER the model/provider is already being resolved defeats the fix: a fresh | ||
| // install would still hit the unregistered path on its first LLM call. | ||
| expect(registerIdx, "registration must precede the generate() call, not follow it").toBeLessThan(generateIdx) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WARNING: Assert registration is awaited, not merely earlier in the file The index comparison still passes if the production call changes to Reply with |
||
| }) | ||
|
|
||
| test("agent.ts's registration call is not swallowed inside the generate() error handler", () => { | ||
| const source = read("agent.ts") | ||
| // The existing `.catch((error) => { spinner.stop(...) ... })` on agentSvc.generate() must | ||
| // stay scoped to the generate call only — folding registration into that same try/catch | ||
| // would mask a registration failure as a generic "LLM failed to generate agent" message. | ||
| const catchIdx = source.indexOf(".catch((error) => {") | ||
| const registerIdx = source.indexOf("autoRegisterWithin(") | ||
| expect(catchIdx, "the existing generate() catch handler must still be present").toBeGreaterThan(-1) | ||
| expect(registerIdx).toBeLessThan(catchIdx) | ||
| }) | ||
|
|
||
| test("review.ts registers Altimate Base BEFORE reviewPullRequest AND outside the latency timer", () => { | ||
| const source = read("review.ts") | ||
| const registerIdx = source.indexOf("autoRegisterWithin(") | ||
| const reviewCallIdx = source.indexOf("await reviewPullRequest({") | ||
| const timerIdx = source.indexOf("const startedAt = Date.now()") | ||
| expect(registerIdx, "review.ts must call autoRegisterWithin()").toBeGreaterThan(-1) | ||
| expect(reviewCallIdx, "review.ts must call reviewPullRequest()").toBeGreaterThan(-1) | ||
| expect(timerIdx, "review.ts must still time the engine via startedAt").toBeGreaterThan(-1) | ||
| expect(registerIdx, "registration must precede the review call").toBeLessThan(reviewCallIdx) | ||
| // A registration wait folded into the timed region would inflate every review_run latency | ||
| // metric on a fresh install (or after a credential rotation) with startup cost that has | ||
| // nothing to do with the review engine itself. | ||
| expect(registerIdx, "registration must be excluded from the engine latency timer").toBeLessThan(timerIdx) | ||
| }) | ||
|
|
||
| test("review.ts's registration call runs even when --no-ai is set (matches R1: registers regardless of the caller's own model)", () => { | ||
| const source = read("review.ts") | ||
| const registerIdx = source.indexOf("autoRegisterWithin(") | ||
| const noAiCheckIdx = source.indexOf("noAi:") | ||
| expect(registerIdx).toBeGreaterThan(-1) | ||
| // The registration call must be unconditional — not gated behind `!args.noAi` — so the | ||
| // CI `--post --mode gate` deterministic-only path still benefits the NEXT invocation that | ||
| // does use the AI lane. Asserted by requiring no `noAi`/`args.ai` reference appears between | ||
| // the register call and its own closing brace. | ||
| const blockEnd = source.indexOf("// altimate_change end", registerIdx) | ||
| const between = source.slice(registerIdx, blockEnd) | ||
| expect(between, "the registration block must not branch on noAi/args.ai").not.toMatch(/noAi|args\.ai\b/) | ||
| expect(noAiCheckIdx).toBeGreaterThan(blockEnd) | ||
| }) | ||
| }) | ||
|
|
||
| describe("v0.12.3: tui.ts startup-feedback timer", () => { | ||
| function readTui(): string { | ||
| return readFileSync(join(cmdDir, "tui.ts"), "utf-8") | ||
| } | ||
|
|
||
| test("the feedback timer is gated behind a short, non-zero delay (never fires instantly)", () => { | ||
| const source = readTui() | ||
| const match = source.match(/setTimeout\(\(\) => UI\.println\("Connecting to Altimate Base…"\), (\d+)\)/) | ||
| expect(match, "tui.ts must set a delayed status line before autoRegisterWithin()").not.toBeNull() | ||
| const delayMs = Number(match![1]) | ||
| // Zero (or missing) would flash on every launch, including the fast/already-registered | ||
| // path this delay exists to protect; too long would defeat the "no feedback during a | ||
| // fresh-install hang" fix End User flagged. Bounded to a sane window around the ~250-300ms | ||
| // suggestion rather than pinned to one exact value. | ||
| expect(delayMs).toBeGreaterThan(0) | ||
| expect(delayMs).toBeLessThanOrEqual(500) | ||
| }) | ||
|
|
||
| test("the feedback timer is always cleared, even if autoRegisterWithin() throws", () => { | ||
| const source = readTui() | ||
| const start = source.indexOf("const registerFeedback = setTimeout(") | ||
| expect(start, "must find the registerFeedback timer declaration").toBeGreaterThan(-1) | ||
| const scope = source.slice(start, start + 400) | ||
| // Must be a try/finally around the await, not a bare await — a throw from | ||
| // autoRegisterWithin() (it currently never throws, but must not be relied upon) would | ||
| // otherwise leak the timer and could still print the status line after the process is | ||
| // already tearing down. | ||
| expect(scope).toMatch(/try\s*\{[\s\S]*?await FreeTier\.autoRegisterWithin\(\)[\s\S]*?\}\s*finally\s*\{[\s\S]*?clearTimeout\(registerFeedback\)/) | ||
| }) | ||
|
|
||
| test("autoRegisterWithin() is still called with no arguments in tui.ts (unchanged contract: TUI renders its own onboarding, no headless callback)", () => { | ||
| const source = readTui() | ||
| // Regression guard: wrapping the call in a delayed-feedback block must not have also, | ||
| // accidentally, started passing a headless disclosure callback — the TUI's own | ||
| // useAltimateBaseDisclosureNotice() owns that surface; a second notice source here would | ||
| // double-print. | ||
| expect(source).toMatch(/await FreeTier\.autoRegisterWithin\(\)\s*$/m) | ||
| }) | ||
| }) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: This heads-up omits the bounded wait and what happens when registration outlasts it: startup continues while registration can finish in the background, and failures back off. Document the three-second limit, background completion, explicit registration route, and retry behavior so users can distinguish a delay from a failed setup.
(Based on your team's feedback about Altimate Base registration timing.)
View Feedback
Prompt for AI agents