fix(workspace-context): require explicit byte budgets

This commit is contained in:
Tianyi Cui
2026-07-12 12:09:35 +08:00
parent 855abe9d60
commit e9a54f0e71
32 changed files with 263 additions and 162 deletions

View File

@@ -61,8 +61,8 @@ export interface Config {
tools?: ToolsConfig
/** Directory the JSONL session backend writes under. Defaults to `./.sessions`. */
persistenceRoot?: string
/** Controls automatic AGENTS.md/CLAUDE.md loading; set `false` for hermetic prompts. */
workspaceContext?: agentCore.Config['workspaceContext']
/** Controls automatic AGENTS.md/CLAUDE.md loading; configure a byte budget or set `false`. */
workspaceContext: agentCore.Config['workspaceContext']
/** Skill registry, local-provider, and model-facing consumer config forwarded to agent-core. */
skills?: agentCore.SkillConfig
}
@@ -76,9 +76,9 @@ export const Config: z<Config> = z.object({
toolOrder: z.array(z.string()).default(undefined as unknown as string[]),
tools: ToolRegistry.Config,
persistenceRoot: z.string().default('./.sessions'),
workspaceContext: z.union([z.const(false), workspaceContext.Config]),
workspaceContext: z.union([z.const(false), workspaceContext.Config]).required(),
skills: agentCore.SkillConfigSchema,
}) as unknown as z<Config>
})
/**
* Compose the spine with the ACP front door. The agent-core bundle pre-creates
@@ -92,7 +92,7 @@ export function apply(ctx: Context, config: Config): void {
...config.persona !== undefined ? { persona: config.persona } : {},
...config.toolOrder !== undefined ? { toolOrder: config.toolOrder } : {},
...config.tools !== undefined ? { tools: config.tools } : {},
...config.workspaceContext !== undefined ? { workspaceContext: config.workspaceContext } : {},
workspaceContext: config.workspaceContext,
...config.skills !== undefined ? { skills: config.skills } : {},
})
ctx.plugin(UserInteractionService)

View File

@@ -67,7 +67,7 @@ async function withIsolatedSkillHomes<T>(run: () => Promise<T>): Promise<T> {
describe('dsh-acp-agent composition', () => {
it('brings up the spine + persistence + the ACP bridge', async () => {
const ctx = await mount({ model: 'mock', persona: 'hi', persistenceRoot: '/tmp/dsh-acp-agent-test', skills: await isolatedSkillsConfig() })
const ctx = await mount({ model: 'mock', persona: 'hi', persistenceRoot: '/tmp/dsh-acp-agent-test', skills: await isolatedSkillsConfig(), workspaceContext: false })
expect(ctx.get('agents')).toBeDefined()
expect(ctx.get('sessions')).toBeDefined()
expect(ctx.get('sessionPersistence')).toBeDefined()
@@ -86,7 +86,7 @@ describe('dsh-acp-agent composition', () => {
// persistenceRoot, so the runtime fallback is the one that fires.
const ctx = new Context()
// No persona: covers the omitted-persona forwarding branch too.
acpAgent.apply(ctx, { model: 'mock', skills: await isolatedSkillsConfig() })
acpAgent.apply(ctx, { model: 'mock', skills: await isolatedSkillsConfig(), workspaceContext: false })
await new Promise(resolve => setTimeout(resolve, 50))
expect(ctx.get('sessionPersistence')).toBeDefined()
await ctx.fiber.dispose()
@@ -107,7 +107,7 @@ describe('dsh-acp-agent composition', () => {
it('uses default skill config when apply is called directly without skills', async () => {
await withIsolatedSkillHomes(async () => {
const ctx = new Context()
acpAgent.apply(ctx, { model: 'mock' })
acpAgent.apply(ctx, { model: 'mock', workspaceContext: false })
await new Promise(resolve => setTimeout(resolve, 50))
expect(ctx.skills).toBeDefined()
expect(await ctx.skills.list()).toEqual([])
@@ -116,7 +116,7 @@ describe('dsh-acp-agent composition', () => {
})
it('forwards skill config into agent-core', async () => {
const ctx = await mount({ model: 'mock', persona: 'hi', skills: await isolatedSkillsConfig(6) })
const ctx = await mount({ model: 'mock', persona: 'hi', skills: await isolatedSkillsConfig(6), workspaceContext: false })
ctx.skills.register({ name: 'acp-skill', description: 'ACP skill', source: 'runtime', content: 'body' })
expect(JSON.stringify(await composePrefix(ctx))).toContain('- `acp-skill`: ACP...')
await ctx.fiber.dispose()
@@ -132,6 +132,7 @@ describe('dsh-acp-agent composition', () => {
model: 'mock',
toolOrder: ['zulu', TOOL_ORDER_REST],
persistenceRoot: '/tmp/dsh-acp-agent-test-tool-order',
workspaceContext: false,
})
// The bundle's own bash tools pend on the absent `ctx.bash` executor in
// this providerless mount, so register two plain tools to order.

View File

@@ -84,8 +84,8 @@ export interface Config {
* (`resumeSessionId: !!js process.env.RESUME_SESSION_ID`).
*/
resumeSessionId?: string
/** Controls automatic AGENTS.md/CLAUDE.md loading; set `false` for hermetic prompts. */
workspaceContext?: agentCore.Config['workspaceContext']
/** Controls automatic AGENTS.md/CLAUDE.md loading; configure a byte budget or set `false`. */
workspaceContext: agentCore.Config['workspaceContext']
}
export const Config: z<Config> = z.object({
@@ -100,8 +100,8 @@ export const Config: z<Config> = z.object({
welcome: z.string().default('ready.'),
skills: agentCore.SkillConfigSchema,
resumeSessionId: z.string(),
workspaceContext: z.union([z.const(false), workspaceContext.Config]),
}) as unknown as z<Config>
workspaceContext: z.union([z.const(false), workspaceContext.Config]).required(),
})
/**
* Compose the spine with the stdio front door. The console logger comes first
@@ -122,7 +122,7 @@ export function apply(ctx: Context, config: Config): void {
cwd: process.cwd(),
...config.resumeSessionId !== undefined ? { resumeSessionId: SessionId(config.resumeSessionId) } : {},
}],
...config.workspaceContext !== undefined ? { workspaceContext: config.workspaceContext } : {},
workspaceContext: config.workspaceContext,
...config.skills !== undefined ? { skills: config.skills } : {},
})
ctx.plugin(SessionPersistenceJsonl, { root: config.persistenceRoot ?? './.sessions' })

View File

@@ -74,7 +74,7 @@ async function withIsolatedSkillHomes<T>(run: () => Promise<T>): Promise<T> {
describe('dsh-stdio-agent app', () => {
it('composes the spine + front-door cluster and pre-creates the main agent', async () => {
const ctx = await mount({ model: 'mock', persona: 'hi', persistenceRoot: '/tmp/dsh-stdio-agent-spec', skills: await isolatedSkillsConfig() })
const ctx = await mount({ model: 'mock', persona: 'hi', persistenceRoot: '/tmp/dsh-stdio-agent-spec', skills: await isolatedSkillsConfig(), workspaceContext: false })
// The spine services (brought up by the agent-core bundle) are all present.
expect(ctx.get('agents')).toBeDefined()
expect(ctx.get('agentLoop')).toBeDefined()
@@ -95,7 +95,7 @@ describe('dsh-stdio-agent app', () => {
// schema-bypassing direct-mount caller.
const ctx = new Context()
// No persona: covers the omitted-persona forwarding branch too.
stdioAgent.apply(ctx, { model: 'mock', skills: await isolatedSkillsConfig() })
stdioAgent.apply(ctx, { model: 'mock', skills: await isolatedSkillsConfig(), workspaceContext: false })
await new Promise(resolve => setTimeout(resolve, 80))
expect(ctx.get('sessionPersistence')).toBeDefined()
expect(ctx.get('agents')?.get(AgentId('main'))).toBeDefined()
@@ -116,7 +116,7 @@ describe('dsh-stdio-agent app', () => {
it('uses default skill config when apply is called directly without skills', async () => {
await withIsolatedSkillHomes(async () => {
const ctx = new Context()
stdioAgent.apply(ctx, { model: 'mock' })
stdioAgent.apply(ctx, { model: 'mock', workspaceContext: false })
await new Promise(resolve => setTimeout(resolve, 80))
expect(ctx.skills).toBeDefined()
expect(await ctx.skills.list()).toEqual([])
@@ -134,13 +134,14 @@ describe('dsh-stdio-agent app', () => {
persistenceRoot: '/tmp/dsh-stdio-agent-spec-resume',
resumeSessionId: 'no-such-session',
skills: await isolatedSkillsConfig(),
workspaceContext: false,
})
expect(ctx.get('agents')?.get(AgentId('main'))).toBeUndefined()
await ctx.fiber.dispose()
})
it('forwards skill config into agent-core', async () => {
const ctx = await mount({ model: 'mock', persona: 'hi', skills: await isolatedSkillsConfig(6) })
const ctx = await mount({ model: 'mock', persona: 'hi', skills: await isolatedSkillsConfig(6), workspaceContext: false })
ctx.skills.register({ name: 'stdio-skill', description: 'Stdio skill', source: 'runtime', content: 'body' })
expect(JSON.stringify(await composePrefix(ctx))).toContain('- `stdio-skill`: Std...')
await ctx.fiber.dispose()
@@ -156,6 +157,7 @@ describe('dsh-stdio-agent app', () => {
model: 'mock',
toolOrder: ['zulu', TOOL_ORDER_REST],
persistenceRoot: '/tmp/dsh-stdio-agent-spec-tool-order',
workspaceContext: false,
})
// The bundle's own bash tools pend on the absent `ctx.bash` executor in
// this providerless mount, so register two plain tools to order.