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:
@@ -53,7 +53,7 @@ Which preset an unnamed session gets is a user setting (`agent-presets.default`)
|
||||
|
||||
**Switching is allowed only while a session is blank.** Once a turn has run, that history was produced under the preset's tools and swapping them would strand logged tool calls, so `agentPreset.select` answers `agent-preset-locked`. A blank switch keeps the agent and the session and replaces only the subtree, because the host discards the `AgentHandle` it creates and there is no delete RPC — and keeping them is the better outcome anyway, since the session id, its workspace attachment, and its projections all stay put. The swap is unmount-then-mount (two compositions would register the same tool names into one layer), so it resolves the new preset before tearing anything down and restores the previous one when the new mount fails.
|
||||
|
||||
**Authoring a preset is an RPC, and a privileged one.** A composition is a file, but "edit it on the filesystem" is not a browser affordance, so the roster gained `read`/`write`/`remove` beside `select`. All four are loopback-pinned: a composition names the plugins a session runs, so reading one is reconnaissance, writing one is arbitrary capability, and selecting one can move a session onto a preset that edits the live runtime. `list` deliberately stays ordinary — ids and trust only, and a LAN client's picker needs it. Containment is a property of the id (`[a-z0-9][a-z0-9-]*`), checked before it becomes a directory name rather than by inspecting the joined path afterwards; the text is parsed with the loader's own schema and dialect, so a save cannot leave a file no session could load. Shipped presets are refused for writes and deletes, because the deployment's copy is what a broken local preset is compared against — which also makes "duplicate, then edit" the authoring path rather than an afterthought.
|
||||
**Authoring a preset is an RPC, and a privileged one.** A composition is a file, but "edit it on the filesystem" is not a browser affordance, so the roster gained `read`/`write`/`remove` beside `select`. Those three are loopback-pinned: a composition names the plugins a session runs, so reading one is reconnaissance and writing one is arbitrary capability. `list` and `select` deliberately stay ordinary. The roster carries ids and trust only, and a LAN client's picker needs it; and choosing a preset looked like escalation — one of them mounts the toolset that edits the live runtime — but `session.create` already takes an `agentPreset`, so pinning only the switch would have left the same capability one method over. The capability is not the preset's to grant either: 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. Containment is a property of the id (`[a-z0-9][a-z0-9-]*`), checked before it becomes a directory name rather than by inspecting the joined path afterwards; the text is parsed with the loader's own schema and dialect, so a save cannot leave a file no session could load. Shipped presets are refused for writes and deletes, because the deployment's copy is what a broken local preset is compared against — which also makes "duplicate, then edit" the authoring path rather than an afterthought.
|
||||
|
||||
**A service with a consumer outside the agent plane cannot move into a preset.** The aggressive split moved the `subagents` registry and its spawn/fork backends into the delegation group's entry-local realm, and `dsh web` then failed to boot: `dsh-host-apiproxy` is a HOST row that injects `subagents` to answer the browser's cross-session queries (`listChildren`, `followup`), so it waited forever for a service only sessions now provided. A per-session copy is wrong twice over — a provider name registers once, so the second session would have collided anyway. The registry and its backends are host-plane; the preset contributes the delegation TOOLS, which resolve the host registry. `workflows` stays entry-local because nothing outside an agent reads it. Grepping injectors is what should have caught this and did not: the search has to include the host packages, not just the agent-plane ones.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user