Commit Graph
9 Commits
Author SHA1 Message Date
pastilhasandClaude Opus 5 7b7147001b fix email account creation: body parser, error shape, and non-Error throws
adding an email account failed with a bare "failed to add account" toast. three
defects stacked, each hiding the next.

the email sidecar's http.ts reconstructs what the platform's middleware used to
provide, but only did two of three — bodyParser was never remounted, so every
write route read ctx.get('body') as undefined and POST /accounts threw on
body.provider before ever reaching the credentials.

its onError then read `.status` off the thrown custom-error, which carries
`statusCode`. every deliberate 4xx fell through to the 500 branch and had its
message replaced with "internal error", so a rejected IMAP login and a genuine
crash looked identical. it also answered JSON where the rest of the api answers
errors as plain text. now mirrors hono.ts's handler rather than inventing a
second shape.

useClient threw a plain object, so the ~33 sites narrowing with
`err instanceof Error ? err.message : <fallback>` always took the fallback and
discarded the server's message. now throws an ApiError subclass keeping both
status and message, so those sites start surfacing real errors.

only email reads ctx.get('body'); every other sidecar is a pure proxy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 19:31:31 +00:00
pastilhasandClaude Opus 5 c9d680b133 remove the telegram and whatsapp channels, reduce discord to notifications
All three existed to drive the platform from a chat app. The phone app does that now, so
they are dead weight — three bot gateways, three command parsers, account pairing, admin
config screens and bot tokens sitting in the database.

Gone entirely: telegram/ and whatsapp/, discord/'s bot and command handler, the shared
channel plumbing they were the only users of (pairing.ts, send-and-await.ts, types.ts,
routes.ts and its 20 config/pairing/status endpoints), the six settings components, and
their sections in the integrations screen.

What Discord keeps is the one piece worth keeping — pushing a message out — as
notify/discord.ts, configured by DISCORD_WEBHOOK_URL in the env. No UI, no pairing, no
stored credential, and it never throws: a notification that fails to send is logged and
dropped. Unset means notifications are silently skipped, which is the default state.

Kept deliberately: send-claude-code.ts and send-opencode.ts. They live under channels/ but
have nothing to do with chat apps — they are how /chat and the pipeline executor drive an
agent turn.

Also drops four dependencies with no remaining importer (discord.js,
node-telegram-bot-api, whatsapp-web.js, qrcode), and the /sync-now route added to the email
sidecar an hour ago, whose only consumer was the channel handlers.

The "Channel Models" settings section stays, with its description corrected — it is keyed
off the general access policy rather than anything channel-specific, so it governs non-owner
accounts, not chat apps. Whether that whole class still earns its place is the open
question already noted against origin-validation.

Not touched: the telegram, whatsapp and discord rows in server_integrations, which still
hold their bot tokens. Deleting rows is a different kind of decision and the SQL is in the
handover.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 14:05:28 +00:00
pastilhasandClaude Opus 5 e9e144962d email: the sidecar schedules its own syncs
Stage 2, and the end of the inversion. The two sync handlers (1,093 lines) ran in the
platform's queue, which meant the sidecar reached back over its registration socket to ask
the platform to enqueue work, and the credentials travelled through Postgres job metadata to
get there. Option (A) from the plan: they run here now, and the Jobs screen is left to the
things it actually describes.

The handlers moved almost unedited. Their bodies were already a list of steps taking a
context, so sync-runner.ts synthesizes that context and runs them; what went away is the
JobHandler wrapper and the registration. `job.userId` is the OWNER'S EMAIL rather than a
numeric id — the queue's naming — and it resolves the mail store path, so it is called out
in the type. That is the same field whose absence made the mailbox read as empty two commits
ago; it is set from user.email and checked this time.

Deliberately not a queue: one run per account, no persistence, no retry. A failure is picked
up by the ten-minute cron like any other, and a sync interrupted by a restart resumes from
the stored cursor rather than the beginning. PermanentError survives as a local class — it
signalled "do not retry" to the queue and now just carries its message to the sync state.

accounts.ts asks the runner whether an account is syncing instead of scanning job rows, and
the queue-over-WS shim in index.ts is gone: enqueueViaWs, listJobsViaWs, the pending-response
map and the queue branch in the command handler. Nothing but a port crosses that socket now.

The three chat channels stop opening the mail store directly. They each carried their own
copy of count-rows / enqueue / poll / count-again, coupling three chat bridges to the mail
schema — and they enqueued `gmail-sync` unconditionally, the OAuth path, for an
app-password account that syncs over IMAP, so the command was already broken. One shared
helper calls a new POST /sync-now on the sidecar, which syncs and reports what arrived.

queue/handlers/ is now empty; both handlers there were email. The queue is untouched and
still serves the Jobs screen.

Not moved, and fine where they are: scripts/migrate-emails-to-sqlite.ts and
scripts/seed-imap-uids.ts are one-off maintenance scripts that open the store directly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 13:33:46 +00:00
pastilhasandClaude Opus 5 06cd6bdca3 email: give the sidecar the whole user, not just the id
The mailbox read as empty after the migration — "No emails synced yet", and Sync Now
reporting no new mail against 18,430 messages that were sitting on disk untouched.

The store is keyed by the owner's email: DATA_PATH/<owner>/email_accounts/<account>/emails.db.
The platform's userMiddleware put the entire user row on the context, so `user.email`
resolved. The proxy injects only an id, and the middleware I wrote to replace it set
`{ id }` and nothing else — so `user.email` was undefined, the path never resolved to the
real mailbox, reads found nothing, and the sync compared against an empty set and concluded
there was nothing new. Nothing was written to the wrong place and no data was touched.

The sidecar loads the full row via getUserById now, matching what the middleware provided.
Cached: it runs on every request and single-user is a hard invariant.

Worth keeping in mind for the sidecars still to be extracted — moving routes across a
process boundary silently changes what is on the context, and it type-checks either way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 13:24:37 +00:00
pastilhasandClaude Opus 5 af56eb36ff email: move the mail store and every route into the sidecar
Email was the one sidecar built inside out. The platform held ~1,800 lines — the per-account
SQLite store, all 14 HTTP routes, account CRUD, resync, IMAP validation — while the 314-line
sidecar was a scheduler that reached BACK into the platform to do anything
(`import { performResync } from '../../api/email/resync'`).

The sidecar now serves its own HTTP listener and announces `email:server`, and
/api/email/* on the platform is createSidecarProxy like every other one: 1,801 lines down
to 22, with no mail knowledge left in it — not a message, not a folder, not a credential.

The routes moved verbatim, Hono and all. http.ts only reconstructs what the platform's
middleware used to provide: `user` on the context, from the X-Officer-User header the proxy
injects (trusted because this server binds loopback), and an error handler that turns
custom-errors into status codes.

The /email/events SSE stream went with them, which removes a whole round trip: the IDLE
watcher used to send `email:new` over the registration socket so the platform could push to
its SSE clients. Those clients are here now, so it calls broadcastEmailNew in-process and
`email:new` is gone from the wire protocol.

DELIBERATELY NOT DONE YET, and left backwards on purpose rather than half-moved:

- The two sync handlers (email-sync 381 lines, gmail-sync 712) still run in the platform's
  queue and now import the store from its new home — a platform → sidecar import, which is
  the wrong direction and is temporary. Moving them is option (A) from the plan: the sidecar
  schedules its own syncs, independent of the platform Jobs list.
- accounts.ts still imports queue/init to enqueue a sync and to report sync status, and
  index.ts still carries the queue-over-WS shim that inversion needs.
- The three channel handlers still open the mail store directly rather than asking over HTTP.

Two things worth knowing while testing: a from-scratch sync holds a proxied request open
well past the 60s idle default, hence timeoutSeconds on the proxy; and `gmail-sync` is
hardcoded in all three channel handlers even though the only account is provider=gmail with
auth_type=password, which routes to IMAP — so "sync emails" from a chat channel is
almost certainly already broken, and folds into the next stage.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 12:10:35 +00:00
pastilhasandClaude Opus 4.8 4b08d0b99c email: push new-mail to /email via SSE (IMAP IDLE -> sidecar -> server -> EventSource)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 17:29:20 +00:00
pastilhasandClaude Opus 4.8 5a0b9ce70f email: real-time IMAP IDLE watchers (push on new mail); cron kept as backstop
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 17:22:46 +00:00
pastilhasandClaude Opus 4.6 b3f1d10bc1 inline email resync instead of job queue, refresh list on completion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 17:07:06 +00:00
pastilhasandClaude Opus 4.6 d88fe3cac7 task logs: migrate from filesystem to postgresql; refactor sidecars into submodules
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:49:39 +00:00