jeo-code 0.7.56 → 0.7.58

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.
@@ -146,10 +146,23 @@ export async function describeModel(
146
146
  return { input, resolved, provider: resolveProvider(resolved) };
147
147
  }
148
148
 
149
- export type ModelRole = "smol" | "slow" | "plan";
150
-
151
- /** Resolve a model role tier (smol/slow/plan) → configured tier model, else defaultModel. */
152
- export function resolveRoleModel(role: ModelRole, config: { defaultModel: string; roles?: { smol?: string; slow?: string; plan?: string } }): string {
149
+ export type ModelRole = "smol" | "medium" | "high" | "xhigh" | "slow" | "plan";
150
+
151
+ /** Resolve a model role tier (smol/medium/high/xhigh/slow/plan) → configured tier model, else defaultModel. */
152
+ export function resolveRoleModel(
153
+ role: ModelRole,
154
+ config: {
155
+ defaultModel: string;
156
+ roles?: {
157
+ smol?: string;
158
+ medium?: string;
159
+ high?: string;
160
+ xhigh?: string;
161
+ slow?: string;
162
+ plan?: string;
163
+ };
164
+ },
165
+ ): string {
153
166
  return config.roles?.[role] || config.defaultModel;
154
167
  }
155
168
 
@@ -58,13 +58,15 @@ h1.ok{color:#22d3ee}h1.fail{color:#f472b6}
58
58
  p{margin:.25rem 0;color:#8b8b96;font-size:.92rem}
59
59
  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}
60
60
  button:hover{background:#1b1c27}
61
- .hint{margin-top:.9rem;font-size:.78rem;color:#5f5f6b}</style></head>
61
+ .hint{margin-top:.9rem;font-size:.78rem;color:#5f5f6b}
62
+ .closed{display:none}</style></head>
62
63
  <body><div class="card">
63
64
  <img class="wordmark" alt="jeo" src="${JEO_WORDMARK_DATA_URI}">
64
65
  <h1 class="__STATUS__">__TITLE__</h1>
65
66
  <p>__MSG__</p>
66
67
  <button id="jeo-close" type="button">Close</button>
67
68
  <p class="hint">Closing automatically in <span id="jeo-countdown">__SECONDS__</span>s…</p>
69
+ <p class="hint closed" id="jeo-closed-msg">Window closed. You can close this tab.</p>
68
70
  </div>
69
71
  <script>__SCRIPT__</script>
70
72
  </body></html>`;
@@ -78,8 +80,16 @@ const AUTO_CLOSE_SECONDS = 5;
78
80
 
79
81
  function renderHtml(ok: boolean, msg: string): string {
80
82
  const script = `(function(){
81
- var n=${AUTO_CLOSE_SECONDS},el=document.getElementById("jeo-countdown"),btn=document.getElementById("jeo-close"),t;
82
- function closeNow(){clearInterval(t);window.close();}
83
+ var n=${AUTO_CLOSE_SECONDS},el=document.getElementById("jeo-countdown"),btn=document.getElementById("jeo-close"),closedMsg=document.getElementById("jeo-closed-msg"),t;
84
+ function closeNow(){
85
+ clearInterval(t);
86
+ var closed=window.close();
87
+ if(!closed){
88
+ btn.style.display="none";
89
+ el.parentElement.style.display="none";
90
+ closedMsg.classList.remove("closed");
91
+ }
92
+ }
83
93
  t=setInterval(function(){n-=1;if(el)el.textContent=String(Math.max(n,0));if(n<=0)closeNow();},1000);
84
94
  if(btn)btn.addEventListener("click",closeNow);
85
95
  })();`;
@@ -8,7 +8,7 @@ import { meter } from "../tui/components/meter";
8
8
  import { size } from "../tui/terminal";
9
9
  import chalk from "chalk";
10
10
  import { extractChatgptAccountId, CODEX_RESPONSES_URL } from "../ai/providers/openai-responses";
11
- import { resolveTierModel } from "../agent/prompt-router";
11
+ import { resolveTierModel, tierModelPool } from "../agent/prompt-router";
12
12
 
13
13
  interface ProbeResult {
14
14
  status: "ok" | "fail" | "skipped";
@@ -197,8 +197,9 @@ export async function runDoctorCommand(args: string[] = []): Promise<void> {
197
197
  if (routingEnabled) {
198
198
  const tierCandidates: { tier: string; model: string | undefined }[] = [
199
199
  { tier: "trivial", model: config.routing?.tiers?.trivial?.model || config.roles?.smol },
200
- { tier: "standard", model: config.routing?.tiers?.standard?.model },
201
- { tier: "complex", model: config.routing?.tiers?.complex?.model || config.roles?.slow },
200
+ { tier: "standard", model: config.routing?.tiers?.standard?.model || config.roles?.medium || config.roles?.high },
201
+ { tier: "high", model: config.routing?.tiers?.high?.model || config.roles?.high || config.roles?.medium },
202
+ { tier: "complex", model: config.routing?.tiers?.complex?.model || config.roles?.xhigh || config.roles?.slow },
202
203
  ];
203
204
  for (const { tier, model } of tierCandidates) {
204
205
  if (!model) continue;
@@ -220,11 +221,36 @@ export async function runDoctorCommand(args: string[] = []): Promise<void> {
220
221
  // informational — never affects ready/--strict, same contract as routingNotes.
221
222
  const routingPreview: { tier: string; model: string; provider: string; source: string }[] = [];
222
223
  if (routingEnabled) {
223
- for (const tier of ["trivial", "standard", "complex"] as const) {
224
- const explicit = config.routing?.tiers?.[tier]?.model || (tier === "trivial" ? config.roles?.smol : tier === "complex" ? config.roles?.slow : undefined);
224
+ // Legacy role-tier fallback per tier, mirroring resolveTierModel's OWN precedence
225
+ // (not just the id it happens to already show up under `routing.tiers.*.model`) —
226
+ // so e.g. a `roles.medium`-only config correctly shows "configured", not
227
+ // "auto-selected", for `standard`.
228
+
229
+ for (const tier of ["trivial", "standard", "high", "complex"] as const) {
230
+ const legacyRole =
231
+ tier === "trivial" ? config.roles?.smol
232
+ : tier === "standard" ? config.roles?.medium || config.roles?.high
233
+ : tier === "high" ? config.roles?.high || config.roles?.medium
234
+ : config.roles?.xhigh || config.roles?.slow;
235
+ const explicit = config.routing?.tiers?.[tier]?.model || legacyRole;
225
236
  const model = resolveTierModel(tier, config);
226
237
  const provider = resolveProvider(model);
227
- const source = explicit ? "configured" : model === config.defaultModel ? "defaultModel" : tier === "trivial" ? "auto-selected: cheapest credentialed" : "auto-selected: strongest credentialed";
238
+ // crossProviderPoolPick (prompt-router.ts) is tried BEFORE every tier's own
239
+ // cheapest/strongest/mid-tier fallback, so a non-empty pool always wins over
240
+ // those once the flag is on — `.length > 0` alone is enough to know the pool,
241
+ // not the tier-specific fallback, produced `model`.
242
+ const pooled = !explicit && model !== config.defaultModel && !!config.routing?.crossProviderPool && tierModelPool(tier, config).length > 0;
243
+ const source = explicit
244
+ ? "configured"
245
+ : model === config.defaultModel
246
+ ? "defaultModel"
247
+ : pooled
248
+ ? "auto-selected: cross-provider pool"
249
+ : tier === "trivial"
250
+ ? "auto-selected: cheapest credentialed"
251
+ : tier === "high"
252
+ ? "auto-selected: strongest mid-tier credentialed"
253
+ : "auto-selected: strongest credentialed";
228
254
  routingPreview.push({ tier, model, provider, source });
229
255
  }
230
256
  }
@@ -885,7 +885,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
885
885
  // selection must stay session-local: running sessions never influence each other.
886
886
  const routingEnabled = sessionRouteOverride ?? !!turnConfig.routing?.enabled;
887
887
  let routed = routingEnabled && !sessionModel
888
- ? await routePrompt(userInput, turnConfig, { hasImages: !!images?.length }).catch(() => null)
888
+ ? await routePrompt(userInput, turnConfig, { hasImages: !!images?.length, sessionId }).catch(() => null)
889
889
  : null;
890
890
  // Fail-open credential gate: `routePrompt` only resolves a tier's model from
891
891
  // config (routing.tiers/roles.smol/roles.slow/defaultModel) — it deliberately
package/src/tui/app.ts CHANGED
@@ -73,7 +73,7 @@ export interface LaunchTuiOptions {
73
73
  * gate fell back). Renders a persistent `⚡tier` marker in the status bar so the
74
74
  * routed model stays visible without depending on the transient `[route]`
75
75
  * console notice. See `StatusBarData.routedTier` in components/status.ts. */
76
- routedTier?: "trivial" | "standard" | "complex";
76
+ routedTier?: "trivial" | "standard" | "high" | "complex";
77
77
  }
78
78
 
79
79
  export interface AgentEventsLike {
@@ -375,7 +375,7 @@ export class LaunchTui {
375
375
  private readonly thinkingLevel?: string;
376
376
  // PromptRouter's resolved tier when routing chose this turn's model; undefined when
377
377
  // routing didn't engage. Renders the persistent `⚡tier` marker in the status bar.
378
- private readonly routedTier?: "trivial" | "standard" | "complex";
378
+ private readonly routedTier?: "trivial" | "standard" | "high" | "complex";
379
379
 
380
380
  constructor(opts: LaunchTuiOptions) {
381
381
  // Backpressure-aware wrapper around the underlying writer (real stdout, or the
@@ -24,7 +24,7 @@ export interface StatusBarData {
24
24
  * marker so the routed model stays visible without depending on the transient
25
25
  * `[route] …` console notice, which intentionally stays silent for routine
26
26
  * unescalated `standard`-tier turns. */
27
- routedTier?: "trivial" | "standard" | "complex";
27
+ routedTier?: "trivial" | "standard" | "high" | "complex";
28
28
  branch?: string;
29
29
  /** Uncommitted-change count for the `?N` dirty flag; omit/0 = clean. */
30
30
  dirtyCount?: number;