email leaves the platform for plugins/email
the sidecar, the router, the table, the screens and the account manager are
gone from src/ entirely - deleted, not commented, because a commented mount is
what assertPermissionTotality cannot see.
three reverse imports had to go first, and they are what decided the boundary.
core may not import a plugin's queries, and one of these had already proved
why: getEmailAccounts was awaited at the top level of the agent sidecar, so
when the table went away on 2026-08-13 the rejection took officer-agent into a
pm2 restart loop with every live chat session inside it (fbb6d6c7).
both survivors now read the DISK instead. findEmailAccountName() in
data-path.ts picks the newest emails.db under an account's email_accounts/
directory - no import, no query, and nothing to throw when the plugin is not
installed. the layout was already the platform's, so this costs no coupling.
the agent keeps its email_db tool and an email-context chat still opens in the
right mailbox.
the google oauth callback no longer appends /email to the dock: a plugin's
route is the plugin's business.
app-store/catalogue.ts entry deleted, and that is the whole point rather than
tidying. permissionAvailability derives from sidecar_installs and a plugin only
ever gets a plugin_installs row, so a leftover entry makes the permission
permanently unavailable - denied route, no dock tile - on a machine where the
plugin is installed and healthy. it has bitten headscale and nearly music.
two app-store tests used email as their config-only fixture; repointed at
notify, which is the same shape.
google-auth stays in core and the plugin imports it via @@/. that direction is
allowed and google-auth knows nothing about mail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,8 +16,8 @@ import { setMcpConfigPath } from './claude-manager';
|
||||
import * as claudeManager from './claude-manager';
|
||||
import { createSidecarConnector } from '../connect';
|
||||
import { sign } from '../../jwt';
|
||||
import { getUserByEmail, getOwnerUser, getEmailAccounts } from 'officerdb';
|
||||
import { DATA_PATH } from '../../data-path';
|
||||
import { getUserByEmail, getOwnerUser } from 'officerdb';
|
||||
import { DATA_PATH, findEmailAccountName } from '../../data-path';
|
||||
import { API_URL, OFFICER_API_URL, ANTHROPIC_PROXY_URL } from '../../officer-url.mjs';
|
||||
|
||||
// PM2 starts this sidecar with no user in its env, so resolve the owner from the database rather than
|
||||
@@ -85,22 +85,14 @@ const OFFICER_AUTH_TOKEN = await sign({ id: dbUser.id, email, username: dbUser.u
|
||||
const homeDir = homedir();
|
||||
const globalToolsDir = join(DATA_PATH, 'tools');
|
||||
|
||||
// The email_db MCP tool reads one account's SQLite store; match the API's choice (first enabled).
|
||||
// The email_db MCP tool reads one account's SQLite store; found on disk, never queried.
|
||||
//
|
||||
// Guarded for the same reason `resolveOwner` above is, and it is the same bug in a second place: this
|
||||
// is a top-level await, so a query that THROWS rejects it and exits the process — straight into the PM2
|
||||
// restart loop, taking every live agent session with it. `resolveOwner` was hardened after that cost a
|
||||
// session on 2026-08-10; this line has the identical shape and was not.
|
||||
//
|
||||
// It throws for real now: email is a plugin, so `email_accounts` is commented out of the schema
|
||||
// (officerdb/src/schema.ts) and does not exist on a core install. An agent must start without it — the
|
||||
// email tool is one tool, not a prerequisite for chat.
|
||||
const emailAccounts = await getEmailAccounts(dbUser.id).catch(() => {
|
||||
console.warn('[agent] no email accounts (the email plugin is not installed) — the email_db tool will be idle');
|
||||
return [] as Awaited<ReturnType<typeof getEmailAccounts>>;
|
||||
});
|
||||
const emailAccount = emailAccounts.find((a) => a.enabled) ?? emailAccounts[0];
|
||||
const emailDbRel = join('email_accounts', emailAccount?.email ?? 'none', 'emails.db');
|
||||
// This used to `await getEmailAccounts(dbUser.id)` at the top level. Email is a plugin now and the platform
|
||||
// may not import its queries — and that import is also what put this process into a PM2 restart loop when
|
||||
// the table went away (`fbb6d6c7`), taking every live chat session with it. A directory read cannot throw
|
||||
// the same way and cannot depend on the plugin being installed.
|
||||
const emailAccountName = findEmailAccountName(email);
|
||||
const emailDbRel = join('email_accounts', emailAccountName ?? 'none', 'emails.db');
|
||||
const userToolsDir = join(DATA_PATH, email, 'tools');
|
||||
|
||||
// ── Path setup ──
|
||||
|
||||
Reference in New Issue
Block a user