Files
zeekay ffe37ab57d feat(slack): Hanzo AI Slack app (@hanzo/slack)
Bolt app bringing Hanzo AI into Slack, built on the shared foundation:
@hanzo/ai (headless client), @hanzo/iam (identity), @hanzo/cards (UI).

- /hanzo slash command → completion, replies with a @hanzo/cards panel
- App Home assistant panel (model picker, quick actions, prompt, sign-in)
  via toSlackBlocks — no hand-written Block Kit
- Interactivity for the stable card action ids (model select, quick
  actions, prompt submit, sign in)
- 'Ask Hanzo' message shortcut + app_mention: pull thread/message context
  via conversations.replies/history, fence it into the prompt
- OAuth install (/slack/install, /slack/oauth_redirect) with a per-workspace
  bot token + per-workspace Hanzo token, server-side only
- Quick actions: Summarize, Draft reply, Explain, Action items
- Endpoints via api.hanzo.ai /v1 only; secrets from env; /health + graceful
  shutdown for ingress/k8s
- One path from Slack to the model (complete); pure modules unit-tested
- README with Slack app manifest + registration steps

Tests: 68 passing. tsc --noEmit clean. Build emits dist/.
2026-07-03 19:15:01 -07:00

47 lines
1.6 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { ActionId } from '@hanzo/cards';
import { parseActionId, quickActionIdFrom, QUICK_ACTION_RE } from '../src/actions.js';
describe('parseActionId', () => {
it('classifies the four stable card action ids', () => {
expect(parseActionId(ActionId.ModelSelect)).toEqual({ kind: 'model_select' });
expect(parseActionId(ActionId.PromptSubmit)).toEqual({ kind: 'prompt_submit' });
expect(parseActionId(ActionId.SignIn)).toEqual({ kind: 'sign_in' });
});
it('classifies quick actions and pulls the id', () => {
expect(parseActionId('hanzo_quick_action:summarize')).toEqual({
kind: 'quick_action',
id: 'summarize',
});
expect(parseActionId('hanzo_quick_action:draft_reply')).toEqual({
kind: 'quick_action',
id: 'draft_reply',
});
});
it('reports unknown ids without throwing', () => {
expect(parseActionId('something_else')).toEqual({
kind: 'unknown',
actionId: 'something_else',
});
});
it('uses the card contract action id, not a hard-coded string', () => {
expect(ActionId.ModelSelect).toBe('hanzo_model_select');
expect(ActionId.QuickAction).toBe('hanzo_quick_action');
});
});
describe('quickActionIdFrom / QUICK_ACTION_RE', () => {
it('extracts the id or returns null', () => {
expect(quickActionIdFrom('hanzo_quick_action:explain')).toBe('explain');
expect(quickActionIdFrom('hanzo_model_select')).toBeNull();
});
it('regex matches only the quick-action namespace', () => {
expect(QUICK_ACTION_RE.test('hanzo_quick_action:x')).toBe(true);
expect(QUICK_ACTION_RE.test('hanzo_prompt_submit')).toBe(false);
});
});