Skip to content

feat(settings): config-file-driven settings manifest + daemon RPC#186

Merged
saucam merged 2 commits into
mainfrom
feat/settings-manifest-daemon
Jul 16, 2026
Merged

feat(settings): config-file-driven settings manifest + daemon RPC#186
saucam merged 2 commits into
mainfrom
feat/settings-manifest-daemon

Conversation

@saucam

@saucam saucam commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

What

The daemon foundation for a comprehensive, config-file-driven settings surface. This is PR 1 of 3 — the single source of truth that the web (Solid) and codeoid-ui (Rust) clients will render generically (mobile later). No UI in this PR.

The design goal (from the ask): a SOTA, config-file-driven settings screen with tabs for every supported backend and each backend's knobs, where power users can also edit the underlying files directly. This PR makes the daemon expose exactly that as a declarative manifest + read/write RPC, so a new knob is one manifest entry picked up by every client — no per-client edits, no drift.

How

  • Manifest (src/daemon/settings/manifest.ts) — every user-tunable knob organized tabs → groups → fields, including a dedicated per-backend tab for each of the six backends (Claude, Codex, Gemini CLI, pi, OpenAI, Gemini), plus General, Memory & Context (incl. the VWS context strategy), Fleet & Dispatch, Identity & Auth, Frontends, and Hooks. Each field declares its kind, backing store, config path / env var, default, bounds, help, and when a change takes effect (applies).
  • Store (src/daemon/settings/store.ts):
    • Read: effective values with the daemon's own precedence (env > config.json > default) and provenance; secret presence + source only, never the value.
    • Write: config patches are merged onto the raw config.json and the whole object is validated against the authoritative zod schema — one bad patch rejects the batch and nothing is written. Env/secret patches update ~/.codeoid/.env (0600), preserving comments and unrelated keys. process.env is updated live so a subsequent get reflects reality and per-session env knobs apply to new sessions without a restart.
  • Protocol (@codeoid/protocol) — manifest + snapshot types, the settings.schema / settings.get / settings.set messages with zod validation, and new SETTINGS_READ / SETTINGS_WRITE scopes. OPERATOR gets read but not write (config can rewrite secrets + provider wiring — owner-only).
  • Handlers — three scope-gated RPC handlers wired into SessionManager.handle.

Design notes

  • Two backing files, both hand-editable: ~/.codeoid/config.json (validated) and ~/.codeoid/.env (secrets + env-only knobs). The settings page is just a typed front-end over them — editing the files directly stays fully supported.
  • All secrets are env-backed so a secret never lands in config.json via the UI, and their values are never sent to a client.
  • Honest applies signal: config-backed fields are read once at boot, so they're marked restart; per-session env knobs (context strategy, seed budget) are next-session. The UI can show an accurate "restart to apply" banner.
  • No new config schema fields yet — env-only knobs (context strategy, codex approval/sandbox, API keys, telegram, fs-browse-root, …) are surfaced as env-backed settings written to .env. Promoting the hot ones to first-class config.json fields is a clean follow-up that won't change the manifest-driven UI.

Tests

src/tests/settings.test.ts — manifest integrity + a config-path drift guard (every config-backed path is a real, correctly-typed schema location), store read/write/validate + secret handling (env-file vs external), and handler scope gating through SessionManager.handle. scopes.test.ts / protocol.test.ts / schemas.test.ts updated for the two new scopes and three new messages.

  • bun run typecheck ✅ · bun run lint ✅ · bun run build ✅ · bun run test ✅ (1473 pass / 0 fail)

Follow-ups

  • PR 2 — web (Solid) tabbed settings page rendering this manifest.
  • PR 3 — codeoid-ui (Rust) settings screen.

🤖 Generated with Claude Code

Add the daemon foundation for a comprehensive, config-file-driven settings
surface that every frontend (web, TUI, later mobile) renders generically.

- Declarative settings manifest (src/daemon/settings/manifest.ts): every
  user-tunable knob across tabs -> groups -> fields, including a per-backend
  tab for each of the six backends. Serves as the single source of truth so
  adding a knob is one entry, picked up by all clients.
- Settings store (src/daemon/settings/store.ts): reads effective values with
  daemon precedence (env > config.json > default) and provenance; writes back
  atomically. Config patches are validated as a whole against the authoritative
  zod schema (one bad patch rejects the batch -> nothing written). Env/secret
  patches update ~/.codeoid/.env (0600), preserving comments. Secret VALUES are
  never returned to a client (presence + source only); process.env is updated
  live.
- Protocol (@codeoid/protocol): settings manifest + snapshot types and the
  settings.schema / settings.get / settings.set messages, zod validation, and
  SETTINGS_READ / SETTINGS_WRITE scopes (operator gets read, not write).
- Wire the three scope-gated RPC handlers into SessionManager.

Both files (config.json + .env) stay directly hand-editable — the page is just
a typed front-end over them.

Tests: manifest integrity + a config-path drift guard, store read/write/
validate/secret handling, and handler scope gating. Full suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread src/daemon/settings/store.ts
Comment thread src/daemon/settings/store.ts
@github-actions

Copy link
Copy Markdown

🤖 Gemini code review

This PR implements a declarative settings manifest and store in the daemon, exposing get/set/schema endpoints to the RPC layer with scope-gated security. While the architecture is sound and well-tested, there are a couple of edge cases in the naive .env file parser regarding multiline variables and double-quote backslash escaping.

Findings: 🔴 0 · 🟠 0 · 🟡 1 · 🟢 1


Tokens spent · ⬆️ Input: 23,079 · ⬇️ Output: 591 · Σ Total: 36,631
Total may be higher due to thinking token counts.

…d-trip)

Address the Gemini review on #186:
- Reject env values containing a newline with a per-key error instead of
  splitting them across lines and corrupting .env (the format is single-line,
  matching loadDotEnv).
- quoteEnv no longer escapes quotes. loadDotEnv strips one surrounding quote
  pair and never unescapes, so escaping broke the round-trip; wrapping-only
  round-trips even for embedded quotes (only the outer pair is stripped).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@saucam
saucam merged commit fc149bd into main Jul 16, 2026
2 checks passed
saucam added a commit that referenced this pull request Jul 21, 2026
0.3.1 (#202) was cut as a version-bump-only release and shipped without a
CHANGELOG entry. Backfill what it carried (placed between 0.3.2 and 0.3.0): the
cross-backend MCP mounter (#197#201), VWS memory across all backends
(#179#185), the config-driven settings surface (#186#188), per-sandbox ZeroID
registrar-key auth (#200), embed SSO via URL-hash handoff (#195), and the codex
approval / fork-mode fixes (#181, #194, #196).

Docs-only; no version change (0.3.1 is already tagged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
saucam added a commit that referenced this pull request Jul 21, 2026
0.3.1 (#202) was cut as a version-bump-only release and shipped without a
CHANGELOG entry. Backfill what it carried (placed between 0.3.2 and 0.3.0): the
cross-backend MCP mounter (#197#201), VWS memory across all backends
(#179#185), the config-driven settings surface (#186#188), per-sandbox ZeroID
registrar-key auth (#200), embed SSO via URL-hash handoff (#195), and the codex
approval / fork-mode fixes (#181, #194, #196).

Docs-only; no version change (0.3.1 is already tagged).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant