### Added

- **Auto-approve takeover** — new env var `JEAN2_AUTO_APPROVE_TAKEOVER` (default `true`). When enabled, session takeover requests are immediately approved without requiring the current controller to respond. Set to `false` to restore the explicit approve/deny flow.
- **Per-session auto-approve severity** — sessions now support an `autoApproveSeverity` field (`'off' | 'none' | 'low' | 'medium' | 'high'`) stored in SQLite. A new `auto_approve_severity TEXT` column is added via backward-compatible migration.
- **Server-side auto-approve fallback** — when a permission ask arrives and no client is connected for the session, the server checks the session's `autoApproveSeverity` and auto-approves with `'once'` scope if the permission's risk level is at or below the configured threshold. This keeps the agent running even when all clients are disconnected (e.g. phone locked).

- **Skill management tool (`skill_manage`)** — new built-in dynamic tool that lets the agent create, update, patch, and delete reusable workspace skills stored as `SKILL.md` files under `.agents/skills/`. Gated by `workspace.settings.skills.managementEnabled`. Supports configurable permission risk levels. Includes prompt guidance injected into the system prompt when enabled.
- **`WorkspaceSkillSettings` SDK type** — new interface with `managementEnabled: boolean` and `permissionRisk: PermissionRiskLevel`. Added as optional `skills?` field to `WorkspaceSettings`.
- **`PATCH /api/workspaces/:id`** — now validates `settings.skills.managementEnabled` and `settings.skills.permissionRisk`.

- **Session search tool (`session_search`)** — new built-in tool that lets the agent search across all messages in the workspace using SQLite FTS5. Supports two modes: full-text search (with snippets, role filters, relevance/newest/oldest sort) and read-around (context window around an anchor message). Gated by `workspace.settings.sessionSearch.enabled`. Permission risk and tool-result inclusion are configurable per workspace.
- **FTS5 search index (`messages_fts`)** — virtual table indexing message content, reasoning text, and tool names with `unicode61` tokenizer. One-time backfill at server startup when the index is empty (batched 500 rows/transaction, with progress logging). Incremental sync via hooks on message/part CRUD keeps the index up to date. Fire-and-forget writes ensure FTS failures never break message operations.
- **`WorkspaceSessionSearchSettings` SDK type** — interface with `enabled`, `permissionRisk`, and `includeToolResults` fields. Added as optional `sessionSearch?` field to `WorkspaceSettings`.
- **`PATCH /api/workspaces/:id`** — now validates `settings.sessionSearch` fields.
- **Orphaned data cleanup** — new startup routine (`cleanupOrphanedData`) removes all data left behind when parent records are missing: parts without a message or session, messages without a session, pending asks / queued messages / attachments / pinned messages without a session, sessions without a workspace, permission grants / workspace paths / terminal sessions without a workspace, and stale FTS rows. Runs in a single transaction before FTS backfill. Logs only when orphaned data is found.

### Changed

- **`Session` SDK type** — added `autoApproveSeverity?: AutoApproveSeverity | null` field and `AutoApproveSeverity` type.
- **`UpdateOptions` in SDK REST client** — added `autoApproveSeverity?: string | null` to the sessions update payload.
- **Auto-approve always uses `'once'` scope** — both client-side and server-side auto-approve now grant `'once'` only, never `'session'` or `'workspace'`. No grants are persisted from auto-approve decisions.
- **`handleRequestTakeover`** — accepts an optional `autoApprove` parameter; when `true`, immediately transfers control instead of entering `takeover_requested` state.
- **`PUT /api/sessions/:id`** — now accepts `autoApproveSeverity` in the request body.
- **`WorkspaceHeader`** — added session search toggle button and dropdown menu item (mirrors memory/skills pattern).
