jeo-code 0.8.18 → 0.8.19
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 +15 -0
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/package.json +1 -1
- package/src/agent/prompt-router.ts +40 -2
- package/src/agent/session.ts +24 -0
- package/src/commands/launch/session-slash.ts +13 -0
- package/src/commands/launch.ts +36 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,21 @@ 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.19] - 2026-07-09
|
|
10
|
+
_"프롬프트 라우팅에 안티그라비티 프로바이더의 경우, 안티그라비티용 소넷과 오퍼스도 3.5급으로 라우팅될수있도록해줘" — Antigravity re-exports 3 distinct model families (Anthropic Claude, Google Gemini, OpenAI GPT-OSS) behind one credential, structurally unlike every other provider. `high`/`complex` tier auto-select always resolved to Google's Gemini 3.5 rows: Anthropic's real 64,000-token output ceiling lost a same-thinking-tier tie to Google's 65,536 by a margin with no practical significance, and Gemini's 1M-token context further outranked Claude's real 200K window — so `antigravity/claude-sonnet-4-6`/`antigravity/claude-opus-4-6-thinking` were NEVER reachable through auto-select, even though both were already correctly `sizeClass`-tagged into the `high`/`complex` pools._
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Antigravity's Claude Sonnet/Opus are now reachable at the `high`/`complex` routing tiers** (`src/agent/prompt-router.ts`) — added `antigravityCompanyPoolPick`: when Antigravity is credentialed and a tier's pool spans 2+ companies, session-stably spreads across ONE candidate per company (the strongest Antigravity row for that company) instead of the plain single-winner comparator always resolving to Google's Gemini rows. Wired into `resolveTierModel`'s `high`/`complex` branches, after the explicit `routing.crossProviderPool` opt-in but before the plain single-winner fallback — purely additive, default-on specifically for Antigravity's multi-vendor case (every other provider represents exactly one vendor and is unaffected).
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- **Antigravity multi-company routing coverage** (`test/prompt-router.test.ts`) — 3 new tests: reachability across all 3 re-exported companies (Anthropic/Google/OpenAI) over 50 sessions, session-stability (same session always resolves to the same pick), and an explicit `roles.high` override still winning over the new spread. Updated one test whose assertion had locked in the old Gemini-only behavior — its actual invariant (never leak a bare public `gemini-*` id) still holds and is now asserted more precisely.
|
|
17
|
+
|
|
18
|
+
### Verified
|
|
19
|
+
- Live: `resolveTierModel("high"/"complex", …)` across 5 distinct real sessionIds confirms `antigravity/claude-sonnet-4-6` reachable at `high` and `antigravity/claude-opus-4-6-thinking` reachable at `complex`, spread across all 3 companies.
|
|
20
|
+
- `bun test test/prompt-router.test.ts test/prompt-router-tiers.test.ts test/model-manager.test.ts test/launch-prompt-routing.test.ts test/model-provider-mapping.test.ts` — 159 pass / 0 fail.
|
|
21
|
+
- Full `bun test` — 2893 pass / 0 fail across 291 files.
|
|
22
|
+
- `bun run typecheck` — no errors.
|
|
23
|
+
|
|
9
24
|
## [0.8.18] - 2026-07-09
|
|
10
25
|
_"라우팅 매 프롬프트마다 변경되는지, route why 의도대로 동작하는지 검증하고 배포할게있는지 확인해줘" — 2 parallel subagents live-verified per-prompt routing + `/route why` (including the previously-only-unit-tested post-call equivalent-model fallback path, now reproduced against a real mock 500-error server) and audited a disconnected `RouteHistory` class shipped as incomplete scaffolding in 0.8.16/0.8.17 (class + 11 tests existed, but nothing ever called it — no `/route history` subcommand, no wiring into the turn loop). Live testing also surfaced one real bug: the TUI footer's model/provider label never updated after a mid-turn fallback, staying on the pre-fallback model for the rest of that turn's render even though the backend decision (`lastRouteDecision`) was already correct._
|
|
11
26
|
|
package/README.ja.md
CHANGED
|
@@ -265,11 +265,11 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
|
|
|
265
265
|
## 変更履歴 (Changelog)
|
|
266
266
|
|
|
267
267
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
268
|
+
- **[0.8.19]** (2026-07-09) — "프롬프트 라우팅에 안티그라비티 프로바이더의 경우, 안티그라비티용 소넷과 오퍼스도 3.5급으로 라우팅될수있도록해줘" — Antigravity re-exports 3 distinct model families (Anthropic Claude, Google Gemini, OpenAI GPT-OSS) behind one credential, structurally unlike every other provider. `high`/`complex` tier auto-select always resolved to Google's Gemini 3.5 rows: Anthropic's real 64,000-token output ceiling lost a same-thinking-tier tie to Google's 65,536 by a margin with no practical significance, and Gemini's 1M-token context further outranked Claude's real 200K window — so `antigravity/claude-sonnet-4-6`/`antigravity/claude-opus-4-6-thinking` were NEVER reachable through auto-select, even though both were already correctly `sizeClass`-tagged into the `high`/`complex` pools.
|
|
268
269
|
- **[0.8.18]** (2026-07-09) — "라우팅 매 프롬프트마다 변경되는지, route why 의도대로 동작하는지 검증하고 배포할게있는지 확인해줘" — 2 parallel subagents live-verified per-prompt routing + `/route why` (including the previously-only-unit-tested post-call equivalent-model fallback path, now reproduced against a real mock 500-error server) and audited a disconnected `RouteHistory` class shipped as incomplete scaffolding in 0.8.16/0.8.17 (class + 11 tests existed, but nothing ever called it — no `/route history` subcommand, no wiring into the turn loop). Live testing also surfaced one real bug: the TUI footer's model/provider label never updated after a mid-turn fallback, staying on the pre-fallback model for the rest of that turn's render even though the backend decision (`lastRouteDecision`) was already correct.
|
|
269
270
|
- **[0.8.17]** (2026-07-09) — "서브에이전트 이용해. 병렬루 부가 나머지모두 개선하고 검증후 배포까지하자" — a prior 4-way parallel subagent cross-verification pass found the README `[!CAUTION]` routing-lock block (added this session for "프롬프트 라우팅이 한번 정해지면 안바뀌는데?") was directionally correct but had 2 real inaccuracies, plus a concrete test-coverage gap: every existing pin/override regression test drove the lock via the `--model` CLI startup flag, never via typing `/model <name>` as an interactive slash command mid-session, and none proved persistence across 3+ consecutive turns.
|
|
270
271
|
- **[0.8.16]** (2026-07-09) — "jeo code의 computer use 기능은 / 명령어로 설정할수있도록하고, 실동작검증까지진행해줘" — the desktop-automation `computer` tool was only togglable by hand-editing `computer.enabled` in `~/.jeo/config.json`; there was no in-session way to enable it for a single run without leaving a permanent config change behind.
|
|
271
272
|
- **[0.8.15]** (2026-07-09) — "api 인증 모델도 사용가능하도록 딥리서치하고 개선해줘" — traced the API-authenticated GPT path to a routing catalog gap: `GET /models` with an OpenAI API key or an OpenAI-compatible provider returned real account-scoped model ids, and `/model` could show/pin them, but prompt routing still built its auto-select pools from static `MODEL_CATALOG` rows only. A custom `openaiBaseUrl` made this worse by treating every public OpenAI row as servable, so routing could choose `gpt-*`/`o*` ids that the configured local/Azure/proxy endpoint never listed.
|
|
272
|
-
- **[0.8.14]** (2026-07-09) — "GPT 연결 실패가 여전히 무응답으로 끝나는 케이스를 잡아줘" — traced the remaining GPT/routing failure path to post-call continuity: the route target could be credentialed and servable, but still end a turn with a recoverable terminal result such as plain `OpenAI returned no content`, rate/quota pressure, model unavailable/not found, or an invalid agent-tool response. jeo already knew the prompt tier and equivalent credentialed fallbacks, but it only applied that fallback before the model call. After the call failed, the turn stopped instead of trying another model in the same tier.
|
|
273
273
|
|
|
274
274
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
275
275
|
<!-- CHANGELOG:END -->
|
package/README.ko.md
CHANGED
|
@@ -266,11 +266,11 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
|
|
|
266
266
|
## 변경 이력 (Changelog)
|
|
267
267
|
|
|
268
268
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
269
|
+
- **[0.8.19]** (2026-07-09) — "프롬프트 라우팅에 안티그라비티 프로바이더의 경우, 안티그라비티용 소넷과 오퍼스도 3.5급으로 라우팅될수있도록해줘" — Antigravity re-exports 3 distinct model families (Anthropic Claude, Google Gemini, OpenAI GPT-OSS) behind one credential, structurally unlike every other provider. `high`/`complex` tier auto-select always resolved to Google's Gemini 3.5 rows: Anthropic's real 64,000-token output ceiling lost a same-thinking-tier tie to Google's 65,536 by a margin with no practical significance, and Gemini's 1M-token context further outranked Claude's real 200K window — so `antigravity/claude-sonnet-4-6`/`antigravity/claude-opus-4-6-thinking` were NEVER reachable through auto-select, even though both were already correctly `sizeClass`-tagged into the `high`/`complex` pools.
|
|
269
270
|
- **[0.8.18]** (2026-07-09) — "라우팅 매 프롬프트마다 변경되는지, route why 의도대로 동작하는지 검증하고 배포할게있는지 확인해줘" — 2 parallel subagents live-verified per-prompt routing + `/route why` (including the previously-only-unit-tested post-call equivalent-model fallback path, now reproduced against a real mock 500-error server) and audited a disconnected `RouteHistory` class shipped as incomplete scaffolding in 0.8.16/0.8.17 (class + 11 tests existed, but nothing ever called it — no `/route history` subcommand, no wiring into the turn loop). Live testing also surfaced one real bug: the TUI footer's model/provider label never updated after a mid-turn fallback, staying on the pre-fallback model for the rest of that turn's render even though the backend decision (`lastRouteDecision`) was already correct.
|
|
270
271
|
- **[0.8.17]** (2026-07-09) — "서브에이전트 이용해. 병렬루 부가 나머지모두 개선하고 검증후 배포까지하자" — a prior 4-way parallel subagent cross-verification pass found the README `[!CAUTION]` routing-lock block (added this session for "프롬프트 라우팅이 한번 정해지면 안바뀌는데?") was directionally correct but had 2 real inaccuracies, plus a concrete test-coverage gap: every existing pin/override regression test drove the lock via the `--model` CLI startup flag, never via typing `/model <name>` as an interactive slash command mid-session, and none proved persistence across 3+ consecutive turns.
|
|
271
272
|
- **[0.8.16]** (2026-07-09) — "jeo code의 computer use 기능은 / 명령어로 설정할수있도록하고, 실동작검증까지진행해줘" — the desktop-automation `computer` tool was only togglable by hand-editing `computer.enabled` in `~/.jeo/config.json`; there was no in-session way to enable it for a single run without leaving a permanent config change behind.
|
|
272
273
|
- **[0.8.15]** (2026-07-09) — "api 인증 모델도 사용가능하도록 딥리서치하고 개선해줘" — traced the API-authenticated GPT path to a routing catalog gap: `GET /models` with an OpenAI API key or an OpenAI-compatible provider returned real account-scoped model ids, and `/model` could show/pin them, but prompt routing still built its auto-select pools from static `MODEL_CATALOG` rows only. A custom `openaiBaseUrl` made this worse by treating every public OpenAI row as servable, so routing could choose `gpt-*`/`o*` ids that the configured local/Azure/proxy endpoint never listed.
|
|
273
|
-
- **[0.8.14]** (2026-07-09) — "GPT 연결 실패가 여전히 무응답으로 끝나는 케이스를 잡아줘" — traced the remaining GPT/routing failure path to post-call continuity: the route target could be credentialed and servable, but still end a turn with a recoverable terminal result such as plain `OpenAI returned no content`, rate/quota pressure, model unavailable/not found, or an invalid agent-tool response. jeo already knew the prompt tier and equivalent credentialed fallbacks, but it only applied that fallback before the model call. After the call failed, the turn stopped instead of trying another model in the same tier.
|
|
274
274
|
|
|
275
275
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
276
276
|
<!-- CHANGELOG:END -->
|
package/README.md
CHANGED
|
@@ -273,11 +273,11 @@ Huge thanks to [gajae-code](https://github.com/Yeachan-Heo/gajae-code) for the i
|
|
|
273
273
|
## Changelog
|
|
274
274
|
|
|
275
275
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
276
|
+
- **[0.8.19]** (2026-07-09) — "프롬프트 라우팅에 안티그라비티 프로바이더의 경우, 안티그라비티용 소넷과 오퍼스도 3.5급으로 라우팅될수있도록해줘" — Antigravity re-exports 3 distinct model families (Anthropic Claude, Google Gemini, OpenAI GPT-OSS) behind one credential, structurally unlike every other provider. `high`/`complex` tier auto-select always resolved to Google's Gemini 3.5 rows: Anthropic's real 64,000-token output ceiling lost a same-thinking-tier tie to Google's 65,536 by a margin with no practical significance, and Gemini's 1M-token context further outranked Claude's real 200K window — so `antigravity/claude-sonnet-4-6`/`antigravity/claude-opus-4-6-thinking` were NEVER reachable through auto-select, even though both were already correctly `sizeClass`-tagged into the `high`/`complex` pools.
|
|
276
277
|
- **[0.8.18]** (2026-07-09) — "라우팅 매 프롬프트마다 변경되는지, route why 의도대로 동작하는지 검증하고 배포할게있는지 확인해줘" — 2 parallel subagents live-verified per-prompt routing + `/route why` (including the previously-only-unit-tested post-call equivalent-model fallback path, now reproduced against a real mock 500-error server) and audited a disconnected `RouteHistory` class shipped as incomplete scaffolding in 0.8.16/0.8.17 (class + 11 tests existed, but nothing ever called it — no `/route history` subcommand, no wiring into the turn loop). Live testing also surfaced one real bug: the TUI footer's model/provider label never updated after a mid-turn fallback, staying on the pre-fallback model for the rest of that turn's render even though the backend decision (`lastRouteDecision`) was already correct.
|
|
277
278
|
- **[0.8.17]** (2026-07-09) — "서브에이전트 이용해. 병렬루 부가 나머지모두 개선하고 검증후 배포까지하자" — a prior 4-way parallel subagent cross-verification pass found the README `[!CAUTION]` routing-lock block (added this session for "프롬프트 라우팅이 한번 정해지면 안바뀌는데?") was directionally correct but had 2 real inaccuracies, plus a concrete test-coverage gap: every existing pin/override regression test drove the lock via the `--model` CLI startup flag, never via typing `/model <name>` as an interactive slash command mid-session, and none proved persistence across 3+ consecutive turns.
|
|
278
279
|
- **[0.8.16]** (2026-07-09) — "jeo code의 computer use 기능은 / 명령어로 설정할수있도록하고, 실동작검증까지진행해줘" — the desktop-automation `computer` tool was only togglable by hand-editing `computer.enabled` in `~/.jeo/config.json`; there was no in-session way to enable it for a single run without leaving a permanent config change behind.
|
|
279
280
|
- **[0.8.15]** (2026-07-09) — "api 인증 모델도 사용가능하도록 딥리서치하고 개선해줘" — traced the API-authenticated GPT path to a routing catalog gap: `GET /models` with an OpenAI API key or an OpenAI-compatible provider returned real account-scoped model ids, and `/model` could show/pin them, but prompt routing still built its auto-select pools from static `MODEL_CATALOG` rows only. A custom `openaiBaseUrl` made this worse by treating every public OpenAI row as servable, so routing could choose `gpt-*`/`o*` ids that the configured local/Azure/proxy endpoint never listed.
|
|
280
|
-
- **[0.8.14]** (2026-07-09) — "GPT 연결 실패가 여전히 무응답으로 끝나는 케이스를 잡아줘" — traced the remaining GPT/routing failure path to post-call continuity: the route target could be credentialed and servable, but still end a turn with a recoverable terminal result such as plain `OpenAI returned no content`, rate/quota pressure, model unavailable/not found, or an invalid agent-tool response. jeo already knew the prompt tier and equivalent credentialed fallbacks, but it only applied that fallback before the model call. After the call failed, the turn stopped instead of trying another model in the same tier.
|
|
281
281
|
|
|
282
282
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
283
283
|
<!-- CHANGELOG:END -->
|
package/README.zh.md
CHANGED
|
@@ -265,11 +265,11 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
|
|
|
265
265
|
## 更新日志 (Changelog)
|
|
266
266
|
|
|
267
267
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
268
|
+
- **[0.8.19]** (2026-07-09) — "프롬프트 라우팅에 안티그라비티 프로바이더의 경우, 안티그라비티용 소넷과 오퍼스도 3.5급으로 라우팅될수있도록해줘" — Antigravity re-exports 3 distinct model families (Anthropic Claude, Google Gemini, OpenAI GPT-OSS) behind one credential, structurally unlike every other provider. `high`/`complex` tier auto-select always resolved to Google's Gemini 3.5 rows: Anthropic's real 64,000-token output ceiling lost a same-thinking-tier tie to Google's 65,536 by a margin with no practical significance, and Gemini's 1M-token context further outranked Claude's real 200K window — so `antigravity/claude-sonnet-4-6`/`antigravity/claude-opus-4-6-thinking` were NEVER reachable through auto-select, even though both were already correctly `sizeClass`-tagged into the `high`/`complex` pools.
|
|
268
269
|
- **[0.8.18]** (2026-07-09) — "라우팅 매 프롬프트마다 변경되는지, route why 의도대로 동작하는지 검증하고 배포할게있는지 확인해줘" — 2 parallel subagents live-verified per-prompt routing + `/route why` (including the previously-only-unit-tested post-call equivalent-model fallback path, now reproduced against a real mock 500-error server) and audited a disconnected `RouteHistory` class shipped as incomplete scaffolding in 0.8.16/0.8.17 (class + 11 tests existed, but nothing ever called it — no `/route history` subcommand, no wiring into the turn loop). Live testing also surfaced one real bug: the TUI footer's model/provider label never updated after a mid-turn fallback, staying on the pre-fallback model for the rest of that turn's render even though the backend decision (`lastRouteDecision`) was already correct.
|
|
269
270
|
- **[0.8.17]** (2026-07-09) — "서브에이전트 이용해. 병렬루 부가 나머지모두 개선하고 검증후 배포까지하자" — a prior 4-way parallel subagent cross-verification pass found the README `[!CAUTION]` routing-lock block (added this session for "프롬프트 라우팅이 한번 정해지면 안바뀌는데?") was directionally correct but had 2 real inaccuracies, plus a concrete test-coverage gap: every existing pin/override regression test drove the lock via the `--model` CLI startup flag, never via typing `/model <name>` as an interactive slash command mid-session, and none proved persistence across 3+ consecutive turns.
|
|
270
271
|
- **[0.8.16]** (2026-07-09) — "jeo code의 computer use 기능은 / 명령어로 설정할수있도록하고, 실동작검증까지진행해줘" — the desktop-automation `computer` tool was only togglable by hand-editing `computer.enabled` in `~/.jeo/config.json`; there was no in-session way to enable it for a single run without leaving a permanent config change behind.
|
|
271
272
|
- **[0.8.15]** (2026-07-09) — "api 인증 모델도 사용가능하도록 딥리서치하고 개선해줘" — traced the API-authenticated GPT path to a routing catalog gap: `GET /models` with an OpenAI API key or an OpenAI-compatible provider returned real account-scoped model ids, and `/model` could show/pin them, but prompt routing still built its auto-select pools from static `MODEL_CATALOG` rows only. A custom `openaiBaseUrl` made this worse by treating every public OpenAI row as servable, so routing could choose `gpt-*`/`o*` ids that the configured local/Azure/proxy endpoint never listed.
|
|
272
|
-
- **[0.8.14]** (2026-07-09) — "GPT 연결 실패가 여전히 무응답으로 끝나는 케이스를 잡아줘" — traced the remaining GPT/routing failure path to post-call continuity: the route target could be credentialed and servable, but still end a turn with a recoverable terminal result such as plain `OpenAI returned no content`, rate/quota pressure, model unavailable/not found, or an invalid agent-tool response. jeo already knew the prompt tier and equivalent credentialed fallbacks, but it only applied that fallback before the model call. After the call failed, the turn stopped instead of trying another model in the same tier.
|
|
273
273
|
|
|
274
274
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
275
275
|
<!-- CHANGELOG:END -->
|
package/package.json
CHANGED
|
@@ -456,6 +456,44 @@ function crossProviderPoolPick(tier: PromptTier, config: RoutingConfig, sessionI
|
|
|
456
456
|
return pool.length > 0 ? selectFromPool(pool, sessionId) : null;
|
|
457
457
|
}
|
|
458
458
|
|
|
459
|
+
/** Antigravity re-exports THREE distinct model families (Anthropic's Claude
|
|
460
|
+
* Sonnet/Opus, Google's own Gemini, OpenAI's GPT-OSS) behind one credential —
|
|
461
|
+
* structurally different from every other provider, which represents exactly
|
|
462
|
+
* one vendor. The tier's plain single-winner pick (`strongestMidTierCredentialed`/
|
|
463
|
+
* `strongestCredentialed`) always resolves to Google's rows for `high`/`complex`:
|
|
464
|
+
* Anthropic's real 64,000-token output ceiling loses a same-thinking-tier tie to
|
|
465
|
+
* Google's 65,536 by a margin with no practical significance, and Gemini's
|
|
466
|
+
* 1M-token context further outranks Claude's real (but comparatively narrower)
|
|
467
|
+
* 200K window — so `antigravity/claude-sonnet-4-6`/`antigravity/claude-opus-4-6-thinking`
|
|
468
|
+
* were NEVER reachable through auto-select even though both are already
|
|
469
|
+
* correctly `sizeClass`-tagged into the `high`/`complex` pools (`tierModelPool`).
|
|
470
|
+
* This applies the SAME session-stable spread `routing.crossProviderPool` uses
|
|
471
|
+
* globally (opt-in, default off), but scoped and DEFAULT-ON specifically for
|
|
472
|
+
* Antigravity's multi-company pool: one candidate per COMPANY (the strongest
|
|
473
|
+
* Antigravity row for that company, by `compareStrengthAscending`) so Google's
|
|
474
|
+
* several same-tier Gemini rows don't crowd out the single Anthropic/OpenAI
|
|
475
|
+
* candidate 3-to-1 — each vendor Antigravity re-exports gets an equal, genuinely
|
|
476
|
+
* reachable share. `null` when Antigravity isn't credentialed this turn, the
|
|
477
|
+
* tier's Antigravity-only pool is empty, or it spans only ONE company (nothing
|
|
478
|
+
* to spread — the plain single-winner path already picks correctly and this
|
|
479
|
+
* would just add indirection). */
|
|
480
|
+
function antigravityCompanyPoolPick(tier: PromptTier, config: RoutingConfig, sessionId: string | undefined): string | null {
|
|
481
|
+
if (!hasAntigravityOauth(config)) return null;
|
|
482
|
+
const pool = tierModelPool(tier, config)
|
|
483
|
+
.map(id => catalogMetadata(id))
|
|
484
|
+
.filter((m): m is CatalogModel => !!m && m.provider === "antigravity");
|
|
485
|
+
if (pool.length === 0) return null;
|
|
486
|
+
const byCompany = new Map<string, CatalogModel>();
|
|
487
|
+
for (const m of pool) {
|
|
488
|
+
const company = m.company ?? "antigravity";
|
|
489
|
+
const current = byCompany.get(company);
|
|
490
|
+
if (!current || compareStrengthAscending(current, m) < 0) byCompany.set(company, m);
|
|
491
|
+
}
|
|
492
|
+
if (byCompany.size < 2) return null; // one vendor in this tier -> nothing to spread across
|
|
493
|
+
const representatives = [...byCompany.values()].map(m => m.canonical).sort();
|
|
494
|
+
return selectFromPool(representatives, sessionId);
|
|
495
|
+
}
|
|
496
|
+
|
|
459
497
|
// --- Tier -> model/thinking resolution (inline off config.roles/config.routing.tiers; no static map). ---
|
|
460
498
|
|
|
461
499
|
/** Exported for `jeo doctor`'s routing-preview diagnostic (same resolution the real
|
|
@@ -466,8 +504,8 @@ export function resolveTierModel(tier: PromptTier, config: RoutingConfig, sessio
|
|
|
466
504
|
if (configured) return configured;
|
|
467
505
|
if (tier === "trivial") return config.roles?.smol || crossProviderPoolPick(tier, config, sessionId) || cheapestCredentialed(config) || config.defaultModel;
|
|
468
506
|
if (tier === "standard") return config.roles?.medium || config.roles?.high || crossProviderPoolPick(tier, config, sessionId) || config.defaultModel;
|
|
469
|
-
if (tier === "high") return config.roles?.high || config.roles?.medium || crossProviderPoolPick(tier, config, sessionId) || strongestMidTierCredentialed(config) || config.defaultModel;
|
|
470
|
-
if (tier === "complex") return config.roles?.xhigh || config.roles?.slow || crossProviderPoolPick(tier, config, sessionId) || strongestCredentialed(config) || config.defaultModel;
|
|
507
|
+
if (tier === "high") return config.roles?.high || config.roles?.medium || crossProviderPoolPick(tier, config, sessionId) || antigravityCompanyPoolPick(tier, config, sessionId) || strongestMidTierCredentialed(config) || config.defaultModel;
|
|
508
|
+
if (tier === "complex") return config.roles?.xhigh || config.roles?.slow || crossProviderPoolPick(tier, config, sessionId) || antigravityCompanyPoolPick(tier, config, sessionId) || strongestCredentialed(config) || config.defaultModel;
|
|
471
509
|
return config.defaultModel;
|
|
472
510
|
}
|
|
473
511
|
|
package/src/agent/session.ts
CHANGED
|
@@ -13,6 +13,11 @@ export interface SessionHeader {
|
|
|
13
13
|
/** Model id pinned to this session; restored on resume so each session can carry
|
|
14
14
|
* its own model independent of the global default (per-session model selection). */
|
|
15
15
|
model?: string;
|
|
16
|
+
/** Unsent input-box draft at last save, restored into the prompt on `/resume`
|
|
17
|
+
* (gajae-code parity: an in-progress prompt survives a quit + resume instead
|
|
18
|
+
* of being silently lost). Absent/empty when there was nothing to restore. */
|
|
19
|
+
draft?: string;
|
|
20
|
+
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
export interface SessionEntry {
|
|
@@ -416,6 +421,25 @@ export async function updateSessionModel(id: string, model: string, cwd = proces
|
|
|
416
421
|
return true;
|
|
417
422
|
}, cwd);
|
|
418
423
|
}
|
|
424
|
+
/**
|
|
425
|
+
* Persist (or clear) the unsent input-box draft in a session's JSONL header so a
|
|
426
|
+
* later `/resume` restores it into the prompt (gajae-code parity). Empty text
|
|
427
|
+
* clears any stale draft rather than writing an empty string. No-op (no write)
|
|
428
|
+
* when the header's current draft already matches. Throws a clear Error if the
|
|
429
|
+
* session file does not exist.
|
|
430
|
+
*/
|
|
431
|
+
export async function updateSessionDraft(id: string, draft: string, cwd = process.cwd()): Promise<void> {
|
|
432
|
+
const trimmed = draft.trim();
|
|
433
|
+
const next = trimmed ? trimmed : undefined;
|
|
434
|
+
await rewriteSessionHeader(id, header => {
|
|
435
|
+
if ((header.draft ?? undefined) === next) return false;
|
|
436
|
+
if (next === undefined) delete header.draft;
|
|
437
|
+
else header.draft = next;
|
|
438
|
+
return true;
|
|
439
|
+
}, cwd);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
|
|
419
443
|
|
|
420
444
|
/**
|
|
421
445
|
* Delete a session file.
|
|
@@ -47,6 +47,11 @@ export interface SessionSlashResult {
|
|
|
47
47
|
lastUserInput?: string;
|
|
48
48
|
/** Present only when changed (by `/session resume` re-seeding retry state). */
|
|
49
49
|
lastReply?: string;
|
|
50
|
+
/** Present only when changed (by `/session resume` restoring header.draft into
|
|
51
|
+
* the prompt box). */
|
|
52
|
+
draft?: string;
|
|
53
|
+
|
|
54
|
+
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
/**
|
|
@@ -62,14 +67,18 @@ export async function runSessionSlash(input: string, ctx: SessionSlashCtx): Prom
|
|
|
62
67
|
let sessionModelChanged = false;
|
|
63
68
|
let lastUserInput: string | undefined;
|
|
64
69
|
let lastReply: string | undefined;
|
|
70
|
+
let draft: string | undefined;
|
|
71
|
+
|
|
65
72
|
|
|
66
73
|
const result = (): SessionSlashResult => ({
|
|
67
74
|
sessionId,
|
|
68
75
|
...(sessionModelChanged ? { sessionModel } : {}),
|
|
69
76
|
...(lastUserInput !== undefined ? { lastUserInput } : {}),
|
|
70
77
|
...(lastReply !== undefined ? { lastReply } : {}),
|
|
78
|
+
...(draft !== undefined ? { draft } : {}),
|
|
71
79
|
});
|
|
72
80
|
|
|
81
|
+
|
|
73
82
|
const tokens = input.substring(8).trim().split(/\s+/).filter(Boolean);
|
|
74
83
|
const sub = (tokens[0] ?? "").toLowerCase();
|
|
75
84
|
|
|
@@ -125,6 +134,10 @@ export async function runSessionSlash(input: string, ctx: SessionSlashCtx): Prom
|
|
|
125
134
|
sessionId = rid;
|
|
126
135
|
// Restore the model this session was last using (per-session model).
|
|
127
136
|
if (header.model) { sessionModel = header.model; sessionModelChanged = true; }
|
|
137
|
+
// Restore the unsent input-box draft left over at last save, if any.
|
|
138
|
+
if (header.draft) draft = header.draft;
|
|
139
|
+
|
|
140
|
+
|
|
128
141
|
// Seed /retry + reply marker from the last user/assistant turn.
|
|
129
142
|
lastUserInput = ""; lastReply = "";
|
|
130
143
|
for (let k = history.length - 1; k >= 1; k--) {
|
package/src/commands/launch.ts
CHANGED
|
@@ -110,6 +110,8 @@ import {
|
|
|
110
110
|
latestSessionId,
|
|
111
111
|
exportSession,
|
|
112
112
|
updateSessionModel,
|
|
113
|
+
updateSessionDraft,
|
|
114
|
+
|
|
113
115
|
appendCompaction,
|
|
114
116
|
resolveSessionRef,
|
|
115
117
|
} from "../agent/session";
|
|
@@ -752,6 +754,10 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
752
754
|
|
|
753
755
|
// pi-style session persistence: resume an existing session or create a new one.
|
|
754
756
|
let sessionId: string | undefined;
|
|
757
|
+
// Unsent input-box draft restored from a resumed session's header (gajae-code
|
|
758
|
+
// parity) — folded into the very first prompt once `queuedPromptInput` exists.
|
|
759
|
+
let restoredDraft = "";
|
|
760
|
+
|
|
755
761
|
let compactionSeq = 0;
|
|
756
762
|
if (!flags.noSession) {
|
|
757
763
|
if (flags.resumeInteractive) {
|
|
@@ -770,6 +776,8 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
770
776
|
for (const m of messages) history.push(m);
|
|
771
777
|
sessionId = id;
|
|
772
778
|
if (!initialSessionModel && header.model) sessionModel = header.model;
|
|
779
|
+
if (header.draft) restoredDraft = header.draft;
|
|
780
|
+
|
|
773
781
|
const modelNote = sessionModel ? ` · model ${sessionModel}` : "";
|
|
774
782
|
console.log(`Resumed session ${id} (${messages.length} messages).${modelNote}`);
|
|
775
783
|
} catch (err) {
|
|
@@ -808,6 +816,8 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
808
816
|
// Restore the model this session was last using unless the CLI explicitly
|
|
809
817
|
// pinned one (flags.model/role/provider → initialSessionModel wins).
|
|
810
818
|
if (!initialSessionModel && header.model) sessionModel = header.model;
|
|
819
|
+
if (header.draft) restoredDraft = header.draft;
|
|
820
|
+
|
|
811
821
|
const modelNote = sessionModel ? ` · model ${sessionModel}` : "";
|
|
812
822
|
console.log(`Resumed session ${id} (${messages.length} messages).${modelNote}`);
|
|
813
823
|
} catch (err) {
|
|
@@ -2183,6 +2193,12 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
2183
2193
|
// (the user explicitly invoked them — no second Enter).
|
|
2184
2194
|
const pendingMidTurnCommands: string[] = [];
|
|
2185
2195
|
const queuedPromptInput: PromptInputQueue = { pendingLines: pendingStdinLines, partial: "", pastedLines: [], inPaste: false };
|
|
2196
|
+
// Fold a restored `/resume` draft (session header `draft`) into the very first
|
|
2197
|
+
// prompt the same way a queued mid-turn prefill lands — visible in the input box,
|
|
2198
|
+
// Enter to run, Esc/Ctrl+U to discard (gajae-code parity: an in-progress prompt
|
|
2199
|
+
// survives a quit + resume instead of being silently lost).
|
|
2200
|
+
if (restoredDraft) queuedPromptInput.partial = restoredDraft;
|
|
2201
|
+
|
|
2186
2202
|
queueBusyInput = (chunk: string) => captureLivePromptInputChunk(queuedPromptInput, chunk);
|
|
2187
2203
|
queueBusyPasteActive = () => queuedPromptInput.inPaste;
|
|
2188
2204
|
queueBusySnapshot = () => ({
|
|
@@ -2194,6 +2210,19 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
2194
2210
|
// default routes to the immediate-dispatch queue served at the top of the loop.
|
|
2195
2211
|
(jeoEnv("NO_MIDTURN_DISPATCH") === "1" ? pendingStdinLines : pendingMidTurnCommands).push(line);
|
|
2196
2212
|
};
|
|
2213
|
+
// Persist (or clear) the unsent input-box draft into the session header so a later
|
|
2214
|
+
// `/resume` restores it (gajae-code parity). Best-effort, mirrors persistSessionModel;
|
|
2215
|
+
// reads readline's own line buffer since that is the single source of truth for
|
|
2216
|
+
// "what's currently typed but not submitted" (same buffer the box-render/prefill
|
|
2217
|
+
// logic above reads from).
|
|
2218
|
+
const persistSessionDraft = async (): Promise<void> => {
|
|
2219
|
+
if (flags.noSession || !sessionId) return;
|
|
2220
|
+
const current = (rl as unknown as { line?: string }).line ?? queuedPromptInput.partial ?? "";
|
|
2221
|
+
try {
|
|
2222
|
+
await updateSessionDraft(sessionId, current, cwd);
|
|
2223
|
+
} catch { /* best-effort */ }
|
|
2224
|
+
};
|
|
2225
|
+
|
|
2197
2226
|
// Bracketed-paste line routing at the PROMPT: readline strips the 2004 markers
|
|
2198
2227
|
// and replays pasted lines as synthetic keypresses, emitting paste-start /
|
|
2199
2228
|
// paste-end around them. Lines submitted INSIDE that window are intentional
|
|
@@ -3695,6 +3724,8 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
3695
3724
|
if (pickResult.sessionModel !== undefined) sessionModel = pickResult.sessionModel;
|
|
3696
3725
|
if (pickResult.lastUserInput !== undefined) lastUserInput = pickResult.lastUserInput;
|
|
3697
3726
|
if (pickResult.lastReply !== undefined) lastReply = pickResult.lastReply;
|
|
3727
|
+
if (pickResult.draft) queuedPromptInput.partial = pickResult.draft;
|
|
3728
|
+
|
|
3698
3729
|
}
|
|
3699
3730
|
|
|
3700
3731
|
// Remote (Telegram) free-text reply / config command routing — wired here,
|
|
@@ -3896,7 +3927,9 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
3896
3927
|
});
|
|
3897
3928
|
sessionId = result.sessionId;
|
|
3898
3929
|
if (result.sessionModel !== undefined) sessionModel = result.sessionModel;
|
|
3899
|
-
if (result.
|
|
3930
|
+
if (result.lastReply !== undefined) lastReply = result.lastReply;
|
|
3931
|
+
if (result.draft) queuedPromptInput.partial = result.draft;
|
|
3932
|
+
|
|
3900
3933
|
if (result.lastReply !== undefined) lastReply = result.lastReply;
|
|
3901
3934
|
continue;
|
|
3902
3935
|
}
|
|
@@ -4598,7 +4631,9 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
4598
4631
|
}
|
|
4599
4632
|
}
|
|
4600
4633
|
disarmPreview(); // clear footer + restore full-screen scrolling before leaving the REPL
|
|
4634
|
+
await persistSessionDraft();
|
|
4601
4635
|
if (hardExitOnLoopEnd) {
|
|
4636
|
+
|
|
4602
4637
|
try {
|
|
4603
4638
|
disarmPreview();
|
|
4604
4639
|
out.write("\x1b[?25h\n");
|