remove the email residue the extraction left in the platform
a sweep for remnants after 2026-08-18. nothing was broken - no dangling import, symbol, route or permission key anywhere in src/ - but the platform was still speaking the plugin's vocabulary in a dozen places. the one that mattered is live code rather than prose: ChatPanelWrapper hardcodes workspace.id === 'email' to send context: 'email', which is the frontend half of the pair whose backend half (resolveEmailCwd) carries a fifteen-line justification. the two ends now both say so, including that adding a SECOND id there is the signal the platform needs a real way for a plugin to declare a chat context. the rest: two dead imports left by removing the settings section, an empty 'Email' type-section header, a protocol.ts section header with no members naming the plugin's SSE design, and a queue comment pointing at sidecar/email/sync-runner.ts - a path in another repository now. CLAUDE.md, AGENTS.md and working-on-officer.md described email as a platform feature, an app permission and a pm2 process. those are the files a fresh agent reads first, so they were the highest-risk staleness in the tree. TODO.md's whole Email section moved to the plugin's own repository. it held OPEN items pointing at sidecar/email/store.ts, which is exactly how a leftover list sends someone to edit files that do not exist. the delete-account dialog promised it would remove email accounts and playlists. both tables belong to plugins now, so that cascade only happens where the plugin is installed - it now says 'anything an installed plugin keeps for them' instead of naming two it cannot guarantee. imapflow and mailparser STAY in package.json. the plugin has no package.json of its own and resolves from the platform's node_modules, so removing them would break the installed plugin's sidecar. the fix is giving plugins their own dependency manifest, not deleting these. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
|
||||
Officer is a self-hosted platform built around one person — the server owner — which since 2026-08-07
|
||||
also admits **additional accounts holding a strict subset of it**. It bundles an AI agent, a terminal,
|
||||
a file browser, a code editor, email, a bitcoin wallet, a remote desktop and customisable dashboards
|
||||
a file browser, a code editor, a bitcoin wallet, a remote desktop and customisable dashboards
|
||||
behind a single web app.
|
||||
|
||||
**The owner/member split, and where the line falls.** This file said "single-user is a hard invariant,
|
||||
@@ -30,7 +30,7 @@ written: `users` holds six rows. The accurate statement is narrower and more use
|
||||
since `getOwnerHomeDir` ignores the email it is passed. That rule lives there once and covers the
|
||||
HTTP routes, the websocket doors and the dock together.
|
||||
|
||||
So "which user is this" has a real answer for the **app** surface (gitea, music, photos, email,
|
||||
So "which user is this" has a real answer for the **app** surface (gitea, music, photos,
|
||||
calendar…) and for the **confined** one (terminal, chat, files), and is still always "the owner" for
|
||||
anything under `execution`.
|
||||
|
||||
@@ -58,7 +58,7 @@ One Bun process (`src/server.tsx`) serves everything:
|
||||
Long-running and privileged work lives in **sidecars**: separate processes that dial back in over
|
||||
`/api/sidecar/register` and are tracked in `src/servers/sidecar-registry.ts`. PM2 runs them
|
||||
(the generated `ecosystem.config.cjs` — see below): `officer` (the server), `officer-anthropic-proxy`, `officer-claude-code`,
|
||||
`officer-opencode`, `officer-email`, `officer-pty`, `officer-vnc`, `officer-music`, `officer-vault`,
|
||||
`officer-opencode`, `officer-pty`, `officer-vnc`, `officer-music`, `officer-vault`,
|
||||
`officer-slskd`, `officer-headscale`, `officer-transmission`, `officer-invoiceshelf`, `officer-wallet`,
|
||||
`officer-photos`, `officer-notify`, `officer-caldav`, `officer-memos`, `officer-jellyfin`, `officer-gitea`
|
||||
— twenty as of 2026-08-06, and a list that goes stale every time a sidecar lands. `pm2 jlist` is the
|
||||
@@ -125,7 +125,7 @@ imported by their package name (`officerdev`, `hooks`, `state`, `types`, `helper
|
||||
Two stores, and the split matters:
|
||||
|
||||
**Postgres** (`src/databases/officer_db`) holds the account, passkeys, settings, dashboards,
|
||||
email accounts, queue and pipeline jobs. Since 2026-08-16 there is a SECOND database,
|
||||
queue and pipeline jobs. Since 2026-08-16 there is a SECOND database,
|
||||
`officer_marketplace` (`src/databases/marketplace_db`), and the split is deliberate: it is a public
|
||||
catalogue served by a process with no login, no session and no user, so sharing a connection would put
|
||||
an unauthenticated surface one query away from every private table. It runs only on the host serving
|
||||
@@ -135,9 +135,9 @@ with the plugin ones commented out. Types inferred from the schema in `src/types
|
||||
|
||||
**The filesystem** holds everything the agent authors. `OFFICER_ITEMS_DIR` (`$OFFICER_ROOT/capabilities`)
|
||||
contains one directory per item under `skills/`, `tools/`, `tasks/`, `processes/`, `extensions/` — no
|
||||
database rows, no scope tiers. `DATA_PATH/<email>/` holds the managed home, attachments and the
|
||||
per-account email SQLite stores — those are the **email sidecar's**, and nothing in the platform opens
|
||||
them.
|
||||
database rows, no scope tiers. `DATA_PATH/<email>/` holds the managed home and attachments. It also holds
|
||||
`email_accounts/`, which is the **email plugin's** — the platform pre-creates the directory and never opens
|
||||
what is inside it.
|
||||
|
||||
**None of those paths is configured.** Since 2026-08-13 `src/servers/data-path.ts` derives the install
|
||||
root as `resolve(process.cwd(), '..')` and hangs `data/`, `permissions/` and `dockers/` off it. That
|
||||
@@ -372,7 +372,7 @@ link-focusable). Half the app still does this; none of the new code should.
|
||||
copy it. A disabled entry renders as a `<span>`; a disabled `<a>` is not a thing. A control that
|
||||
_mutates_ rather than navigates stays a `<button>`.
|
||||
- **Route pairs.** A bare screen route plus a param route rendering the same component: `/chat` +
|
||||
`/chat/:sessionId`, `/jobs` + `/jobs/:id`, `/email` + `/email/:emailId`, `/headscale` +
|
||||
`/chat/:sessionId`, `/jobs` + `/jobs/:id`, `/headscale` +
|
||||
`/headscale/:section`. One `<Navigate … replace />` guard in the screen, placed after all hooks,
|
||||
canonicalises both the bare route and a bogus param.
|
||||
- **Master list + live preview uses `?selected=<id>`**, not the detail route — linking rows straight
|
||||
|
||||
Reference in New Issue
Block a user