fix(web): compose a forked session, and give the shell realm its provider
Two consequences of moving the agent plane behind presets, both invisible until the host plane stopped carrying model-facing rows. `sessions.fork` built its child with a bare `installTarget` and a `meta` without `agentPreset`. That was harmless while every tool sat in the host plane — the child inherited them for free. It now comes up with an EMPTY tool set. The child composes the parent's preset instead, for the same reason a resumed session keeps its own: the seeded history was produced under those tools. `bashEnv` lives in its own `dsh-bash-env` row rather than inside `tool-bash`, so a preset that isolates the realm must compose the provider beside its consumer; the host row is disabled here like every other model-facing one. Nothing outside the agent plane injects `bashEnv`, so it stays per-session.
This commit is contained in:
@@ -22,6 +22,11 @@
|
|||||||
isolate:
|
isolate:
|
||||||
bashEnv: true
|
bashEnv: true
|
||||||
config:
|
config:
|
||||||
|
# The registry and its consumer share the realm: a consumer left outside
|
||||||
|
# would resolve the host's `bashEnv`, which this plane no longer provides.
|
||||||
|
- id: bash-env
|
||||||
|
name: '@deepseek-ai/dsh-bash-env'
|
||||||
|
|
||||||
- id: tool-bash
|
- id: tool-bash
|
||||||
name: '@deepseek-ai/dsh-tool-bash'
|
name: '@deepseek-ai/dsh-tool-bash'
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,11 @@
|
|||||||
isolate:
|
isolate:
|
||||||
bashEnv: true
|
bashEnv: true
|
||||||
config:
|
config:
|
||||||
|
# The registry and its consumer share the realm: a consumer left outside
|
||||||
|
# would resolve the host's `bashEnv`, which this plane no longer provides.
|
||||||
|
- id: bash-env
|
||||||
|
name: '@deepseek-ai/dsh-bash-env'
|
||||||
|
|
||||||
- id: tool-bash
|
- id: tool-bash
|
||||||
name: '@deepseek-ai/dsh-tool-bash'
|
name: '@deepseek-ai/dsh-tool-bash'
|
||||||
|
|
||||||
|
|||||||
@@ -154,6 +154,36 @@ describe('the shipped Web composition', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('a forked session', () => {
|
||||||
|
it('inherits the composition its seeded history was produced under', async () => {
|
||||||
|
const parent = await ctx.agents.create({
|
||||||
|
sessionId: SessionId('preset-fork-parent'),
|
||||||
|
meta: { agentPreset: 'core-web' },
|
||||||
|
setup: agentCtx => ctx.agentPresets.mount(agentCtx, 'core-web').then(() => undefined),
|
||||||
|
})
|
||||||
|
const inherited = parent.agent.session.header.agentPreset
|
||||||
|
const child = await ctx.agents.create({
|
||||||
|
sessionId: SessionId('preset-fork-child'),
|
||||||
|
meta: {
|
||||||
|
parentSession: SessionId('preset-fork-parent'),
|
||||||
|
seedLength: 0,
|
||||||
|
...inherited === undefined ? {} : { agentPreset: inherited },
|
||||||
|
},
|
||||||
|
setup: agentCtx => ctx.agentPresets.mount(agentCtx, inherited).then(() => undefined),
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
// Composing nothing would leave the child empty: this layer moved every
|
||||||
|
// model-facing row out of the host plane, so there is nothing to inherit
|
||||||
|
// for free any more.
|
||||||
|
expect(toolNames(ctx, child.agent)).toEqual(toolNames(ctx, parent.agent))
|
||||||
|
expect(toolNames(ctx, child.agent).length).toBeGreaterThan(0)
|
||||||
|
} finally {
|
||||||
|
await child.dispose()
|
||||||
|
await parent.dispose()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('a session keeps the preset it was created with', () => {
|
describe('a session keeps the preset it was created with', () => {
|
||||||
it('refuses to adopt a live session under a different preset', async () => {
|
it('refuses to adopt a live session under a different preset', async () => {
|
||||||
const handle = await ctx.agents.create({
|
const handle = await ctx.agents.create({
|
||||||
|
|||||||
@@ -201,6 +201,9 @@
|
|||||||
# absent from a surface overlay would silently reappear the day someone reorders
|
# absent from a surface overlay would silently reappear the day someone reorders
|
||||||
# the composition.
|
# the composition.
|
||||||
|
|
||||||
|
- id: bash-env
|
||||||
|
disabled: true
|
||||||
|
|
||||||
- id: tool-bash
|
- id: tool-bash
|
||||||
disabled: true
|
disabled: true
|
||||||
|
|
||||||
|
|||||||
@@ -1898,6 +1898,12 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
const childId = `session-${randomUUID()}` as SessionId
|
const childId = `session-${randomUUID()}` as SessionId
|
||||||
|
// The child inherits the parent's composition for the same reason a
|
||||||
|
// resumed session keeps its own: the seeded history was produced under
|
||||||
|
// those tools, and composing anything else would strand the tool calls
|
||||||
|
// it already carries. Now that no model-facing row sits in the host
|
||||||
|
// plane, composing nothing would leave the child with no tools at all.
|
||||||
|
const forkComposition = await composeAgent(source.header.agentPreset)
|
||||||
try {
|
try {
|
||||||
await ctx.agents.create({
|
await ctx.agents.create({
|
||||||
sessionId: childId,
|
sessionId: childId,
|
||||||
@@ -1906,9 +1912,12 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
|
|||||||
...source.header.cwd === undefined ? {} : { cwd: source.header.cwd },
|
...source.header.cwd === undefined ? {} : { cwd: source.header.cwd },
|
||||||
parentSession: source.id,
|
parentSession: source.id,
|
||||||
seedLength: cut,
|
seedLength: cut,
|
||||||
|
...forkComposition.agentPreset === undefined
|
||||||
|
? {}
|
||||||
|
: { agentPreset: forkComposition.agentPreset },
|
||||||
},
|
},
|
||||||
agentOptions,
|
agentOptions,
|
||||||
setup: installTarget,
|
setup: forkComposition.setup,
|
||||||
})
|
})
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
return err(request, {
|
return err(request, {
|
||||||
|
|||||||
Reference in New Issue
Block a user