Repository navigation
Phase 3: approve from anywhere, and team workspaces - #8
Merged
Merged
Conversation
…ifier - decisions.ts: decide()/undo() used by the web routes now, and by push and Slack next. Edited inputs are re-checked against block rules; every decision records where it was made (tool_calls.decided_via) - notify/: NotificationChannel interface and a Notifier that announces each new approval once (tool_calls.notified_at, compare-and-set) and reports decisions back - The worker announces approvals when a run starts waiting
- Connect Slack per user (OAuth v2, bot scopes chat:write, im:write, users:read); state is sealed and bound to the user; the bot token is sealed; disconnect revokes and deletes - Slack notification channel: Block Kit DM per recipient with Approve / Deny / Open in Synapse, input summary, policy reason and a warning for tainted tasks; message ts stored in slack_messages and rewritten when the action is decided anywhere - POST /api/slack/interactions: signature (v0 HMAC, 5 min skew, constant time) checked over the raw body first; only the connected Slack user can act, only on actions sent to them; acks at once and decides via decide(..., via: "slack"); double clicks show the current state - Connections page Slack card (connect, test DM, disconnect, setup guidance), docs/slack.md with an app manifest, .env.example, docs/api.md, README
- PWA: app/manifest.ts, icons rasterized from the brand glyph (paper #F4F1EA), apple-icon, appleWebApp metadata; public/sw.js registered in production builds (or NEXT_PUBLIC_ENABLE_SW=true), no caching - Web Push (web-push 3.6.7, VAPID): push_subscriptions table with sealed keys; /api/push config, subscriptions (list/upsert/delete) and test; endpoints limited to known push services; 404/410 forgets the device. Dev generates VAPID keys once into .data/vapid.json, production never does - PushChannel: one notification per approval (tag = toolCallId, Approve/Deny actions), one summary per person when several arrive at once, delivered to every recipient's devices; skips anything already decided; no lock-screen Approve for sends/deletes when the undo window is off - Service worker Approve/Deny posts the decision with via: "push" (the decision route now accepts via: web|push), shows "Approved · sending in Ns" with Undo, and opens the task if anything fails - Email alerts (nodemailer 10.1.0) when SMTP_URL and EMAIL_FROM are set, opt-in via user_settings.email_alerts; preview, why, task link, no approve links - Settings → Notifications: device permission flow with iOS Home Screen guidance, device list, send a test, email toggle; graceful unsupported/denied/unconfigured - docs/notifications.md, docs/api.md, .env.example; tests with a fake push sender and fake SMTP transport
# Conflicts: # .env.example # apps/server/src/app.ts # apps/server/src/config.ts # apps/server/src/services.ts # docs/api.md # packages/db/drizzle/meta/0006_snapshot.json # packages/db/drizzle/meta/_journal.json # packages/db/src/schema.ts
…port A team's rules are evaluated alongside each member's own and the strictest answer wins (block > ask an admin decides > ask > allow); a team block can't be overridden and plan grants can't stand in for an admin. Calls a team rule reserves for an admin go to the team's owners and admins (decide path, notifier recipients, admin inbox); the member sees them waiting. Invite by single-use link, roles with a last-owner guard, team rules editor, and a formula-safe RFC 4180 audit CSV. Tasks, connections and memory stay personal.
# Conflicts: # apps/server/src/app.ts # apps/web/app/(app)/settings/page.tsx # apps/web/lib/api.ts # docs/api.md # packages/db/drizzle/meta/0006_snapshot.json # packages/db/drizzle/meta/_journal.json
…approve from Slack - A member who leaves (or is removed) can't approve, alone, what needed an admin: their pending admin-only calls are denied and the run re-queued so the agent can respond - Test: a member's admin-only request reaches the admin's Slack, and the admin approves it there (decided_by = admin, decided_via = slack)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 3 of making SynapseAI stronger: decisions reach you wherever you are, and teams get shared rules and admin sign-off. I laid a shared foundation first, three parallel agents built the features on it, and I integrated and verified them here.
Foundation
decisions.tshandles web, push and Slack the same way:tool_calls.decided_via).notify/announces each new approval exactly once (a compare-and-set ontool_calls.notified_at) to itsrecipients, and reports decisions back to every channel.Approve from your phone
Approve from Slack
Team workspaces
Fixed while integrating
decided_byis the admin,decided_viais slack).Schema
One new migration,
0006_reach_and_teams, plus0005for the foundation columns.Testing
via: push;Not verified against the real thing
All three are covered by fakes, and each needs credentials to try (see
docs/slack.mdanddocs/notifications.md).🤖 Generated with Claude Code