Commit Graph

10 Commits

Author SHA1 Message Date
Yichen Jiang
4bb9836abe fix(web): drop the Close button from the read-only preset view
A shipped preset has nothing to commit or abandon, and the back link above
already leaves the screen; a lone Close button below the composition was a
second way out of the same place. The action row now renders only when the
draft is writable, where Cancel and Save are both real choices.

The composition label named `cordis.yml`; the file a preset actually holds
is `agent.cordis.yml`.
2026-08-07 00:41:50 +08:00
Yichen Jiang
64fc536982 feat(web): order the shipped presets, and edit one on its own screen
The picker listed presets alphabetically by id, so the shipped set read
cordis, minimal, standard — reverse order of capability. A preset may now
declare `order` in its metadata; the shipped three declare 1/2/3 and read
standard, minimal, cordis. A preset that declares none sorts behind those
that do, then by id, so authored presets stay stable.

Editing had nowhere good to live. Inside a card it was squeezed into a
~268px column; hanging off the end of the grid it was orphaned from the card
it edits. It now replaces the list: a back link, what is being edited, and
the form at full width. One thing on screen at a time, which is what the
form's height wanted all along.

Cards in different grid rows sized independently, so a short description made
a short card. `grid-auto-rows: 1fr` makes every row the same height.

The trust badge lost its pill when the card CSS was rewritten, and `In use`
never had one; both are tags now. Icon labels moved from `title` to a drawn
tooltip — the native one waits about a second, which reads as nothing
happening.
2026-08-07 00:41:50 +08:00
Yichen Jiang
4f41608cbc feat(web): make the preset card the control that picks it
Four changes to the Agent 预设 section, all from the same reading: choosing
a preset is the common act and everything else is occasional.

Picking is now clicking the card rather than hunting a small "Set as
default" button. The card body is the button; the action row sits outside it
because nesting buttons is invalid and those actions operate ON a card
rather than select it. The body's accessible name is set explicitly —
otherwise it is the whole card read aloud, title through id.

Actions are icon-only with their label on hover and on `aria-label`, so four
controls stop competing with the content for attention.

Built-in and custom presets are separated under headings, and the trust
badge moved up beside the title where it qualifies the name. A group with no
members renders no heading.

`Local` reads as a location; these are the user's own, so the badge and the
heading both say `Custom` / 自定义.
2026-08-07 00:41:50 +08:00
Yichen Jiang
2886c6391b feat(agent-presets): rename the two-tool preset to minimal
`core-web` said neither of the things that matter about it. The `-web`
suffix is a leftover from the whole-process `core-web.cordis.yml` overlay,
and presets are per-session and not web-specific. `core` reads as "the
foundational one" when it is in fact the one with the fewest capabilities.

`minimal` says what it is and orders the shipped set legibly by capability:
minimal, standard, cordis.

Breaking: a session created under `core-web` records that id in its header
and will fail to resolve it on resume. Nothing outside this repository has
shipped, so no migration path is offered.

The identically named `config/core-web.cordis.yml` — the legacy
whole-process overlay behind the web snapshot test — is a different thing
and keeps its name.
2026-08-07 00:41:50 +08:00
Yichen Jiang
7281615d44 feat(agent-presets): give a preset a name and a description
A picker showed directory names, so the settings page could only ever list
`standard` / `core-web` / `cordis` and hope the reader knew what they meant.
A preset may now publish display text in an optional `preset.yml` beside
its composition, and the section renders cards — name, description, and the
one in use — instead of rows.

The file carries display text ONLY. `id` is the directory name and `trust`
comes from the root a preset was discovered under, so neither is writable
there: otherwise a locally authored preset could name itself into the
shipped set. It is a separate file because a composition is a top-level list
of plugin rows — YAML cannot carry sibling keys beside it, and a fake
metadata row would hand the Loader something to load.

Every read failure degrades to no metadata; absent, malformed, wrongly
typed, and blank all mean the same thing and the picker falls back to the
id. Presentation is not capability: a preset whose name is broken still
mounts.

The editor gained name and description fields above the YAML, and clearing
both removes the file rather than storing a blank name.
2026-08-07 00:41:50 +08:00
Yichen Jiang
5b93f48f78 feat(web): name the agent-preset surface "Agent 预设" in Chinese
The Chinese copy was mixed: the nav read 智能体 while every other string kept
`preset` untranslated (本会话的 agent preset, 新建 preset, preset 名称).
It is now one register throughout — Agent stays, preset becomes 预设, the
standard Chinese UI word for a named, switchable configuration bundle, which
is what a preset is when a session picks one.

UI copy only. The docs keep `agent preset` as the technical term for the
composition and the `agent-presets` plugin.
2026-08-07 00:41:50 +08:00
Yichen Jiang
9edfdb589d fix(web): persist the default agent preset, and give its page an identity
Three fixes to the settings surface.

The default never reached settings.yaml. The api-proxy keeps an explicit
allowlist of settings namespaces it exposes to configuration clients, and
`agent-presets` was never added — so both pickers moved and then silently
forgot, which is worse than refusing the control. The host seam was fine all
along; only the wire boundary refused. The regression test fails with the
namespace removed.

The nav row showed the fallback gear and read `Agent preset` in Chinese,
matching neither sibling (通用设置 / 模型). It is now 智能体 with the think
glyph, the only unused icon in the set whose semantics point at the agent
rather than at tuning sliders (Personalization is already the workspace
browser's filter control).
2026-08-07 00:41:50 +08:00
Yichen Jiang
6dfc568ec2 feat(web): author agent presets from a settings page
A composition is a file, but "edit it on the filesystem" is not a browser
affordance. The roster gains `read`/`write`/`remove` beside `select`, and
the browser gains a settings section over them: the presets as rows, one
composition open in a YAML editor at a time, and per-row default, duplicate,
and delete.

All four authoring methods 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. `agentPreset.list` deliberately stays ordinary
and now reports `authorable`, so a surface knows whether creating is
possible at all rather than offering a button whose save always fails.

Authoring starts by duplicating: a shipped preset opens read-only because
the deployment's copy is what a broken local one is compared against. Ids
are contained before they become directory names, and the text is parsed
with the loader's own schema, so a save cannot leave a file no session
could load.

Fixes a defect the real-composition test found: a preset written under the
user's home could never mount, because the loader resolves a row against the
composition's own directory and Node's `node_modules` walk from there never
reaches the installed harness. The mount now records the host base and sends
bare specifiers there, leaving relative paths resolving from the preset.

Also closes the coverage the earlier surfaces in this stack shipped without —
the General row, the composer seat, and the plugin halves now have tests.
2026-08-07 00:41:50 +08:00
Yichen Jiang
a2ab09003f feat(web): pick a session's agent preset from the composer
A seat in the composer tool row, left of the model select, showing the preset
THIS session runs — its own recorded one, not the deployment default, because a
resumed session runs what it was created with. `SessionSummary` carries
`agentPreset` for that, alongside `cwd` and `origin`.

The switch exists only while the conversation has not started. After the first
turn the seat becomes a plain label rather than a disabled menu: a greyed
control reads as "temporarily unavailable", when in fact the choice is gone for
good. The seat never asks in that state, and the host refuses independently
with `agent-preset-locked`, so a stale client cannot slip one past it.

A rejected switch restores the previous value and surfaces the host's message
rather than leaving the seat showing something the session is not running.
2026-08-07 00:38:11 +08:00
Yichen Jiang
6758da87ae feat(web): choose the default agent preset from General settings
One settings row picking which preset new sessions are composed from.

It is deliberately a new-session preference, not a live switch: a session's
preset is fixed at creation and the host refuses to adopt an existing session
under a different one, so the row says "applies to sessions you start from now
on" rather than implying it can retune a running agent.

Options and the current value come from one `agentPreset.list` call — the
roster already reports which id an unspecified session gets, so the row needs
no settings-schema introspection, unlike the permission row it is modelled on.
The write targets only the namespace's `default` field.

The menu marks `user` rows: a locally authored preset is exactly as privileged
as the plugins it names, and presenting it identically to a shipped one would
hide that.

An empty roster reads as `unavailable` and renders nothing, because composing
no presets is a valid deployment rather than a failure — distinct from a
roster call that failed, which surfaces its message.
2026-08-07 00:38:11 +08:00