Files
deepseek-harness/packages/settings/settings-local
Yichen Jiang 85a3a158dd fix(settings-local): one operation chain, read-modify-write under a writer lock, and diff-shaped YAML edits
Review round three found the provider's write path could destroy state it
never observed:

- Watcher reloads and document writes ran on two independent promise
  chains, and a write rendered the whole next document from the cached
  text. An external edit still inside the debounce window (or missed
  outright) was overwritten, and the follow-up reload no-oped because the
  post-rename content matched the cache — the edit vanished without a
  trace. Reloads and writes now share one operation chain, and every write
  starts by reconciling the on-disk text into the seam before rendering,
  so unobserved sibling sections survive and publish first. An unparsable
  on-disk document fails the write loud instead of being overwritten.
- The initial load raced the watcher's own setup: a change written between
  that read and the watcher becoming active never fired an event. The
  watcher's ready signal now queues one reconcile, closing the gap.
- Two processes sharing a harness home rendered from independent caches,
  last writer winning. Writes now hold a wx-created <file>.lock sibling
  around the read-render-rename cycle with bounded backoff, a crashed-
  holder stale takeover, and a deadline failure; readers stay lock-free
  because the rename commit is atomic.
- renderYaml replaced the whole namespace node, dropping every comment
  inside the section. The next section now lands as a leaf-level diff
  (set changed values, delete removed keys), so comments, anchors, and
  formatting survive on every untouched node and on the key of every
  changed pair; arrays still replace wholesale when unequal.
2026-07-30 13:39:22 +08:00
..

@deepseek-ai/dsh-settings-local

English | 中文

File-backed settings provider. One YAML or JSON document carries every namespace section; external edits hot-publish through ctx.settings, and update() writes back atomically while preserving the user's YAML comments and any section owned by a plugin that is not currently loaded.

Config

Field Meaning Default
path Settings document path; extension picks the format (.yaml/.yml/.json) settings.yaml under the harness home
dshHome Harness home used when path is omitted $DSH_HOME or ~/.dsh
watch Watch the document and hot-publish external edits true
debounceMs Watcher write-settle window in milliseconds 100

Defaulting is one explicit resolveSpec(config) step; an unsupported extension fails at load.

Behavior

  • Boot fails loud, reload keeps last-good. An existing-but-invalid document fails plugin load; once live, an unreadable or unparsable edit warns and keeps the last good sections. A missing document resolves every namespace from defaults and base; deleting it publishes the same empty state.
  • Write-back is atomic, owner-only, and symlink-proof. persist exclusive-creates a random-suffix temp sibling with mode 0600 (wx refuses to follow a planted symlink) and renames over the target, cleaning the temp up on failure. YAML writes patch one namespace in the comment-preserving document; JSON re-serializes.
  • Cross-namespace writes serialize on one document. Every namespace shares the file, so persists from different namespace queues chain internally; each render sees the text the previous write committed.
  • Dispose quiesces. Teardown stops accepting watcher events, closes the watcher, then waits out any queued or in-flight reload, so nothing publishes after disposal.
  • Self-write suppression by content. The provider caches the last good text; a watcher event whose content equals the cache (its own write included) is a no-op.

Model Experience

Indirectly, through consumers of ctx.settings: this provider only stores and publishes namespace sections, and each consumer's own surface documents any model effect.

KV Cache effect

No direct invalidation; the consuming plugin owns any request-prefix changes.

Known Limitations and Deferred Work

  • No cross-process write lock — concurrent writers (for example TUI and web on one home) converge by atomic replace plus watcher reload, last write wins; a lockfile is deferred until real contention shows up.
  • Comment preservation is YAML-only — JSON documents re-serialize without comments (JSON has none) and lose hand formatting.
  • No value indirection — sections hold literal values; ${env:VAR}-style references for secrets are a deferred seam-level feature.