@bitkyc08/opencodex 2.7.2 → 2.7.3

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.
@@ -37,4 +37,4 @@ v2: 多线程代理(spawn_agent)。所有模型使用 v2 协作界面。
37
37
  -d '{
38
38
  "model": "gpt-5.4",
39
39
  "input": "Hello, world!"
40
- }'`})]})]})}var Ft=`opencodex-api-token`,It=!1,Lt=null;function Rt(e){try{let t=e instanceof Request?e.url:String(e);return new URL(t,window.location.href).pathname}catch{return null}}function zt(e){let t=Rt(e);return!!t&&(t.startsWith(`/api/`)||t.startsWith(`/v1/`))}function Bt(){try{return sessionStorage.getItem(Ft)?.trim()||null}catch{return null}}function Vt(e){try{sessionStorage.setItem(Ft,e)}catch{}}function Ht(){try{sessionStorage.removeItem(Ft)}catch{}}function Ut(e,t,n){let r=new Headers(t?.headers??(e instanceof Request?e.headers:void 0));return r.set(`X-OpenCodex-API-Key`,n),e instanceof Request?[new Request(e,{headers:r}),t?{...t,headers:r}:void 0]:[e,{...t,headers:r}]}async function Wt(){return Lt||(Lt=Promise.resolve().then(()=>window.prompt(`OpenCodex API token`)?.trim()||null).finally(()=>{Lt=null}),Lt)}function Gt(){if(It)return;It=!0;let e=window.fetch.bind(window);window.fetch=async(t,n)=>{if(!zt(t))return e(t,n);let r=Bt(),[i,a]=r?Ut(t,n,r):[t,n],o=await e(i,a);if(o.status!==401)return o;r&&Ht();let s=await Wt();if(!s)return o;Vt(s);let[c,l]=Ut(t,n,s),u=await e(c,l);return u.status===401&&Ht(),u}}Gt();var Kt=new Set([`dashboard`,`providers`,`models`,`subagents`,`logs`,`debug`,`usage`,`codex-auth`,`api`]);function qt(){let e=location.hash.replace(/^#\/?/,``);return Kt.has(e)?e:`dashboard`}var Jt=``,Yt=`ocx-theme`,Xt=[{id:`dashboard`,tkey:`nav.dashboard`,Icon:w},{id:`providers`,tkey:`nav.providers`,Icon:T},{id:`models`,tkey:`nav.models`,Icon:E},{id:`subagents`,tkey:`nav.subagents`,Icon:ee},{id:`logs`,tkey:`nav.logs`,Icon:D},{id:`debug`,tkey:`nav.debug`,Icon:te},{id:`usage`,tkey:`nav.usage`,Icon:ne},{id:`codex-auth`,tkey:`nav.codexAuth`,Icon:L},{id:`api`,tkey:`nav.api`,Icon:me}],Zt={light:de,dark:fe,system:pe},Qt={light:`theme.light`,dark:`theme.dark`,system:`theme.system`};function $t(e){if(!e||typeof e!=`object`||!(`version`in e))return null;let t=e.version;return typeof t==`string`&&t.length>0?t:null}function en(){let e=localStorage.getItem(Yt);return e===`light`||e===`dark`?e:`system`}function tn(){let[e,t]=(0,_.useState)(qt),n=e=>{location.hash=e,t(e)},[r,i]=(0,_.useState)(en),[a,o]=(0,_.useState)(null),{locale:s,setLocale:c}=Ce(),l=we();(0,_.useEffect)(()=>{let e=()=>t(qt());return window.addEventListener(`hashchange`,e),()=>window.removeEventListener(`hashchange`,e)},[]),(0,_.useEffect)(()=>{let e=document.documentElement;r===`system`?(e.removeAttribute(`data-theme`),localStorage.removeItem(Yt)):(e.setAttribute(`data-theme`,r),localStorage.setItem(Yt,r))},[r]),(0,_.useEffect)(()=>{let e=!1,t=async()=>{try{let t=await fetch(`${Jt}/healthz`);if(!t.ok)return;let n=$t(await t.json());!e&&n&&o(n)}catch{}};t();let n=setInterval(t,3e4);return()=>{e=!0,clearInterval(n)}},[]);let u=()=>i(e=>e===`light`?`dark`:e===`dark`?`system`:`light`),d=Zt[r],f=a??`2.7.2`,[p,m]=(0,_.useState)(!1);return(0,S.jsxs)(`div`,{className:`app`,children:[(0,S.jsxs)(`aside`,{className:`sidebar`,children:[(0,S.jsxs)(`div`,{className:`brand`,children:[(0,S.jsx)(`span`,{className:`brand-logo`,role:`img`,"aria-label":`opencodex logo`}),(0,S.jsx)(`span`,{className:`name`,children:`opencodex`}),(0,S.jsxs)(`span`,{className:`ver`,children:[`v`,f]})]}),(0,S.jsx)(`nav`,{children:Xt.map(({id:t,tkey:r,Icon:i})=>(0,S.jsxs)(`button`,{className:`nav-item${e===t?` active`:``}`,"data-page":t,onClick:()=>n(t),"aria-current":e===t?`page`:void 0,children:[(0,S.jsx)(i,{}),` `,l(r)]},t))}),(0,S.jsxs)(`div`,{className:`sidebar-foot`,children:[(0,S.jsxs)(`div`,{className:`lang-toggle`,children:[(0,S.jsx)(me,{"aria-hidden":!0}),(0,S.jsx)(Ae,{value:s,options:_e.map(e=>({value:e.code,label:e.name})),onChange:e=>c(e),label:l(`lang.label`),placement:`right`,style:{flex:1,minWidth:0,width:`100%`}})]}),(0,S.jsxs)(`button`,{type:`button`,className:`theme-toggle`,onClick:u,"aria-label":`${l(`theme.label`)}: ${l(Qt[r])}`,title:`${l(`theme.label`)}: ${l(Qt[r])}`,children:[(0,S.jsx)(d,{}),` `,(0,S.jsx)(`span`,{className:`mode`,children:l(Qt[r])})]}),(0,S.jsxs)(`button`,{type:`button`,className:`theme-toggle stop-toggle`,onClick:async()=>{if(confirm(l(`dash.stopConfirm`))){m(!0);try{await fetch(`${Jt}/api/stop`,{method:`POST`})}catch{}}},disabled:p,"aria-label":l(`dash.stop`),title:l(`dash.stop`),children:[(0,S.jsx)(I,{}),` `,(0,S.jsx)(`span`,{className:`mode`,children:l(p?`dash.stopping`:`dash.stop`)})]}),(0,S.jsxs)(`a`,{className:`sidebar-link`,href:`https://github.com/lidge-jun/opencodex`,target:`_blank`,rel:`noreferrer`,children:[(0,S.jsx)(F,{}),` `,l(`common.github`)]})]})]}),(0,S.jsx)(`main`,{className:`main`,children:(0,S.jsxs)(`div`,{className:`main-inner`,children:[e===`dashboard`&&(0,S.jsx)(He,{apiBase:Jt}),e===`providers`&&(0,S.jsx)(nt,{apiBase:Jt}),e===`models`&&(0,S.jsx)(ct,{apiBase:Jt}),e===`subagents`&&(0,S.jsx)(lt,{apiBase:Jt}),e===`logs`&&(0,S.jsx)(yt,{apiBase:Jt}),e===`debug`&&(0,S.jsx)(bt,{apiBase:Jt}),e===`usage`&&(0,S.jsx)(Dt,{apiBase:Jt}),e===`codex-auth`&&(0,S.jsx)(kt,{apiBase:Jt}),e===`api`&&(0,S.jsx)(Pt,{apiBase:Jt})]})})]})}v.createRoot(document.getElementById(`root`)).render((0,S.jsx)(_.StrictMode,{children:(0,S.jsx)(Se,{children:(0,S.jsx)(tn,{})})}));
40
+ }'`})]})]})}var Ft=`opencodex-api-token`,It=!1,Lt=null;function Rt(e){try{let t=e instanceof Request?e.url:String(e);return new URL(t,window.location.href).pathname}catch{return null}}function zt(e){let t=Rt(e);return!!t&&(t.startsWith(`/api/`)||t.startsWith(`/v1/`))}function Bt(){try{return sessionStorage.getItem(Ft)?.trim()||null}catch{return null}}function Vt(e){try{sessionStorage.setItem(Ft,e)}catch{}}function Ht(){try{sessionStorage.removeItem(Ft)}catch{}}function Ut(e,t,n){let r=new Headers(t?.headers??(e instanceof Request?e.headers:void 0));return r.set(`X-OpenCodex-API-Key`,n),e instanceof Request?[new Request(e,{headers:r}),t?{...t,headers:r}:void 0]:[e,{...t,headers:r}]}async function Wt(){return Lt||(Lt=Promise.resolve().then(()=>window.prompt(`OpenCodex API token`)?.trim()||null).finally(()=>{Lt=null}),Lt)}function Gt(){if(It)return;It=!0;let e=window.fetch.bind(window);window.fetch=async(t,n)=>{if(!zt(t))return e(t,n);let r=Bt(),[i,a]=r?Ut(t,n,r):[t,n],o=await e(i,a);if(o.status!==401)return o;r&&Ht();let s=await Wt();if(!s)return o;Vt(s);let[c,l]=Ut(t,n,s),u=await e(c,l);return u.status===401&&Ht(),u}}Gt();var Kt=new Set([`dashboard`,`providers`,`models`,`subagents`,`logs`,`debug`,`usage`,`codex-auth`,`api`]);function qt(){let e=location.hash.replace(/^#\/?/,``);return Kt.has(e)?e:`dashboard`}var Jt=``,Yt=`ocx-theme`,Xt=[{id:`dashboard`,tkey:`nav.dashboard`,Icon:w},{id:`providers`,tkey:`nav.providers`,Icon:T},{id:`models`,tkey:`nav.models`,Icon:E},{id:`subagents`,tkey:`nav.subagents`,Icon:ee},{id:`logs`,tkey:`nav.logs`,Icon:D},{id:`debug`,tkey:`nav.debug`,Icon:te},{id:`usage`,tkey:`nav.usage`,Icon:ne},{id:`codex-auth`,tkey:`nav.codexAuth`,Icon:L},{id:`api`,tkey:`nav.api`,Icon:me}],Zt={light:de,dark:fe,system:pe},Qt={light:`theme.light`,dark:`theme.dark`,system:`theme.system`};function $t(e){if(!e||typeof e!=`object`||!(`version`in e))return null;let t=e.version;return typeof t==`string`&&t.length>0?t:null}function en(){let e=localStorage.getItem(Yt);return e===`light`||e===`dark`?e:`system`}function tn(){let[e,t]=(0,_.useState)(qt),n=e=>{location.hash=e,t(e)},[r,i]=(0,_.useState)(en),[a,o]=(0,_.useState)(null),{locale:s,setLocale:c}=Ce(),l=we();(0,_.useEffect)(()=>{let e=()=>t(qt());return window.addEventListener(`hashchange`,e),()=>window.removeEventListener(`hashchange`,e)},[]),(0,_.useEffect)(()=>{let e=document.documentElement;r===`system`?(e.removeAttribute(`data-theme`),localStorage.removeItem(Yt)):(e.setAttribute(`data-theme`,r),localStorage.setItem(Yt,r))},[r]),(0,_.useEffect)(()=>{let e=!1,t=async()=>{try{let t=await fetch(`${Jt}/healthz`);if(!t.ok)return;let n=$t(await t.json());!e&&n&&o(n)}catch{}};t();let n=setInterval(t,3e4);return()=>{e=!0,clearInterval(n)}},[]);let u=()=>i(e=>e===`light`?`dark`:e===`dark`?`system`:`light`),d=Zt[r],f=a??`2.7.3`,[p,m]=(0,_.useState)(!1);return(0,S.jsxs)(`div`,{className:`app`,children:[(0,S.jsxs)(`aside`,{className:`sidebar`,children:[(0,S.jsxs)(`div`,{className:`brand`,children:[(0,S.jsx)(`span`,{className:`brand-logo`,role:`img`,"aria-label":`opencodex logo`}),(0,S.jsx)(`span`,{className:`name`,children:`opencodex`}),(0,S.jsxs)(`span`,{className:`ver`,children:[`v`,f]})]}),(0,S.jsx)(`nav`,{children:Xt.map(({id:t,tkey:r,Icon:i})=>(0,S.jsxs)(`button`,{className:`nav-item${e===t?` active`:``}`,"data-page":t,onClick:()=>n(t),"aria-current":e===t?`page`:void 0,children:[(0,S.jsx)(i,{}),` `,l(r)]},t))}),(0,S.jsxs)(`div`,{className:`sidebar-foot`,children:[(0,S.jsxs)(`div`,{className:`lang-toggle`,children:[(0,S.jsx)(me,{"aria-hidden":!0}),(0,S.jsx)(Ae,{value:s,options:_e.map(e=>({value:e.code,label:e.name})),onChange:e=>c(e),label:l(`lang.label`),placement:`right`,style:{flex:1,minWidth:0,width:`100%`}})]}),(0,S.jsxs)(`button`,{type:`button`,className:`theme-toggle`,onClick:u,"aria-label":`${l(`theme.label`)}: ${l(Qt[r])}`,title:`${l(`theme.label`)}: ${l(Qt[r])}`,children:[(0,S.jsx)(d,{}),` `,(0,S.jsx)(`span`,{className:`mode`,children:l(Qt[r])})]}),(0,S.jsxs)(`button`,{type:`button`,className:`theme-toggle stop-toggle`,onClick:async()=>{if(confirm(l(`dash.stopConfirm`))){m(!0);try{await fetch(`${Jt}/api/stop`,{method:`POST`})}catch{}}},disabled:p,"aria-label":l(`dash.stop`),title:l(`dash.stop`),children:[(0,S.jsx)(I,{}),` `,(0,S.jsx)(`span`,{className:`mode`,children:l(p?`dash.stopping`:`dash.stop`)})]}),(0,S.jsxs)(`a`,{className:`sidebar-link`,href:`https://github.com/lidge-jun/opencodex`,target:`_blank`,rel:`noreferrer`,children:[(0,S.jsx)(F,{}),` `,l(`common.github`)]})]})]}),(0,S.jsx)(`main`,{className:`main`,children:(0,S.jsxs)(`div`,{className:`main-inner`,children:[e===`dashboard`&&(0,S.jsx)(He,{apiBase:Jt}),e===`providers`&&(0,S.jsx)(nt,{apiBase:Jt}),e===`models`&&(0,S.jsx)(ct,{apiBase:Jt}),e===`subagents`&&(0,S.jsx)(lt,{apiBase:Jt}),e===`logs`&&(0,S.jsx)(yt,{apiBase:Jt}),e===`debug`&&(0,S.jsx)(bt,{apiBase:Jt}),e===`usage`&&(0,S.jsx)(Dt,{apiBase:Jt}),e===`codex-auth`&&(0,S.jsx)(kt,{apiBase:Jt}),e===`api`&&(0,S.jsx)(Pt,{apiBase:Jt})]})})]})}v.createRoot(document.getElementById(`root`)).render((0,S.jsx)(_.StrictMode,{children:(0,S.jsx)(Se,{children:(0,S.jsx)(tn,{})})}));
@@ -16,7 +16,7 @@
16
16
  } catch (e) {}
17
17
  })();
18
18
  </script>
19
- <script type="module" crossorigin src="/assets/index-DGFbiUtS.js"></script>
19
+ <script type="module" crossorigin src="/assets/index-DT4C-vKW.js"></script>
20
20
  <link rel="stylesheet" crossorigin href="/assets/index-D7o1qwy-.css">
21
21
  </head>
22
22
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitkyc08/opencodex",
3
- "version": "2.7.2",
3
+ "version": "2.7.3",
4
4
  "description": "Universal provider proxy for OpenAI Codex — use any LLM with Codex CLI/App/SDK",
5
5
  "type": "module",
6
6
  "main": "./bin/package-main.mjs",
@@ -133,25 +133,12 @@ export function collabSurface(parsed: OcxParsedRequest): "v1" | "v2" | null {
133
133
  /**
134
134
  * Multi-agent guidance for this turn, or null when nothing applies.
135
135
  *
136
- * codex-rs only emits its Proactive delegation developer message on the v2 surface,
137
- * so when a v1-surface turn arrives at the synthetic top tier (codex converts
138
- * ultra -> max on the wire, so max arrival means the user picked the top rung) the
139
- * proxy supplies the same one-liner, wrapped in codex's own <multi_agent_mode> tags
140
- * (v1 turns never carry that fragment, so there is nothing to collide with).
141
- * Ultra is always advertised, so the guidance fires regardless of the multi_agent_v2
142
- * toggle.
143
- *
144
- * Dynamic model injection: when the user has configured a specific injectionModel,
145
- * the prompt names it so the agent knows which routed model to delegate to.
146
- *
147
- * Effort gate relaxation: when an injectionModel is set, the prompt fires at every
148
- * effort level, not just max/ultra — the user opted into delegation.
149
- *
150
- * Reasoning-effort injection: when an injectionEffort is configured alongside the
151
- * model, the prompt also tells the agent to pass `reasoning_effort` in spawn_agent
152
- * calls (codex-rs validates spawn efforts by catalog membership; unsupported rungs
153
- * are clamped on the wire). An effort WITHOUT a model changes nothing — the gate
154
- * and the base prompt stay exactly as before.
136
+ * V1 surface: codex-rs only emits its Proactive delegation developer message on the
137
+ * v2 surface, so when a v1-surface turn arrives at the synthetic top tier (codex
138
+ * converts ultra -> max on the wire, so max arrival means the user picked the top
139
+ * rung) the proxy supplies the same one-liner, wrapped in codex's own
140
+ * <multi_agent_mode> tags. That is ALL v1 gets no model designation, no roster
141
+ * (kept lean by request, devlog 260710): ultra-tier injection is sufficient there.
155
142
  *
156
143
  * V2 surface (flat spawn_agent — sol/terra under the default mode, EVERY model when
157
144
  * `ocx v2 mode v2` forces the pins): codex-rs already emits its own Proactive text
@@ -169,16 +156,12 @@ export function collabSurface(parsed: OcxParsedRequest): "v1" | "v2" | null {
169
156
  * regardless of the flag (spawn.rs), so the prompt tells the model to pass the
170
157
  * arguments even though the schema does not list them.
171
158
  *
172
- * Prompt structure follows the OpenAI prompt-engineering guidance (developers.openai.com):
173
- * explicit rule lists first, a concrete few-shot example call, reference context (the
174
- * roster) last. A user-configured `injectionPrompt` replaces the built-in body with
175
- * {{model}}/{{effort}}/{{roster}} placeholder substitution; firing gates are unchanged.
176
- *
177
- * Featured roster: whenever guidance fires (either surface), the configured
178
- * `subagentModels` (the 5-list the catalog features for spawn_agent) are appended
179
- * with the effort ladder each advertises in the injected catalog — the same list
180
- * codex-rs validates spawn efforts against — so the agent can pick per-task models
181
- * and honest effort rungs instead of flying blind behind the hidden schema.
159
+ * The v2 body is budgeted to <= 700 chars (V2_GUIDANCE_CHAR_BUDGET): rules first,
160
+ * then the preferred model, then the compact roster of configured `subagentModels`
161
+ * with the effort ladder each advertises in the injected catalog (the list codex-rs
162
+ * validates spawn efforts against). A user-configured `injectionPrompt` replaces the
163
+ * v2 body with {{model}}/{{effort}}/{{roster}} placeholder substitution (own length,
164
+ * user-owned); firing gates are unchanged.
182
165
  */
183
166
  export async function multiAgentGuidanceText(parsed: OcxParsedRequest, injectionModel?: string, injectionEffort?: string, subagentModels?: string[], injectionPrompt?: string): Promise<string | null> {
184
167
  const surface = collabSurface(parsed);
@@ -193,60 +176,33 @@ export async function multiAgentGuidanceText(parsed: OcxParsedRequest, injection
193
176
  if (injectionPrompt) {
194
177
  return `<multi_agent_mode>${applyInjectionPlaceholders(injectionPrompt, injectionModel, injectionEffort, roster)}</multi_agent_mode>`;
195
178
  }
196
- const rules = [
197
- "- This spawn_agent tool ACCEPTS optional \"model\" and \"reasoning_effort\" string arguments even though its published schema does not list them. The runtime parses and applies both. Never claim sub-agent models cannot be selected.",
198
- "- When the user names a sub-agent model, pass it in the \"model\" argument exactly as given.",
199
- "- Any spawn_agent call that sets \"model\" or \"reasoning_effort\" MUST also set \"fork_turns\" to \"none\" (or a positive integer string such as \"3\" for a partial fork); overrides are rejected on a full-history fork.",
200
- "- With fork_turns \"none\", the child receives NO parent context: write a fully self-contained task message with paths, goals, and constraints.",
201
- ];
179
+ let text = "spawn_agent also accepts hidden \"model\" and \"reasoning_effort\" string arguments "
180
+ + "(not in the schema, but parsed and applied) never claim sub-agent models cannot be selected. "
181
+ + "When setting either, set fork_turns to \"none\" (or e.g. \"3\"; full-history forks reject overrides) "
182
+ + "and make the message self-contained.";
202
183
  if (injectionModel) {
203
- rules.push(`- A preferred sub-agent model is configured: "${injectionModel}". Use it for independent sub-tasks unless the user explicitly asks for another model.`);
184
+ text += ` Preferred sub-agent: model "${injectionModel}"`
185
+ + (injectionEffort ? `, reasoning_effort "${injectionEffort}"` : "")
186
+ + " — use it unless the user names another.";
204
187
  }
205
- if (injectionEffort) {
206
- rules.push(`- A preferred sub-agent reasoning effort is configured: "${injectionEffort}". Pass it in the "reasoning_effort" argument of those spawn_agent calls.`);
188
+ text += roster;
189
+ if (text.length > V2_GUIDANCE_CHAR_BUDGET) {
190
+ // Roster is the only unbounded part — drop it before breaking the budget.
191
+ text = text.slice(0, text.length - roster.length);
207
192
  }
208
- const exampleModel = injectionModel ?? "gpt-5.6-terra";
209
- const example = "## Example spawn_agent call\n"
210
- + "```json\n"
211
- + JSON.stringify({
212
- task_name: "example_task",
213
- message: "Self-contained task description with paths, goals, and constraints.",
214
- fork_turns: "none",
215
- model: exampleModel,
216
- ...(injectionEffort ? { reasoning_effort: injectionEffort } : {}),
217
- }, null, 2)
218
- + "\n```";
219
- const text = `## Sub-agent model selection rules\n${rules.join("\n")}\n\n${example}${roster}`;
220
193
  return `<multi_agent_mode>${text}</multi_agent_mode>`;
221
194
  }
222
195
 
223
196
  const effort = parsed.options.reasoning;
224
- // When the user has selected a specific injection model, fire the delegation prompt
225
- // at ANY effort level. Otherwise preserve the original gate: top tier only (max/ultra).
226
- if (!injectionModel && effort !== "max" && effort !== "ultra") return null;
227
-
228
- if (injectionPrompt) {
229
- const roster = await subagentRosterText(subagentModels);
230
- return `<multi_agent_mode>${applyInjectionPlaceholders(injectionPrompt, injectionModel, injectionEffort, roster)}</multi_agent_mode>`;
231
- }
232
-
233
- let text = PROACTIVE_MULTI_AGENT_MODE_TEXT;
234
-
235
- // Append the selected model when the user has configured a specific injection target.
236
- if (injectionModel) {
237
- text += `\n\nA preferred sub-agent model is configured: "${injectionModel}". `
238
- + `When delegating, call spawn_agent and set its model argument to exactly "${injectionModel}". `
239
- + "Use it for independent sub-tasks unless the user explicitly asks for another model.";
240
- if (injectionEffort) {
241
- text += ` A preferred sub-agent reasoning effort is also configured: "${injectionEffort}". `
242
- + `Set the reasoning_effort argument of spawn_agent to exactly "${injectionEffort}" for those sub-agents.`;
243
- }
244
- }
245
-
246
- text += await subagentRosterText(subagentModels);
247
- return `<multi_agent_mode>${text}</multi_agent_mode>`;
197
+ // v1 keeps only the upstream-parity behavior: Proactive text at the top tier
198
+ // (ultra arrives as max on the wire). No designation/roster payload here.
199
+ if (effort !== "max" && effort !== "ultra") return null;
200
+ return `<multi_agent_mode>${PROACTIVE_MULTI_AGENT_MODE_TEXT}</multi_agent_mode>`;
248
201
  }
249
202
 
203
+ /** Hard budget for the built-in v2 guidance body (user request: keep injection lean). */
204
+ export const V2_GUIDANCE_CHAR_BUDGET = 700;
205
+
250
206
  /** {{model}}/{{effort}}/{{roster}} substitution for the user-configured injectionPrompt. */
251
207
  function applyInjectionPlaceholders(prompt: string, model?: string, effort?: string, roster?: string): string {
252
208
  return prompt
@@ -256,7 +212,7 @@ function applyInjectionPlaceholders(prompt: string, model?: string, effort?: str
256
212
  }
257
213
 
258
214
  /**
259
- * "\n\nOther available sub-agent models..." roster block, or "" when no configured
215
+ * Compact one-line roster of configured sub-agent models, or "" when no configured
260
216
  * model resolves to a catalog entry. Efforts come from the injected catalog
261
217
  * (catalogModelEfforts) so only rungs codex-rs will actually accept are advertised.
262
218
  */
@@ -265,13 +221,17 @@ async function subagentRosterText(subagentModels?: string[]): Promise<string> {
265
221
  if (featured.length === 0) return "";
266
222
  const { catalogModelEfforts } = await import("../codex/catalog");
267
223
  const efforts = catalogModelEfforts(featured);
268
- const lines = featured
269
- .filter(id => efforts.has(id))
270
- .map(id => `- "${id}" (reasoning_effort options: ${efforts.get(id)!.join(", ")})`);
271
- if (lines.length === 0) return "";
272
- return "\n\nConfigured sub-agent model roster (valid values for spawn_agent's \"model\" argument, "
273
- + "with the reasoning_effort each supports):\n"
274
- + lines.join("\n");
224
+ const resolved = featured.filter(id => efforts.has(id));
225
+ if (resolved.length === 0) return "";
226
+ const ladders = new Set(resolved.map(id => efforts.get(id)!.join("/")));
227
+ if (ladders.size === 1) {
228
+ // Shared ladder (the common case: the injected catalog advertises one rung set)
229
+ // -> state it once instead of per model, keeping the roster inside the budget.
230
+ const ids = resolved.map(id => `"${id}"`).join(", ");
231
+ return ` Available models (reasoning_effort ${[...ladders][0]}): ${ids}.`;
232
+ }
233
+ const entries = resolved.map(id => `"${id}" (${efforts.get(id)!.join("/")})`);
234
+ return ` Available models (valid reasoning_effort): ${entries.join(", ")}.`;
275
235
  }
276
236
 
277
237
  /**