Skip to content

fix: slack channel diagnostics, GW default persistence, model registry#57

Merged
meidad merged 1 commit into
mainfrom
fix/slack-channels-and-model-corrections
May 27, 2026
Merged

fix: slack channel diagnostics, GW default persistence, model registry#57
meidad merged 1 commit into
mainfrom
fix/slack-channels-and-model-corrections

Conversation

@meidad
Copy link
Copy Markdown
Collaborator

@meidad meidad commented May 27, 2026

Summary

Grab-bag of fixes for issues that surfaced after #56 merged. Three themes:

Slack channels and diagnostics

  • /api/slack/channels was returning 500 because it read secrets raw from SQL — but the integrations table encrypts at rest. Added decryptSecret() step. (settings/src/app/api/slack/channels/route.ts)
  • account_inactive diagnostic upgraded to actionable in both slack-user.ts and slack-polling.ts — exact fix steps (reinstall at api.slack.com/apps + Reconnect) instead of a generic WARN. The onAuthError callback grew an {kind, reason} info argument so the gateway can broadcast a structured `auth_error` event to the UI.
  • Default-channel reachability probe at boot — runs conversations.info on both bot AND user tokens, surfaces distinct diagnostics for wrong workspace token / stale default vs bot not in channel.
  • sendAsAgent retries with user client when the bot post fails with `channel_not_found` (the common "forgot to /invite @nomos to this channel" case). Agent stops going dark.
  • User-token workspace mismatch detector — validates auth.team_id matches the workspace's expected teamId at boot. Catches OAuth callbacks that wrote the wrong token to a per-workspace row.

Google Workspace "Make default" wasn't persisting

The integrations table's `metadata` column was being double-encoded via `JSON.stringify({...})::jsonb` — postgres.js wraps an already-stringified JSON object again, producing JSONB STRINGS like `"{\"is_default\":true}"` instead of JSONB OBJECTS. Every `metadata->>'is_default'` lookup returned NULL, so the PATCH's `jsonb_set` was a no-op.

Fixes:

Model registry made dynamic + corrected to Anthropic docs

  • New src/sdk/model-capabilities.ts and settings/src/lib/model-capabilities.ts with the full lineup: Opus 4.7/4.6/4.5, Sonnet 4.6/4.5, Haiku 4.5. Per-model `contextWindow`, `maxOutputTokens`, family.
  • Corrected per docs.claude.com/en/docs/about-claude/models/overview:
    • 1M context (no beta): Opus 4.7, Opus 4.6, Sonnet 4.6
    • 200K context: Opus 4.5, Sonnet 4.5, Haiku 4.5
    • Max output: Opus 4.7/4.6 → 128K (was 32K), Haiku 4.5 → 64K (was 8K)
  • /admin/context Model Limits panel renders from the registry with the current model highlighted; reports the actual context window from the running config.
  • Settings page model picker and `/model` slash-command derive from the registry too — add a new model in one place, it shows up everywhere.

Bonus: `NOMOS_BETAS` is now mapped to the `app.betas` config row so future betas can be exposed via UI without another schema change.

Test plan

  • Settings → Slack → reconnect a workspace, verify the channel picker loads (was 500ing).
  • In Settings → Google, with two accounts, click "Make default" on the non-default one. Verify the badge flips AND survives a page reload.
  • /admin/context: confirm the context window reported at top matches the model in use (1M for Opus 4.7, etc.). The per-model card grid highlights the current model.
  • In CLI, run `/model` — see Opus 4.7 in the picker with the "1M context" note.
  • If your Slack bot is in a workspace where it's not invited to the default channel, restart the daemon and try sending a message. Agent should reply (falling back to user-mode); look for the warn line about `/invite @`.
  • If the Slack bot account is genuinely inactive in a workspace, restart the daemon and see the actionable ERROR + `auth_error` event.

🤖 Generated with Claude Code

A grab-bag of fixes for issues that surfaced after #56 landed. Three
themes:

1) Slack channels weren't usable after workspace reconnect
   - /api/slack/channels was reading the workspace's `secrets` column
     raw from SQL, but the integrations table encrypts at rest. Added
     a `decryptSecret()` step so the channel list loads again.
   - SlackPollingAdapter and SlackUserAdapter now emit a structured
     `account_inactive` diagnostic with the exact fix (reinstall the
     Slack app at api.slack.com/apps + Reconnect in Settings). The
     `onAuthError` callback grew an `{kind, reason}` info argument so
     gateway can broadcast `auth_error` events to the UI with proper
     context.
   - At adapter boot, probe the default channel via
     conversations.info on both bot AND user tokens. Surface different
     diagnostics for "wrong workspace token / stale default" vs "bot
     not in channel".
   - `sendAsAgent` retries with the user client when the bot post
     fails with channel_not_found (the common "forgot to /invite
     @nomos to this channel" case). Agent stops going dark.
   - Validate the user token's team_id matches the workspace's
     teamId at boot — catches OAuth callbacks that wrote the wrong
     token to a per-workspace row.

2) Google Workspace "Make default" wasn't persisting
   - The integrations table's `metadata` column was being
     double-encoded via `JSON.stringify({...})::jsonb`, producing
     JSONB STRINGS like `"{\"is_default\":true}"` instead of JSONB
     OBJECTS. Every `metadata->>'is_default'` lookup returned NULL,
     so the PATCH's `jsonb_set` was a no-op on existing rows.
   - Fixed all writers to use `sql.json({...})`, and switched the
     PATCH to overwrite metadata wholesale instead of jsonb_set —
     wholesale replace self-heals legacy bad rows on the next click.
   - /api/google/status now reads the manifest first (source of
     truth), so it agrees with /api/google/accounts and the make-
     default change shows up immediately.

3) Model registry made dynamic + corrected to match Anthropic docs
   - New `src/sdk/model-capabilities.ts` (and a settings-side mirror)
     with the full lineup: Opus 4.7/4.6/4.5, Sonnet 4.6/4.5, Haiku
     4.5. Per-model `contextWindow`, `maxOutputTokens`, family.
   - Corrected to match docs.claude.com/en/docs/about-claude/models:
     Opus 4.7, Opus 4.6, and Sonnet 4.6 are 1M-context by default
     (no beta needed). Older Opus / Sonnet 4.5 / Haiku 4.5 stay at
     200K. Also fixed maxOutputTokens (Opus 4.7/4.6: 128K, Haiku
     4.5: 64K).
   - /admin/context Model Limits panel now renders from the registry
     with the current model highlighted, and reports the actual
     context window from the running config (NOMOS_MODEL +
     NOMOS_BETAS via /api/admin/context).
   - Settings page model picker and /model slash-command picker
     derive their model lists from the registry too — add a new
     model in one place, it shows up everywhere.

Bonus: `NOMOS_BETAS` is now mapped to the `app.betas` config row
(both settings/lib/env.ts and src/db/app-config.ts) so future betas
can be exposed in UI without another schema change. The 1M-context
beta toggle was scaffolded then removed in this same commit once we
confirmed it's not needed for any current model.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@meidad meidad merged commit 02149ca into main May 27, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant