283 lines
14 KiB
Markdown
283 lines
14 KiB
Markdown
---
|
|
phase: 03-operator-experience
|
|
plan: 02
|
|
type: execute
|
|
wave: 2
|
|
depends_on: ["03-01"]
|
|
files_modified:
|
|
- packages/portal/app/api/slack/callback/route.ts
|
|
- packages/portal/app/(dashboard)/onboarding/page.tsx
|
|
- packages/portal/app/(dashboard)/onboarding/steps/connect-channel.tsx
|
|
- packages/portal/app/(dashboard)/onboarding/steps/configure-agent.tsx
|
|
- packages/portal/app/(dashboard)/onboarding/steps/test-message.tsx
|
|
- packages/portal/app/(dashboard)/settings/api-keys/page.tsx
|
|
- packages/portal/lib/queries.ts
|
|
- packages/portal/components/onboarding-stepper.tsx
|
|
autonomous: false
|
|
requirements:
|
|
- PRTA-03
|
|
- PRTA-04
|
|
- LLM-03
|
|
|
|
must_haves:
|
|
truths:
|
|
- "Operator can click Add to Slack and complete OAuth flow to connect their workspace"
|
|
- "Operator can paste WhatsApp credentials and have them validated and stored"
|
|
- "After connecting a channel, operator can send a test message that verifies end-to-end connectivity"
|
|
- "Agent goes live automatically after test message succeeds"
|
|
- "Operator completes the full onboarding sequence (connect -> configure -> test) in a guided wizard"
|
|
- "Operator can add, view, and delete BYO LLM API keys from a settings page"
|
|
artifacts:
|
|
- path: "packages/portal/app/api/slack/callback/route.ts"
|
|
provides: "Next.js Route Handler for Slack OAuth redirect"
|
|
- path: "packages/portal/app/(dashboard)/onboarding/page.tsx"
|
|
provides: "Onboarding wizard with 3-step stepper"
|
|
- path: "packages/portal/app/(dashboard)/settings/api-keys/page.tsx"
|
|
provides: "BYO API key management page"
|
|
key_links:
|
|
- from: "packages/portal/app/(dashboard)/onboarding/steps/connect-channel.tsx"
|
|
to: "/api/portal/channels/slack/install"
|
|
via: "fetch to get OAuth URL, then window.location redirect"
|
|
pattern: "channels/slack/install"
|
|
- from: "packages/portal/app/api/slack/callback/route.ts"
|
|
to: "/api/portal/channels/slack/callback"
|
|
via: "proxy the OAuth callback to FastAPI backend"
|
|
pattern: "channels/slack/callback"
|
|
- from: "packages/portal/app/(dashboard)/onboarding/steps/test-message.tsx"
|
|
to: "/api/portal/channels/{tenant_id}/test"
|
|
via: "POST to send test message"
|
|
pattern: "channels.*test"
|
|
- from: "packages/portal/app/(dashboard)/settings/api-keys/page.tsx"
|
|
to: "/api/portal/tenants/{tenant_id}/llm-keys"
|
|
via: "GET/POST/DELETE for BYO key CRUD (endpoints created in Plan 01 Task 3)"
|
|
pattern: "tenants.*llm-keys"
|
|
---
|
|
|
|
<objective>
|
|
Channel connection wizard (Slack OAuth + WhatsApp manual setup), onboarding flow with 3-step stepper, and BYO API key management page.
|
|
|
|
Purpose: Operators can connect their messaging channels and onboard their AI employee through a guided wizard, plus manage their own LLM API keys -- all from the portal UI.
|
|
Output: Onboarding wizard (connect channel -> configure agent -> test message), Slack OAuth callback handler, WhatsApp manual connect form, BYO key settings page.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@/home/adelorenzo/.claude/get-shit-done/workflows/execute-plan.md
|
|
@/home/adelorenzo/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/03-operator-experience/03-CONTEXT.md
|
|
@.planning/phases/03-operator-experience/03-RESEARCH.md
|
|
@.planning/phases/03-operator-experience/03-01-SUMMARY.md
|
|
|
|
<interfaces>
|
|
<!-- From Plan 01 backend APIs -->
|
|
|
|
From packages/shared/shared/api/channels.py (created in Plan 01):
|
|
```python
|
|
# GET /api/portal/channels/slack/install?tenant_id={id} -> { "authorize_url": "https://slack.com/oauth/v2/authorize?..." }
|
|
# GET /api/portal/channels/slack/callback?code={code}&state={state} -> { "success": true, "workspace_name": "..." }
|
|
# POST /api/portal/channels/whatsapp/connect -> { "success": true } (body: { tenant_id, phone_number_id, waba_id, system_user_token })
|
|
# POST /api/portal/channels/{tenant_id}/test -> { "success": true, "message": "Test message sent" } (body: { channel_type })
|
|
```
|
|
|
|
From packages/shared/shared/api/llm_keys.py (created in Plan 01 Task 3):
|
|
```python
|
|
# GET /api/portal/tenants/{tenant_id}/llm-keys -> [{ id, provider, label, key_hint, created_at }]
|
|
# POST /api/portal/tenants/{tenant_id}/llm-keys -> { id, provider, label, key_hint, created_at } (body: { provider, label, api_key })
|
|
# DELETE /api/portal/tenants/{tenant_id}/llm-keys/{key_id} -> 204
|
|
```
|
|
|
|
From packages/shared/shared/api/billing.py (created in Plan 01):
|
|
```python
|
|
# POST /api/portal/billing/checkout -> { "checkout_url": "https://checkout.stripe.com/..." }
|
|
# POST /api/portal/billing/portal -> { "portal_url": "https://billing.stripe.com/..." }
|
|
```
|
|
|
|
From packages/shared/shared/crypto.py (created in Plan 01):
|
|
```python
|
|
class KeyEncryptionService:
|
|
def encrypt(self, plaintext: str) -> str: ...
|
|
def decrypt(self, ciphertext: str) -> str: ...
|
|
def rotate(self, ciphertext: str) -> str: ...
|
|
```
|
|
|
|
<!-- Existing portal patterns -->
|
|
|
|
From packages/portal/lib/api.ts:
|
|
```typescript
|
|
// API client for FastAPI backend — use this for all portal API calls
|
|
```
|
|
|
|
From packages/portal/lib/queries.ts:
|
|
```typescript
|
|
// TanStack Query hooks — add new hooks for channels, billing, usage here
|
|
```
|
|
|
|
Established patterns:
|
|
- shadcn/ui components
|
|
- react-hook-form + zod v4 + standardSchemaResolver
|
|
- TanStack Query for data fetching
|
|
- App Router with (dashboard) route group
|
|
- proxy.ts for auth protection (Next.js 16 pattern)
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Slack OAuth callback route, onboarding wizard with 3-step stepper, and channel connection forms</name>
|
|
<files>
|
|
packages/portal/app/api/slack/callback/route.ts,
|
|
packages/portal/app/(dashboard)/onboarding/page.tsx,
|
|
packages/portal/app/(dashboard)/onboarding/steps/connect-channel.tsx,
|
|
packages/portal/app/(dashboard)/onboarding/steps/configure-agent.tsx,
|
|
packages/portal/app/(dashboard)/onboarding/steps/test-message.tsx,
|
|
packages/portal/components/onboarding-stepper.tsx,
|
|
packages/portal/lib/queries.ts
|
|
</files>
|
|
<action>
|
|
1. Create `packages/portal/app/api/slack/callback/route.ts`:
|
|
- Next.js Route Handler (GET) that receives the OAuth redirect from Slack
|
|
- Extract `code` and `state` from searchParams
|
|
- Forward to FastAPI backend: GET /api/portal/channels/slack/callback?code={code}&state={state}
|
|
- On success: redirect to /onboarding?step=2&channel=slack&connected=true
|
|
- On error: redirect to /onboarding?step=1&error=slack_auth_failed
|
|
|
|
2. Create `packages/portal/components/onboarding-stepper.tsx`:
|
|
- 3-step stepper component using shadcn/ui: "Connect Channel" -> "Configure Agent" -> "Test Message"
|
|
- Visual progress indicator (numbered steps with active/complete/pending states)
|
|
- Step state managed via URL searchParams (step=1|2|3) for shareable/refreshable URLs
|
|
- Each step renders the corresponding step component
|
|
|
|
3. Create `packages/portal/app/(dashboard)/onboarding/page.tsx`:
|
|
- Reads `step` from searchParams (default 1)
|
|
- Reads `tenant_id` from session or query param
|
|
- Renders OnboardingStepper with the appropriate step component
|
|
- Guards: if no tenant selected, redirect to tenant selection
|
|
|
|
4. Create `packages/portal/app/(dashboard)/onboarding/steps/connect-channel.tsx`:
|
|
- Two cards: "Add to Slack" and "Connect WhatsApp"
|
|
- **Slack card:** Button that fetches /api/portal/channels/slack/install?tenant_id={id}, then does window.location.href = authorize_url (external redirect to Slack)
|
|
- **WhatsApp card:** Expandable form with 3 fields (Phone Number ID, WhatsApp Business Account ID, System User Token) + step-by-step instructions text
|
|
- On submit: POST /api/portal/channels/whatsapp/connect
|
|
- Validation with zod + react-hook-form
|
|
- On successful connection (either channel): advance to step 2 automatically
|
|
- If returning from Slack OAuth (connected=true in searchParams): show success toast and advance
|
|
|
|
5. Create `packages/portal/app/(dashboard)/onboarding/steps/configure-agent.tsx`:
|
|
- If tenant already has an agent: show existing agent summary with "Edit" link to Agent Designer
|
|
- If no agent: redirect to Agent Designer page with return URL back to onboarding?step=3
|
|
- Minimal step — the Agent Designer (from Phase 1) handles all agent configuration
|
|
- "Next" button enabled only when at least one active agent exists for the tenant
|
|
|
|
6. Create `packages/portal/app/(dashboard)/onboarding/steps/test-message.tsx`:
|
|
- Shows connected channel(s) with a "Send Test Message" button per channel
|
|
- On click: POST /api/portal/channels/{tenant_id}/test with {channel_type}
|
|
- Shows loading state while test runs, then success/failure result
|
|
- On success: show "Your AI employee is live!" celebration message
|
|
- Agent goes live automatically (is_active already true by default) — NO separate "Go Live" button per user decision
|
|
- Per user decision: test message step is REQUIRED, not skippable
|
|
|
|
7. Add TanStack Query hooks to `packages/portal/lib/queries.ts`:
|
|
- useSlackInstallUrl(tenantId) — GET /channels/slack/install
|
|
- useConnectWhatsApp() — mutation POST /channels/whatsapp/connect
|
|
- useSendTestMessage() — mutation POST /channels/{tenantId}/test
|
|
- useChannelConnections(tenantId) — GET to list existing connections
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/adelorenzo/repos/konstruct/packages/portal && npx next build 2>&1 | tail -20</automated>
|
|
</verify>
|
|
<done>
|
|
- Slack OAuth callback route handler proxies to FastAPI and redirects appropriately
|
|
- Onboarding page renders 3-step stepper with progress indicator
|
|
- Connect Channel step shows Slack OAuth button and WhatsApp manual form
|
|
- Configure Agent step links to existing Agent Designer
|
|
- Test Message step sends test and shows result
|
|
- Agent goes live automatically after successful test (no Go Live button)
|
|
- All TanStack Query hooks defined
|
|
- Portal builds without errors
|
|
</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: BYO API key management settings page</name>
|
|
<files>
|
|
packages/portal/app/(dashboard)/settings/api-keys/page.tsx,
|
|
packages/portal/lib/queries.ts
|
|
</files>
|
|
<action>
|
|
1. Create `packages/portal/app/(dashboard)/settings/api-keys/page.tsx`:
|
|
- Tenant-level settings page (per user decision — simpler than per-agent for v1)
|
|
- List existing BYO keys: show provider name, label, key_hint (last 4 chars), created date (NOT the key itself — never display decrypted keys)
|
|
- "Add API Key" button opens a form:
|
|
- Provider: select dropdown (OpenAI, Anthropic, Custom)
|
|
- Label: text input (human-readable name, e.g., "Production OpenAI key")
|
|
- API Key: password input (masked by default)
|
|
- Submit: POST to /api/portal/tenants/{tenant_id}/llm-keys (backend endpoint created in Plan 01 Task 3)
|
|
- Delete button per key with confirmation dialog
|
|
- Use shadcn/ui Card, Table, Dialog, Button, Input, Select components
|
|
- react-hook-form + zod for validation (provider required, label 3-100 chars, key not empty)
|
|
|
|
2. Add TanStack Query hooks to `packages/portal/lib/queries.ts`:
|
|
- useLlmKeys(tenantId) — GET /api/portal/tenants/{tenant_id}/llm-keys
|
|
- useAddLlmKey() — mutation POST /api/portal/tenants/{tenant_id}/llm-keys
|
|
- useDeleteLlmKey() — mutation DELETE /api/portal/tenants/{tenant_id}/llm-keys/{keyId}
|
|
|
|
3. Add navigation link to settings/api-keys in the dashboard layout sidebar (if sidebar exists) or in the tenant detail page.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/adelorenzo/repos/konstruct/packages/portal && npx next build 2>&1 | tail -20</automated>
|
|
</verify>
|
|
<done>
|
|
- BYO API key settings page renders with list of existing keys (redacted — shows key_hint only)
|
|
- Add key form validates and submits to backend
|
|
- Delete key with confirmation dialog works
|
|
- Portal builds without errors
|
|
</done>
|
|
</task>
|
|
|
|
<task type="checkpoint:human-verify" gate="blocking">
|
|
<name>Task 3: Verify onboarding wizard and BYO key management</name>
|
|
<files>n/a</files>
|
|
<action>
|
|
Human verifies the onboarding wizard and BYO key management UI:
|
|
1. Start the portal: `cd packages/portal && npm run dev`
|
|
2. Navigate to /onboarding — verify 3-step stepper displays
|
|
3. Step 1: Verify "Add to Slack" button and WhatsApp form are present
|
|
4. Step 2: Verify it links to Agent Designer or shows existing agent
|
|
5. Step 3: Verify "Send Test Message" button is present
|
|
6. Navigate to /settings/api-keys — verify key list and add form render
|
|
7. Try adding a BYO key with the form — verify it submits without JS errors
|
|
8. Confirm there is NO separate "Go Live" button — agent goes live after test
|
|
</action>
|
|
<verify>Human visual inspection of onboarding flow and settings page</verify>
|
|
<done>Operator confirms onboarding wizard works through all 3 steps and BYO key page renders correctly</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- Portal builds successfully: `cd packages/portal && npx next build`
|
|
- Onboarding wizard navigable through all 3 steps
|
|
- BYO API key page renders and accepts input
|
|
- No separate "Go Live" button exists (per user decision)
|
|
- Test message step is required (not skippable)
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- Operator can initiate Slack OAuth from the portal
|
|
- Operator can paste WhatsApp credentials via guided form
|
|
- Onboarding wizard completes in 3 steps: connect -> configure -> test
|
|
- Agent goes live automatically after successful test message
|
|
- Operator can manage BYO API keys from settings page (backed by Plan 01 Task 3 endpoints)
|
|
- Portal builds without errors
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/03-operator-experience/03-02-SUMMARY.md`
|
|
</output>
|