@bitkyc08/opencodex 2.7.2 → 2.7.4-preview.20260710

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.
@@ -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-DulXo5ZJ.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.4-preview.20260710",
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",
package/src/cli/debug.ts CHANGED
@@ -2,7 +2,7 @@ import { findLiveProxy, probeHostname } from "../server/proxy-liveness";
2
2
  import { DEBUG_ENV, type DebugSettingsView } from "../lib/debug-settings";
3
3
  import { runningProxyUpdateHeaders } from "../oauth/login-cli";
4
4
 
5
- type DebugScope = "provider" | "usage";
5
+ type DebugScope = "provider" | "usage" | "injection";
6
6
 
7
7
  async function requireLiveProxy() {
8
8
  const live = await findLiveProxy();
@@ -50,10 +50,14 @@ function printScopeStatus(scope: DebugScope, view: DebugSettingsView): void {
50
50
  console.log(`Provider debug: ${view.enabled ? "ON" : "off"}`);
51
51
  console.log(` env=${view.env.debug ? "on" : "off"}, runtime=${view.runtimeOverride.debug === undefined ? "env/default" : view.runtimeOverride.debug ? "on" : "off"}`);
52
52
  console.log(" Tail: ocx debug provider logs [-f]");
53
- } else {
53
+ } else if (scope === "usage") {
54
54
  console.log(`Usage debug: ${view.usage ? "ON" : "off"}`);
55
55
  console.log(` env=${view.env.usage ? "on" : "off"}, runtime=${view.runtimeOverride.usage === undefined ? "env/default" : view.runtimeOverride.usage ? "on" : "off"}`);
56
56
  console.log(" Tail: ocx debug usage logs [-f] (via running proxy API)");
57
+ } else {
58
+ console.log(`Injection debug: ${view.injection ? "ON" : "off"}`);
59
+ console.log(` env=${view.env.injection ? "on" : "off"}, runtime=${view.runtimeOverride.injection === undefined ? "env/default" : view.runtimeOverride.injection ? "on" : "off"}`);
60
+ console.log(" Lines appear on the proxy console when multi-agent guidance is injected.");
57
61
  }
58
62
  }
59
63
 
@@ -138,7 +142,7 @@ async function handleScopeCommand(scope: DebugScope, actionArgv: string[]): Prom
138
142
 
139
143
  if (action === "on" || action === "off") {
140
144
  const enabled = action === "on";
141
- const body = scope === "provider" ? { debug: enabled } : { usage: enabled };
145
+ const body = scope === "provider" ? { debug: enabled } : scope === "usage" ? { usage: enabled } : { injection: enabled };
142
146
  printScopeStatus(scope, await putDebugSettings(body));
143
147
  console.log(`\n${scope} debug is now ${enabled ? "enabled" : "disabled"}.`);
144
148
  return;
@@ -150,20 +154,26 @@ async function handleScopeCommand(scope: DebugScope, actionArgv: string[]): Prom
150
154
  }
151
155
 
152
156
  if (action === "reset") {
153
- const resetKey = scope === "provider" ? "provider" : "usage";
157
+ const resetKey = scope === "provider" ? "provider" : scope;
154
158
  printScopeStatus(scope, await putDebugSettings({ reset: resetKey }));
155
159
  console.log(`\nRuntime override cleared for ${scope}; effective value follows env again.`);
156
160
  return;
157
161
  }
158
162
 
159
163
  if (action === "logs") {
164
+ if (scope === "injection") {
165
+ console.error("Injection debug has no buffered log stream; lines print on the proxy console.");
166
+ process.exit(1);
167
+ }
160
168
  const follow = actionArgv.slice(1).some(arg => arg === "-f" || arg === "--follow");
161
169
  if (scope === "provider") await printProviderLogs(follow);
162
170
  else await printUsageLogs(follow);
163
171
  return;
164
172
  }
165
173
 
166
- console.error(`Usage: ocx debug ${scope} on|off|status|reset|logs [-f]`);
174
+ console.error(scope === "injection"
175
+ ? "Usage: ocx debug injection on|off|status|reset"
176
+ : `Usage: ocx debug ${scope} on|off|status|reset|logs [-f]`);
167
177
  process.exit(1);
168
178
  }
169
179
 
@@ -172,16 +182,18 @@ function printTopLevelHelp(): void {
172
182
  console.log("");
173
183
  console.log(" ocx debug provider on|off|status|reset|logs [-f]");
174
184
  console.log(" ocx debug usage on|off|status|reset|logs [-f]");
185
+ console.log(" ocx debug injection on|off|status|reset");
175
186
  console.log("");
176
187
  console.log("Env defaults on start:");
177
188
  console.log(" provider → OCX_DEBUG=1 (legacy OCX_DEBUG_FRAMES still works)");
178
189
  console.log(` usage → ${DEBUG_ENV.usage}=1`);
190
+ console.log(` injection→ ${DEBUG_ENV.injection}=1`);
179
191
  }
180
192
 
181
193
  export async function handleDebugCommand(argv: string[]): Promise<void> {
182
194
  const sub = (argv[0] ?? "").trim().toLowerCase();
183
195
 
184
- if (sub === "provider" || sub === "usage") {
196
+ if (sub === "provider" || sub === "usage" || sub === "injection") {
185
197
  await handleScopeCommand(sub, argv.slice(1));
186
198
  return;
187
199
  }
@@ -192,6 +204,7 @@ export async function handleDebugCommand(argv: string[]): Promise<void> {
192
204
  console.log("Proxy is not running — env defaults for the next start:");
193
205
  console.log(` provider → OCX_DEBUG = ${envDebugEnabled() ? "on" : "off"}`);
194
206
  console.log(` usage → ${DEBUG_ENV.usage} = ${process.env[DEBUG_ENV.usage] === "1" ? "on" : "off"}`);
207
+ console.log(` injection→ ${DEBUG_ENV.injection} = ${process.env[DEBUG_ENV.injection] === "1" ? "on" : "off"}`);
195
208
  console.log("");
196
209
  }
197
210
  printTopLevelHelp();
@@ -2,12 +2,15 @@
2
2
  * Runtime-controllable debug flags.
3
3
  * Provider debug: `ocx debug provider on|off|status|reset|logs [-f]` (or OCX_DEBUG=1 on start).
4
4
  * Usage capture: `ocx debug usage on|off|status|reset|logs [-f]` (or OPENCODEX_USAGE_DEBUG=1).
5
+ * Injection log: `ocx debug injection on|off|status|reset` (or OCX_INJECTION_DEBUG=1) —
6
+ * multi-agent guidance-injection console lines, default OFF.
5
7
  * `/api/debug` and `ocx debug` override env defaults without restart.
6
8
  */
7
9
 
8
10
  export const DEBUG_ENV = {
9
11
  debug: "OCX_DEBUG",
10
12
  usage: "OPENCODEX_USAGE_DEBUG",
13
+ injection: "OCX_INJECTION_DEBUG",
11
14
  } as const;
12
15
 
13
16
  /** Legacy env var that still enables provider debug logging. */
@@ -18,6 +21,7 @@ export type DebugFlag = keyof typeof DEBUG_ENV;
18
21
  export interface DebugSettingsView {
19
22
  enabled: boolean;
20
23
  usage: boolean;
24
+ injection: boolean;
21
25
  runtimeOverride: Partial<Record<DebugFlag, boolean>>;
22
26
  env: Record<DebugFlag, boolean>;
23
27
  }
@@ -47,20 +51,28 @@ export function isUsageDebugEnabled(): boolean {
47
51
  return envFlag(DEBUG_ENV.usage);
48
52
  }
49
53
 
54
+ /** Multi-agent guidance-injection log lines (default OFF; GUI checkbox / API / CLI). */
55
+ export function isInjectionDebugEnabled(): boolean {
56
+ if (runtimeOverride.injection !== undefined) return runtimeOverride.injection;
57
+ return envFlag(DEBUG_ENV.injection);
58
+ }
59
+
50
60
  export function getDebugSettings(): DebugSettingsView {
51
61
  return {
52
62
  enabled: isDebugEnabled(),
53
63
  usage: isUsageDebugEnabled(),
64
+ injection: isInjectionDebugEnabled(),
54
65
  runtimeOverride: { ...runtimeOverride },
55
66
  env: {
56
67
  debug: envFlag(DEBUG_ENV.debug) || legacyDebugEnvEnabled(),
57
68
  usage: envFlag(DEBUG_ENV.usage),
69
+ injection: envFlag(DEBUG_ENV.injection),
58
70
  },
59
71
  };
60
72
  }
61
73
 
62
74
  export function setDebugSettings(partial: Partial<Record<DebugFlag, boolean>>): DebugSettingsView {
63
- for (const key of ["debug", "usage"] as const) {
75
+ for (const key of ["debug", "usage", "injection"] as const) {
64
76
  if (partial[key] !== undefined) runtimeOverride[key] = partial[key];
65
77
  }
66
78
  return getDebugSettings();
@@ -72,7 +84,7 @@ export function clearDebugSetting(flag: DebugFlag): DebugSettingsView {
72
84
  }
73
85
 
74
86
  export function clearDebugSettings(): DebugSettingsView {
75
- for (const key of ["debug", "usage"] as const) {
87
+ for (const key of ["debug", "usage", "injection"] as const) {
76
88
  delete runtimeOverride[key];
77
89
  }
78
90
  return getDebugSettings();
@@ -208,19 +208,20 @@ export async function handleManagementAPI(req: Request, url: URL, config: OcxCon
208
208
  }
209
209
 
210
210
  if (url.pathname === "/api/debug" && req.method === "PUT") {
211
- let body: { debug?: unknown; usage?: unknown; reset?: unknown };
211
+ let body: { debug?: unknown; usage?: unknown; injection?: unknown; reset?: unknown };
212
212
  try { body = await req.json(); } catch { return jsonResponse({ error: "invalid JSON body" }, 400); }
213
213
  if (body.reset === true) return jsonResponse(clearDebugSettings());
214
214
  if (body.reset === "debug" || body.reset === "provider") return jsonResponse(clearDebugSetting("debug"));
215
215
  if (body.reset === "usage") return jsonResponse(clearDebugSetting("usage"));
216
+ if (body.reset === "injection") return jsonResponse(clearDebugSetting("injection"));
216
217
  const partial: Partial<Record<DebugFlag, boolean>> = {};
217
- for (const key of ["debug", "usage"] as const) {
218
+ for (const key of ["debug", "usage", "injection"] as const) {
218
219
  if (body[key] === undefined) continue;
219
220
  if (typeof body[key] !== "boolean") return jsonResponse({ error: `${key} must be a boolean` }, 400);
220
221
  partial[key] = body[key];
221
222
  }
222
223
  if (Object.keys(partial).length === 0) {
223
- return jsonResponse({ error: "provide debug/usage booleans or reset:true" }, 400);
224
+ return jsonResponse({ error: "provide debug/usage/injection booleans or reset:true" }, 400);
224
225
  }
225
226
  return jsonResponse(setDebugSettings(partial));
226
227
  }
@@ -9,6 +9,7 @@ import { buildCompactV1Output, COMPACT_PROMPT, decodeCompactionSummary, extractC
9
9
  import { FORWARD_HEADERS } from "../adapters/openai-responses";
10
10
  import { expandPreviousResponseInput, previousResponseConversationId, rememberResponseState } from "../responses/state";
11
11
  import { routeModel } from "../router";
12
+ import { isInjectionDebugEnabled } from "../lib/debug-settings";
12
13
  import { modelInList, namespacedToolName } from "../types";
13
14
  import type { AdapterEvent, OcxConfig, OcxParsedRequest, OcxProviderConfig } from "../types";
14
15
  import {
@@ -133,25 +134,12 @@ export function collabSurface(parsed: OcxParsedRequest): "v1" | "v2" | null {
133
134
  /**
134
135
  * Multi-agent guidance for this turn, or null when nothing applies.
135
136
  *
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.
137
+ * V1 surface: codex-rs only emits its Proactive delegation developer message on the
138
+ * v2 surface, so when a v1-surface turn arrives at the synthetic top tier (codex
139
+ * converts ultra -> max on the wire, so max arrival means the user picked the top
140
+ * rung) the proxy supplies the same one-liner, wrapped in codex's own
141
+ * <multi_agent_mode> tags. That is ALL v1 gets no model designation, no roster
142
+ * (kept lean by request, devlog 260710): ultra-tier injection is sufficient there.
155
143
  *
156
144
  * V2 surface (flat spawn_agent — sol/terra under the default mode, EVERY model when
157
145
  * `ocx v2 mode v2` forces the pins): codex-rs already emits its own Proactive text
@@ -169,16 +157,12 @@ export function collabSurface(parsed: OcxParsedRequest): "v1" | "v2" | null {
169
157
  * regardless of the flag (spawn.rs), so the prompt tells the model to pass the
170
158
  * arguments even though the schema does not list them.
171
159
  *
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.
160
+ * The v2 body is budgeted to <= 700 chars (V2_GUIDANCE_CHAR_BUDGET): rules first,
161
+ * then the preferred model, then the compact roster of configured `subagentModels`
162
+ * with the effort ladder each advertises in the injected catalog (the list codex-rs
163
+ * validates spawn efforts against). A user-configured `injectionPrompt` replaces the
164
+ * v2 body with {{model}}/{{effort}}/{{roster}} placeholder substitution (own length,
165
+ * user-owned); firing gates are unchanged.
182
166
  */
183
167
  export async function multiAgentGuidanceText(parsed: OcxParsedRequest, injectionModel?: string, injectionEffort?: string, subagentModels?: string[], injectionPrompt?: string): Promise<string | null> {
184
168
  const surface = collabSurface(parsed);
@@ -193,60 +177,33 @@ export async function multiAgentGuidanceText(parsed: OcxParsedRequest, injection
193
177
  if (injectionPrompt) {
194
178
  return `<multi_agent_mode>${applyInjectionPlaceholders(injectionPrompt, injectionModel, injectionEffort, roster)}</multi_agent_mode>`;
195
179
  }
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
- ];
180
+ let text = "spawn_agent also accepts hidden \"model\" and \"reasoning_effort\" string arguments "
181
+ + "(not in the schema, but parsed and applied) never claim sub-agent models cannot be selected. "
182
+ + "When setting either, set fork_turns to \"none\" (or e.g. \"3\"; full-history forks reject overrides) "
183
+ + "and make the message self-contained.";
202
184
  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.`);
185
+ text += ` Preferred sub-agent: model "${injectionModel}"`
186
+ + (injectionEffort ? `, reasoning_effort "${injectionEffort}"` : "")
187
+ + " — use it unless the user names another.";
204
188
  }
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.`);
189
+ text += roster;
190
+ if (text.length > V2_GUIDANCE_CHAR_BUDGET) {
191
+ // Roster is the only unbounded part — drop it before breaking the budget.
192
+ text = text.slice(0, text.length - roster.length);
207
193
  }
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
194
  return `<multi_agent_mode>${text}</multi_agent_mode>`;
221
195
  }
222
196
 
223
197
  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>`;
198
+ // v1 keeps only the upstream-parity behavior: Proactive text at the top tier
199
+ // (ultra arrives as max on the wire). No designation/roster payload here.
200
+ if (effort !== "max" && effort !== "ultra") return null;
201
+ return `<multi_agent_mode>${PROACTIVE_MULTI_AGENT_MODE_TEXT}</multi_agent_mode>`;
248
202
  }
249
203
 
204
+ /** Hard budget for the built-in v2 guidance body (user request: keep injection lean). */
205
+ export const V2_GUIDANCE_CHAR_BUDGET = 700;
206
+
250
207
  /** {{model}}/{{effort}}/{{roster}} substitution for the user-configured injectionPrompt. */
251
208
  function applyInjectionPlaceholders(prompt: string, model?: string, effort?: string, roster?: string): string {
252
209
  return prompt
@@ -256,7 +213,7 @@ function applyInjectionPlaceholders(prompt: string, model?: string, effort?: str
256
213
  }
257
214
 
258
215
  /**
259
- * "\n\nOther available sub-agent models..." roster block, or "" when no configured
216
+ * Compact one-line roster of configured sub-agent models, or "" when no configured
260
217
  * model resolves to a catalog entry. Efforts come from the injected catalog
261
218
  * (catalogModelEfforts) so only rungs codex-rs will actually accept are advertised.
262
219
  */
@@ -265,13 +222,17 @@ async function subagentRosterText(subagentModels?: string[]): Promise<string> {
265
222
  if (featured.length === 0) return "";
266
223
  const { catalogModelEfforts } = await import("../codex/catalog");
267
224
  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");
225
+ const resolved = featured.filter(id => efforts.has(id));
226
+ if (resolved.length === 0) return "";
227
+ const ladders = new Set(resolved.map(id => efforts.get(id)!.join("/")));
228
+ if (ladders.size === 1) {
229
+ // Shared ladder (the common case: the injected catalog advertises one rung set)
230
+ // -> state it once instead of per model, keeping the roster inside the budget.
231
+ const ids = resolved.map(id => `"${id}"`).join(", ");
232
+ return ` Available models (reasoning_effort ${[...ladders][0]}): ${ids}.`;
233
+ }
234
+ const entries = resolved.map(id => `"${id}" (${efforts.get(id)!.join("/")})`);
235
+ return ` Available models (valid reasoning_effort): ${entries.join(", ")}.`;
275
236
  }
276
237
 
277
238
  /**
@@ -503,8 +464,8 @@ export async function handleResponses(
503
464
  const guidance = await multiAgentGuidanceText(parsed, config.injectionModel, config.injectionEffort, config.subagentModels, config.injectionPrompt);
504
465
  if (guidance) {
505
466
  injectDeveloperMessage(parsed, guidance);
506
- console.log(`[opencodex] ${route.modelId}: multi-agent guidance injected (surface=${collabSurface(parsed)}, ${guidance.length} chars)`);
507
- } else if (collabSurface(parsed) !== null) {
467
+ if (isInjectionDebugEnabled()) console.log(`[opencodex] ${route.modelId}: multi-agent guidance injected (surface=${collabSurface(parsed)}, ${guidance.length} chars)`);
468
+ } else if (isInjectionDebugEnabled() && collabSurface(parsed) !== null) {
508
469
  console.log(`[opencodex] ${route.modelId}: collab surface=${collabSurface(parsed)}, guidance silent (effort=${parsed.options.reasoning ?? "unset"}, injectionModel=${config.injectionModel ?? "unset"})`);
509
470
  }
510
471
  }