Commit Graph
10 Commits
Author SHA1 Message Date
NotExpectedYet 8ab3299694 fix: a delegated session inherits the facet it was spawned under
Release / release (push) Successful in 6s
The user's declaration is keyed by session id, and a session spawned or forked
from a scoped one has none of its own — so its facet was its own working
directory, one level above its parent's. Delegation was therefore a way around
the scope rather than work inside it: the parent could not read the file itself
and could have a subagent read it.

A session now inherits the root resolved for the session named by its
`parentSession` header, which the harness already records durably for both a
spawned child and a fork. The inherited root is the parent's *effective* root, so
it already includes any narrowing the parent made for itself: delegating is not
a way back out of a self-imposed focus either. Because each session records its
own root as it resolves a scope, and a scope is resolved for every model request
and every tool call, inheritance holds at any depth of delegation rather than for
one hop.

Two boundaries are deliberate. A declaration the user made *for the child* is a
more specific statement about that session and stands — inheritance only answers
the case where they made none. And a parent's recorded root outlives its session
while any child that inherited it is still running, because disposing a parent
must not quietly widen a subagent that is still working.
2026-09-27 07:43:03 +01:00
NotExpectedYet a9a25490db fix: judge a search that names no path against the directory it would use
The guard's rule was that a call naming no path is not judged, because a tool
that names no path cannot leave the facet. That is true of a tool with no path
argument and false of three that have one with a default: `grep` and `glob`
document theirs as "Defaults to the session workspace", and `recall` falls back
to a directory its own marker search picks. On any session whose facet is
narrower than its directory — every session whose focus was set from the panel
rather than by starting the conversation in the folder — a bare `grep` searches
outside the facet and returns what it finds.

An absent argument is not an absent path; it is the default one. The three tools
are now judged against the directory they would have searched, through the same
containment test that decides every other call, and the refusal is its own
sentence rather than the "that path is outside" one: the path at issue is one the
model never wrote, so the remedy is a second argument, not a grant.

A tool with no path argument at all is still left alone. `bash` is still the
documented way out, and the first deployment running this plugin confirmed it is
a real one: an agent scoped to one module used a shell command to parse a config
file outside its facet, having been told in the same prompt not to. That is what
this plugin is for, and what it cannot do is stated in the README rather than
implied.
2026-09-27 07:42:54 +01:00
NotExpectedYet b0902017fd fix: name the allowed-paths action for what it adds, a path or a file
"Add folder or path" promised one thing and offered another: the listing is
folders only, because the Host's browse primitive has no file rows, while the
extras legitimately name files — `../../CONTROLLER.md` is the example the panel
itself gives.

The action is "Add path or file", its field is "Path or file", and the hint says
which of the two the list can show and how the other one is added. Nothing about
the interaction moved; the copy was the part that was wrong.
2026-09-27 07:31:50 +01:00
NotExpectedYet 5d06829118 feat: pick the focus and the allowed paths from a directory listing
Release / release (push) Successful in 6s
Both scope controls were free-text boxes, and the vocabulary they asked the user
for is paths relative to the session folder. "Which folder is `../shared`" is the
one question a text box cannot answer, so the panel now shows the Host's own
directory: the `uiWorkspace.listDirectory` primitive the workspace picker's
browser drives, one level of child folders, a breadcrumb trail, and a
hidden-entries toggle. The path field stays editable inside the picker, because
the Host lists folders and an extra may legitimately name a file.

Picks are stored in the form that keeps their meaning. A folder at
`/work/root/src/shared` is declared `src/shared` when the session started in
`/work/root`, and `../shared` going up; only a path with nothing shared — another
drive, another tree — stays absolute. The Focus row shows that declared form,
with the resolved path in its title, which is what the agent will see.

`Set` is gone with the box it committed: the panel writes on pick, as every other
control here already did. `Clear` stays, because empty means the session folder —
the schema's own default rather than an override naming the same place.

The card was 380px wide and grew without bound, a single column of prose taller
than the viewport. It is 760px now, with a two-column grid that `auto-fit`
collapses to one on a narrow card, the reference prose folded into `<details>`,
and a scroll-bounded body. `Modal` sizes its own card from a hashed class in the
primitives' stylesheet, so this bundle installs one small stylesheet of its own
and outranks that rule with a doubled class selector — no `!important`, and
nothing reaching into a class name it does not own.

Two fixes came with it. The footer's "Deployment mode:" line read a slot prop
that never carried the mode, so it said "ask" in every deployment; it reads the
settings document now. And `uiWorkspace` is resolved with `ctx.get` at the call
rather than injected, because the panel is still the only way to declare scope in
a deployment with no directory browser: it keeps the panel and says what it
cannot do, instead of losing the button.
2026-09-27 07:05:47 +01:00
NotExpectedYet 266e90aa2d feat: set a conversation's scope from the composer, where the agent cannot reach
Release / release (push) Successful in 6s
The per-session scope the host half now reads had no writer. This is it: a
crosshair on the composer tool row, present only in sessions running the facet
preset, opening a panel that sets the focus and the paths allowed outside it.

The gate is the session's own `agentPreset` projection rather than anything
inferred from the URL or from the plugin being installed, so a control for a
plugin that is not running never appears.

It writes through the settings document — the same one the plugin card edits —
because `settings.mutate` accepts a path under a volatile node and refuses
anything else. That is what makes the split real rather than aspirational: the
agent has no write path to `scopes`, and the panel's write is the only one that
can widen a session.

The panel also carries the mode details and the usage notes, which the preset
picker's guide dialog cannot show for a preset no core package ships — its
guide table holds four shipped ids and is looked up only for built-ins.

Also adds `@deepseek-ai/dsh-client-ui-conversation` to the client half's inject
list: registering into `conversation.input.left` is a dependency on the package
that declares it, and `slots.inject` waits for a slot rather than creating one,
so naming another package's slot without loading it first registers nothing —
silently, because nothing errors.
2026-09-27 06:15:53 +01:00
NotExpectedYet 1be3a67b97 fix: put the settings card on the Plugins page with a browser half
Release / release (push) Successful in 6s
The `volatile()` markers made the Host serve the namespace, and that was as
far as it went: the Plugins page does not build a card from a `Config` schema.
It draws only what browser halves register into the slots it declares, which is
why dsh-slopo, dsh-recall and dsh-context all ship a `client/` — and why this
plugin, with two served fields, still showed nothing.

The card registers into `plugins.bundle.config` while
`configForms.whileServed` reports the namespace served. The two keys are
deliberately different strings and that is the trap in the file: the settings
document is filed under the profile entry id (`facet-policy`) while the card is
keyed by the package name (`dsh-facet`). Every sibling plugin happens to name
them identically. Binding the form to the wrong one yields a card that renders,
saves, and writes nowhere, so `test/client.test.js` pins both, holds the card's
field list to the schema's volatile fields, and refuses to register at all when
the namespace is not served.
2026-09-26 07:39:57 +01:00
NotExpectedYet 28f20a8aac fix: show the settings card, and make an edit actually take effect
`mode` and `extraRoots` carried no `volatile()` marker, so the framework's
form projection dropped the whole entry from `settings.describe` and the plugin
had no card under Plugins at all. The marker is what creates the card.

It is also what turns each field into a reference rather than a value: the
resolved config hands `apply` a `{get(), [write]}` per volatile field, which is
how a settings write lands in a running plugin without re-applying it. So the
policy and the guard now read both fields at the point of use instead of
snapshotting them at mount, and `sectionOrder`/`contextOrder` stay unmarked
because a prompt section cannot be re-ordered without a remount.

A section is also registered once as a plain string, so the rule could not
follow a live `mode` without freezing at load. The mode-specific sentence moved
into the binding, which is a callback.
2026-09-26 07:13:33 +01:00
NotExpectedYet 651093a00c docs: document installing the bundle from the repository
The Install section only showed a local path, which is the one form that
cannot be updated: pnpm records a commit for a bare git URL, so
`dsh plugin update` has no version to move between. Show the
`#semver:^0.1.0` form, and say why the fragment is the difference.
2026-09-26 06:53:13 +01:00
NotExpectedYet cd43e87684 feat: tag and publish a Gitea release on every push to master
Release / release (push) Successful in 6s
A plugin installed as a bare git+https URL is pinned to a commit, so with no
tags there is nothing for `dsh plugin update` to move to. The workflow reads
the commit subjects since the last tag, bumps package.json, prepends
CHANGELOG.md, tags vX.Y.Z, and publishes the Gitea release with those subjects
as the body. The decision logic lives in .gitea/scripts/release.sh so it runs
and is tested without a runner (`npm run test:release`).
2026-09-26 06:45:52 +01:00
NotExpectedYet 0858862cda init 2026-09-26 06:45:50 +01:00