- Home
- Plugin
- Strumenti e capacità
- dsh-profile-settings
dsh-profile-settings
xmoon/dsh-profile-settings
Per-profile settings overlay for DeepSeek Harness: global settings.yaml stays the baseline while each profile overrides any namespace through its own profiles/<name>/settings.patch.yml — object sections merge recursively, scalars and arrays replace wholesale, and !unset masks an inherited value. The overlay is transparent to existing plugins (they keep reading ctx.settings unchanged), writes land in the profile overlay only, and official schema validation, revision semantics, expectedRevision conflicts, watchers and events are untouched. Ships a settings command family (get/set/unset/mask/unmask/promote/demote/migrate/diff/layers) plus a Profile Settings section in the web Settings panel over a loopback RPC channel.
Installazione
dsh plugin --profile web add github:xmoon/dsh-profile-settingsREADME
@xmoon76/dsh-profile-settings
English | 中文
A DSH profile bundle that adds a per-profile settings overlay on top of the
official user-settings seam. $DSH_HOME/settings.yaml stays the global
baseline; every profile can override any settings namespace through its own
$DSH_HOME/profiles/<name>/settings.patch.yml — object sections merge
recursively, arrays and scalars replace wholesale, and !unset explicitly
deletes an inherited value.
Schema Defaults
↓
Composition Base
↓
~/.dsh/settings.yaml (global user layer)
↓
~/.dsh/profiles/<name>/settings.patch.yml (profile user layer)
↓
Effective Settings
Existing plugins keep using ctx.settings exactly as before — the overlay is
transparent. Writes (update / replace / mutate) land in the profile
overlay only; replace({}) re-inherits the global layer, never the
composition default. The official schema validation, revision semantics,
expectedRevision conflict detection, watchers, and events are untouched.
Installation
Requires a profile whose bundles include @deepseek-ai/dsh-base (every
shipped web / headless template does). Install into each profile that
should get its own overlay:
dsh plugin --profile web add @xmoon76/dsh-profile-settings
dsh plugin --profile pi-tui add @xmoon76/dsh-profile-settings
dsh plugin --profile <name> add installs the package and reconciles it into
the profile's dsh.profile.bundles automatically (the manifest declares
dsh.bundle). On the next boot the bundle patch (cordis.patch.yml)
- disables the base
settingsrow (@deepseek-ai/dsh-settings-file), and - inserts the
profile-settingsrow owningctx.settings.
One composition may only hold one ctx.settings owner: if the base row is
still active beside this one, boot fails loud (Cordis duplicate-service
error) — never a silent override.
Configuration
The inserted row needs no config (defaults below); a profile's own
cordis.patch.yml may override the row's config:
| key | default | meaning |
|---|---|---|
profile | auto-detected | explicit active profile name |
globalPath | $DSH_HOME/settings.yaml | global settings document |
profileFile | settings.patch.yml | overlay filename inside the profile dir |
dshHome | $DSH_HOME or ~/.dsh | harness home |
watch | true | hot-reload both documents |
debounceMs | 100 | watcher write-settle window |
writable | true | allow in-process writes into the overlay |
The active profile is resolved in this order (never from cwd): explicit
profile config → install location
(profiles/<name>/node_modules/…, the dsh plugin layout) → launcher
--profile <name> / --profile=<name> → DSH_PROFILE environment variable.
If none can be determined, boot fails loud.
The overlay file
# $DSH_HOME/profiles/web/settings.patch.yml
agent-default-model:
provider: pi-ai
model: gpt-5.6
permission:
mode: danger-full-access
some-plugin:
endpoint: !unset
Plain YAML values are overrides; !unset masks the lower layers (the
inherited value is deleted and the schema default applies unless the overlay
supplies its own value). Masks never enter the resolved JSON document — the
UI never sees them as values. The file is edited by hand or through the
settings command; in-process writes land here, never in settings.yaml.
The settings command
When a command runtime is mounted, ctx.profileSettings and the settings
command family are available:
settings layers [ns [path]] per-leaf source provenance
settings get <ns.path> effective value
settings set <ns.path> <value> write the profile overlay
settings unset <ns.path> delete an overlay value (re-inherit)
settings mask <ns.path> write !unset
settings unmask <ns.path> remove the mask
settings reset <ns> replace({}) on the overlay
settings promote <ns.path> move a value up to the global document
settings demote <ns.path> move a value down to the profile overlay
settings migrate <ns.path> [--copy] move a global value into the overlay with
.bak.<timestamp> backups (--copy keeps it)
settings diff [ns] leaf-level global vs overlay diff
settings ui [ns] machine-readable layer snapshot (Web UI)
settings profile active profile and document paths
Web UI (Profile Settings page)
The bundle ships a browser half (dsh.client): the web Settings panel gains
a Profile Settings section with per-field source badges (Default /
Composition / Global / Profile / Masked), the effective value, and
set / unset / mask / unmask / promote / reset actions. All host interaction
flows through the /profile-settings loopback RPC channel registered by the
host half — no session context, no command logs.
Configuration chain and design notes
The provider keeps the official base-class document as the profile raw
section, so update/replace/mutate/revision/expectedRevision/
describe keep their exact semantics; the global layer is folded into each
registration's composed base (applyMasks(merge(composition, global), masks)).
Only a narrow typed facade over the base class's TS-private members is
touched — no Settings machinery is forked. See docs/research.md for the
full M0 findings.
Fail-loud (boot): unresolvable profile, overlay root not a map, non-object
namespace section, !unset inside an array, unsupported YAML tags
(!!js/*, !!python/*, custom tags), overlay path escaping the profile
directory, global and overlay pointing at the same file, duplicate
ctx.settings owner.
Warn + keep last good (hot reload): temporarily invalid YAML in either document, unregistered namespaces (they are preserved for later-loading plugins).
Concurrency model and limitations
| Scope | Guarantee |
|---|---|
In-process ordinary update/replace/mutate | official Settings semantics (per-namespace serialized write queue, expectedRevision conflict detection) |
| Cross-process file integrity (read-modify-write) | writer locks on both documents + atomic rename |
Cross-process custom layer operations (promote/demote/migrate/mask/unmask) | true transactions: both locks in a fixed order, fences checked inside the locks, layer revisions advance on every text change |
Cross-process ordinary update/mutate on the SAME profile + namespace | inherits the official seam limitation: the official revision is process-local to its write queue, so cross-process CAS on one namespace is best-effort (the last writer wins under the file lock) |
Development
npm run typecheck # tsc over src + tests
npm test # vitest
npm run build # tsc build + copy-lib (lib/)
npm pack # prepack build + postpack tarball smoke (leak checks)
Requires Node ≥ 22.6 (type stripping) and a DSH harness (0.1.1-rc.2 family) for the peer dependencies, which resolve from the running installation — the package never bundles its own harness copy, so no module twin.
License
MIT
Plugin correlati
WeKnora (dsh-weknora)
tencent/weknora
weknora
tencent/weknora
archify (deepseek-harness)
tt-a1i/archify
BrowserSkill (dsh-plugin-browserskill)
tencent/browserskill