fix(agent-presets,connection): reclaim a deleted default, unpin a fence beside an open gate

Deleting the preset a user default names left the setting pointed at an id
nothing will ever supply again, and every session created without an explicit
pick then failed to start — the delete dialog called it 'new sessions cannot
select it', which understates a hard creation error. `remove` now clears the
user layer when it named the preset just deleted, exposing the deployment's own
default underneath. Storing a default that does not exist YET stays deliberate:
the roster is a live directory, so a name absent now may exist by the time a
session asks, and `resolve` still reports that case.

`agentPreset.select` also leaves the loopback set. It was pinned as a real
escalation — one preset mounts the toolset that edits the live runtime — but
`session.create` already takes an `agentPreset`, so pinning only the switch left
the same capability one method over. The deeper reason is that the capability is
not the preset's to grant: the deployment's own default already carries `bash`
and the filesystem tools, so any caller that may start a session at all can
already run commands as this process. `read`/`write`/`remove` stay pinned on
their own footing — those touch files, not sessions.
This commit is contained in:
Yichen Jiang
2026-08-08 11:41:47 +08:00
parent 68389b2a89
commit 5cf5e0cb76
17 changed files with 102 additions and 34 deletions
+23 -2
View File
@@ -13,7 +13,7 @@
import { Context, Service } from 'cordis'
import { scopeOf } from '@deepseek-ai/dsh-scope'
import z from 'schemastery'
import { settingsNamespace, type SettingsScope } from '@deepseek-ai/dsh-settings'
import { settingsNamespace, type SettingsScope, type default as SettingsService } from '@deepseek-ai/dsh-settings'
import { discoverPresets } from './discovery.ts'
import { deleteComposition, readComposition, writeComposition } from './authoring.ts'
import type { PresetMetadata } from './metadata.ts'
@@ -83,6 +83,12 @@ export class AgentPresets extends Service {
*/
private settings: SettingsScope<AgentPresetSettings> | undefined
/**
* The settings service behind {@link settings}, held for the one write this
* service makes: clearing a user default it has just deleted.
*/
private settingsService: SettingsService | undefined
constructor(ctx: Context, public config: Config) {
super(ctx, 'agentPresets')
// Deliberately not `installSettingsSection`: that helper exists to re-judge
@@ -96,7 +102,11 @@ export class AgentPresets extends Service {
AgentPresetSettingsSchema,
{ base: { default: config.default } },
)
settingsCtx.effect(() => () => { this.settings = undefined }, 'agentPresets.settings()')
this.settingsService = settingsCtx.settings
settingsCtx.effect(() => () => {
this.settings = undefined
this.settingsService = undefined
}, 'agentPresets.settings()')
})
}
@@ -196,6 +206,17 @@ export class AgentPresets extends Service {
*/
async remove(id: string): Promise<void> {
await deleteComposition(this.config.roots, await this.resolve(id))
// Storing a default that does not exist YET is deliberate — the roster is a
// live directory, so a name absent now may exist by the time a session asks
// for it, and `resolve` reports it then. A default this call just deleted is
// not that case: nothing will ever supply it again, and left in place every
// session created without an explicit pick would fail to start. Clearing it
// exposes the deployment's own default underneath, which is the layering.
if (this.settings?.get().default !== id) return
await this.settingsService?.mutate(
settingsNamespace(SETTINGS_NAMESPACE),
[{ op: 'unset', path: ['default'] }],
)
}
/**