jeo-code 0.7.22 → 0.7.24

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,40 @@ 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.7.24] - 2026-07-02
10
+ _OAuth callback page redesign with embedded jeo wordmark + Close button; memory budget O(n²)→O(n) incremental tracking fix._
11
+
12
+ ### Added
13
+ - **OAuth callback page: jeo wordmark + Close button.** The post-login browser page now embeds the jeo synthwave wordmark as an inline WebP data URI (no static-asset pipeline required in a compiled binary), and gains an explicit `id="jeo-close"` button that calls `window.close()` immediately — the countdown timer remains as a fallback (bumped 3→5 s). Both success and failure pages now auto-close so OAuth tabs never linger.
14
+ - **`HEADER_BY_TYPE` lookup for memory budget tracking.** Extracted from `TYPE_LAYOUT` alongside the existing `DIR_BY_TYPE`, enabling incremental section-header length computation in `selectWithinBudget` without a full re-render.
15
+
16
+ ### Fixed
17
+ - **`selectWithinBudget` O(n²)→O(n) budget tracking.** Extracted `renderConceptItem` and introduced per-type accumulated section-length tracking so each candidate's budget contribution is computed incrementally (header + item delta) rather than by re-rendering the entire accumulated selection. Produces identical truncation boundaries with O(n) work instead of O(n²); locked by a new regression test (`memory-search-okf.test.ts`) that verifies the exact 50-of-60 cutoff against a hand-computed derivation.
18
+ - **OAuth failure page now shows auto-close / Close button.** Previously the failure page intentionally suppressed `window.close()`; it now uses the same countdown + Close button as the success page so users are not stranded on an error screen.
19
+
20
+ ### Verified
21
+ - `bun run typecheck` clean; full suite green — 2064 pass / 0 fail across 237 files — including the new `selectWithinBudget` incremental-budget regression test and updated OAuth callback page assertions (`id="jeo-countdown"`, `id="jeo-close"`, `window.close()` present on both success and failure pages).
22
+
23
+ ## [0.7.23] - 2026-07-01
24
+ _Anthropic 5th-generation model catalog: adds Sonnet 5, Fable 5, and Mythos 5, promotes the 4.6 generation to 1M context / 128k output, and retires the superseded 4.5/3.5/4.1 ids. Bumps the default model to Sonnet 4.6, prices the new tiers, and fixes the thinking transport for Haiku 4.5 and every 5th-gen model._
25
+
26
+ ### Added
27
+ - **Anthropic 5th-generation models.** New catalog entries `claude-sonnet-5`, `claude-fable-5` (most capable widely-released, adaptive thinking always on), and `claude-mythos-5` (limited-availability, callable by id). `inferCatalogMetadata` now recognizes the `fable`/`mythos` families, and the Anthropic version parser accepts dateless single-major ids (e.g. `claude-sonnet-5`).
28
+ - **1M context / 128k output for the 4.6 generation onward.** `claude-opus-4-6/4-7/4-8`, `claude-sonnet-4-6`, and all 5th-gen ids now advertise 1,000,000-token context and 128,000-token max output (Anthropic docs); dated pre-4.6 ids stay at 200k/64k.
29
+ - **Pricing for the new tiers.** Opus 4.8 (`$5`/`$25` per M), Fable/Mythos (`$10`/`$50`), and Sonnet 5 (`$2`/`$10`), ordered ahead of the generic Opus/Sonnet families so the newer tiers win.
30
+
31
+ ### Fixed
32
+ - **Haiku 4.5 thinking transport.** Haiku 4.5 supports `budget_tokens` thinking but rejects `output_config.effort` ("This model does not support the effort parameter"), so it now uses the plain budget transport instead of its sonnet/opus 4.5 siblings' budget-effort mode.
33
+ - **Adaptive thinking display for 5th-gen models.** `display: "summarized"` is now enabled for every 5th-generation+ model (Sonnet 5, Fable 5, Mythos 5) in addition to Opus ≥ 4.7, so their reasoning streams instead of being silently omitted.
34
+ - **Gemini path no longer inherits a `claude-` model id.** A `claude-`-prefixed model falls back to `gemini-2.0-flash` on the Gemini transport instead of forwarding an incompatible id.
35
+
36
+ ### Changed
37
+ - **Default model bumped to `claude-sonnet-4-6`.** Runtime default, setup defaults, recommended-model set, and the `sonnet`/`opus` aliases now point at the 4.6 generation.
38
+ - **Retired superseded ids.** Removed `claude-3-5-sonnet`, `claude-opus-4-1`, `claude-opus-4-5`, and `claude-sonnet-4-5` (plus their Antigravity `-thinking` variants) from the catalog.
39
+
40
+ ### Verified
41
+ - `bun run typecheck` clean; full `bun test` suite green. Updated model-catalog, pricing, registry, and Anthropic-stream tests cover the new families, 1M/128k metadata, and the Haiku 4.5 transport.
42
+
9
43
  ## [0.7.22] - 2026-06-30
10
44
  _gajae-code 0.7.3–0.7.8 parity sweep: terminal-bell notifications, a gruvbox-dark TUI theme, a resilient `jeo update` runtime check, and an ultragoal artifact gate. Ports the user-facing improvements from upstream gajae-code that jeo did not yet have, re-implemented to fit jeo's config, command, theming, and skill surfaces._
11
45
 
package/README.ja.md CHANGED
@@ -205,11 +205,11 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
205
205
  ## 変更履歴 (Changelog)
206
206
 
207
207
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
208
+ - **[0.7.24]** (2026-07-02) — OAuth callback page redesign with embedded jeo wordmark + Close button; memory budget O(n²)→O(n) incremental tracking fix.
209
+ - **[0.7.23]** (2026-07-01) — Anthropic 5th-generation model catalog: adds Sonnet 5, Fable 5, and Mythos 5, promotes the 4.6 generation to 1M context / 128k output, and retires the superseded 4.5/3.5/4.1 ids. Bumps the default model to Sonnet 4.6, prices the new tiers, and fixes the thinking transport for Haiku 4.5 and every 5th-gen model.
208
210
  - **[0.7.22]** (2026-06-30) — gajae-code 0.7.3–0.7.8 parity sweep: terminal-bell notifications, a gruvbox-dark TUI theme, a resilient `jeo update` runtime check, and an ultragoal artifact gate. Ports the user-facing improvements from upstream gajae-code that jeo did not yet have, re-implemented to fit jeo's config, command, theming, and skill surfaces.
209
211
  - **[0.7.21]** (2026-06-26) — Global llm-wiki vault integration, Gemini/Antigravity thinking indicators, generous file-reading windows, and autopilot flag validation. Adds a shared global wiki root configuration with a /wiki slash command, fires reasoning start signals up front for Gemini/Antigravity models, adjusts the large-file reading discipline to use generous windows, and validates autopilot goal and integer flags.
210
212
  - **[0.7.20]** (2026-06-26) — OKF concept-memory retrieval gains a hybrid reranker ported from memsearch. Injection priority no longer rides one raw keyword score — it fuses two complementary ranked channels by Reciprocal Rank Fusion (RRF): IDF-weighted lexical relevance (the sparse/BM25 channel, so rare discriminating terms steer recall) and concept-graph proximity (the local dense/semantic-neighbour channel, so a hub linked from multiple query hits surfaces even with no keyword of its own). All embedding-free and deterministic, layered atop the existing failure-first tier and pinned-invariant reserved budget.
211
- - **[0.7.19]** (2026-06-26) — The live model picker gains gajae-code's `/model` provider tabs, and skill invocation is consolidated onto a single `$` entrypoint. The picker now shows an `ALL` tab plus one tab per provider that `tab`/`shift+tab` cycles, and skills (including their declared aliases) are invoked only via `$` — the slash palette stays builtins-only.
212
- - **[0.7.18]** (2026-06-26) — Slash-command discovery and the `/model` flow reach gajae-code parity. The slash palette/autocomplete now fuzzy-matches command names (with a description fallback for intent-style queries), resolved skills can contribute their own `/aliases` as real dispatchable commands, and `/model` runs gjc's two-menu target → reasoning flow so a picked model can be assigned to the default or any subagent role with its own thinking budget.
213
213
 
214
214
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
215
215
  <!-- CHANGELOG:END -->
package/README.ko.md CHANGED
@@ -205,11 +205,11 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
205
205
  ## 변경 이력 (Changelog)
206
206
 
207
207
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
208
+ - **[0.7.24]** (2026-07-02) — OAuth callback page redesign with embedded jeo wordmark + Close button; memory budget O(n²)→O(n) incremental tracking fix.
209
+ - **[0.7.23]** (2026-07-01) — Anthropic 5th-generation model catalog: adds Sonnet 5, Fable 5, and Mythos 5, promotes the 4.6 generation to 1M context / 128k output, and retires the superseded 4.5/3.5/4.1 ids. Bumps the default model to Sonnet 4.6, prices the new tiers, and fixes the thinking transport for Haiku 4.5 and every 5th-gen model.
208
210
  - **[0.7.22]** (2026-06-30) — gajae-code 0.7.3–0.7.8 parity sweep: terminal-bell notifications, a gruvbox-dark TUI theme, a resilient `jeo update` runtime check, and an ultragoal artifact gate. Ports the user-facing improvements from upstream gajae-code that jeo did not yet have, re-implemented to fit jeo's config, command, theming, and skill surfaces.
209
211
  - **[0.7.21]** (2026-06-26) — Global llm-wiki vault integration, Gemini/Antigravity thinking indicators, generous file-reading windows, and autopilot flag validation. Adds a shared global wiki root configuration with a /wiki slash command, fires reasoning start signals up front for Gemini/Antigravity models, adjusts the large-file reading discipline to use generous windows, and validates autopilot goal and integer flags.
210
212
  - **[0.7.20]** (2026-06-26) — OKF concept-memory retrieval gains a hybrid reranker ported from memsearch. Injection priority no longer rides one raw keyword score — it fuses two complementary ranked channels by Reciprocal Rank Fusion (RRF): IDF-weighted lexical relevance (the sparse/BM25 channel, so rare discriminating terms steer recall) and concept-graph proximity (the local dense/semantic-neighbour channel, so a hub linked from multiple query hits surfaces even with no keyword of its own). All embedding-free and deterministic, layered atop the existing failure-first tier and pinned-invariant reserved budget.
211
- - **[0.7.19]** (2026-06-26) — The live model picker gains gajae-code's `/model` provider tabs, and skill invocation is consolidated onto a single `$` entrypoint. The picker now shows an `ALL` tab plus one tab per provider that `tab`/`shift+tab` cycles, and skills (including their declared aliases) are invoked only via `$` — the slash palette stays builtins-only.
212
- - **[0.7.18]** (2026-06-26) — Slash-command discovery and the `/model` flow reach gajae-code parity. The slash palette/autocomplete now fuzzy-matches command names (with a description fallback for intent-style queries), resolved skills can contribute their own `/aliases` as real dispatchable commands, and `/model` runs gjc's two-menu target → reasoning flow so a picked model can be assigned to the default or any subagent role with its own thinking budget.
213
213
 
214
214
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
215
215
  <!-- CHANGELOG:END -->
package/README.md CHANGED
@@ -232,11 +232,11 @@ Huge thanks to [gajae-code](https://github.com/Yeachan-Heo/gajae-code) for the i
232
232
  ## Changelog
233
233
 
234
234
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
235
+ - **[0.7.24]** (2026-07-02) — OAuth callback page redesign with embedded jeo wordmark + Close button; memory budget O(n²)→O(n) incremental tracking fix.
236
+ - **[0.7.23]** (2026-07-01) — Anthropic 5th-generation model catalog: adds Sonnet 5, Fable 5, and Mythos 5, promotes the 4.6 generation to 1M context / 128k output, and retires the superseded 4.5/3.5/4.1 ids. Bumps the default model to Sonnet 4.6, prices the new tiers, and fixes the thinking transport for Haiku 4.5 and every 5th-gen model.
235
237
  - **[0.7.22]** (2026-06-30) — gajae-code 0.7.3–0.7.8 parity sweep: terminal-bell notifications, a gruvbox-dark TUI theme, a resilient `jeo update` runtime check, and an ultragoal artifact gate. Ports the user-facing improvements from upstream gajae-code that jeo did not yet have, re-implemented to fit jeo's config, command, theming, and skill surfaces.
236
238
  - **[0.7.21]** (2026-06-26) — Global llm-wiki vault integration, Gemini/Antigravity thinking indicators, generous file-reading windows, and autopilot flag validation. Adds a shared global wiki root configuration with a /wiki slash command, fires reasoning start signals up front for Gemini/Antigravity models, adjusts the large-file reading discipline to use generous windows, and validates autopilot goal and integer flags.
237
239
  - **[0.7.20]** (2026-06-26) — OKF concept-memory retrieval gains a hybrid reranker ported from memsearch. Injection priority no longer rides one raw keyword score — it fuses two complementary ranked channels by Reciprocal Rank Fusion (RRF): IDF-weighted lexical relevance (the sparse/BM25 channel, so rare discriminating terms steer recall) and concept-graph proximity (the local dense/semantic-neighbour channel, so a hub linked from multiple query hits surfaces even with no keyword of its own). All embedding-free and deterministic, layered atop the existing failure-first tier and pinned-invariant reserved budget.
238
- - **[0.7.19]** (2026-06-26) — The live model picker gains gajae-code's `/model` provider tabs, and skill invocation is consolidated onto a single `$` entrypoint. The picker now shows an `ALL` tab plus one tab per provider that `tab`/`shift+tab` cycles, and skills (including their declared aliases) are invoked only via `$` — the slash palette stays builtins-only.
239
- - **[0.7.18]** (2026-06-26) — Slash-command discovery and the `/model` flow reach gajae-code parity. The slash palette/autocomplete now fuzzy-matches command names (with a description fallback for intent-style queries), resolved skills can contribute their own `/aliases` as real dispatchable commands, and `/model` runs gjc's two-menu target → reasoning flow so a picked model can be assigned to the default or any subagent role with its own thinking budget.
240
240
 
241
241
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
242
242
  <!-- CHANGELOG:END -->
package/README.zh.md CHANGED
@@ -205,11 +205,11 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
205
205
  ## 更新日志 (Changelog)
206
206
 
207
207
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
208
+ - **[0.7.24]** (2026-07-02) — OAuth callback page redesign with embedded jeo wordmark + Close button; memory budget O(n²)→O(n) incremental tracking fix.
209
+ - **[0.7.23]** (2026-07-01) — Anthropic 5th-generation model catalog: adds Sonnet 5, Fable 5, and Mythos 5, promotes the 4.6 generation to 1M context / 128k output, and retires the superseded 4.5/3.5/4.1 ids. Bumps the default model to Sonnet 4.6, prices the new tiers, and fixes the thinking transport for Haiku 4.5 and every 5th-gen model.
208
210
  - **[0.7.22]** (2026-06-30) — gajae-code 0.7.3–0.7.8 parity sweep: terminal-bell notifications, a gruvbox-dark TUI theme, a resilient `jeo update` runtime check, and an ultragoal artifact gate. Ports the user-facing improvements from upstream gajae-code that jeo did not yet have, re-implemented to fit jeo's config, command, theming, and skill surfaces.
209
211
  - **[0.7.21]** (2026-06-26) — Global llm-wiki vault integration, Gemini/Antigravity thinking indicators, generous file-reading windows, and autopilot flag validation. Adds a shared global wiki root configuration with a /wiki slash command, fires reasoning start signals up front for Gemini/Antigravity models, adjusts the large-file reading discipline to use generous windows, and validates autopilot goal and integer flags.
210
212
  - **[0.7.20]** (2026-06-26) — OKF concept-memory retrieval gains a hybrid reranker ported from memsearch. Injection priority no longer rides one raw keyword score — it fuses two complementary ranked channels by Reciprocal Rank Fusion (RRF): IDF-weighted lexical relevance (the sparse/BM25 channel, so rare discriminating terms steer recall) and concept-graph proximity (the local dense/semantic-neighbour channel, so a hub linked from multiple query hits surfaces even with no keyword of its own). All embedding-free and deterministic, layered atop the existing failure-first tier and pinned-invariant reserved budget.
211
- - **[0.7.19]** (2026-06-26) — The live model picker gains gajae-code's `/model` provider tabs, and skill invocation is consolidated onto a single `$` entrypoint. The picker now shows an `ALL` tab plus one tab per provider that `tab`/`shift+tab` cycles, and skills (including their declared aliases) are invoked only via `$` — the slash palette stays builtins-only.
212
- - **[0.7.18]** (2026-06-26) — Slash-command discovery and the `/model` flow reach gajae-code parity. The slash palette/autocomplete now fuzzy-matches command names (with a description fallback for intent-style queries), resolved skills can contribute their own `/aliases` as real dispatchable commands, and `/model` runs gjc's two-menu target → reasoning flow so a picked model can be assigned to the default or any subagent role with its own thinking budget.
213
213
 
214
214
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
215
215
  <!-- CHANGELOG:END -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jeo-code",
3
- "version": "0.7.22",
3
+ "version": "0.7.24",
4
4
  "description": "Clean, highly optimized AI coding agent using spec-first loop",
5
5
  "type": "module",
6
6
  "main": "src/cli.ts",
@@ -39,6 +39,7 @@ const TYPE_LAYOUT = [
39
39
  ] as const;
40
40
 
41
41
  const DIR_BY_TYPE: Record<string, string> = Object.fromEntries(TYPE_LAYOUT.map(t => [t.type, t.dir]));
42
+ const HEADER_BY_TYPE: Record<string, string> = Object.fromEntries(TYPE_LAYOUT.map(t => [t.type, t.header]));
42
43
 
43
44
  export function memoryFilePath(cwd: string): string {
44
45
  return path.join(cwd, ".jeo", "memory", "MEMORY.md");
@@ -180,17 +181,23 @@ export async function migrateLegacyMemory(cwd: string): Promise<MigrationResult>
180
181
  return { migrated: true, conceptCount: written.length, backupPath };
181
182
  }
182
183
 
184
+ /** Render one concept as its bullet + indented body lines (no header) — the atomic
185
+ * unit `renderConceptSection` stacks under a `## header`. Extracted so budget
186
+ * selection (`selectWithinBudget`) can compute a candidate's exact contribution
187
+ * without re-rendering the whole accumulated section (see there for why that
188
+ * matters). */
189
+ function renderConceptItem(c: { title: string; description: string; body: string }): string {
190
+ const lines = [`- **${c.title}**${c.description ? `: ${c.description}` : ""}`];
191
+ if (c.body) {
192
+ for (const bodyLine of c.body.split("\n")) lines.push(` ${bodyLine}`);
193
+ }
194
+ return lines.join("\n");
195
+ }
196
+
183
197
  /** Render a single index.md-style section: a `## header` followed by one bullet
184
198
  * per concept (`**title**: description`), with the concept body indented beneath. */
185
199
  function renderConceptSection(header: string, list: { title: string; description: string; body: string }[]): string {
186
- const lines = [`## ${header}`];
187
- for (const c of list) {
188
- lines.push(`- **${c.title}**${c.description ? `: ${c.description}` : ""}`);
189
- if (c.body) {
190
- for (const bodyLine of c.body.split("\n")) lines.push(` ${bodyLine}`);
191
- }
192
- }
193
- return lines.join("\n");
200
+ return [`## ${header}`, ...list.map(renderConceptItem)].join("\n");
194
201
  }
195
202
 
196
203
  /** A loaded OKF concept: frontmatter fields + body + bundle-relative path. */
@@ -532,12 +539,42 @@ function renderConcepts(concepts: Concept[]): string {
532
539
  * Pinned (load-bearing) invariants get a RESERVED budget: they are selected FIRST,
533
540
  * before any query/recency fill, so a tight cap can never evict an invariant the
534
541
  * gates depend on (philosophy-synthesis consensus #3). The remaining budget then
535
- * holds the failure-first / core / query-relevant fill. */
542
+ * holds the failure-first / core / query-relevant fill.
543
+ *
544
+ * Perf: computes each candidate's exact incremental contribution to the final
545
+ * `renderConcepts` output (header + item lines, "\n" within a section, "\n\n"
546
+ * between sections) instead of re-rendering the whole accumulated set per
547
+ * candidate — O(n) instead of O(n²) for a bundle of n concepts. This mirrors
548
+ * `renderConceptSection`/`renderConcepts`'s exact join rules; a mismatch here
549
+ * would silently mis-size the budget, so `memory-search-okf.test.ts` locks an
550
+ * exact hand-computed truncation boundary as a regression check. */
551
+
536
552
  function selectWithinBudget(concepts: Concept[], query: string | undefined, budget: number): Concept[] {
537
553
  const selected: Concept[] = [];
554
+ const sectionLenByType = new Map<string, number>(); // type -> rendered length of that type's section so far
555
+ let total = 0; // rendered length of `selected` under renderConcepts
556
+
538
557
  const take = (pool: Concept[]) => {
539
558
  for (const c of priorityOrder(pool, query)) {
540
- if (renderConcepts([...selected, c]).length <= budget) selected.push(c);
559
+ const itemLen = renderConceptItem(c).length;
560
+ const priorSectionLen = sectionLenByType.get(c.type);
561
+ let newSectionLen: number;
562
+ let newTotal: number;
563
+ if (priorSectionLen === undefined) {
564
+ // New section: "## header" + "\n" + item — plus a "\n\n" join if a section already exists.
565
+ const header = HEADER_BY_TYPE[c.type] ?? c.type;
566
+ newSectionLen = `## ${header}`.length + 1 + itemLen;
567
+ newTotal = total === 0 ? newSectionLen : total + 2 + newSectionLen;
568
+ } else {
569
+ // Existing section: one more "\n" + item line.
570
+ newSectionLen = priorSectionLen + 1 + itemLen;
571
+ newTotal = total - priorSectionLen + newSectionLen;
572
+ }
573
+ if (newTotal <= budget) {
574
+ selected.push(c);
575
+ sectionLenByType.set(c.type, newSectionLen);
576
+ total = newTotal;
577
+ }
541
578
  }
542
579
  };
543
580
  take(concepts.filter(c => c.pinned)); // reserved budget: invariants survive the cap
@@ -174,7 +174,7 @@ export interface WorkflowState {
174
174
  /** The built-in default model when neither disk config nor JEO_DEFAULT_MODEL provides one.
175
175
  * Shared by envDefaultConfig (runtime) and readRawGlobalConfig (persistence base) so a
176
176
  * fresh-install saveConfigPatch never bakes a DIFFERENT default than the runtime resolves. */
177
- const DEFAULT_MODEL = "claude-sonnet-4-5";
177
+ const DEFAULT_MODEL = "claude-sonnet-4-6";
178
178
 
179
179
  /**
180
180
  * Resolve the global config directory at call time (not import time) so that a
@@ -22,7 +22,7 @@ export interface ModelCatalogEntry {
22
22
  }
23
23
 
24
24
  /** Canonical ids that should be surfaced as a provider's recommended default. */
25
- const RECOMMENDED = new Set(["claude-sonnet-4-5", "gpt-4o", "gemini-2.0-flash", "antigravity/gemini-3-pro-low", "qwen2.5"]);
25
+ const RECOMMENDED = new Set(["claude-sonnet-4-6", "gpt-4o", "gemini-2.0-flash", "antigravity/gemini-3-pro-low", "qwen2.5"]);
26
26
 
27
27
  export function normalizeModelId(id: string): string {
28
28
  return (id ?? "").trim().toLowerCase();
@@ -35,14 +35,11 @@ const FULL: ThinkLevel[] = ["minimal", "low", "medium", "high", "xhigh"];
35
35
  const STD: ThinkLevel[] = ["minimal", "low", "medium", "high"];
36
36
 
37
37
  export const ANTIGRAVITY_MODELS = [
38
- "claude-opus-4-5-thinking",
39
38
  "claude-opus-4-6-thinking",
40
39
  "claude-opus-4-7",
41
40
  "claude-opus-4-7-thinking",
42
41
  "claude-opus-4-8",
43
42
  "claude-opus-4-8-thinking",
44
- "claude-sonnet-4-5",
45
- "claude-sonnet-4-5-thinking",
46
43
  "claude-sonnet-4-6",
47
44
  "claude-sonnet-4-6-thinking",
48
45
  "gemini-2.5-flash",
@@ -60,18 +57,23 @@ export const ANTIGRAVITY_MODELS = [
60
57
  /** A curated set of common public models with their documented capabilities. */
61
58
  export const MODEL_CATALOG: readonly CatalogModel[] = [
62
59
  // Anthropic
63
- { canonical: "claude-3-5-sonnet", provider: "anthropic", providerModel: "claude-3-5-sonnet-20241022", contextTokens: 200_000, maxOutputTokens: 8192, thinking: [], images: true },
64
60
  { canonical: "claude-haiku-4-5", provider: "anthropic", providerModel: "claude-haiku-4-5-20251001", contextTokens: 200_000, maxOutputTokens: 64_000, thinking: FULL, images: true },
65
- { canonical: "claude-sonnet-4-5", provider: "anthropic", providerModel: "claude-sonnet-4-5-20250929", contextTokens: 200_000, maxOutputTokens: 64_000, thinking: FULL, images: true },
66
- { canonical: "claude-opus-4-1", provider: "anthropic", providerModel: "claude-opus-4-1-20250805", contextTokens: 200_000, maxOutputTokens: 32_000, thinking: FULL, images: true },
67
- { canonical: "claude-opus-4-5", provider: "anthropic", providerModel: "claude-opus-4-5-20251101", contextTokens: 200_000, maxOutputTokens: 64_000, thinking: FULL, images: true },
68
61
  // NOTE: opus 4.6+ use Anthropic ADAPTIVE thinking (type:"adaptive" + output_config.effort).
69
62
  // opus 4.7/4.8 OMIT visible thought unless the request opts into `display: "summarized"` —
70
63
  // anthropic.ts sets that on the adaptive transport so reasoning streams again (gjc parity).
71
64
  // The nativizable path still replays signature-only thinking blocks for cross-turn continuity.
72
- { canonical: "claude-opus-4-6", provider: "anthropic", providerModel: "claude-opus-4-6", contextTokens: 200_000, maxOutputTokens: 64_000, thinking: FULL, images: true },
73
- { canonical: "claude-opus-4-7", provider: "anthropic", providerModel: "claude-opus-4-7", contextTokens: 200_000, maxOutputTokens: 64_000, thinking: FULL, images: true },
74
- { canonical: "claude-opus-4-8", provider: "anthropic", providerModel: "claude-opus-4-8", contextTokens: 200_000, maxOutputTokens: 64_000, thinking: FULL, images: true },
65
+ // The 4.6 generation onward is 1M context / 128k sync max output (Anthropic docs:
66
+ // Opus 4.8 & Sonnet 5 comparison table; Opus 4.6/4.7, Sonnet 4.6 share the gen's
67
+ // dateless-snapshot 1M/128k + 300k batch-output beta). Dated pre-4.6 ids keep 200k/64k.
68
+ { canonical: "claude-opus-4-6", provider: "anthropic", providerModel: "claude-opus-4-6", contextTokens: 1_000_000, maxOutputTokens: 128_000, thinking: FULL, images: true },
69
+ { canonical: "claude-opus-4-7", provider: "anthropic", providerModel: "claude-opus-4-7", contextTokens: 1_000_000, maxOutputTokens: 128_000, thinking: FULL, images: true },
70
+ { canonical: "claude-opus-4-8", provider: "anthropic", providerModel: "claude-opus-4-8", contextTokens: 1_000_000, maxOutputTokens: 128_000, thinking: FULL, images: true },
71
+ { canonical: "claude-sonnet-4-6", provider: "anthropic", providerModel: "claude-sonnet-4-6", contextTokens: 1_000_000, maxOutputTokens: 128_000, thinking: FULL, images: true },
72
+ { canonical: "claude-sonnet-5", provider: "anthropic", providerModel: "claude-sonnet-5", contextTokens: 1_000_000, maxOutputTokens: 128_000, thinking: FULL, images: true },
73
+ // Fable 5 = Anthropic's most capable widely-released model (adaptive thinking always on);
74
+ // Mythos 5 is limited-availability (Project Glasswing) but callable by id for approved accounts.
75
+ { canonical: "claude-fable-5", provider: "anthropic", providerModel: "claude-fable-5", contextTokens: 1_000_000, maxOutputTokens: 128_000, thinking: FULL, images: true },
76
+ { canonical: "claude-mythos-5", provider: "anthropic", providerModel: "claude-mythos-5", contextTokens: 1_000_000, maxOutputTokens: 128_000, thinking: FULL, images: true },
75
77
  // OpenAI
76
78
  { canonical: "gpt-4o", provider: "openai", providerModel: "gpt-4o", contextTokens: 128_000, maxOutputTokens: 16_384, thinking: [], images: true },
77
79
  { canonical: "gpt-4o-mini", provider: "openai", providerModel: "gpt-4o-mini", contextTokens: 128_000, maxOutputTokens: 16_384, thinking: [], images: true },
@@ -200,7 +202,7 @@ export function inferCatalogMetadata(modelId: string): CatalogModel | undefined
200
202
 
201
203
  // Anthropic Claude: opus/sonnet/haiku. Major version >= 4 ships extended
202
204
  // thinking (mirrors every catalogued claude-4-x entry); claude-3-x does not.
203
- const claude = id.match(/^claude-(opus|sonnet|haiku)-(\d+)(?:[-.](\d+))?/);
205
+ const claude = id.match(/^claude-(opus|sonnet|haiku|fable|mythos)-(\d+)(?:[-.](\d+))?/);
204
206
  if (claude) {
205
207
  const major = Number(claude[2]);
206
208
  const thinking = major >= 4 ? FULL : [];
@@ -208,8 +210,8 @@ export function inferCatalogMetadata(modelId: string): CatalogModel | undefined
208
210
  canonical: raw,
209
211
  provider: antigravity ? "antigravity" : "anthropic",
210
212
  providerModel: id,
211
- contextTokens: 200_000,
212
- maxOutputTokens: claude[1] === "haiku" ? 64_000 : 64_000,
213
+ contextTokens: major >= 5 ? 1_000_000 : 200_000,
214
+ maxOutputTokens: claude[1] === "haiku" ? 64_000 : major >= 5 ? 128_000 : 64_000,
213
215
  thinking,
214
216
  images: true,
215
217
  company: antigravity ? "Anthropic via Antigravity" : "Anthropic",
@@ -160,7 +160,7 @@ export interface ModelManager {
160
160
  resolveProvider: typeof resolveProvider;
161
161
  }
162
162
 
163
- const ALIAS_DEFAULTS = { fast: "ollama/qwen2.5:0.5b", local: "ollama/qwen2.5:0.5b", sonnet: "claude-sonnet-4-5", opus: "claude-opus-4-5", haiku: "claude-haiku-4-5", gpt: "gpt-5.5", flash: "gemini-2.5-flash", grok: "grok-4.3" };
163
+ const ALIAS_DEFAULTS = { fast: "ollama/qwen2.5:0.5b", local: "ollama/qwen2.5:0.5b", sonnet: "claude-sonnet-4-6", opus: "claude-opus-4-6", haiku: "claude-haiku-4-5", gpt: "gpt-5.5", flash: "gemini-2.5-flash", grok: "grok-4.3" };
164
164
 
165
165
  /**
166
166
  * Build retry options from a config `retry` budget (gjc parity). `requestMaxRetries`
@@ -9,8 +9,8 @@ export interface ModelAliases {
9
9
  export const BUILTIN_ALIASES: ModelAliases = {
10
10
  fast: "ollama/qwen2.5:0.5b",
11
11
  local: "ollama/qwen2.5:0.5b",
12
- sonnet: "claude-sonnet-4-5",
13
- opus: "claude-opus-4-5",
12
+ sonnet: "claude-sonnet-4-6",
13
+ opus: "claude-opus-4-6",
14
14
  haiku: "claude-haiku-4-5",
15
15
  gpt: "gpt-5.5",
16
16
  flash: "gemini-2.5-flash",
package/src/ai/pricing.ts CHANGED
@@ -25,7 +25,13 @@ export interface TokenUsage {
25
25
  */
26
26
  const PRICE_TABLE: ReadonlyArray<readonly [pattern: string, price: ModelPrice]> = [
27
27
  // Anthropic Claude
28
+ // 4.6-generation specifics (Anthropic docs pricing) — MUST precede the generic
29
+ // `claude-opus`/`claude-sonnet` families so the newer, cheaper Opus tier wins.
30
+ ["claude-opus-4-8", { inPerM: 5, outPerM: 25 }],
31
+ ["claude-fable", { inPerM: 10, outPerM: 50 }],
32
+ ["claude-mythos", { inPerM: 10, outPerM: 50 }],
28
33
  ["claude-opus", { inPerM: 15, outPerM: 75 }],
34
+ ["claude-sonnet-5", { inPerM: 2, outPerM: 10 }],
29
35
  ["claude-sonnet", { inPerM: 3, outPerM: 15 }],
30
36
  ["claude-haiku", { inPerM: 0.8, outPerM: 4 }],
31
37
  ["opus", { inPerM: 15, outPerM: 75 }],
@@ -108,34 +108,44 @@ function anthropicThinkingBudget(effort: CallOptions["reasoningEffort"], maxToke
108
108
  /** Parse an Anthropic model id's family + version for thinking-transport selection.
109
109
  * Matches the modern `claude-<family>-<major>-<minor>[...]` naming (opus/sonnet/haiku 4.x+);
110
110
  * legacy ids (claude-3-5-sonnet) and non-Anthropic-compatible names return undefined. */
111
- function parseAnthropicVersion(model: string): { kind: "opus" | "sonnet" | "haiku"; major: number; minor: number } | undefined {
112
- const m = /claude-(opus|sonnet|haiku)-(\d+)-(\d+)/.exec(model);
111
+ function parseAnthropicVersion(model: string): { kind: "opus" | "sonnet" | "haiku" | "fable" | "mythos"; major: number; minor: number } | undefined {
112
+ // `<major>-<minor>` (opus/sonnet/haiku 4.x) OR a single `<major>` (Sonnet 5, Fable 5,
113
+ // Mythos 5 — the dateless 5th-gen ids), with new families fable/mythos. Legacy ids
114
+ // (claude-3-5-sonnet: number before family) and non-Anthropic names return undefined.
115
+ const m = /claude-(opus|sonnet|haiku|fable|mythos)-(\d+)(?:-(\d+))?/.exec(model);
113
116
  if (!m) return undefined;
114
- return { kind: m[1] as "opus" | "sonnet" | "haiku", major: Number(m[2]), minor: Number(m[3]) };
117
+ return { kind: m[1] as "opus" | "sonnet" | "haiku" | "fable" | "mythos", major: Number(m[2]), minor: m[3] !== undefined ? Number(m[3]) : 0 };
115
118
  }
116
119
 
117
120
  /** Adaptive thinking `display` is supported starting with Opus 4.7. Without it, Opus 4.7/4.8
118
121
  * OMIT thinking content entirely (tokens billed, signature present, but zero visible thought —
119
122
  * the "reasoning doesn't show" bug). Older adaptive models (Opus 4.6, Sonnet 4.6+) reject the
120
- * field, so it is gated to Opus ≥ 4.7. (gjc: supportsAdaptiveThinkingDisplay) */
123
+ * field, so it is gated to Opus ≥ 4.7 and every 5th-gen+ model. (gjc: supportsAdaptiveThinkingDisplay) */
121
124
  function supportsAdaptiveThinkingDisplay(model: string): boolean {
122
125
  const v = parseAnthropicVersion(model);
123
- if (!v || v.kind !== "opus") return false;
124
- return v.major > 4 || (v.major === 4 && v.minor >= 7);
126
+ if (!v) return false;
127
+ // display:"summarized" was introduced at Opus 4.7 and carried forward, so every
128
+ // 5th-generation+ model (Sonnet 5, Fable 5, Mythos 5, …) supports it too. Only the
129
+ // first adaptive gen (Opus 4.6, Sonnet 4.6) rejects the field.
130
+ if (v.major >= 5) return true;
131
+ return v.kind === "opus" && v.major === 4 && v.minor >= 7;
125
132
  }
126
133
 
127
134
  /** Thinking transport for a model (gjc parity — inferThinkingControlMode):
128
135
  * - Anthropic ≥ 4.6 → "adaptive" (model decides depth; effort rides output_config, NO budget)
129
- * - Anthropic 4.5 → "budget-effort" (budget_tokens + output_config effort)
130
- * - otherwise → "budget" (budget_tokens only).
136
+ * - Anthropic 4.5 sonnet/opus → "budget-effort" (budget_tokens + output_config effort)
137
+ * - otherwise (incl. Haiku 4.5) → "budget" (budget_tokens only).
131
138
  * The adaptive shift is the core opus-4.7/4.8 reasoning fix: those models reject the legacy
132
- * budget transport's visible-thought contract and require type:"adaptive" + display:summarized. */
139
+ * budget transport's visible-thought contract and require type:"adaptive" + display:summarized.
140
+ * Haiku 4.5 supports budget_tokens thinking but REJECTS output_config.effort ("This model does
141
+ * not support the effort parameter"), so it stays on the plain budget transport unlike its
142
+ * sonnet/opus 4.5 siblings. */
133
143
  type AnthropicThinkingMode = "adaptive" | "budget-effort" | "budget";
134
144
  function anthropicThinkingMode(model: string): AnthropicThinkingMode {
135
145
  const v = parseAnthropicVersion(model);
136
146
  if (!v) return "budget";
137
147
  if (v.major > 4 || (v.major === 4 && v.minor >= 6)) return "adaptive";
138
- if (v.major === 4 && v.minor === 5) return "budget-effort";
148
+ if (v.major === 4 && v.minor === 5 && v.kind !== "haiku") return "budget-effort";
139
149
  return "budget";
140
150
  }
141
151
 
@@ -73,7 +73,7 @@ export function geminiNativizable(m: Message, modelKey: string, thinkingEnabled:
73
73
  export function buildGeminiPayload(messages: Message[], options: CallOptions, stripArtifacts = false): { geminiModel: string; payload: Record<string, unknown> } {
74
74
  const resolvedModel = options.model.replace(/^(google|gemini)\//, "");
75
75
  let geminiModel = resolvedModel;
76
- if (!geminiModel || geminiModel === "claude-3-5-sonnet") geminiModel = "gemini-2.0-flash";
76
+ if (!geminiModel || geminiModel.startsWith("claude-")) geminiModel = "gemini-2.0-flash";
77
77
 
78
78
  const systemPrompt = options.systemPrompt ?? messages.find(m => m.role === "system")?.content;
79
79
  const thinkingBudget = geminiThinkingBudget(geminiModel, options.reasoningEffort, options.maxTokens);
@@ -27,32 +27,58 @@ export interface OAuthCallbackFlowOptions {
27
27
 
28
28
  export type CallbackResult = { code: string; state: string };
29
29
 
30
- const PAGE_HTML = `<!doctype html><html><head><meta charset="utf-8"><title>jeo — login complete</title>
31
- <style>body{font-family:system-ui,sans-serif;background:#0d1117;color:#e6edf3;display:flex;align-items:center;justify-content:center;height:100vh;margin:0}
32
- .card{text-align:center;padding:2rem 3rem;border:1px solid #30363d;border-radius:12px;background:#161b22;max-width:30rem}
33
- h1{margin:0 0 .5rem;font-size:1.4rem}p{margin:.25rem 0;color:#8b949e}.hint{margin-top:1rem;font-size:.85rem;color:#6e7681}</style></head>
34
- <body><div class="card"><h1>__TITLE__</h1><p>__MSG__</p>__EXTRA__</div>__SCRIPT__</body></html>`;
35
-
36
- // Seconds before the success tab attempts to auto-close. `window.close()` only
30
+ // "jeo" wordmark — bold forged monospace lettering on a neon blue→violet→pink
31
+ // gradient (the mascot's synthwave palette), generated via god-tibo-imagen and
32
+ // matching jeo-pi's bold-wordmark typographic treatment. Embedded as a data URI
33
+ // (not a served static file) so the callback page renders standalone even from
34
+ // a bundled/compiled `jeo` binary with no static-asset pipeline.
35
+ const JEO_WORDMARK_DATA_URI =
36
+ "data:image/webp;base64,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";
37
+
38
+ const PAGE_HTML = `<!doctype html><html><head><meta charset="utf-8"><title>jeo — status</title>
39
+ <style>:root{color-scheme:dark}*{box-sizing:border-box}
40
+ body{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;background:#0b0a10;color:#e7e7ec;display:flex;align-items:center;justify-content:center;height:100vh;margin:0}
41
+ .card{text-align:center;padding:2.5rem 3rem;border:1px solid #2a2a35;border-radius:20px;background:#12131c;max-width:26rem}
42
+ .wordmark{width:88px;height:88px;display:block;margin:0 auto 1.25rem;border-radius:14px}
43
+ h1{margin:0 0 .5rem;font-size:1.1rem;font-weight:600}
44
+ h1.ok{color:#22d3ee}h1.fail{color:#f472b6}
45
+ p{margin:.25rem 0;color:#8b8b96;font-size:.92rem}
46
+ button{margin-top:1.5rem;padding:.55rem 1.6rem;border-radius:999px;border:1px solid #2a2a35;background:transparent;color:#e7e7ec;font:inherit;font-size:.88rem;cursor:pointer}
47
+ button:hover{background:#1b1c27}
48
+ .hint{margin-top:.9rem;font-size:.78rem;color:#5f5f6b}</style></head>
49
+ <body><div class="card">
50
+ <img class="wordmark" alt="jeo" src="${JEO_WORDMARK_DATA_URI}">
51
+ <h1 class="__STATUS__">__TITLE__</h1>
52
+ <p>__MSG__</p>
53
+ <button id="jeo-close" type="button">Close</button>
54
+ <p class="hint">Closing automatically in <span id="jeo-countdown">__SECONDS__</span>s…</p>
55
+ </div>
56
+ <script>__SCRIPT__</script>
57
+ </body></html>`;
58
+
59
+ // Seconds before the tab auto-closes — applies to BOTH the success and the
60
+ // failure page now (previously only success auto-closed). Pressing "Close"
61
+ // closes immediately without waiting out the countdown. `window.close()` only
37
62
  // succeeds on tabs the script itself opened; browsers may ignore it for
38
- // OS-launched OAuth tabs, so the page also shows a clear "you can close" hint.
39
- // ponytail: upgrade path — none needed; countdown is inline, no timer lib.
40
- const AUTO_CLOSE_SECONDS = 3;
63
+ // OS-launched OAuth tabs, so the button remains as the reliable fallback.
64
+ const AUTO_CLOSE_SECONDS = 5;
41
65
 
42
66
  function renderHtml(ok: boolean, msg: string): string {
43
- const extra = ok
44
- ? `<p class="hint">This window will close in <span id="jeo-countdown">${AUTO_CLOSE_SECONDS}</span>s — you can also close it manually.</p>`
45
- : "";
46
- const script = ok
47
- ? `<script>(function(){var n=${AUTO_CLOSE_SECONDS},el=document.getElementById("jeo-countdown");var t=setInterval(function(){n-=1;if(el)el.textContent=String(Math.max(n,0));if(n<=0){clearInterval(t);window.close();}},1000);})();</script>`
48
- : "";
67
+ const script = `(function(){
68
+ var n=${AUTO_CLOSE_SECONDS},el=document.getElementById("jeo-countdown"),btn=document.getElementById("jeo-close"),t;
69
+ function closeNow(){clearInterval(t);window.close();}
70
+ t=setInterval(function(){n-=1;if(el)el.textContent=String(Math.max(n,0));if(n<=0)closeNow();},1000);
71
+ if(btn)btn.addEventListener("click",closeNow);
72
+ })();`;
49
73
  return PAGE_HTML
74
+ .replace("__STATUS__", ok ? "ok" : "fail")
50
75
  .replace("__TITLE__", ok ? "Login complete \u2713" : "Login failed")
51
76
  .replace("__MSG__", msg)
52
- .replace("__EXTRA__", extra)
77
+ .replace("__SECONDS__", String(AUTO_CLOSE_SECONDS))
53
78
  .replace("__SCRIPT__", script);
54
79
  }
55
80
 
81
+
56
82
  export abstract class OAuthCallbackFlow {
57
83
  protected ctrl: OAuthController;
58
84
  protected preferredPort: number;
@@ -27,7 +27,7 @@ function reportModelChoice(r: { warning?: string; suggestions: string[] }): void
27
27
  type ProviderChoice = "anthropic" | "openai" | "gemini" | "ollama" | "lmstudio" | "openai-compatible";
28
28
 
29
29
  const DEFAULT_MODELS: Record<ProviderChoice, string> = {
30
- anthropic: "claude-sonnet-4-5",
30
+ anthropic: "claude-sonnet-4-6",
31
31
  openai: "gpt-4o",
32
32
  gemini: "gemini-2.0-flash",
33
33
  ollama: "ollama/llama3.1:8b",
@@ -236,7 +236,7 @@ export function resetThemeConfigCache(): void {
236
236
 
237
237
  function getExplicitThemeFromConfig(env: EnvLike): string | undefined {
238
238
  const home = os.homedir ? os.homedir() : undefined;
239
- const dir = env.JEO_CONFIG_DIR || env.JEO_CONFIG_DIR || (home ? path.join(home, ".jeo") : undefined);
239
+ const dir = env.JEO_CONFIG_DIR || (home ? path.join(home, ".jeo") : undefined);
240
240
  if (!dir) return undefined;
241
241
  if (configThemeCache && configThemeCache.dir === dir) return configThemeCache.value;
242
242
  const filePath = path.join(dir, "config.json");