Conversation
Adds PermissionsConfigSchema and EvolutionUiConfigSchema and wires both into PhantomConfigSchema so the operator-tunable blocks live in phantom.yaml. Both default to safe values, so existing phantom.yaml files keep loading unchanged. Inline PhantomConfig test fixtures updated to match the inferred type.
Adds a section TEXT column to settings_audit_log so the PR6 phantom-config endpoint can tag rows with the six new sections (identity, model_cost, evolution, channels, memory, permissions). Existing rows keep NULL and render as "legacy" in the audit drawer. Migration indices assertion bumped to include the new entry.
Replaces the UI-facing settings surface. Reads and writes config/phantom.yaml plus the cadence overlay at phantom-config/meta/evolution.json, the channels enable flags at config/channels.yaml, and memory context limits at config/memory.yaml. Every write is tmp-file-plus-rename so a crash cannot leave a torn file. - Derives PhantomConfigForUiSchema via z.pick() off PhantomConfigSchema so the UI surface cannot drift from the loader. - Uses .strict() at every level so unknown keys reject at parse; Anthropic keys, Slack tokens, webhook secrets, and email passwords are not part of the shape and cannot be written through PUT. - Writes one settings_audit_log row per dirty field, tagged with its section (identity, model_cost, evolution, channels, memory, permissions). - Test case for the mid-write crash path: a throwing rename stub leaves phantom.yaml byte-identical and the audit table empty. 23 new tests covering 401, GET shape, secret omission, single-field PUT, nested permissions partial update, out-of-range validation, unknown-key rejection, secret injection rejection, mid-write failure, no-op PUT, evolution cadence overlay write, channel toggle with secret preservation, memory limit write preserving the collections block, audit pagination and ceiling, malformed yaml, and invalid JSON body.
…hemas + storage The old /ui/api/settings endpoint and the src/settings-editor module wrote to ~/.claude/settings.json (Agent SDK internals). Operators who need those knobs edit the file via SSH. The new /ui/api/phantom-config endpoint replaces it end to end with zero overlap. Also splits phantom-config.ts so each file stays under ~250 LOC: - phantom-config-schemas.ts owns the Zod shapes and the derived types. - phantom-config-storage.ts owns file IO, projection to the UI shape, the deep-merge patcher, and the write-plan builder. - phantom-config.ts stays as the thin handler that orchestrates them. No production consumer referenced the deleted surface.
… fixes) Rewrites public/dashboard/settings.js against /ui/api/phantom-config. Six sections: Identity, Model and cost, Evolution, Channels, Memory, Permissions. Each section has its own form state, dirty indicator, Save button, and Discard button that routes through the shared unsaved-changes modal. Bugs fixed (matching the audit doc): - Permission default mode is now fully settable with a real "use Phantom default" option and drops the SDK-only plan / dontAsk enums. - Text inputs and selects now submit null (not "" / undefined) for unset, so the audit log captures a consistent shape per field. - Discard wraps in a confirmation modal, matching the Memory tab pattern. - Audit log UI is live: the History drawer lazy-loads 20 newest rows on first expand. - Every dirty field is tracked individually; the "N dirty" count reflects touched fields rather than top-level slices. - Inline error slots under every field surface Zod validation errors in place, with role="alert" and auto-scroll to the first error. - Cadence minutes get preset chips (30 / 60 / 180 / 360 / 1440) so the common values are one click away. CSS: adds .dash-settings-section-actions (per-section footer), .dash-field-error (inline error), .dash-settings-history (collapsible history drawer with caret indicator), plus a mobile stack rule that flips section actions to full-width below 720px. Also updates src/agent/prompt-blocks/dashboard-awareness.ts so the agent describes Settings as an operator-facing form over phantom.yaml with six sections, not a curated form over settings.json.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2aed8d5f28
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return json({ | ||
| config: after, | ||
| dirty_keys: changes.map((c) => c.field), | ||
| }); |
There was a problem hiding this comment.
Apply saved config to the running runtime/cadence
This handler returns success after writing files/audit, but it never notifies live runtime objects about the new values. In current wiring, AgentRuntime and EvolutionCadence are initialized once at startup from disk (src/index.ts), so edits saved here (model/cost/permissions/evolution cadence) do not take effect until restart despite UI text claiming next-message/next-tick behavior. Please add a post-save apply hook (or dependency callback) so successful PUTs update in-memory runtime state.
Useful? React with 👍 / 👎.
| const memRes = readYamlFile("config/memory.yaml"); | ||
| if (!memRes.ok) return errJson(memRes.error, 500); |
There was a problem hiding this comment.
Skip memory.yaml parse for non-memory updates
handlePut always parses config/memory.yaml before computing the write plan, even when the incoming patch does not touch memory fields. If memory.yaml is malformed, unrelated saves (e.g., identity/model/permissions) will fail with 500 and become impossible through the dashboard. This parse should be conditional on memory changes (or non-memory saves should tolerate memory parse failures).
Useful? React with 👍 / 👎.
…sible, honest labels P0 (reviewer): the Permissions section shipped with dashboard controls that silently did nothing. runtime.ts and chat-query.ts both hardcoded permissionMode='bypassPermissions' and ignored config.permissions. Introduced src/agent/permission-options.ts to project the PhantomConfig permissions block onto the SDK query() options (permissionMode, allow tools, deny tools, skip-permissions flag). Both call sites spread it into their options object, so Save in /ui/dashboard/#/settings now actually changes agent behavior on the next message. The allowDangerouslySkipPermissions flag only flips on in bypass mode; the acceptEdits and default modes leave it off as the SDK expects. 9 unit tests cover the mapping. P0 (reviewer, Codex): the Evolution and Model+cost sections labeled themselves 'live on the next message' but the runtime is constructor-snapshotted. Rather than ship a claim that does not hold, relabel the five sections that are not live-reloadable as 'Save (restart required)' and update their help copy to match. Permissions keeps 'Live on the next message' because it now actually is. Full live-reload wiring for model/effort/max_budget/memory/cadence is a focused follow-up PR. P2 (Codex): handlePut read config/memory.yaml on every request, even for non-memory patches, so a malformed memory.yaml returned 500 for identity/model/permissions saves too. Skip the read unless the patch touches the memory section. planWrites now accepts a null memoryBefore when the caller opted out. Two new tests cover both paths (non-memory PUT tolerates corruption, memory PUT still 500s). P1 (reviewer): the audit log serialized raw before/after values, so an operator pasting 'sk-ant-api03-...' or 'xoxb-...' into a free-form field (role, domain, name) had the literal token recorded in the history pane. Added a defense-in-depth redactor keyed on known secret-shape prefixes (Anthropic, OpenAI, Slack bot/user/app, GitHub personal/user/server/refresh, Telegram bot). JSON.stringify runs after redaction so nested fields and arrays are walked. Two new tests assert redaction on Anthropic and Slack shapes.
Bumps the version to 0.20.0 in every place it's referenced: - package.json (1) - src/core/server.ts VERSION constant - src/mcp/server.ts MCP server identity - src/cli/index.ts phantom --version output - README.md version + tests badges - CLAUDE.md tagline + bun test count - CONTRIBUTING.md test count Tests: 1,799 pass / 10 skip / 0 fail. Typecheck and lint clean. No 0.19.1 or 1,584-tests references remain in source, docs, or badges. v0.20 shipped eight PRs on top of v0.19.1: #71 entrypoint dashboard sync + / redirect + /health HTML #72 Sessions dashboard tab #73 Cost dashboard tab #74 Scheduler tab + create-job + Sonnet describe-assist #75 Evolution Phase A + Memory explorer tabs #76 Settings page restructure (phantom.yaml, 6 sections) #77 Agent avatar upload across 14 identity surfaces #79 Landing page redesign (hero, starter tiles, live pages list)
Summary
~/.claude/settings.json(Agent SDK internals) to an operator-facing form overconfig/phantom.yaml. Six sections: Identity, Model + cost, Evolution, Channels, Memory, Permissions.GET/PUT /ui/api/phantom-configendpoint with Zod-derived UI schema, atomic YAML writes, and per-field audit rows tagged by section..env/channels.yamlonly. The Zod schema uses.strict()so attempting to write a secret through PUT returns 400 at parse time.phantom.yamlbyte-identical to before and no audit row is written.Verdicts on the 30 existing fields
KEEP: 3 ▪ CHANGE: 2 ▪ REMOVE: 25. Only 5 of the 30 fields survive, and 2 of those 5 migrate from
settings.jsontophantom.yaml.New operator controls
Bugs fixed
permissions.defaultModecan now be unset via the UI (new schema + explicit Phantom default option).nullat submit time (was""for text,undefinedfor select).role="alert"and auto-scroll..strict().Backend contract
All routes behind cookie auth. Invalid method returns 405. Unknown top-level key on PUT returns 400. Out-of-range numerics return 400 with a
fieldpath. Mid-write rename failure returns 500 with the temp file cleaned up.Test plan
bun testgreen (1,729 pass, 0 fail).bun run typecheckclean.bun run lintclean.phantom.yamlfiles unchanged; new optional sections get defaults.GET /ui/api/phantom-configreturns the full UI shape.PUTwith{ name: "ghost", permissions: { allow: ["Bash(git:*)"] } }writes two audit rows (one per section) and updates phantom.yaml with all siblings preserved./ui/dashboard/#/settings, edit Identity.name, save, verify audit row in History drawer.Notes
settings.jsonis no longer touched by this page. Agents that need SDK-internal knobs edit that file directly via SSH./ui/api/settingsendpoint and thesrc/settings-editormodule are removed with their tests; no production consumer referenced them.settings_audit_logwith a newsectioncolumn (migration 46). Secrets are never logged because the schema does not accept them.