jeo-code 0.7.58 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  The README mirrors the latest 5 entries — regenerate with `bun run changelog:sync`.
8
8
 
9
+ ## [0.8.1] - 2026-07-08
10
+ _Fix-forward for a functional regression this same session shipped in v0.8.0. This repo's working tree is shared with a concurrent interactive session; `git add`-ing `src/ai/provider-status.ts` for v0.8.0's routing-veto-gate fix also swept up that session's already-modified, uncommitted copy of the SAME file (containing an unrelated design change: gemini OAuth alone no longer serving `google/gemini-*` models) without its companion piece in `src/auth/flows/index.ts` — so v0.8.0 shipped with a test suite (`provider-status.test.ts`) asserting behavior whose supporting data flag never landed. CI caught this immediately post-release: 2 failing tests on the exact tagged commit. Investigated, confirmed the missing piece was real (and independently observable live via `jeo doctor` before this session had made ANY of its own edits — not a stray/speculative change), and completed it._
11
+
12
+ ### Fixed
13
+ - **`OAUTH_FLOW_REGISTRY.gemini.verifiedEndToEnd` flipped `true` → `false`** (`src/auth/flows/index.ts`) — Google removed the gemini-cli/Cloud Code Assist masquerade that let a plain `jeo auth login gemini` OAuth token serve `google/gemini-*` calls directly; a `GEMINI_API_KEY` is now required (the same models remain OAuth-only reachable via `antigravity/*`). Without this flag, `effectiveCredentialForProvider`/`oauthServesModel` (`src/ai/model-manager.ts`) treated a gemini-OAuth-only credential as usable, so a real turn's API call hit Google's endpoint with a token it no longer accepts — a raw provider error instead of jeo's actionable "set GEMINI_API_KEY, or use antigravity/\*" message. Confirmed via live smoke test on the isolated fixed tree: `jeo doctor` now correctly reports `status: "fail"` with the full actionable hint for a gemini-OAuth-only config, instead of the false `"ok"` the stale flag produced.
14
+ - **`test/launch-role-model.test.ts`'s antigravity-readiness test updated** to match the (already-shipped, correct) v0.8.0 behavior: a live gemini-fallback OAuth credential now makes `antigravity/*` genuinely `ready: true` (matching `model-manager.ts`'s real call-path fallback), not just catalog-listed-with-a-warning. Split into two tests — one asserting the new ready-via-fallback path, one preserving the original "genuinely no credential at all" not-ready/warned-not-refused coverage.
15
+ - **`test/model-manager.test.ts`/`test/doctor.test.ts`** — updated the 2 tests whose expectations depended on the stale `verifiedEndToEnd` flag (gemini OAuth+key now correctly prefers the API key; gemini OAuth-only now correctly fails with the actionable hint instead of asserting the removed Cloud Code Assist probe response).
16
+
17
+ ### Verified
18
+ Built a fully isolated tree (`git archive` of the tagged commit + only these 5 fix files copied in — zero contamination from the still-active concurrent session) and ran the complete suite there: `bun run typecheck` clean, **2712/2712 tests pass, 0 fail** (up from 2 failures on the as-shipped v0.8.0 tag, reproduced first to confirm they were real before fixing). Live end-to-end smoke test on that same isolated tree: `jeo doctor --json` against a gemini-OAuth-only config now correctly reports `"status": "fail"` with the actionable `GEMINI_API_KEY`/`antigravity` hint.
19
+
20
+ ## [0.8.0] - 2026-07-08
21
+ _Root-caused a genuine reliability bug in the routing veto gate ("라우트가 동작후 api 키인증오류가나오는데 근본적인 원인 개선해"), then regenerated the animated README mascot to visualize the smart cross-provider routing this release cycle has been building: prompt routing that finds the cheapest usable path, with a "money saver" visual motif._
22
+
23
+ ### Fixed
24
+ - **Routing veto gate's dead-OAuth blind spot** (`src/ai/provider-status.ts`) — `launch.ts`'s existing veto gate (which is supposed to catch "routed to a provider with no usable credential" before a turn runs) relied on `describeProvider().ready`, which only checked whether an OAuth credential OBJECT was present in config, never whether it was actually alive. A definitively dead OAuth token (expired, refresh token revoked or absent) reported `ready: true` anyway, so routing would commit a turn to that provider and the FIRST place the dead credential was ever noticed was a raw 401 from the real provider API — after routing had already discarded the working `defaultModel` fallback. Added `oauthEntryDead()`, a pure local check (expired AND no refresh token, no network call) wired into all three OAuth readiness branches (anthropic/openai/kimi, antigravity's own OAuth, antigravity's gemini fallback). An expired token WITH a refresh token is deliberately left alone — it self-heals via the existing `resolveCredential` auto-refresh on the real call path, confirmed live in this exact environment. Each newly-caught dead credential now surfaces an actionable label ("OAuth expired, no refresh token — run 'jeo auth login \<provider\>' to re-authenticate") instead of silently showing "OAuth" while quietly unusable.
25
+
26
+ ### Changed
27
+ - **`assets/character.gif` regenerated** — the animated README mascot now depicts the jeo crayfish-wizard performing smart prompt routing: evaluating three provider paths on a holographic dispatch panel, selecting the cheapest/most-efficient one (highlighted green), gold coins streaming in as savings, settling into a satisfied glow. Same character identity (crimson shell, cyan-magenta gradient glasses, purple DNA-patterned robe, plush companions) and same dark-navy/cyan-grid backdrop as before — only the scene changed. Generated via the `god-tibo-imagen` skill (`gti` CLI, Codex/ChatGPT image backend, `gpt-5.4`) using the existing `character.png` as an identity reference, chained across 4 generations for pose-to-pose consistency, then assembled to 320×320 with a single shared color palette across all 4 frames (prevents the per-frame color flicker a naive per-frame quantization would produce in the loop).
28
+
9
29
  ## [0.7.58] - 2026-07-07
10
30
  _Applied the previous entry's `routing.crossProviderPool` to the real live session config, then deep-researched every catalogued model's actual public release date (2026-07 sourced, per-provider parallel research with citations) to answer "모델 설정은 2026년도 기준으로 딥서치해서 모델리스트 최신순으로 정렬" — while researching, found and fixed a genuine ranking bug this surfaced: `strongestCredentialed`'s tiebreak on a full capability tie (identical thinking/output/context) fell through to a raw alphabetical canonical-id compare, which silently picked the OLDEST model in a same-spec family (`claude-opus-4-6` over `claude-opus-4-7`/`4-8`, purely because `"4-6" < "4-8"` as a string) — confirmed reproducible before the fix, confirmed corrected after._
11
31
 
package/README.ja.md CHANGED
@@ -16,7 +16,7 @@
16
16
  </p>
17
17
 
18
18
  <p align="center">
19
- <img src="assets/character.gif" alt="動く jeo-code 赤いザリガニマスコット" width="320" />
19
+ <img src="assets/character.gif" alt="最も安価なプロバイダーへプロンプトを賢くルーティングし、コインを稼ぐ動く jeo-code 赤いザリガニマスコット" width="320" />
20
20
  </p>
21
21
 
22
22
  <p align="center">
@@ -259,11 +259,11 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
259
259
  ## 変更履歴 (Changelog)
260
260
 
261
261
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
262
+ - **[0.8.1]** (2026-07-08) — Fix-forward for a functional regression this same session shipped in v0.8.0. This repo's working tree is shared with a concurrent interactive session; `git add`-ing `src/ai/provider-status.ts` for v0.8.0's routing-veto-gate fix also swept up that session's already-modified, uncommitted copy of the SAME file (containing an unrelated design change: gemini OAuth alone no longer serving `google/gemini-*` models) without its companion piece in `src/auth/flows/index.ts` — so v0.8.0 shipped with a test suite (`provider-status.test.ts`) asserting behavior whose supporting data flag never landed. CI caught this immediately post-release: 2 failing tests on the exact tagged commit. Investigated, confirmed the missing piece was real (and independently observable live via `jeo doctor` before this session had made ANY of its own edits — not a stray/speculative change), and completed it.
263
+ - **[0.8.0]** (2026-07-08) — Root-caused a genuine reliability bug in the routing veto gate ("라우트가 동작후 api 키인증오류가나오는데 근본적인 원인 개선해"), then regenerated the animated README mascot to visualize the smart cross-provider routing this release cycle has been building: prompt routing that finds the cheapest usable path, with a "money saver" visual motif.
262
264
  - **[0.7.58]** (2026-07-07) — Applied the previous entry's `routing.crossProviderPool` to the real live session config, then deep-researched every catalogued model's actual public release date (2026-07 sourced, per-provider parallel research with citations) to answer "모델 설정은 2026년도 기준으로 딥서치해서 모델리스트 최신순으로 정렬" — while researching, found and fixed a genuine ranking bug this surfaced: `strongestCredentialed`'s tiebreak on a full capability tie (identical thinking/output/context) fell through to a raw alphabetical canonical-id compare, which silently picked the OLDEST model in a same-spec family (`claude-opus-4-6` over `claude-opus-4-7`/`4-8`, purely because `"4-6" < "4-8"` as a string) — confirmed reproducible before the fix, confirmed corrected after.
263
265
  - **[0.7.57]** (2026-07-07) — "동급모델간 크로스 프로바이더 라우팅이 동작하게 개선" — v0.7.56's tier auto-select was single-winner by design (always the one cheapest/strongest model), so a user with `roles.smol` pinned (or simply multiple credentialed providers) never saw actual distribution across equivalent-class models. Deep-research-informed redesign: production LLM routers (LiteLLM, Bifrost/VoidLLM) model this as a named "model group" spanning provider deployments with a distribution strategy, not a single deterministic pick — jeo now supports both, opt-in.
264
266
  - **[0.7.56]** (2026-07-07) — "서로다른 프로바이더로 급에 따라 나눠서 설정하도록 개선" — PromptRouter's `trivial`/`complex` tiers previously collapsed to `defaultModel` whenever `roles.smol`/`roles.slow`/`routing.tiers.*` were left unconfigured (the documented "safe no-op absent configuration" contract), which meant a user with multiple providers credentialed never actually got cross-provider routing without hand-picking a model per tier. Also the long-term ask: routing must stay correct as jeo's model catalog evolves, never a hand-maintained tier→model table that goes stale.
265
- - **[0.7.55]** (2026-07-07) — Cross-provider routing (PromptRouter, v0.7.47+) already named the routed tier in the status bar (v0.7.52), but nothing surfaced WHICH model/provider actually produced a turn's reasoning in the Thinking-block surfaces — a gap noticed while manually verifying the v0.7.52 status-bar work end-to-end.
266
- - **[0.7.54]** (2026-07-06) — Root-cause fix for a live-reported GPT-5.5 (Codex/ChatGPT OAuth backend) failure: "Error: stream exceeded the overall deadline (JEO_STREAM_MAX_MS) — slow-drip stream aborted". Deep-dive traced this to a REGRESSION in v0.7.42 — a separate, correct fix for a genuinely-different bug (a connected-but-never-terminating stream) silently flipped `streamMaxMs()`'s default from opt-in-off to an ALWAYS-ON 300s overall wall-clock cap, which then killed any HIGH/XHIGH-reasoning-effort completion (GPT-5.5/o3-class) whose ACTIVELY-emitting generation legitimately ran past 5 minutes — OpenAI's own guidance states xhigh trades latency for depth by design. The non-streaming `call()` path (used by every subagent, compaction, and goal-verify call, since none wire `onModelStream`) carried the IDENTICAL bug with zero idle/activity tracking at all, exposed under a different, unhelpful raw `DOMException: The operation timed out.` message instead.
267
267
 
268
268
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
269
269
  <!-- CHANGELOG:END -->
package/README.ko.md CHANGED
@@ -16,7 +16,7 @@
16
16
  </p>
17
17
 
18
18
  <p align="center">
19
- <img src="assets/character.gif" alt="움직이는 jeo-code 붉은 가재 마스코트" width="320" />
19
+ <img src="assets/character.gif" alt="가장 저렴한 프로바이더로 프롬프트를 스마트하게 라우팅하며 동전을 모으는 움직이는 jeo-code 붉은 가재 마스코트" width="320" />
20
20
  </p>
21
21
 
22
22
  <p align="center">
@@ -260,11 +260,11 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
260
260
  ## 변경 이력 (Changelog)
261
261
 
262
262
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
263
+ - **[0.8.1]** (2026-07-08) — Fix-forward for a functional regression this same session shipped in v0.8.0. This repo's working tree is shared with a concurrent interactive session; `git add`-ing `src/ai/provider-status.ts` for v0.8.0's routing-veto-gate fix also swept up that session's already-modified, uncommitted copy of the SAME file (containing an unrelated design change: gemini OAuth alone no longer serving `google/gemini-*` models) without its companion piece in `src/auth/flows/index.ts` — so v0.8.0 shipped with a test suite (`provider-status.test.ts`) asserting behavior whose supporting data flag never landed. CI caught this immediately post-release: 2 failing tests on the exact tagged commit. Investigated, confirmed the missing piece was real (and independently observable live via `jeo doctor` before this session had made ANY of its own edits — not a stray/speculative change), and completed it.
264
+ - **[0.8.0]** (2026-07-08) — Root-caused a genuine reliability bug in the routing veto gate ("라우트가 동작후 api 키인증오류가나오는데 근본적인 원인 개선해"), then regenerated the animated README mascot to visualize the smart cross-provider routing this release cycle has been building: prompt routing that finds the cheapest usable path, with a "money saver" visual motif.
263
265
  - **[0.7.58]** (2026-07-07) — Applied the previous entry's `routing.crossProviderPool` to the real live session config, then deep-researched every catalogued model's actual public release date (2026-07 sourced, per-provider parallel research with citations) to answer "모델 설정은 2026년도 기준으로 딥서치해서 모델리스트 최신순으로 정렬" — while researching, found and fixed a genuine ranking bug this surfaced: `strongestCredentialed`'s tiebreak on a full capability tie (identical thinking/output/context) fell through to a raw alphabetical canonical-id compare, which silently picked the OLDEST model in a same-spec family (`claude-opus-4-6` over `claude-opus-4-7`/`4-8`, purely because `"4-6" < "4-8"` as a string) — confirmed reproducible before the fix, confirmed corrected after.
264
266
  - **[0.7.57]** (2026-07-07) — "동급모델간 크로스 프로바이더 라우팅이 동작하게 개선" — v0.7.56's tier auto-select was single-winner by design (always the one cheapest/strongest model), so a user with `roles.smol` pinned (or simply multiple credentialed providers) never saw actual distribution across equivalent-class models. Deep-research-informed redesign: production LLM routers (LiteLLM, Bifrost/VoidLLM) model this as a named "model group" spanning provider deployments with a distribution strategy, not a single deterministic pick — jeo now supports both, opt-in.
265
267
  - **[0.7.56]** (2026-07-07) — "서로다른 프로바이더로 급에 따라 나눠서 설정하도록 개선" — PromptRouter's `trivial`/`complex` tiers previously collapsed to `defaultModel` whenever `roles.smol`/`roles.slow`/`routing.tiers.*` were left unconfigured (the documented "safe no-op absent configuration" contract), which meant a user with multiple providers credentialed never actually got cross-provider routing without hand-picking a model per tier. Also the long-term ask: routing must stay correct as jeo's model catalog evolves, never a hand-maintained tier→model table that goes stale.
266
- - **[0.7.55]** (2026-07-07) — Cross-provider routing (PromptRouter, v0.7.47+) already named the routed tier in the status bar (v0.7.52), but nothing surfaced WHICH model/provider actually produced a turn's reasoning in the Thinking-block surfaces — a gap noticed while manually verifying the v0.7.52 status-bar work end-to-end.
267
- - **[0.7.54]** (2026-07-06) — Root-cause fix for a live-reported GPT-5.5 (Codex/ChatGPT OAuth backend) failure: "Error: stream exceeded the overall deadline (JEO_STREAM_MAX_MS) — slow-drip stream aborted". Deep-dive traced this to a REGRESSION in v0.7.42 — a separate, correct fix for a genuinely-different bug (a connected-but-never-terminating stream) silently flipped `streamMaxMs()`'s default from opt-in-off to an ALWAYS-ON 300s overall wall-clock cap, which then killed any HIGH/XHIGH-reasoning-effort completion (GPT-5.5/o3-class) whose ACTIVELY-emitting generation legitimately ran past 5 minutes — OpenAI's own guidance states xhigh trades latency for depth by design. The non-streaming `call()` path (used by every subagent, compaction, and goal-verify call, since none wire `onModelStream`) carried the IDENTICAL bug with zero idle/activity tracking at all, exposed under a different, unhelpful raw `DOMException: The operation timed out.` message instead.
268
268
 
269
269
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
270
270
  <!-- CHANGELOG:END -->
package/README.md CHANGED
@@ -16,7 +16,7 @@
16
16
  </p>
17
17
 
18
18
  <p align="center">
19
- <img src="assets/character.gif" alt="animated jeo-code red crayfish mascot" width="320" />
19
+ <img src="assets/character.gif" alt="animated jeo-code red crayfish mascot smart-routing a prompt to the cheapest provider and saving coins" width="320" />
20
20
  </p>
21
21
 
22
22
  <p align="center">
@@ -264,11 +264,11 @@ Huge thanks to [gajae-code](https://github.com/Yeachan-Heo/gajae-code) for the i
264
264
  ## Changelog
265
265
 
266
266
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
267
+ - **[0.8.1]** (2026-07-08) — Fix-forward for a functional regression this same session shipped in v0.8.0. This repo's working tree is shared with a concurrent interactive session; `git add`-ing `src/ai/provider-status.ts` for v0.8.0's routing-veto-gate fix also swept up that session's already-modified, uncommitted copy of the SAME file (containing an unrelated design change: gemini OAuth alone no longer serving `google/gemini-*` models) without its companion piece in `src/auth/flows/index.ts` — so v0.8.0 shipped with a test suite (`provider-status.test.ts`) asserting behavior whose supporting data flag never landed. CI caught this immediately post-release: 2 failing tests on the exact tagged commit. Investigated, confirmed the missing piece was real (and independently observable live via `jeo doctor` before this session had made ANY of its own edits — not a stray/speculative change), and completed it.
268
+ - **[0.8.0]** (2026-07-08) — Root-caused a genuine reliability bug in the routing veto gate ("라우트가 동작후 api 키인증오류가나오는데 근본적인 원인 개선해"), then regenerated the animated README mascot to visualize the smart cross-provider routing this release cycle has been building: prompt routing that finds the cheapest usable path, with a "money saver" visual motif.
267
269
  - **[0.7.58]** (2026-07-07) — Applied the previous entry's `routing.crossProviderPool` to the real live session config, then deep-researched every catalogued model's actual public release date (2026-07 sourced, per-provider parallel research with citations) to answer "모델 설정은 2026년도 기준으로 딥서치해서 모델리스트 최신순으로 정렬" — while researching, found and fixed a genuine ranking bug this surfaced: `strongestCredentialed`'s tiebreak on a full capability tie (identical thinking/output/context) fell through to a raw alphabetical canonical-id compare, which silently picked the OLDEST model in a same-spec family (`claude-opus-4-6` over `claude-opus-4-7`/`4-8`, purely because `"4-6" < "4-8"` as a string) — confirmed reproducible before the fix, confirmed corrected after.
268
270
  - **[0.7.57]** (2026-07-07) — "동급모델간 크로스 프로바이더 라우팅이 동작하게 개선" — v0.7.56's tier auto-select was single-winner by design (always the one cheapest/strongest model), so a user with `roles.smol` pinned (or simply multiple credentialed providers) never saw actual distribution across equivalent-class models. Deep-research-informed redesign: production LLM routers (LiteLLM, Bifrost/VoidLLM) model this as a named "model group" spanning provider deployments with a distribution strategy, not a single deterministic pick — jeo now supports both, opt-in.
269
271
  - **[0.7.56]** (2026-07-07) — "서로다른 프로바이더로 급에 따라 나눠서 설정하도록 개선" — PromptRouter's `trivial`/`complex` tiers previously collapsed to `defaultModel` whenever `roles.smol`/`roles.slow`/`routing.tiers.*` were left unconfigured (the documented "safe no-op absent configuration" contract), which meant a user with multiple providers credentialed never actually got cross-provider routing without hand-picking a model per tier. Also the long-term ask: routing must stay correct as jeo's model catalog evolves, never a hand-maintained tier→model table that goes stale.
270
- - **[0.7.55]** (2026-07-07) — Cross-provider routing (PromptRouter, v0.7.47+) already named the routed tier in the status bar (v0.7.52), but nothing surfaced WHICH model/provider actually produced a turn's reasoning in the Thinking-block surfaces — a gap noticed while manually verifying the v0.7.52 status-bar work end-to-end.
271
- - **[0.7.54]** (2026-07-06) — Root-cause fix for a live-reported GPT-5.5 (Codex/ChatGPT OAuth backend) failure: "Error: stream exceeded the overall deadline (JEO_STREAM_MAX_MS) — slow-drip stream aborted". Deep-dive traced this to a REGRESSION in v0.7.42 — a separate, correct fix for a genuinely-different bug (a connected-but-never-terminating stream) silently flipped `streamMaxMs()`'s default from opt-in-off to an ALWAYS-ON 300s overall wall-clock cap, which then killed any HIGH/XHIGH-reasoning-effort completion (GPT-5.5/o3-class) whose ACTIVELY-emitting generation legitimately ran past 5 minutes — OpenAI's own guidance states xhigh trades latency for depth by design. The non-streaming `call()` path (used by every subagent, compaction, and goal-verify call, since none wire `onModelStream`) carried the IDENTICAL bug with zero idle/activity tracking at all, exposed under a different, unhelpful raw `DOMException: The operation timed out.` message instead.
272
272
 
273
273
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
274
274
  <!-- CHANGELOG:END -->
package/README.zh.md CHANGED
@@ -16,7 +16,7 @@
16
16
  </p>
17
17
 
18
18
  <p align="center">
19
- <img src="assets/character.gif" alt="动态 jeo-code 红色小龙虾吉祥物" width="320" />
19
+ <img src="assets/character.gif" alt="动态 jeo-code 红色小龙虾吉祥物智能路由到最便宜的提供商并赚取金币" width="320" />
20
20
  </p>
21
21
 
22
22
  <p align="center">
@@ -259,11 +259,11 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
259
259
  ## 更新日志 (Changelog)
260
260
 
261
261
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
262
+ - **[0.8.1]** (2026-07-08) — Fix-forward for a functional regression this same session shipped in v0.8.0. This repo's working tree is shared with a concurrent interactive session; `git add`-ing `src/ai/provider-status.ts` for v0.8.0's routing-veto-gate fix also swept up that session's already-modified, uncommitted copy of the SAME file (containing an unrelated design change: gemini OAuth alone no longer serving `google/gemini-*` models) without its companion piece in `src/auth/flows/index.ts` — so v0.8.0 shipped with a test suite (`provider-status.test.ts`) asserting behavior whose supporting data flag never landed. CI caught this immediately post-release: 2 failing tests on the exact tagged commit. Investigated, confirmed the missing piece was real (and independently observable live via `jeo doctor` before this session had made ANY of its own edits — not a stray/speculative change), and completed it.
263
+ - **[0.8.0]** (2026-07-08) — Root-caused a genuine reliability bug in the routing veto gate ("라우트가 동작후 api 키인증오류가나오는데 근본적인 원인 개선해"), then regenerated the animated README mascot to visualize the smart cross-provider routing this release cycle has been building: prompt routing that finds the cheapest usable path, with a "money saver" visual motif.
262
264
  - **[0.7.58]** (2026-07-07) — Applied the previous entry's `routing.crossProviderPool` to the real live session config, then deep-researched every catalogued model's actual public release date (2026-07 sourced, per-provider parallel research with citations) to answer "모델 설정은 2026년도 기준으로 딥서치해서 모델리스트 최신순으로 정렬" — while researching, found and fixed a genuine ranking bug this surfaced: `strongestCredentialed`'s tiebreak on a full capability tie (identical thinking/output/context) fell through to a raw alphabetical canonical-id compare, which silently picked the OLDEST model in a same-spec family (`claude-opus-4-6` over `claude-opus-4-7`/`4-8`, purely because `"4-6" < "4-8"` as a string) — confirmed reproducible before the fix, confirmed corrected after.
263
265
  - **[0.7.57]** (2026-07-07) — "동급모델간 크로스 프로바이더 라우팅이 동작하게 개선" — v0.7.56's tier auto-select was single-winner by design (always the one cheapest/strongest model), so a user with `roles.smol` pinned (or simply multiple credentialed providers) never saw actual distribution across equivalent-class models. Deep-research-informed redesign: production LLM routers (LiteLLM, Bifrost/VoidLLM) model this as a named "model group" spanning provider deployments with a distribution strategy, not a single deterministic pick — jeo now supports both, opt-in.
264
266
  - **[0.7.56]** (2026-07-07) — "서로다른 프로바이더로 급에 따라 나눠서 설정하도록 개선" — PromptRouter's `trivial`/`complex` tiers previously collapsed to `defaultModel` whenever `roles.smol`/`roles.slow`/`routing.tiers.*` were left unconfigured (the documented "safe no-op absent configuration" contract), which meant a user with multiple providers credentialed never actually got cross-provider routing without hand-picking a model per tier. Also the long-term ask: routing must stay correct as jeo's model catalog evolves, never a hand-maintained tier→model table that goes stale.
265
- - **[0.7.55]** (2026-07-07) — Cross-provider routing (PromptRouter, v0.7.47+) already named the routed tier in the status bar (v0.7.52), but nothing surfaced WHICH model/provider actually produced a turn's reasoning in the Thinking-block surfaces — a gap noticed while manually verifying the v0.7.52 status-bar work end-to-end.
266
- - **[0.7.54]** (2026-07-06) — Root-cause fix for a live-reported GPT-5.5 (Codex/ChatGPT OAuth backend) failure: "Error: stream exceeded the overall deadline (JEO_STREAM_MAX_MS) — slow-drip stream aborted". Deep-dive traced this to a REGRESSION in v0.7.42 — a separate, correct fix for a genuinely-different bug (a connected-but-never-terminating stream) silently flipped `streamMaxMs()`'s default from opt-in-off to an ALWAYS-ON 300s overall wall-clock cap, which then killed any HIGH/XHIGH-reasoning-effort completion (GPT-5.5/o3-class) whose ACTIVELY-emitting generation legitimately ran past 5 minutes — OpenAI's own guidance states xhigh trades latency for depth by design. The non-streaming `call()` path (used by every subagent, compaction, and goal-verify call, since none wire `onModelStream`) carried the IDENTICAL bug with zero idle/activity tracking at all, exposed under a different, unhelpful raw `DOMException: The operation timed out.` message instead.
267
267
 
268
268
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
269
269
  <!-- CHANGELOG:END -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jeo-code",
3
- "version": "0.7.58",
3
+ "version": "0.8.1",
4
4
  "description": "Clean, highly optimized AI coding agent using spec-first loop",
5
5
  "type": "module",
6
6
  "main": "src/cli.ts",
@@ -299,9 +299,12 @@ export function resolveRetryOptions(retry: Config["retry"], kind: "request" | "s
299
299
  * OpenAI OAuth (ChatGPT/Codex) only serves Codex models, so any other OpenAI model
300
300
  * must use an API key. Kimi OAuth (Kimi Code subscription) only serves the Kimi Code
301
301
  * catalog (api.kimi.com/coding) — Moonshot API-platform ids (kimi-latest, moonshot-v1-*)
302
- * need a KIMI_API_KEY. A provider whose OAuth backend is not verified end-to-end
303
- * cannot serve any model over OAuth. Everything else (Anthropic Messages, Gemini /
304
- * Antigravity Cloud Code Assist) is served end-to-end.
302
+ * need a KIMI_API_KEY. Gemini OAuth no longer masquerades as gemini-cli against Cloud
303
+ * Code Assist, so it never serves google/gemini-* models on its own — a GEMINI_API_KEY
304
+ * is required (the SAME models remain OAuth-only reachable via antigravity/*). A
305
+ * provider whose OAuth backend is not verified end-to-end cannot serve any model over
306
+ * OAuth. Everything else (Anthropic Messages, Antigravity Cloud Code Assist) is served
307
+ * end-to-end.
305
308
  */
306
309
  function oauthServesModel(provider: AuthProvider, model: string): boolean {
307
310
  if (provider === "openai") return CODEX_MODELS.includes(model);
@@ -331,8 +334,11 @@ export function effectiveCredentialForProvider(
331
334
  const apiKey = config.providers[provider];
332
335
  if (apiKey) return { kind: "api_key", provider, token: apiKey };
333
336
  if (isOAuthProvider(provider) && OAUTH_FLOW_REGISTRY[provider].verifiedEndToEnd === false) {
337
+ const hint = provider === "gemini"
338
+ ? " Or use an antigravity/* model instead — the SAME Gemini models served OAuth-only via Cloud Code Assist (run 'jeo auth login antigravity')."
339
+ : "";
334
340
  throw new Error(
335
- `Provider '${provider}' has only an OAuth token, but its OAuth backend is not compatible with the bundled adapter. Set ${provider.toUpperCase()}_API_KEY (or run 'jeo setup') to use ${model}.`,
341
+ `Provider '${provider}' has only an OAuth token, but its OAuth backend is not compatible with the bundled adapter. Set ${provider.toUpperCase()}_API_KEY (or run 'jeo setup') to use ${model}.${hint}`,
336
342
  );
337
343
  }
338
344
  }
@@ -425,8 +431,10 @@ async function resolveCall(options: Partial<CallOptions>, kind: "request" | "str
425
431
 
426
432
  if (provider === "antigravity") {
427
433
  // Prefer the dedicated Antigravity login (its client is what the agent
428
- // backend authorizes); fall back to a gemini-cli OAuth token for users with
429
- // their own project/permissions.
434
+ // backend authorizes); fall back to a plain gemini OAuth token for users with
435
+ // their own project/permissions — this is now the DEFAULT OAuth-served path for
436
+ // Gemini models (google/gemini-* itself requires GEMINI_API_KEY, see
437
+ // `oauthServesModel` above).
430
438
  let credential = await resolveCredential("antigravity");
431
439
  if (credential.kind !== "oauth") credential = await resolveCredential("gemini");
432
440
  if (credential.kind !== "oauth") {
@@ -72,6 +72,21 @@ function oauthLoginInfo(stored: string | StoredOAuth | undefined): { loggedIn: b
72
72
  return { loggedIn: true, oauthEmail: stored.email, oauthExpires: stored.expires };
73
73
  }
74
74
 
75
+ /** True when a stored OAuth entry is DEFINITIVELY unusable right now: its access
76
+ * token is past expiry AND it has no refresh token to recover with. This is the
77
+ * gap `ready` used to miss — checking only "is a credential object present",
78
+ * never whether it's actually alive — so a session could route to a provider
79
+ * whose OAuth died (refresh token revoked/missing) and only discover that via a
80
+ * raw 401 from the real API call, after routing had already committed to it.
81
+ * An expired token WITH a refresh token is NOT flagged dead: `resolveCredential`
82
+ * (the real call path) auto-refreshes those before use, so a live refresh token
83
+ * self-heals without ever reaching this check. Legacy string-only stored tokens
84
+ * (no tracked expiry) are never flagged dead either — there's nothing to check. */
85
+ function oauthEntryDead(stored: string | StoredOAuth | undefined): boolean {
86
+ if (!stored || typeof stored === "string") return false;
87
+ return !!stored.expires && stored.expires <= Date.now() && !stored.refresh;
88
+ }
89
+
75
90
  function configuredCredential(provider: AuthProvider, cfg: Config): Credential {
76
91
  const stored = cfg.oauth?.[provider];
77
92
  const oauth = oauthAccess(stored);
@@ -118,29 +133,39 @@ export async function describeProvider(name: ProviderName, config?: Config): Pro
118
133
  }
119
134
  const ownProvider = name as AuthProvider;
120
135
  const ownCred = configuredCredential(ownProvider, cfg);
121
- // Antigravity prefers its own login but accepts a gemini-cli OAuth fallback.
136
+ // Antigravity prefers its own login but accepts a gemini OAuth fallback (the
137
+ // DEFAULT OAuth-served path for Gemini models now that plain gemini/* requires
138
+ // an API key).
122
139
  const cred = name === "antigravity" && ownCred.kind === "none" ? configuredCredential("gemini", cfg) : ownCred;
123
140
  const credentialProvider: AuthProvider = name === "antigravity" && ownCred.kind === "none" ? "gemini" : ownProvider;
124
141
  const effective = name === "antigravity" ? cred : effectiveCredential(credentialProvider, cred, cfg);
125
142
  const kind: CredentialKind = effective.kind === "api_key" ? "api_key" : effective.kind === "oauth" ? "oauth" : "none";
126
143
  const baseUrl = name === "openai" && kind !== "oauth" ? cfg.openaiBaseUrl : undefined;
127
- let ready = kind !== "none" || (name === "openai" && !!cfg.openaiBaseUrl);
144
+ let ready = kind === "api_key" || (kind === "oauth" && !oauthEntryDead(cfg.oauth?.[credentialProvider])) || (name === "openai" && !!cfg.openaiBaseUrl);
128
145
  let label = ready && kind === "none" ? "keyless (local base URL)" : credentialLabel(kind);
129
146
  if (name === "antigravity") {
130
- const hasOwnOAuth = ownCred.kind === "oauth";
131
- const hasGeminiFallback = !hasOwnOAuth && configuredCredential("gemini", cfg).kind === "oauth";
132
- ready = hasOwnOAuth;
147
+ const hasOwnOAuth = ownCred.kind === "oauth" && !oauthEntryDead(cfg.oauth?.antigravity);
148
+ const hasGeminiFallback = !hasOwnOAuth && configuredCredential("gemini", cfg).kind === "oauth" && !oauthEntryDead(cfg.oauth?.gemini);
149
+ const ownDead = ownCred.kind === "oauth" && !hasOwnOAuth;
150
+ ready = hasOwnOAuth || hasGeminiFallback;
133
151
  label = hasOwnOAuth
134
152
  ? "OAuth (Antigravity Cloud Code Assist)"
135
153
  : hasGeminiFallback
136
- ? "OAuth catalog via Gemini CLI; calls need 'jeo auth login antigravity'"
137
- : "none (run 'jeo auth login antigravity')";
154
+ ? "OAuth (Cloud Code Assist via gemini login fallback)"
155
+ : ownDead
156
+ ? "OAuth expired, no refresh token — run 'jeo auth login antigravity' to re-authenticate"
157
+ : "none (run 'jeo auth login antigravity')";
158
+ } else if (name === "gemini" && kind === "oauth") {
159
+ // OAuth alone no longer serves google/gemini-* (the gemini-cli/Cloud Code Assist
160
+ // masquerade was removed) — GEMINI_API_KEY is required. The SAME models remain
161
+ // OAuth-only reachable via antigravity/*.
162
+ ready = false;
163
+ label = "OAuth catalog only; set GEMINI_API_KEY, or use antigravity/* models (Cloud Code Assist)";
138
164
  } else if (kind === "oauth" && isOAuthProvider(credentialProvider) && OAUTH_FLOW_REGISTRY[credentialProvider].verifiedEndToEnd === false) {
139
165
  ready = false;
140
166
  label = "OAuth (API key needed)";
141
- } else if (name === "gemini" && kind === "oauth") {
142
- // gemini-cli OAuth is served end-to-end via Cloud Code Assist — no API key.
143
- label = "OAuth (Gemini CLI / Cloud Code Assist)";
167
+ } else if (kind === "oauth" && oauthEntryDead(cfg.oauth?.[credentialProvider])) {
168
+ label = `OAuth expired, no refresh token — run 'jeo auth login ${credentialProvider}' to re-authenticate`;
144
169
  }
145
170
  // Login status reflects the provider's OWN stored OAuth (e.g. "logged in to antigravity"),
146
171
  // independent of any cross-provider credential fallback used for readiness.
@@ -39,11 +39,11 @@ export const OAUTH_FLOW_REGISTRY: Record<OAuthProvider, OAuthFlow> = {
39
39
  },
40
40
  gemini: {
41
41
  provider: "gemini",
42
- label: "Google (Gemini CLI / Cloud Code Assist)",
42
+ label: "Google (Gemini API)",
43
43
  login: loginGoogle,
44
44
  refresh: refreshGoogleToken,
45
- verifiedEndToEnd: true,
46
- note: "Served via the Cloud Code Assist backend (cloudcode-pa.googleapis.com) with an auto-discovered project — gemini-cli parity, no API key needed. A GEMINI_API_KEY, when set, takes precedence and uses the public generativelanguage API.",
45
+ verifiedEndToEnd: false,
46
+ note: "This OAuth login alone no longer serves google/gemini-* models (the Cloud Code Assist / gemini-cli masquerade was removed) — set GEMINI_API_KEY to use them. The SAME Gemini models remain reachable OAuth-only via antigravity/* (run 'jeo auth login antigravity'; this gemini login also works as its fallback credential).",
47
47
  },
48
48
  antigravity: {
49
49
  provider: "antigravity",