Merge pull request #1974 from ControlNet/dev
This commit is contained in:
commit
e758623a2e
@ -1133,6 +1133,7 @@ Opt-in experimental features that may change or be removed in future versions. U
|
||||
"truncate_all_tool_outputs": true,
|
||||
"aggressive_truncation": true,
|
||||
"auto_resume": true,
|
||||
"disable_omo_env": false,
|
||||
"dynamic_context_pruning": {
|
||||
"enabled": false,
|
||||
"notification": "detailed",
|
||||
@ -1164,6 +1165,7 @@ Opt-in experimental features that may change or be removed in future versions. U
|
||||
| `truncate_all_tool_outputs` | `false` | Truncates ALL tool outputs instead of just whitelisted tools (Grep, Glob, LSP, AST-grep). Tool output truncator is enabled by default - disable via `disabled_hooks`. |
|
||||
| `aggressive_truncation` | `false` | When token limit is exceeded, aggressively truncates tool outputs to fit within limits. More aggressive than the default truncation behavior. Falls back to summarize/revert if insufficient. |
|
||||
| `auto_resume` | `false` | Automatically resumes session after successful recovery from thinking block errors or thinking disabled violations. Extracts last user message and continues. |
|
||||
| `disable_omo_env` | `false` | When `true`, disables auto-injected `<omo-env>` block generation (date, time, timezone, locale). When unset or `false`, current behavior is preserved. Setting this to `true` will improve the cache hit rate and reduce the API cost. |
|
||||
| `dynamic_context_pruning` | See below | Dynamic context pruning configuration for managing context window usage automatically. See [Dynamic Context Pruning](#dynamic-context-pruning) below. |
|
||||
|
||||
### Dynamic Context Pruning
|
||||
|
||||
@ -69,8 +69,10 @@ export async function createBuiltinAgents(
|
||||
browserProvider?: BrowserAutomationProvider,
|
||||
uiSelectedModel?: string,
|
||||
disabledSkills?: Set<string>,
|
||||
useTaskSystem = false
|
||||
useTaskSystem = false,
|
||||
disableOmoEnv = false
|
||||
): Promise<Record<string, AgentConfig>> {
|
||||
|
||||
const connectedProviders = readConnectedProvidersCache()
|
||||
const providerModelsConnected = connectedProviders
|
||||
? (readProviderModelsCache()?.connected ?? [])
|
||||
@ -112,6 +114,7 @@ export async function createBuiltinAgents(
|
||||
uiSelectedModel,
|
||||
availableModels,
|
||||
disabledSkills,
|
||||
disableOmoEnv,
|
||||
})
|
||||
|
||||
const registeredAgents = parseRegisteredAgentSummaries(customAgentSummaries)
|
||||
@ -145,6 +148,7 @@ export async function createBuiltinAgents(
|
||||
directory,
|
||||
userCategories: categories,
|
||||
useTaskSystem,
|
||||
disableOmoEnv,
|
||||
})
|
||||
if (sisyphusConfig) {
|
||||
result["sisyphus"] = sisyphusConfig
|
||||
@ -162,6 +166,7 @@ export async function createBuiltinAgents(
|
||||
mergedCategories,
|
||||
directory,
|
||||
useTaskSystem,
|
||||
disableOmoEnv,
|
||||
})
|
||||
if (hephaestusConfig) {
|
||||
result["hephaestus"] = hephaestusConfig
|
||||
|
||||
@ -1,8 +1,16 @@
|
||||
import type { AgentConfig } from "@opencode-ai/sdk"
|
||||
import { createEnvContext } from "../env-context"
|
||||
|
||||
export function applyEnvironmentContext(config: AgentConfig, directory?: string): AgentConfig {
|
||||
if (!directory || !config.prompt) return config
|
||||
type ApplyEnvironmentContextOptions = {
|
||||
disableOmoEnv?: boolean
|
||||
}
|
||||
|
||||
export function applyEnvironmentContext(
|
||||
config: AgentConfig,
|
||||
directory?: string,
|
||||
options: ApplyEnvironmentContextOptions = {}
|
||||
): AgentConfig {
|
||||
if (options.disableOmoEnv || !directory || !config.prompt) return config
|
||||
const envContext = createEnvContext()
|
||||
return { ...config, prompt: config.prompt + envContext }
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ export function collectPendingBuiltinAgents(input: {
|
||||
availableModels: Set<string>
|
||||
disabledSkills?: Set<string>
|
||||
useTaskSystem?: boolean
|
||||
disableOmoEnv?: boolean
|
||||
}): { pendingAgentConfigs: Map<string, AgentConfig>; availableAgents: AvailableAgent[] } {
|
||||
const {
|
||||
agentSources,
|
||||
@ -37,6 +38,7 @@ export function collectPendingBuiltinAgents(input: {
|
||||
uiSelectedModel,
|
||||
availableModels,
|
||||
disabledSkills,
|
||||
disableOmoEnv = false,
|
||||
} = input
|
||||
|
||||
const availableAgents: AvailableAgent[] = []
|
||||
@ -81,7 +83,7 @@ export function collectPendingBuiltinAgents(input: {
|
||||
}
|
||||
|
||||
if (agentName === "librarian") {
|
||||
config = applyEnvironmentContext(config, directory)
|
||||
config = applyEnvironmentContext(config, directory, { disableOmoEnv })
|
||||
}
|
||||
|
||||
config = applyOverrides(config, override, mergedCategories, directory)
|
||||
|
||||
@ -4,7 +4,7 @@ import type { CategoryConfig } from "../../config/schema"
|
||||
import type { AvailableAgent, AvailableCategory, AvailableSkill } from "../dynamic-agent-prompt-builder"
|
||||
import { AGENT_MODEL_REQUIREMENTS, isAnyProviderConnected } from "../../shared"
|
||||
import { createHephaestusAgent } from "../hephaestus"
|
||||
import { createEnvContext } from "../env-context"
|
||||
import { applyEnvironmentContext } from "./environment-context"
|
||||
import { applyCategoryOverride, mergeAgentConfig } from "./agent-overrides"
|
||||
import { applyModelResolution, getFirstFallbackModel } from "./model-resolution"
|
||||
|
||||
@ -20,6 +20,7 @@ export function maybeCreateHephaestusConfig(input: {
|
||||
mergedCategories: Record<string, CategoryConfig>
|
||||
directory?: string
|
||||
useTaskSystem: boolean
|
||||
disableOmoEnv?: boolean
|
||||
}): AgentConfig | undefined {
|
||||
const {
|
||||
disabledAgents,
|
||||
@ -33,6 +34,7 @@ export function maybeCreateHephaestusConfig(input: {
|
||||
mergedCategories,
|
||||
directory,
|
||||
useTaskSystem,
|
||||
disableOmoEnv = false,
|
||||
} = input
|
||||
|
||||
if (disabledAgents.includes("hephaestus")) return undefined
|
||||
@ -79,10 +81,7 @@ export function maybeCreateHephaestusConfig(input: {
|
||||
hephaestusConfig = applyCategoryOverride(hephaestusConfig, hepOverrideCategory, mergedCategories)
|
||||
}
|
||||
|
||||
if (directory && hephaestusConfig.prompt) {
|
||||
const envContext = createEnvContext()
|
||||
hephaestusConfig = { ...hephaestusConfig, prompt: hephaestusConfig.prompt + envContext }
|
||||
}
|
||||
hephaestusConfig = applyEnvironmentContext(hephaestusConfig, directory, { disableOmoEnv })
|
||||
|
||||
if (hephaestusOverride) {
|
||||
hephaestusConfig = mergeAgentConfig(hephaestusConfig, hephaestusOverride, directory)
|
||||
|
||||
@ -22,6 +22,7 @@ export function maybeCreateSisyphusConfig(input: {
|
||||
directory?: string
|
||||
userCategories?: CategoriesConfig
|
||||
useTaskSystem: boolean
|
||||
disableOmoEnv?: boolean
|
||||
}): AgentConfig | undefined {
|
||||
const {
|
||||
disabledAgents,
|
||||
@ -36,6 +37,7 @@ export function maybeCreateSisyphusConfig(input: {
|
||||
mergedCategories,
|
||||
directory,
|
||||
useTaskSystem,
|
||||
disableOmoEnv = false,
|
||||
} = input
|
||||
|
||||
const sisyphusOverride = agentOverrides["sisyphus"]
|
||||
@ -78,7 +80,9 @@ export function maybeCreateSisyphusConfig(input: {
|
||||
}
|
||||
|
||||
sisyphusConfig = applyOverrides(sisyphusConfig, sisyphusOverride, mergedCategories, directory)
|
||||
sisyphusConfig = applyEnvironmentContext(sisyphusConfig, directory)
|
||||
sisyphusConfig = applyEnvironmentContext(sisyphusConfig, directory, {
|
||||
disableOmoEnv,
|
||||
})
|
||||
|
||||
return sisyphusConfig
|
||||
}
|
||||
|
||||
@ -662,6 +662,178 @@ describe("createBuiltinAgents with requiresProvider gating (hephaestus)", () =>
|
||||
})
|
||||
})
|
||||
|
||||
describe("Hephaestus environment context toggle", () => {
|
||||
let fetchSpy: ReturnType<typeof spyOn>
|
||||
|
||||
beforeEach(() => {
|
||||
fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["openai/gpt-5.3-codex"])
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
fetchSpy.mockRestore()
|
||||
})
|
||||
|
||||
async function buildAgents(disableFlag?: boolean) {
|
||||
return createBuiltinAgents(
|
||||
[],
|
||||
{},
|
||||
"/tmp/work",
|
||||
TEST_DEFAULT_MODEL,
|
||||
undefined,
|
||||
undefined,
|
||||
[],
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
disableFlag
|
||||
)
|
||||
}
|
||||
|
||||
test("includes <omo-env> tag when disable flag is unset", async () => {
|
||||
// #when
|
||||
const agents = await buildAgents(undefined)
|
||||
|
||||
// #then
|
||||
expect(agents.hephaestus).toBeDefined()
|
||||
expect(agents.hephaestus.prompt).toContain("<omo-env>")
|
||||
})
|
||||
|
||||
test("includes <omo-env> tag when disable flag is false", async () => {
|
||||
// #when
|
||||
const agents = await buildAgents(false)
|
||||
|
||||
// #then
|
||||
expect(agents.hephaestus).toBeDefined()
|
||||
expect(agents.hephaestus.prompt).toContain("<omo-env>")
|
||||
})
|
||||
|
||||
test("omits <omo-env> tag when disable flag is true", async () => {
|
||||
// #when
|
||||
const agents = await buildAgents(true)
|
||||
|
||||
// #then
|
||||
expect(agents.hephaestus).toBeDefined()
|
||||
expect(agents.hephaestus.prompt).not.toContain("<omo-env>")
|
||||
})
|
||||
})
|
||||
|
||||
describe("Sisyphus and Librarian environment context toggle", () => {
|
||||
let fetchSpy: ReturnType<typeof spyOn>
|
||||
|
||||
beforeEach(() => {
|
||||
fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["anthropic/claude-opus-4-6", "google/gemini-3-flash"])
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
fetchSpy.mockRestore()
|
||||
})
|
||||
|
||||
async function buildAgents(disableFlag?: boolean) {
|
||||
return createBuiltinAgents(
|
||||
[],
|
||||
{},
|
||||
"/tmp/work",
|
||||
TEST_DEFAULT_MODEL,
|
||||
undefined,
|
||||
undefined,
|
||||
[],
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
disableFlag
|
||||
)
|
||||
}
|
||||
|
||||
test("includes <omo-env> for sisyphus and librarian when disable flag is unset", async () => {
|
||||
const agents = await buildAgents(undefined)
|
||||
|
||||
expect(agents.sisyphus).toBeDefined()
|
||||
expect(agents.librarian).toBeDefined()
|
||||
expect(agents.sisyphus.prompt).toContain("<omo-env>")
|
||||
expect(agents.librarian.prompt).toContain("<omo-env>")
|
||||
})
|
||||
|
||||
test("includes <omo-env> for sisyphus and librarian when disable flag is false", async () => {
|
||||
const agents = await buildAgents(false)
|
||||
|
||||
expect(agents.sisyphus).toBeDefined()
|
||||
expect(agents.librarian).toBeDefined()
|
||||
expect(agents.sisyphus.prompt).toContain("<omo-env>")
|
||||
expect(agents.librarian.prompt).toContain("<omo-env>")
|
||||
})
|
||||
|
||||
test("omits <omo-env> for sisyphus and librarian when disable flag is true", async () => {
|
||||
const agents = await buildAgents(true)
|
||||
|
||||
expect(agents.sisyphus).toBeDefined()
|
||||
expect(agents.librarian).toBeDefined()
|
||||
expect(agents.sisyphus.prompt).not.toContain("<omo-env>")
|
||||
expect(agents.librarian.prompt).not.toContain("<omo-env>")
|
||||
})
|
||||
})
|
||||
|
||||
describe("Atlas is unaffected by environment context toggle", () => {
|
||||
let fetchSpy: ReturnType<typeof spyOn>
|
||||
|
||||
beforeEach(() => {
|
||||
fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.2"])
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
fetchSpy.mockRestore()
|
||||
})
|
||||
|
||||
test("atlas prompt is unchanged and never contains <omo-env>", async () => {
|
||||
const agentsDefault = await createBuiltinAgents(
|
||||
[],
|
||||
{},
|
||||
"/tmp/work",
|
||||
TEST_DEFAULT_MODEL,
|
||||
undefined,
|
||||
undefined,
|
||||
[],
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
false
|
||||
)
|
||||
|
||||
const agentsDisabled = await createBuiltinAgents(
|
||||
[],
|
||||
{},
|
||||
"/tmp/work",
|
||||
TEST_DEFAULT_MODEL,
|
||||
undefined,
|
||||
undefined,
|
||||
[],
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
)
|
||||
|
||||
expect(agentsDefault.atlas).toBeDefined()
|
||||
expect(agentsDisabled.atlas).toBeDefined()
|
||||
expect(agentsDefault.atlas.prompt).not.toContain("<omo-env>")
|
||||
expect(agentsDisabled.atlas.prompt).not.toContain("<omo-env>")
|
||||
expect(agentsDisabled.atlas.prompt).toBe(agentsDefault.atlas.prompt)
|
||||
})
|
||||
})
|
||||
|
||||
describe("createBuiltinAgents with requiresAnyModel gating (sisyphus)", () => {
|
||||
test("sisyphus is created when at least one fallback model is available", async () => {
|
||||
// #given
|
||||
|
||||
@ -741,6 +741,59 @@ describe("ExperimentalConfigSchema feature flags", () => {
|
||||
}
|
||||
})
|
||||
|
||||
test("accepts disable_omo_env as true", () => {
|
||||
//#given
|
||||
const config = { disable_omo_env: true }
|
||||
|
||||
//#when
|
||||
const result = ExperimentalConfigSchema.safeParse(config)
|
||||
|
||||
//#then
|
||||
expect(result.success).toBe(true)
|
||||
if (result.success) {
|
||||
expect(result.data.disable_omo_env).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
test("accepts disable_omo_env as false", () => {
|
||||
//#given
|
||||
const config = { disable_omo_env: false }
|
||||
|
||||
//#when
|
||||
const result = ExperimentalConfigSchema.safeParse(config)
|
||||
|
||||
//#then
|
||||
expect(result.success).toBe(true)
|
||||
if (result.success) {
|
||||
expect(result.data.disable_omo_env).toBe(false)
|
||||
}
|
||||
})
|
||||
|
||||
test("disable_omo_env is optional", () => {
|
||||
//#given
|
||||
const config = { safe_hook_creation: true }
|
||||
|
||||
//#when
|
||||
const result = ExperimentalConfigSchema.safeParse(config)
|
||||
|
||||
//#then
|
||||
expect(result.success).toBe(true)
|
||||
if (result.success) {
|
||||
expect(result.data.disable_omo_env).toBeUndefined()
|
||||
}
|
||||
})
|
||||
|
||||
test("rejects non-boolean disable_omo_env", () => {
|
||||
//#given
|
||||
const config = { disable_omo_env: "true" }
|
||||
|
||||
//#when
|
||||
const result = ExperimentalConfigSchema.safeParse(config)
|
||||
|
||||
//#then
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
|
||||
test("rejects non-boolean hashline_edit", () => {
|
||||
//#given
|
||||
const config = { hashline_edit: "true" }
|
||||
|
||||
@ -15,6 +15,8 @@ export const ExperimentalConfigSchema = z.object({
|
||||
plugin_load_timeout_ms: z.number().min(1000).optional(),
|
||||
/** Wrap hook creation in try/catch to prevent one failing hook from crashing the plugin (default: true at call site) */
|
||||
safe_hook_creation: z.boolean().optional(),
|
||||
/** Disable auto-injected <omo-env> context in prompts (experimental) */
|
||||
disable_omo_env: z.boolean().optional(),
|
||||
/** Enable hashline_edit tool for improved file editing with hash-based line anchors */
|
||||
hashline_edit: z.boolean().optional(),
|
||||
})
|
||||
|
||||
@ -76,6 +76,7 @@ export async function applyAgentConfig(params: {
|
||||
const currentModel = params.config.model as string | undefined;
|
||||
const disabledSkills = new Set<string>(params.pluginConfig.disabled_skills ?? []);
|
||||
const useTaskSystem = params.pluginConfig.experimental?.task_system ?? false;
|
||||
const disableOmoEnv = params.pluginConfig.experimental?.disable_omo_env ?? false;
|
||||
|
||||
const builtinAgents = await createBuiltinAgents(
|
||||
migratedDisabledAgents,
|
||||
@ -90,6 +91,7 @@ export async function applyAgentConfig(params: {
|
||||
currentModel,
|
||||
disabledSkills,
|
||||
useTaskSystem,
|
||||
disableOmoEnv,
|
||||
);
|
||||
|
||||
const includeClaudeAgents = params.pluginConfig.claude_code?.agents ?? true;
|
||||
|
||||
@ -1275,3 +1275,69 @@ describe("per-agent todowrite/todoread deny when task_system enabled", () => {
|
||||
expect(agentResult[getAgentDisplayName("sisyphus")]?.permission?.todoread).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("disable_omo_env pass-through", () => {
|
||||
test("omits <omo-env> in generated sisyphus prompt when disable_omo_env is true", async () => {
|
||||
//#given
|
||||
;(agents.createBuiltinAgents as any)?.mockRestore?.()
|
||||
;(shared.fetchAvailableModels as any).mockResolvedValue(
|
||||
new Set(["anthropic/claude-opus-4-6", "google/gemini-3-flash"])
|
||||
)
|
||||
|
||||
const pluginConfig: OhMyOpenCodeConfig = {
|
||||
experimental: { disable_omo_env: true },
|
||||
}
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
ctx: { directory: "/tmp" },
|
||||
pluginConfig,
|
||||
modelCacheState: {
|
||||
anthropicContext1MEnabled: false,
|
||||
modelContextLimitsCache: new Map(),
|
||||
},
|
||||
})
|
||||
|
||||
//#when
|
||||
await handler(config)
|
||||
|
||||
//#then
|
||||
const agentResult = config.agent as Record<string, { prompt?: string }>
|
||||
const sisyphusPrompt = agentResult[getAgentDisplayName("sisyphus")]?.prompt
|
||||
expect(sisyphusPrompt).toBeDefined()
|
||||
expect(sisyphusPrompt).not.toContain("<omo-env>")
|
||||
})
|
||||
|
||||
test("keeps <omo-env> in generated sisyphus prompt when disable_omo_env is omitted", async () => {
|
||||
//#given
|
||||
;(agents.createBuiltinAgents as any)?.mockRestore?.()
|
||||
;(shared.fetchAvailableModels as any).mockResolvedValue(
|
||||
new Set(["anthropic/claude-opus-4-6", "google/gemini-3-flash"])
|
||||
)
|
||||
|
||||
const pluginConfig: OhMyOpenCodeConfig = {}
|
||||
const config: Record<string, unknown> = {
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
agent: {},
|
||||
}
|
||||
const handler = createConfigHandler({
|
||||
ctx: { directory: "/tmp" },
|
||||
pluginConfig,
|
||||
modelCacheState: {
|
||||
anthropicContext1MEnabled: false,
|
||||
modelContextLimitsCache: new Map(),
|
||||
},
|
||||
})
|
||||
|
||||
//#when
|
||||
await handler(config)
|
||||
|
||||
//#then
|
||||
const agentResult = config.agent as Record<string, { prompt?: string }>
|
||||
const sisyphusPrompt = agentResult[getAgentDisplayName("sisyphus")]?.prompt
|
||||
expect(sisyphusPrompt).toBeDefined()
|
||||
expect(sisyphusPrompt).toContain("<omo-env>")
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user