|
@@ -55,8 +55,10 @@ function selectable(ctx: Context, agent: Awaited<ReturnType<Context['agents']['c
|
|
|
&& ctx.tools.schemas(agent).some(candidate => candidate.name === 'list_subagent_models')
|
|
&& ctx.tools.schemas(agent).some(candidate => candidate.name === 'list_subagent_models')
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/** Mount the real settings, Agent, provider, and tool services. */
|
|
|
|
|
-async function boot(): Promise<Context> {
|
|
|
|
|
|
|
+const modelSelectionPresets = new WeakMap<Context, ReturnType<typeof createScope>>()
|
|
|
|
|
+
|
|
|
|
|
+/** Mount the real settings, Agent, provider, and optional preset tool services. */
|
|
|
|
|
+async function boot(withPreset = true): Promise<Context> {
|
|
|
const ctx = new Context()
|
|
const ctx = new Context()
|
|
|
await ctx.plugin(MemorySettings)
|
|
await ctx.plugin(MemorySettings)
|
|
|
await ctx.plugin(SubagentModelSelectionConfig)
|
|
await ctx.plugin(SubagentModelSelectionConfig)
|
|
@@ -65,23 +67,30 @@ async function boot(): Promise<Context> {
|
|
|
await ctx.plugin(AgentLoop, { agents: [] })
|
|
await ctx.plugin(AgentLoop, { agents: [] })
|
|
|
await ctx.plugin(SubagentRuntime)
|
|
await ctx.plugin(SubagentRuntime)
|
|
|
await ctx.plugin(SubagentSpawn, { providerName: 'spawn' })
|
|
await ctx.plugin(SubagentSpawn, { providerName: 'spawn' })
|
|
|
|
|
+ if (withPreset) {
|
|
|
|
|
+ const preset = createScope(ctx, { preset: 'model-selection-test' })
|
|
|
|
|
+ await preset.ctx.plugin(tool, {
|
|
|
|
|
+ provider: 'spawn',
|
|
|
|
|
+ modelSelectionSettings: true,
|
|
|
|
|
+ backgroundMode: 'continuable',
|
|
|
|
|
+ })
|
|
|
|
|
+ modelSelectionPresets.set(ctx, preset)
|
|
|
|
|
+ }
|
|
|
return ctx
|
|
return ctx
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/** Create one Agent whose setup mounts the settings-controlled tool preset row. */
|
|
|
|
|
|
|
+/** Create one Agent joined to the test's standing preset. */
|
|
|
async function createAgent(ctx: Context, id: string, options: {
|
|
async function createAgent(ctx: Context, id: string, options: {
|
|
|
meta?: { parentSession: SessionId; origin: 'subagent' }
|
|
meta?: { parentSession: SessionId; origin: 'subagent' }
|
|
|
seed?: readonly SessionEvent[]
|
|
seed?: readonly SessionEvent[]
|
|
|
} = {}) {
|
|
} = {}) {
|
|
|
|
|
+ const preset = modelSelectionPresets.get(ctx)
|
|
|
|
|
+ if (preset === undefined) throw new Error('context has no model-selection preset')
|
|
|
const handle = await ctx.agents.create({
|
|
const handle = await ctx.agents.create({
|
|
|
sessionId: SessionId(id),
|
|
sessionId: SessionId(id),
|
|
|
...options,
|
|
...options,
|
|
|
- setup: async (agentCtx) => {
|
|
|
|
|
- await agentCtx.plugin(tool, {
|
|
|
|
|
- provider: 'spawn',
|
|
|
|
|
- modelSelectionSettings: true,
|
|
|
|
|
- backgroundMode: 'continuable',
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ setup: (agentCtx) => {
|
|
|
|
|
+ bindScopeParent(scopeOf(agentCtx)!, scopeOf(preset.ctx)!)
|
|
|
},
|
|
},
|
|
|
})
|
|
})
|
|
|
return handle.agent
|
|
return handle.agent
|
|
@@ -146,7 +155,7 @@ describe('SubagentModelSelectionConfig', () => {
|
|
|
await ctx.fiber.dispose()
|
|
await ctx.fiber.dispose()
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- it('samples each new root session without changing existing Agents', async () => {
|
|
|
|
|
|
|
+ it('samples each new root Session without changing existing definitions', async () => {
|
|
|
const ctx = await boot()
|
|
const ctx = await boot()
|
|
|
const disabled = await createAgent(ctx, 'disabled')
|
|
const disabled = await createAgent(ctx, 'disabled')
|
|
|
expect(selectable(ctx, disabled)).toBe(false)
|
|
expect(selectable(ctx, disabled)).toBe(false)
|
|
@@ -168,6 +177,39 @@ describe('SubagentModelSelectionConfig', () => {
|
|
|
await ctx.fiber.dispose()
|
|
await ctx.fiber.dispose()
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+ it('installs a direct Agent setup before Session publication', async () => {
|
|
|
|
|
+ const ctx = await boot(false)
|
|
|
|
|
+ await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
|
|
|
|
|
+ enabled: true,
|
|
|
|
|
+ allowedModels: ALLOWED_MODELS,
|
|
|
|
|
+ })
|
|
|
|
|
+ let prepared: Awaited<ReturnType<Context['agents']['create']>>['agent'] | undefined
|
|
|
|
|
+ let visibleAtSessionCreated = false
|
|
|
|
|
+ ctx.on('session/created', () => {
|
|
|
|
|
+ visibleAtSessionCreated = prepared !== undefined && selectable(ctx, prepared)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const handle = await ctx.agents.create({
|
|
|
|
|
+ sessionId: SessionId('direct-agent-setup'),
|
|
|
|
|
+ setup: async (agentCtx, agent) => {
|
|
|
|
|
+ prepared = agent
|
|
|
|
|
+ const fiber = agentCtx.inject(tool.inject, (runtimeCtx) => {
|
|
|
|
|
+ tool.apply(runtimeCtx, {
|
|
|
|
|
+ provider: 'spawn',
|
|
|
|
|
+ modelSelectionSettings: true,
|
|
|
|
|
+ backgroundMode: 'continuable',
|
|
|
|
|
+ }, agent.session)
|
|
|
|
|
+ })
|
|
|
|
|
+ await fiber.await()
|
|
|
|
|
+ },
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ expect(visibleAtSessionCreated).toBe(true)
|
|
|
|
|
+ expect(selectable(ctx, handle.agent)).toBe(true)
|
|
|
|
|
+ await handle.dispose()
|
|
|
|
|
+ await ctx.fiber.dispose()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
it('rejects a forced route outside the Session policy before child creation', async () => {
|
|
it('rejects a forced route outside the Session policy before child creation', async () => {
|
|
|
const ctx = await boot()
|
|
const ctx = await boot()
|
|
|
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
|
|
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
|
|
@@ -195,12 +237,12 @@ describe('SubagentModelSelectionConfig', () => {
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
it('installs per-Agent definitions for a shared preset scope', async () => {
|
|
it('installs per-Agent definitions for a shared preset scope', async () => {
|
|
|
- const ctx = await boot()
|
|
|
|
|
|
|
+ const ctx = await boot(false)
|
|
|
await ctx.plugin(InvariantRegistry, { enabled: true })
|
|
await ctx.plugin(InvariantRegistry, { enabled: true })
|
|
|
await ctx.plugin(ToolInvariant)
|
|
await ctx.plugin(ToolInvariant)
|
|
|
const preset = createScope(ctx, { preset: 'standard' })
|
|
const preset = createScope(ctx, { preset: 'standard' })
|
|
|
const other = createScope(ctx, { preset: 'minimal' })
|
|
const other = createScope(ctx, { preset: 'minimal' })
|
|
|
- await preset.ctx.plugin(tool, {
|
|
|
|
|
|
|
+ const mounted = await preset.ctx.plugin(tool, {
|
|
|
provider: 'spawn',
|
|
provider: 'spawn',
|
|
|
modelSelectionSettings: true,
|
|
modelSelectionSettings: true,
|
|
|
backgroundMode: 'continuable',
|
|
backgroundMode: 'continuable',
|
|
@@ -245,6 +287,10 @@ describe('SubagentModelSelectionConfig', () => {
|
|
|
await expect(ctx.waterfall(ctx as never, 'agent/pre-step', payload, next))
|
|
await expect(ctx.waterfall(ctx as never, 'agent/pre-step', payload, next))
|
|
|
.resolves.toEqual({ kind: 'enter', messages: [] })
|
|
.resolves.toEqual({ kind: 'enter', messages: [] })
|
|
|
|
|
|
|
|
|
|
+ await mounted.dispose()
|
|
|
|
|
+ expect(selectable(ctx, enabled.agent)).toBe(false)
|
|
|
|
|
+ expect(selectable(ctx, disabled.agent)).toBe(false)
|
|
|
|
|
+
|
|
|
await enabled.dispose()
|
|
await enabled.dispose()
|
|
|
ctx.emit(scopeTarget({}, scopeOf(preset.ctx)), 'tools/change')
|
|
ctx.emit(scopeTarget({}, scopeOf(preset.ctx)), 'tools/change')
|
|
|
await disabled.dispose()
|
|
await disabled.dispose()
|
|
@@ -252,7 +298,7 @@ describe('SubagentModelSelectionConfig', () => {
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
it('releases a shared-preset installation reservation after policy selection fails', async () => {
|
|
it('releases a shared-preset installation reservation after policy selection fails', async () => {
|
|
|
- const ctx = await boot()
|
|
|
|
|
|
|
+ const ctx = await boot(false)
|
|
|
const preset = createScope(ctx, { preset: 'standard' })
|
|
const preset = createScope(ctx, { preset: 'standard' })
|
|
|
const other = createScope(ctx, { preset: 'minimal' })
|
|
const other = createScope(ctx, { preset: 'minimal' })
|
|
|
await preset.ctx.plugin(tool, {
|
|
await preset.ctx.plugin(tool, {
|
|
@@ -320,7 +366,7 @@ describe('SubagentModelSelectionConfig', () => {
|
|
|
await ctx.fiber.dispose()
|
|
await ctx.fiber.dispose()
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- it('requires both the Host setting owner and a composition scope', async () => {
|
|
|
|
|
|
|
+ it('requires both the Host setting owner and a scoped standing preset', async () => {
|
|
|
const withoutSettings = new Context()
|
|
const withoutSettings = new Context()
|
|
|
await mountAgentLoopTestDependencies(withoutSettings)
|
|
await mountAgentLoopTestDependencies(withoutSettings)
|
|
|
await withoutSettings.plugin(SessionProjectionRegistry)
|
|
await withoutSettings.plugin(SessionProjectionRegistry)
|
|
@@ -334,14 +380,15 @@ describe('SubagentModelSelectionConfig', () => {
|
|
|
}).toThrow('requires @deepseek-ai/dsh-tool-subagent/model-selection-settings')
|
|
}).toThrow('requires @deepseek-ai/dsh-tool-subagent/model-selection-settings')
|
|
|
await withoutSettings.fiber.dispose()
|
|
await withoutSettings.fiber.dispose()
|
|
|
|
|
|
|
|
- const withoutAgent = await boot()
|
|
|
|
|
|
|
+ const withoutAgent = await boot(false)
|
|
|
expect(() => {
|
|
expect(() => {
|
|
|
tool.apply(withoutAgent, {
|
|
tool.apply(withoutAgent, {
|
|
|
provider: 'spawn',
|
|
provider: 'spawn',
|
|
|
modelSelectionSettings: true,
|
|
modelSelectionSettings: true,
|
|
|
backgroundMode: 'continuable',
|
|
backgroundMode: 'continuable',
|
|
|
})
|
|
})
|
|
|
- }).toThrow('requires an Agent or preset scope')
|
|
|
|
|
|
|
+ }).toThrow('requires a scoped preset Context')
|
|
|
|
|
+
|
|
|
await withoutAgent.fiber.dispose()
|
|
await withoutAgent.fiber.dispose()
|
|
|
})
|
|
})
|
|
|
|
|
|