Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ All notable changes to this project will be documented in this file.
- Removed experimental `collab` runtime mode and related template wiring from mainline plugin behavior.
- Simplified installer surface to a single idempotent `install` flow.
- Updated docs, schema, and workflow configuration for current supported modes (`native`, `codex`).
- Added experimental Codex collaboration profile gates (`runtime.collaborationProfile`, `runtime.orchestratorSubagents`) for plan/orchestrator parity.
- Collaboration features now auto-enable by default in `runtime.mode="codex"` and can be explicitly enabled/disabled in any mode.
- Added `runtime.collaborationToolProfile` (`opencode` | `codex`) to choose OpenCode tool translation guidance vs codex-style tool semantics in injected collaboration instructions.
- Added managed `orchestrator` agent template sync under `~/.config/opencode/agents`, with visibility auto-gated by runtime mode.

## 0.2.3 - 2026-02-11

Expand Down
12 changes: 12 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@ The plugin loads config in this order:
- Adds explicit `before-header-transform` and `after-header-transform` request snapshots for message fetches.
- `runtime.pidOffset: boolean`
- Enables session-aware offset behavior for account selection.
- `runtime.collaborationProfile: boolean`
- Experimental: enables Codex-style collaboration mode mapping from agent names (`plan` -> plan mode, `orchestrator` -> code mode profile).
- If omitted, defaults to `true` in `runtime.mode="codex"` and `false` otherwise.
- Explicit `true`/`false` works in any mode.
- `runtime.orchestratorSubagents: boolean`
- Experimental: enables Codex-style subagent header hints for helper agents under collaboration profile mode.
- If omitted, inherits `runtime.collaborationProfile` effective value.
- Explicit `true`/`false` works in any mode.
- `runtime.collaborationToolProfile: "opencode" | "codex"`
- Controls tool-language guidance in injected collaboration instructions.
- `opencode` (default): translates Codex semantics to OpenCode tool names.
- `codex`: prefers Codex tool semantics and falls back to OpenCode equivalents.

### Model behavior

Expand Down
11 changes: 10 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ import {
getCompatInputSanitizerEnabled,
getCodexCompactionOverrideEnabled,
getBehaviorSettings,
getCollaborationToolProfile,
getCollaborationProfileEnabled,
getDebugEnabled,
getHeaderTransformDebugEnabled,
getHeaderSnapshotsEnabled,
getOrchestratorSubagentsEnabled,
getMode,
getRemapDeveloperMessagesToUserEnabled,
getRotationStrategy,
Expand All @@ -34,6 +37,7 @@ import { generatePersonaSpec } from "./lib/persona-tool"
import { createPersonalityFile } from "./lib/personality-create"
import { installCreatePersonalityCommand } from "./lib/personality-command"
import { installPersonalityBuilderSkill } from "./lib/personality-skill"
import { reconcileOrchestratorAgentVisibility } from "./lib/orchestrator-agent"
import { runOneProactiveRefreshTick } from "./lib/proactive-refresh"
import { toolOutputForStatus } from "./lib/codex-status-tool"
import { requireOpenAIMultiOauthAuth, saveAuthStorage } from "./lib/storage"
Expand All @@ -56,8 +60,11 @@ export const OpenAIMultiAuthPlugin: Plugin = async (input) => {
file: loadConfigFile({ env: process.env })
})
const runtimeMode = getMode(cfg)
const collaborationProfileEnabled = getCollaborationProfileEnabled(cfg)
const log = createLogger({ debug: getDebugEnabled(cfg) })

await reconcileOrchestratorAgentVisibility({ visible: collaborationProfileEnabled }).catch(() => {})

if (getProactiveRefreshEnabled(cfg)) {
const bufferMs = getProactiveRefreshBufferMs(cfg)
const timer = setInterval(() => {
Expand Down Expand Up @@ -92,11 +99,13 @@ export const OpenAIMultiAuthPlugin: Plugin = async (input) => {
codexCompactionOverride: getCodexCompactionOverrideEnabled(cfg),
headerSnapshots: getHeaderSnapshotsEnabled(cfg),
headerTransformDebug: getHeaderTransformDebugEnabled(cfg),
collaborationProfileEnabled,
orchestratorSubagentsEnabled: getOrchestratorSubagentsEnabled(cfg),
collaborationToolProfile: getCollaborationToolProfile(cfg),
behaviorSettings: getBehaviorSettings(cfg)
})

const z = tool.schema

hooks.tool = {
...hooks.tool,
"codex-status": tool({
Expand Down
21 changes: 19 additions & 2 deletions lib/codex-native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
PluginRuntimeMode,
PromptCacheKeyStrategy
} from "./config"
import type { CollaborationToolProfile } from "./codex-native/collaboration"
import { formatToastMessage } from "./toast"
import type { CodexModelInfo } from "./model-catalog"
import { createRequestSnapshots } from "./request-snapshots"
Expand Down Expand Up @@ -164,6 +165,9 @@ export type CodexAuthPluginOptions = {
codexCompactionOverride?: boolean
headerSnapshots?: boolean
headerTransformDebug?: boolean
collaborationProfileEnabled?: boolean
orchestratorSubagentsEnabled?: boolean
collaborationToolProfile?: CollaborationToolProfile
}

export async function CodexAuthPlugin(input: PluginInput, opts: CodexAuthPluginOptions = {}): Promise<Hooks> {
Expand All @@ -179,6 +183,14 @@ export async function CodexAuthPlugin(input: PluginInput, opts: CodexAuthPluginO
const remapDeveloperMessagesToUserEnabled = spoofMode === "codex" && opts.remapDeveloperMessagesToUser !== false
const codexCompactionOverrideEnabled =
opts.codexCompactionOverride !== undefined ? opts.codexCompactionOverride : runtimeMode === "codex"
const collaborationProfileEnabled =
typeof opts.collaborationProfileEnabled === "boolean" ? opts.collaborationProfileEnabled : runtimeMode === "codex"
const orchestratorSubagentsEnabled =
typeof opts.orchestratorSubagentsEnabled === "boolean"
? opts.orchestratorSubagentsEnabled
: collaborationProfileEnabled
const collaborationToolProfile: CollaborationToolProfile =
opts.collaborationToolProfile === "codex" ? "codex" : "opencode"
void refreshCodexClientVersionFromGitHub(opts.log).catch(() => {})
const resolveCatalogHeaders = (): {
originator: string
Expand Down Expand Up @@ -353,15 +365,20 @@ export async function CodexAuthPlugin(input: PluginInput, opts: CodexAuthPluginO
lastCatalogModels,
behaviorSettings: opts.behaviorSettings,
fallbackPersonality: opts.personality,
spoofMode
spoofMode,
collaborationProfileEnabled,
orchestratorSubagentsEnabled,
collaborationToolProfile
})
},
"chat.headers": async (hookInput, output) => {
await handleChatHeadersHook({
hookInput,
output,
spoofMode,
internalCollaborationModeHeader: INTERNAL_COLLABORATION_MODE_HEADER
internalCollaborationModeHeader: INTERNAL_COLLABORATION_MODE_HEADER,
collaborationProfileEnabled,
orchestratorSubagentsEnabled
})
},
"experimental.session.compacting": async (hookInput, output) => {
Expand Down
63 changes: 61 additions & 2 deletions lib/codex-native/chat-hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ import {
readSessionMessageInfo,
sessionUsesOpenAIProvider
} from "./session-messages"
import {
CODEX_CODE_MODE_INSTRUCTIONS,
CODEX_ORCHESTRATOR_INSTRUCTIONS,
CODEX_PLAN_MODE_INSTRUCTIONS,
mergeInstructions,
resolveCollaborationInstructions,
resolveCollaborationProfile,
resolveSubagentHeaderValue,
resolveToolingInstructions,
type CollaborationToolProfile
} from "./collaboration"

function normalizeVerbositySetting(value: unknown): "default" | "low" | "medium" | "high" | undefined {
if (typeof value !== "string") return undefined
Expand Down Expand Up @@ -60,13 +71,17 @@ export async function handleChatParamsHook(input: {
api?: { id?: string }
capabilities?: { toolcall?: boolean }
}
agent?: unknown
message: unknown
}
output: Parameters<typeof applyCodexRuntimeDefaultsToParams>[0]["output"]
lastCatalogModels: CodexModelInfo[] | undefined
behaviorSettings?: BehaviorSettings
fallbackPersonality?: PersonalityOption
spoofMode: CodexSpoofMode
collaborationProfileEnabled: boolean
orchestratorSubagentsEnabled: boolean
collaborationToolProfile: CollaborationToolProfile
}): Promise<void> {
if (input.hookInput.model.providerID !== "openai") return
const modelOptions = isRecord(input.hookInput.model.options) ? input.hookInput.model.options : {}
Expand Down Expand Up @@ -137,13 +152,34 @@ export async function handleChatParamsHook(input: {
preferCodexInstructions: input.spoofMode === "codex",
output: input.output
})

if (!input.collaborationProfileEnabled) return

const profile = resolveCollaborationProfile(input.hookInput.agent)
if (!profile.enabled || !profile.kind) return

const collaborationInstructions = resolveCollaborationInstructions(profile.kind, {
plan: CODEX_PLAN_MODE_INSTRUCTIONS,
code: CODEX_CODE_MODE_INSTRUCTIONS
})
let mergedInstructions = mergeInstructions(asString(input.output.options.instructions), collaborationInstructions)

if (profile.isOrchestrator && input.orchestratorSubagentsEnabled) {
mergedInstructions = mergeInstructions(mergedInstructions, CODEX_ORCHESTRATOR_INSTRUCTIONS)
}

mergedInstructions = mergeInstructions(mergedInstructions, resolveToolingInstructions(input.collaborationToolProfile))

input.output.options.instructions = mergedInstructions
}

export async function handleChatHeadersHook(input: {
hookInput: { model: { providerID?: string }; sessionID: string }
hookInput: { model: { providerID?: string }; sessionID: string; agent?: unknown }
output: { headers: Record<string, unknown> }
spoofMode: CodexSpoofMode
internalCollaborationModeHeader: string
collaborationProfileEnabled: boolean
orchestratorSubagentsEnabled: boolean
}): Promise<void> {
if (input.hookInput.model.providerID !== "openai") return
const originator = resolveCodexOriginator(input.spoofMode)
Expand All @@ -152,10 +188,33 @@ export async function handleChatHeadersHook(input: {
input.output.headers.session_id = input.hookInput.sessionID
delete input.output.headers["OpenAI-Beta"]
delete input.output.headers.conversation_id
if (input.spoofMode !== "native") {

if (!input.collaborationProfileEnabled) {
delete input.output.headers["x-openai-subagent"]
delete input.output.headers[input.internalCollaborationModeHeader]
return
}

const profile = resolveCollaborationProfile(input.hookInput.agent)
if (!profile.enabled || !profile.kind) {
delete input.output.headers["x-openai-subagent"]
delete input.output.headers[input.internalCollaborationModeHeader]
return
}

input.output.headers[input.internalCollaborationModeHeader] = profile.kind

if (input.orchestratorSubagentsEnabled) {
const subagentHeader = resolveSubagentHeaderValue(input.hookInput.agent)
if (subagentHeader) {
input.output.headers["x-openai-subagent"] = subagentHeader
} else {
delete input.output.headers["x-openai-subagent"]
}
return
}

delete input.output.headers["x-openai-subagent"]
}

export async function handleSessionCompactingHook(input: {
Expand Down
Loading
Loading