toga-ai 1.0.653 → 1.0.655
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/knowledge/2.0/apps/_underscore/features/apirequest-json-content-type.md +28 -2
- package/knowledge/2.0/apps/ai-bdr/INDEX.md +2 -1
- package/knowledge/2.0/apps/ai-bdr/features/landing-call-flow.md +226 -0
- package/knowledge/2.0/apps/ai-bdr/features/landing-chat-drawer.md +63 -13
- package/knowledge/2.0/apps/ai-bdr/features/live-call-status.md +9 -1
- package/knowledge/2.0/apps/ai-bdr/features/security-landing-page.md +101 -40
- package/knowledge/2.0/apps/ai-bdr/features/web-funnel-app.md +17 -1
- package/knowledge/2.0/apps/toga-blox/INDEX.md +1 -0
- package/knowledge/2.0/apps/toga-blox/features/toaster.md +100 -0
- package/knowledge/2.0/apps/toga-blox/workflows/dynamic-publish-pipeline.md +27 -0
- package/knowledge/2.0/apps/toga2-commerce/INDEX.md +1 -1
- package/knowledge/2.0/apps/toga2-commerce/features/order-submit-sync-sequencing.md +15 -2
- package/knowledge/2.0/apps/toga2-commerce/workflows/amplify-build-and-deploy.md +50 -6
- package/knowledge/2.0/apps/worker2/INDEX.md +1 -0
- package/knowledge/2.0/apps/worker2/features/creating-worker-actions.md +17 -2
- package/knowledge/2.0/apps/worker2/features/netsuite-opportunity-client-labels.md +138 -0
- package/knowledge/2.0/apps/worker2/features/netsuite-opportunity-sync.md +66 -7
- package/knowledge/2.0/apps/worker2/features/notification-email.md +19 -2
- package/knowledge/2.0/standards/frontend-deploy.md +8 -3
- package/knowledge/2.0/standards/frontend.md +28 -6
- package/knowledge/INDEX.md +3 -3
- package/knowledge/standalone/apps/claude/workflows/mcp-tool-usage.md +19 -2
- package/package.json +1 -1
|
@@ -6,8 +6,8 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: ["jcardinal"]
|
|
9
|
+
updated: 2026-08-26
|
|
10
|
+
owners: ["jcardinal", "ajean"]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/ApiRequest.php
|
|
13
13
|
related:
|
|
@@ -74,7 +74,33 @@ case-insensitive scan of already-set headers, so a caller-supplied `Content-Type
|
|
|
74
74
|
save) lives **inside `execute()`** and would affect every framework caller, so it is deferred
|
|
75
75
|
pending architecture review. Do **not** work around it per-caller.
|
|
76
76
|
|
|
77
|
+
- **Hand-rolled `Logs.Api` inserts must set `isAuthRequest`, `instanceId` and `hostname` — they are
|
|
78
|
+
NOT NULL with no defaults.** `_ApiRequest`'s own logging sets all three (`ApiRequest.php:260-264`),
|
|
79
|
+
so a helper that writes its own log row while only setting `direction`/`source`/`method`/`route`
|
|
80
|
+
throws `Column 'X' cannot be null` on **every** insert. Because such helpers are usually a
|
|
81
|
+
*wrapper* around the real call, the throw propagates out of the wrapped call and, under a
|
|
82
|
+
`catch(Throwable)`, turns the whole feature into a **silent no-op that still reports success**.
|
|
83
|
+
Two worker2 handlers shipped with exactly this bug and one went unnoticed for months — see
|
|
84
|
+
[netsuite-opportunity-sync](../../worker2/features/netsuite-opportunity-sync.md#gotchas--known-issues).
|
|
85
|
+
Prefer letting `_ApiRequest` log rather than writing `Logs.Api` by hand.
|
|
86
|
+
- **⚠ Auto-logging persists the `Authorization` header in PLAINTEXT.** `ApiRequest.php:267` writes
|
|
87
|
+
the full request headers to `Logs.Api.requestHeaders`, so any component that sends a bearer
|
|
88
|
+
token/API key and does not `setLogging(false)` is depositing that credential into the logs DB on
|
|
89
|
+
every call. Known live instance: `_underscore/Component/Api/Clickup/Clickup.php` (which also
|
|
90
|
+
**hardcodes** its token in committed source — flagged for a rotate-and-move-to-config ticket; 11
|
|
91
|
+
worker2 files use it). When adding a new component, decide deliberately: log and accept the header
|
|
92
|
+
exposure, strip/redact the auth header before the call is logged, or `setLogging(false)`.
|
|
93
|
+
- **`Logs.Api` is 1.4M+ rows and `source` is NOT indexed.** Any diagnostic query must be bounded by
|
|
94
|
+
`dtStamp` (indexed) or it times out — `WHERE source = '…'` alone will not return.
|
|
95
|
+
|
|
77
96
|
## Change history
|
|
97
|
+
- 2026-08-26 — Documented the **manual-insert column contract** (`isAuthRequest`, `instanceId`,
|
|
98
|
+
`hostname` are NOT NULL with no defaults, and `_ApiRequest` sets them at `ApiRequest.php:260-264`),
|
|
99
|
+
after a hand-rolled `logged()` helper in two worker2 handlers threw on every insert and silently
|
|
100
|
+
no-op'd an entire feature behind a `catch(Throwable)`. Also recorded that auto-logging persists the
|
|
101
|
+
**`Authorization` header in plaintext** into `Logs.Api.requestHeaders` (`ApiRequest.php:267`) — the
|
|
102
|
+
reason `_Component_Api_Clickup`'s hardcoded token needs rotation — and that `Logs.Api.source` is
|
|
103
|
+
unindexed at 1.4M+ rows, so queries must be bounded by `dtStamp`. (ajean)
|
|
78
104
|
- 2026-08-05 — Documented (no code change) that the `direction = 'OUT'` api-log row is
|
|
79
105
|
**committed immediately** inside `execute()` (`transactionCommit(DB_CLIENT_LOGS)`), so it
|
|
80
106
|
survives a later rollback and `Logs_<Client>.Api` is definitive evidence of whether an outbound
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
| [BDR Web Funnel — Full Implementation Plan](features/bdr-web-funnel-plan.md) | > **Execution status (2026-07-16):** PLAN **Phases 0–5 are BUILT and QA'd**, Phase 6 > was **mostly already built** (the `info` contract was implemented at comm | bdr/PLAN.md, bdr/mockup/app.jsx, bdr/mockup/screens.jsx, bdr/mockup/components.jsx |
|
|
7
7
|
| [Call Orchestration — PHP Worker ↔ Vapi (the integration seam)](features/call-orchestration.md) | The **PHP worker** is the orchestrator; **Vapi** is the actor. | ai-bdr/docs/client-onboarding-sop.md, ai-bdr/docs/vapi-firstmessage-timing-fix.md, ai-bdr/docs/vapi-inbound-callback-assistant-request.md, ai-bdr/docs/vapi-voicemail-iphone-screening.md, ai-bdr/scripts/update_structured_output.py |
|
|
8
8
|
| [AI-BDR Campaign Chat — Backend Contract + Frontend Widget Handoff](features/campaign-chat-handoff.md) | The consolidated backend + frontend handoff for the **AI-BDR campaign chat** — a grounded AI-BDR chat widget for landing pages, powered by the Talos backend at | ai-bdr-campaign-handoff.md, bdr/src/lib/talosChat.ts |
|
|
9
|
+
| [Landing "Call me now" Flow (/landing) — screens + the dormant real callback route](features/landing-call-flow.md) | The **"Call me now" path on `/landing`**: a two-screen flow (call form → calling/recap) plus a **real, wired, but deliberately dormant** backend route (`POST /a | bdr/src/flow/screens/SecurityCallNow.tsx, bdr/src/flow/screens/SecurityCalling.tsx, bdr/src/lib/securityCallFlow.ts, bdr/src/lib/leadName.ts, bdr/src/app/api/landing-call/route.ts, bdr/src/flow/landingCallApi.ts, bdr/src/flow/screens/SecurityLanding.tsx, bdr/src/components/ChatPanel.tsx, bdr/src/content/security.ts, bdr/src/app/security.css, bdr/src/proxy.ts, bdr/.env.example, bdr/test/securityCallFlow.test.ts, bdr/test/landingCallRoute.test.ts |
|
|
9
10
|
| [Talos Chat Drawer on /landing (first campaign-chat consumer)](features/landing-chat-drawer.md) | The **right-anchored chat drawer on `/landing`**, wired to the **real** Talos campaign-chat backend at `https://api.togaiq.com`. | bdr/src/components/ChatPanel.tsx, bdr/src/components/ChatMarkdown.tsx, bdr/src/lib/talosChat.ts, bdr/src/content/security.ts, bdr/src/flow/screens/SecurityLanding.tsx, bdr/src/app/security.css, bdr/test/talosChat.test.ts, bdr/test/chatMarkdown.test.ts |
|
|
10
11
|
| [Live Call Status — Vapi call API → funnel Success screen lifecycle](features/live-call-status.md) | The BDR funnel's Success screen shows the **real** state of the call it just requested — dialing / ringing / answered-and-talking / ended / not-answered — inste | bdr/src/server/vapiCall.ts, bdr/src/server/callStatus.ts, bdr/src/server/statusToken.ts, bdr/src/server/callbackService.ts, bdr/src/server/workerDialer.ts, bdr/src/lib/callStatus.ts, bdr/src/app/api/call-status/route.ts, bdr/src/app/api/call-now/route.ts, bdr/src/flow/useCallStatusViewModel.ts, bdr/src/flow/callStatusApi.ts, bdr/src/flow/screens/Success.tsx, bdr/src/content/schema.ts, bdr/src/content/default.ts, bdr/src/proxy.ts |
|
|
11
|
-
| [Cybersecurity Landing Page (/landing — the site's current main page)](features/security-landing-page.md) | A single-page **cybersecurity services marketing landing page** at **`/landing`** in the `bdr` repo — and, as of 2026-08-24, **the site's main page**: `/` **307 | bdr/src/app/landing/page.tsx, bdr/src/app/security.css, bdr/src/flow/screens/SecurityLanding.tsx, bdr/src/content/security.ts, bdr/src/components/LogoSymbol.tsx, bdr/src/components/AssistantMark.tsx, bdr/src/components/Ico.tsx, bdr/src/components/Particles.tsx, bdr/src/components/ChatPanel.tsx, bdr/src/components/ChatMarkdown.tsx, bdr/src/lib/talosChat.ts, bdr/next.config.ts, bdr/eslint.config.mjs |
|
|
12
|
+
| [Cybersecurity Landing Page (/landing — the site's current main page)](features/security-landing-page.md) | A single-page **cybersecurity services marketing landing page** at **`/landing`** in the `bdr` repo — and, as of 2026-08-24, **the site's main page**: `/` **307 | bdr/src/app/landing/page.tsx, bdr/src/app/security.css, bdr/src/flow/screens/SecurityLanding.tsx, bdr/src/flow/screens/SecurityCallNow.tsx, bdr/src/flow/screens/SecurityCalling.tsx, bdr/src/lib/securityCallFlow.ts, bdr/src/content/security.ts, bdr/src/components/LogoSymbol.tsx, bdr/src/components/AssistantMark.tsx, bdr/src/components/Ico.tsx, bdr/src/components/Particles.tsx, bdr/src/components/ChatPanel.tsx, bdr/src/components/ChatMarkdown.tsx, bdr/src/lib/talosChat.ts, bdr/next.config.ts, bdr/eslint.config.mjs |
|
|
12
13
|
| [Vapi Integration — Assistants, Tools, Structured Output](features/vapi-integration.md) | Everything inside Vapi: the three assistants, the three shared tools, the shared structured-output schema, the Liquid-templated system prompt, and the Python sc | ai-bdr/vapi/templates/assistant.template.json, ai-bdr/vapi/templates/system-prompt.template.md, ai-bdr/prompts/archive/prompt-may-15.txt, ai-bdr/prompts/campaigns/healthcare-2026-03-18.md, ai-bdr/prompts/campaigns/healthcare-v2-2026-03-25.md, ai-bdr/prompts/templates/campaign-template.md, ai-bdr/scripts/update_assistant.py, ai-bdr/scripts/update_system_prompt.py, ai-bdr/scripts/update_structured_output.py, ai-bdr/scripts/update_call_summary_context.py, ai-bdr/scripts/fix_booking_guardrails.py, ai-bdr/scripts/get_assistant.py, ai-bdr/docs/vapi-firstmessage-timing-fix.md, ai-bdr/docs/vapi-voicemail-iphone-screening.md |
|
|
13
14
|
| [Web Funnel — Built Next.js App (structure, stack, how to run)](features/web-funnel-app.md) | > **READ FIRST (2026-08-24) — the funnel is currently UNREACHABLE.** `/` now > **307-redirects to `/landing`**, the new cybersecurity landing page > ([security- | bdr/PLAN.md, bdr/next.config.ts, bdr/.gitignore, bdr/src/server/hubspot.ts, bdr/src/server/leadSink.ts, bdr/src/server/callbackService.ts, bdr/src/server/workerDialer.ts, bdr/src/server/mailer.ts, bdr/src/server/statusToken.ts, bdr/src/server/vapiCall.ts, bdr/src/server/callStatus.ts, bdr/src/lib/summaryText.ts, bdr/src/lib/emailFormat.ts, bdr/src/lib/callStatus.ts, bdr/src/app/api/share-summary/route.ts, bdr/src/app/api/call-status/route.ts, bdr/src/flow/shareSummaryApi.ts, bdr/src/flow/callStatusApi.ts, bdr/src/flow/useCallStatusViewModel.ts, bdr/src/flow/screens/Success.tsx, bdr/src/flow/screens/CallNow.tsx, bdr/src/flow/screens/Schedule.tsx, bdr/src/flow/screens/CallSummary.tsx, bdr/src/content/schema.ts, bdr/src/content/default.ts, bdr/test/togaEnrichment.test.ts, bdr/test/fastDial.test.ts, bdr/eslint.config.mjs, bdr/amplify.yml, bdr/src/app, bdr/src/app/layout.tsx, bdr/src/app/page.tsx, bdr/src/app/studio.css, bdr/src/lib/resolveCampaignId.ts, bdr/src/lib/scheduleSlots.ts, bdr/src/lib/formatPhone.ts, bdr/src/lib/analytics.ts, bdr/src/lib/rateLimit.ts, bdr/src/lib/clientIp.ts, bdr/src/proxy.ts, bdr/.env.example, bdr/src/components/GoogleAnalytics.tsx, bdr/src/components/RichText.tsx, bdr/src/content, bdr/src/components, bdr/src/flow, bdr/src/server, bdr/src/server/toga.ts, bdr/src/app/api, bdr/test, bdr/test/togaPrimaryPhone.test.ts, bdr/public, bdr/mockup/styles.css |
|
|
14
15
|
| [Web Funnel — Next.js UI + Config-Driven Campaign Content](features/web-funnel-content-model.md) | > **Status note (RESOLVED 2026-07-16):** the content **contract** below (schema, > provider seam, adapters) is BUILT and QA'd (PLAN Phases 0–5). | bdr/src/content/schema.ts, bdr/src/content/provider.ts, bdr/src/content/useCampaign.ts, bdr/src/content/default.ts, bdr/src/lib/resolveCampaignId.ts, bdr/src/server/leadSink.ts, bdr/src/server/callbackService.ts, bdr/src/app/api/call-now/route.ts, bdr/src/app/api/call-later/route.ts, bdr/src/app/api/contact/route.ts |
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Landing "Call me now" Flow (/landing) — screens + the dormant real callback route
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: ai-bdr
|
|
5
|
+
project: AI-BDR
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-08-26
|
|
10
|
+
owners: [tcox]
|
|
11
|
+
files:
|
|
12
|
+
- bdr/src/flow/screens/SecurityCallNow.tsx
|
|
13
|
+
- bdr/src/flow/screens/SecurityCalling.tsx
|
|
14
|
+
- bdr/src/lib/securityCallFlow.ts
|
|
15
|
+
- bdr/src/lib/leadName.ts
|
|
16
|
+
- bdr/src/app/api/landing-call/route.ts
|
|
17
|
+
- bdr/src/flow/landingCallApi.ts
|
|
18
|
+
- bdr/src/flow/screens/SecurityLanding.tsx
|
|
19
|
+
- bdr/src/components/ChatPanel.tsx
|
|
20
|
+
- bdr/src/content/security.ts
|
|
21
|
+
- bdr/src/app/security.css
|
|
22
|
+
- bdr/src/proxy.ts
|
|
23
|
+
- bdr/.env.example
|
|
24
|
+
- bdr/test/securityCallFlow.test.ts
|
|
25
|
+
- bdr/test/landingCallRoute.test.ts
|
|
26
|
+
related:
|
|
27
|
+
- security-landing-page.md
|
|
28
|
+
- landing-chat-drawer.md
|
|
29
|
+
- live-call-status.md
|
|
30
|
+
- call-orchestration.md
|
|
31
|
+
- web-funnel-app.md
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## What this is
|
|
35
|
+
|
|
36
|
+
The **"Call me now" path on `/landing`**: a two-screen flow (call form → calling/recap)
|
|
37
|
+
plus a **real, wired, but deliberately dormant** backend route
|
|
38
|
+
(`POST /api/landing-call`) that requests an actual Vapi callback for an **anonymous**
|
|
39
|
+
visitor.
|
|
40
|
+
|
|
41
|
+
It was built 2026-08-26 on the design lead's UI review of the landing page: *"mock is
|
|
42
|
+
right; missing a Call me now button + flow; book a meeting slightly large; check with
|
|
43
|
+
Alex re shared component."*
|
|
44
|
+
|
|
45
|
+
**Read this first:** the route is **off by default**. It requires
|
|
46
|
+
`TOGA_SECURITY_CAMPAIGN_UUID`, and **no cybersecurity campaign exists in Toga yet**, so
|
|
47
|
+
in every environment today the flow runs its **simulation**. That is the intended end
|
|
48
|
+
state for now — *"wired up and ready for the campaign"* — not an unfinished build.
|
|
49
|
+
|
|
50
|
+
## The two campaigns are NOT the same thing (read before wiring anything)
|
|
51
|
+
|
|
52
|
+
AI-BDR has **two independent campaign systems** with unrelated ids. Confusing them is the
|
|
53
|
+
easiest mistake to make on this page, because both serve the same cybersecurity offer.
|
|
54
|
+
|
|
55
|
+
| | **Talos chat campaign** | **Toga dialer campaign** |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| Lives in | `api.togaiq.com` → `campaign_registry` | Toga platform → `Client_True.Campaigns` |
|
|
58
|
+
| Used by | the chat drawer ([landing-chat-drawer.md](landing-chat-drawer.md)) | the callback/dialer path (this doc) |
|
|
59
|
+
| Security id | `43fdf2b6-7a5d-4e85-be6b-911f42aa787f` ("External Pen Testing") | **none exists** |
|
|
60
|
+
| Env var | `NEXT_PUBLIC_TALOS_API_BASE` (base URL only) | `TOGA_SECURITY_CAMPAIGN_UUID` |
|
|
61
|
+
|
|
62
|
+
**`43fdf2b6-7a5d-4e85-be6b-911f42aa787f` cannot be used for
|
|
63
|
+
`TOGA_SECURITY_CAMPAIGN_UUID`.** It is a Talos chat-grounding campaign, not a dialer
|
|
64
|
+
campaign, and Toga has never heard of it.
|
|
65
|
+
|
|
66
|
+
`Client_True.Campaigns` in **prod** holds exactly two rows (verified 2026-08-26), both
|
|
67
|
+
**healthcare**, both `c_meetingType = discovery-call`:
|
|
68
|
+
|
|
69
|
+
- `15d0c02f-501e-e081-2ead-61b78c854223` — "26.05 - AI BDR - Ryan Nitti"
|
|
70
|
+
- `d8f7abe6-8f06-0218-03d1-50db575` — "26.04 - AI BDR TEST CAMPAIGN"
|
|
71
|
+
|
|
72
|
+
So there is **no cybersecurity dialer campaign and no cybersecurity Vapi assistant**.
|
|
73
|
+
That is the single blocker for real calls from `/landing`, and it is owned **outside the
|
|
74
|
+
`bdr` repo** (Toga platform + Vapi config).
|
|
75
|
+
|
|
76
|
+
## Screen flow
|
|
77
|
+
|
|
78
|
+
`SecurityLanding.tsx` gained a page-level `screen` state — `landing | call | calling` —
|
|
79
|
+
swapped with `SwapFade` using the **mock's app-shell timings** (`outMs 240`, `inMs 460`,
|
|
80
|
+
`rise 12`, `drop 18`). `backToOverview` resets the recap rotation and scrolls to top.
|
|
81
|
+
|
|
82
|
+
1. **`SecurityCallNow.tsx`** (new) — the mock's security *CallNow* screen: trust column,
|
|
83
|
+
**business email** field with a live hint (*"we'll assess the domain **acme.com**"*,
|
|
84
|
+
derived by `emailDomain`), and a masked phone field. Submit is enabled only when
|
|
85
|
+
`isValidEmail` **and** `isViablePhone` (`isCallFormComplete`).
|
|
86
|
+
2. **`SecurityCalling.tsx`** (new) — the mock's *Success* screen in calling mode:
|
|
87
|
+
`dialing` → **Connected** with an `mm:ss` timer → **End call** → **Call ended** with a
|
|
88
|
+
recap, **Copy**, and three exits: *Call me again* / *Book a meeting* (booking link) /
|
|
89
|
+
*Back to overview*.
|
|
90
|
+
3. **`lib/securityCallFlow.ts`** (new) — the pure helpers, all unit-tested:
|
|
91
|
+
`fillTokens`, `emailDomain`, `isCallFormComplete`, `formatCallTimer`, and the
|
|
92
|
+
recap-summary rotation (short / medium / large by visit count).
|
|
93
|
+
|
|
94
|
+
Entry points: the drawer's new **"Call me now"** button (see
|
|
95
|
+
[landing-chat-drawer.md](landing-chat-drawer.md)) via `ChatPanel`'s new `onCall` prop.
|
|
96
|
+
All copy lives in `src/content/security.ts` under the new `callNow` and `calling` groups
|
|
97
|
+
plus `talos.callCta`.
|
|
98
|
+
|
|
99
|
+
### Why new screens instead of reusing the studio ones
|
|
100
|
+
|
|
101
|
+
`src/flow/screens/CallNow.tsx` and `Success.tsx` (the parked studio funnel) read the
|
|
102
|
+
studio **`CampaignBundle` + lead contexts**, which `/landing` does not provide, and the
|
|
103
|
+
studio `CallNow` has **no email field** at all (the studio funnel primes identity from
|
|
104
|
+
`?hsContactId`). Reuse would have meant threading bundle/lead plumbing into a static
|
|
105
|
+
marketing page.
|
|
106
|
+
|
|
107
|
+
### Deliberate deviations from the mock
|
|
108
|
+
|
|
109
|
+
- **No `AmbientQuestions`** — that component is bundle-driven.
|
|
110
|
+
- Recap has **Copy** but **no Share sheet**.
|
|
111
|
+
- The mock's *"Schedule a call"* became **"Book a meeting"** (booking link), because
|
|
112
|
+
`/landing` has no schedule screen.
|
|
113
|
+
- Footnote **Privacy / Terms are live `togatech.com` links** (funnel precedent), not the
|
|
114
|
+
mock's inert `#` hashes.
|
|
115
|
+
|
|
116
|
+
## The real path — `POST /api/landing-call` (wired, dormant)
|
|
117
|
+
|
|
118
|
+
Built as a **separate route from `/api/call-now`** because the landing visitor is
|
|
119
|
+
**anonymous**: there is no `?hsContactId` priming on `/landing`, so there is no HubSpot
|
|
120
|
+
contact and no lead ref to work from — only the email and phone the visitor just typed.
|
|
121
|
+
|
|
122
|
+
Request `{ email, phoneNumber }`. Order of operations:
|
|
123
|
+
|
|
124
|
+
1. **Validate** — `isValidEmail` + `MAX_EMAIL_LENGTH`, `isValidUSPhone`.
|
|
125
|
+
2. **Config gate** — require `TOGA_SECURITY_CAMPAIGN_UUID` (must be a Toga uuid) or
|
|
126
|
+
return a **503 envelope** *"Live callback is not configured"* **before any upstream
|
|
127
|
+
call**. This is what keeps the route inert without a feature flag.
|
|
128
|
+
3. **Lead** — `togaLeadSink.upsertLead`, with `firstName`/`lastName` derived from the
|
|
129
|
+
email local part by the new `src/lib/leadName.ts` (`nameFromEmail`):
|
|
130
|
+
`jane.doe@…` → `Jane` `Doe`; `jdoe@…` → `Jdoe` + an **empty** lastName. Links the
|
|
131
|
+
campaign.
|
|
132
|
+
4. **Dial** — `togaCallbackService.requestCall(uuid, { type: "now" }, phone)` — the
|
|
133
|
+
**same code path as `/api/call-now`** (contact write + worker fast dial; see
|
|
134
|
+
[live-call-status.md](live-call-status.md)).
|
|
135
|
+
5. **Tokens** — mints the same purpose-scoped status/share tokens as the studio flow.
|
|
136
|
+
|
|
137
|
+
`src/flow/landingCallApi.ts` (`submitLandingCall`) is the client seam and returns
|
|
138
|
+
`{ live, statusHandle }`. **Any** non-success — 503, 400, 500, network — resolves to
|
|
139
|
+
`live: false` and the screen runs the **simulation**. `SecurityCallNow`'s submit posts
|
|
140
|
+
the request but **never blocks the screen advance**.
|
|
141
|
+
|
|
142
|
+
### Live vs. simulation in `SecurityCalling`
|
|
143
|
+
|
|
144
|
+
Mirrors the studio `Success.tsx` split, using the existing
|
|
145
|
+
`useCallStatusViewModel` (see [live-call-status.md](live-call-status.md)):
|
|
146
|
+
|
|
147
|
+
| | Simulation (today) | Live (once configured) |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
| Phases | scripted dialing → connected → ended | real Vapi states: dialing → on call (real elapsed) → ended / *"We could not reach you"* |
|
|
150
|
+
| Recap | static, rotating short/medium/large | the **real Vapi recap** replaces it |
|
|
151
|
+
| "End call" button | shown | **hidden** — a browser cannot hang up a phone call |
|
|
152
|
+
| Status unavailable | n/a | **holds at dialing** with a note; never fakes progress |
|
|
153
|
+
|
|
154
|
+
Rate limiting: `/api/landing-call` was added to `src/proxy.ts`'s **call-limiter branch and
|
|
155
|
+
matcher**, sharing the `/api/call-now` bucket.
|
|
156
|
+
|
|
157
|
+
## Turning it on later (config only — no code change)
|
|
158
|
+
|
|
159
|
+
1. Create the **cybersecurity campaign + Vapi assistant** in Toga — runbook:
|
|
160
|
+
`ai-bdr/docs/client-onboarding-sop.md` (see [call-orchestration.md](call-orchestration.md)).
|
|
161
|
+
2. Set `TOGA_SECURITY_CAMPAIGN_UUID` in `.env.local` and in the **Amplify console** (it is
|
|
162
|
+
already covered by the build-time `env | grep` recipe in `amplify.yml` that materializes
|
|
163
|
+
`TOGA_*` into `.env.production` — see [web-funnel-app.md](web-funnel-app.md)).
|
|
164
|
+
3. Redeploy (console vars only reach a build that starts **after** they are saved).
|
|
165
|
+
|
|
166
|
+
**Rejected alternatives (do not redo them):**
|
|
167
|
+
|
|
168
|
+
- *Remove the route until the campaign exists* — it would simply be rebuilt later; the
|
|
169
|
+
env gate already makes it inert.
|
|
170
|
+
- *Point it at a healthcare campaign to test with a real phone* — **do not**. The
|
|
171
|
+
assistant would pitch the wrong offer to a real person.
|
|
172
|
+
|
|
173
|
+
## Open items before the first live run
|
|
174
|
+
|
|
175
|
+
- **An empty `lastName` on an email-only lead is UNVERIFIED against Toga.** `jdoe@…`
|
|
176
|
+
produces `Jdoe` + `""`; nobody has confirmed Toga accepts that on `upsertLead`.
|
|
177
|
+
- **The deployed environment needs `TOGA_VAPI_TOKEN`** for live status to render. Without
|
|
178
|
+
it the call still happens, but the screen **holds at dialing with the unavailable note**
|
|
179
|
+
— by design, not a bug.
|
|
180
|
+
- The recap's "Book a meeting" inherits the landing page's booking-link problem (see
|
|
181
|
+
[security-landing-page.md](security-landing-page.md)).
|
|
182
|
+
|
|
183
|
+
## Gotchas
|
|
184
|
+
|
|
185
|
+
- **Two campaign systems, same product.** See the table above. A Talos `campaign_id` in
|
|
186
|
+
`TOGA_SECURITY_CAMPAIGN_UUID` fails the uuid/Toga check and the route 503s — which
|
|
187
|
+
looks exactly like "not configured," so check *which* id you pasted before debugging.
|
|
188
|
+
- **The 503 is a designed state, not an error to fix.** A dormant route returning
|
|
189
|
+
*"Live callback is not configured"* is the expected production behavior today.
|
|
190
|
+
- **Never let the network decide the UX.** `submitLandingCall` is fire-and-continue: the
|
|
191
|
+
screen advances regardless, and only the *content* (real vs. simulated) depends on the
|
|
192
|
+
response.
|
|
193
|
+
- **No "End call" on the live path.** If you see one during a live call, the live/sim
|
|
194
|
+
branch is wrong — the browser has no way to hang up a Vapi phone call.
|
|
195
|
+
- **An anchor styled as `.btn` renders smaller than a real `<button>` sibling** unless
|
|
196
|
+
`font-size` is pinned — see the drawer doc's gotcha; the same trap produced the
|
|
197
|
+
oversized "Book a meeting" button this session.
|
|
198
|
+
|
|
199
|
+
## How to work on it
|
|
200
|
+
|
|
201
|
+
- 18 new Vitest tests: `test/securityCallFlow.test.ts` (9) and
|
|
202
|
+
`test/landingCallRoute.test.ts` (9).
|
|
203
|
+
- Verified 2026-08-26: `tsc` clean, `eslint` clean, **vitest 385/385**, `next build`
|
|
204
|
+
clean — the route builds as `ƒ /api/landing-call` and **`/landing` stays prerendered
|
|
205
|
+
static** (the call screens are client-side; the POST only happens on submit).
|
|
206
|
+
- **Nothing is committed** — all of it lives in the `bdr` working tree; the developer
|
|
207
|
+
merges and commits.
|
|
208
|
+
|
|
209
|
+
## Change history
|
|
210
|
+
- 2026-08-26 — Initial doc. BUILT the `/landing` "Call me now" flow: `ChatPanel` gained an
|
|
211
|
+
`onCall` prop and a "Call me now" action above "Book a meeting"; new `SecurityCallNow` /
|
|
212
|
+
`SecurityCalling` screens driven by a page-level `landing|call|calling` state in
|
|
213
|
+
`SecurityLanding` (SwapFade on the mock's app-shell timings), new pure helpers in
|
|
214
|
+
`lib/securityCallFlow.ts`, and new `callNow`/`calling` content groups. BUILT the REAL
|
|
215
|
+
callback path — `POST /api/landing-call` for ANONYMOUS visitors (validate → require
|
|
216
|
+
`TOGA_SECURITY_CAMPAIGN_UUID` else 503 before any upstream call → `upsertLead` with a
|
|
217
|
+
name derived from the email local part via new `lib/leadName.ts` → `requestCall` on the
|
|
218
|
+
same path as `/api/call-now` → purpose-scoped status/share tokens), the
|
|
219
|
+
`submitLandingCall` client seam, the live/sim split in `SecurityCalling`, and the
|
|
220
|
+
`/api/call-now` rate-limit bucket. DECIDED to keep it **dormant** until the campaign
|
|
221
|
+
exists (enabling is config only); rejected deleting the route and rejected testing
|
|
222
|
+
against a healthcare campaign. DISCOVERED the Talos chat campaign id is NOT a Toga
|
|
223
|
+
dialer campaign and that prod has only two healthcare dialer campaigns — no
|
|
224
|
+
cybersecurity campaign or Vapi assistant exists, which is the real blocker. Open:
|
|
225
|
+
empty-`lastName` acceptance and `TOGA_VAPI_TOKEN` in the deployed env. tsc/eslint clean,
|
|
226
|
+
385/385 tests, `/landing` still prerendered static. Uncommitted at capture. (tcox)
|
|
@@ -6,7 +6,7 @@ project: AI-BDR
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-26
|
|
10
10
|
owners: [tcox]
|
|
11
11
|
files:
|
|
12
12
|
- bdr/src/components/ChatPanel.tsx
|
|
@@ -19,6 +19,7 @@ files:
|
|
|
19
19
|
- bdr/test/chatMarkdown.test.ts
|
|
20
20
|
related:
|
|
21
21
|
- security-landing-page.md
|
|
22
|
+
- landing-call-flow.md
|
|
22
23
|
- campaign-chat-handoff.md
|
|
23
24
|
- web-funnel-app.md
|
|
24
25
|
- ../../toga-blox/features/talos-assistant.md
|
|
@@ -92,11 +93,22 @@ in the adapter).
|
|
|
92
93
|
into the response type by `toTalosResponse()`. The backend's **silent
|
|
93
94
|
lead/qualification fields are never read** — deliberate, per the handoff.
|
|
94
95
|
|
|
96
|
+
## Drawer actions (both render as of 2026-08-26)
|
|
97
|
+
|
|
98
|
+
The drawer's action row under the message list now carries **two** buttons, in the
|
|
99
|
+
mockup's order:
|
|
100
|
+
|
|
101
|
+
1. **"Call me now"** — new 2026-08-26, driven by `ChatPanel`'s new **`onCall` prop**. It
|
|
102
|
+
swaps `/landing` to the call screens; the flow, its dormant real backend, and the
|
|
103
|
+
Talos-chat-vs-Toga-dialer campaign distinction live in
|
|
104
|
+
[landing-call-flow.md](landing-call-flow.md). **(This corrects the previous "Call me
|
|
105
|
+
now is NOT rendered" note** — that was true only while the sole destination was the
|
|
106
|
+
parked studio call screen. Purpose-built landing screens replaced that dead end.)
|
|
107
|
+
2. **"Book a meeting"** — an `<a>` to the same booking href as the page's primary CTA
|
|
108
|
+
(still the dead Calendly link; see [security-landing-page.md](security-landing-page.md)).
|
|
109
|
+
|
|
95
110
|
## Deliberate deviations from the mockup drawer
|
|
96
111
|
|
|
97
|
-
- **"Call me now" is NOT rendered.** Its destination is the parked studio call screen,
|
|
98
|
-
unreachable from this page. The standing drawer action is **"Book a meeting"** →
|
|
99
|
-
the same Calendly href as the page's primary CTA.
|
|
100
112
|
- **`cta` and `cited_slides` are captured and typed but not rendered yet.** Both are
|
|
101
113
|
clean follow-ups; `cited_slides` additionally needs `GET /api/ai/campaign/{id}` plus
|
|
102
114
|
presigned-URL freshness handling (those URLs expire ~7 days — see the handoff).
|
|
@@ -128,15 +140,36 @@ So the decision: build the shell now, swap it later.
|
|
|
128
140
|
documented theming ceiling), so even fully themed it will **not** match the security
|
|
129
141
|
mockup drawer pixel-for-pixel. Accept that at swap time or fund the spacing-token pass.
|
|
130
142
|
|
|
143
|
+
**Status 2026-08-26 — decision re-affirmed: keep the disposable bridge shell.** A note to
|
|
144
|
+
**apeterson** is **drafted but not yet sent** (the developer will send it). What it has to
|
|
145
|
+
cover, so the alignment conversation starts from facts:
|
|
146
|
+
|
|
147
|
+
- our adapter **already matches his `TalosAdapter` seam** and is **wired live**;
|
|
148
|
+
- the two blockers are still blox-side and unscheduled — **unpublished to npm**, and
|
|
149
|
+
**React `^18` peers vs. BDR's React 19**;
|
|
150
|
+
- the **design divergence** the security mock introduces versus blox `TalosPanel`: the
|
|
151
|
+
**"Call me now" / "Book a meeting" action buttons under the message list**, and the
|
|
152
|
+
cobalt **`.security-shell` theme**. Those must be reconciled as **blox props/theme vs.
|
|
153
|
+
app-side composition** before a swap is possible — that decision is the real gate now,
|
|
154
|
+
not just publishing.
|
|
155
|
+
|
|
131
156
|
## Styling
|
|
132
157
|
|
|
133
158
|
The `.chatveil` / `.chatpanel` block was ported from the security mockup's `styles.css`
|
|
134
159
|
into `security.css` and is **scoped under `.security-shell`** like every other landing
|
|
135
|
-
rule. (
|
|
136
|
-
is
|
|
160
|
+
rule. (**Correction to an earlier note:** the mockup folder was deleted 2026-08-25 but
|
|
161
|
+
**re-added 2026-08-26**, so `security.css` is *not* the only copy — the mockup is back in
|
|
162
|
+
the repo as the design reference. `security.css` remains the only *shipped* copy.)
|
|
163
|
+
`z-index: 120`, width `min(400px, 92vw)`, reduced-motion respected.
|
|
137
164
|
`.chatmd-p` / `.chatmd-list` carry the reply rhythm. Escape and scrim click close the
|
|
138
165
|
drawer; focus moves into the composer on open and is restored to the opener on close.
|
|
139
166
|
|
|
167
|
+
**Action-row sizing (fixed 2026-08-26).** "Book a meeting" rendered visibly larger than the
|
|
168
|
+
mockup's. Root cause: the mockup's chat actions are `<button>`s, which browsers draw at the
|
|
169
|
+
~13.3px default button font because `.btn` never sets `font: inherit`; ours is an `<a>`
|
|
170
|
+
inheriting the panel's body size. Both are now pinned:
|
|
171
|
+
`.security-shell .chatactions .btn{font-size:13.5px;padding:0 16px;min-height:44px}`.
|
|
172
|
+
|
|
140
173
|
## Gotchas
|
|
141
174
|
|
|
142
175
|
- **`api.togaiq.com` chat endpoints are origin/UA-gated — verified 2026-08-25 with
|
|
@@ -157,21 +190,38 @@ drawer; focus moves into the composer on open and is restored to the opener on c
|
|
|
157
190
|
any merged/trimmed turns would break the strict alternating shape and return a 422.
|
|
158
191
|
- **The chat is fully client-side**, which is why `/landing` still builds as a
|
|
159
192
|
**prerendered static** route. Adding a server call here changes that.
|
|
160
|
-
- **`/landing` is not rate-limited** (`src/proxy.ts` matches
|
|
161
|
-
|
|
162
|
-
|
|
193
|
+
- **`/landing` is not rate-limited** (`src/proxy.ts` matches `/` and the API routes, not
|
|
194
|
+
the page). The drawer talks to a third-party origin, not a BDR API route, so the limiter
|
|
195
|
+
still does not apply to *chat* — but the abuse surface exists on the page. The drawer's
|
|
196
|
+
new "Call me now" action does hit a BDR route, and that one **is** limited
|
|
197
|
+
(`/api/landing-call`, on the `/api/call-now` bucket).
|
|
163
198
|
|
|
164
199
|
## How to work on it
|
|
165
200
|
|
|
166
201
|
- 30 new Vitest tests across `test/talosChat.test.ts` and `test/chatMarkdown.test.ts`
|
|
167
202
|
(message-shape rules including the 30-cap alternation, 401 re-mint-once, fallback on
|
|
168
203
|
network error and on 5xx, alternation preserved across a fallback turn, adapter
|
|
169
|
-
reset, markdown safety). Suite **
|
|
170
|
-
no-em-dash rule), and `next build` all clean
|
|
171
|
-
- **
|
|
172
|
-
|
|
204
|
+
reset, markdown safety). Suite **385/385 green** as of 2026-08-26 (was 367 before the
|
|
205
|
+
call-flow tests); `tsc`, `eslint` (incl. the no-em-dash rule), and `next build` all clean.
|
|
206
|
+
- **Shipped state:** the drawer landed on `_dev-sandbox` via **TRUE-80965**, merged by the
|
|
207
|
+
developer 2026-08-25. **TRUE-80966** ("Connect Talos UI to Backend Services") is
|
|
208
|
+
engineering-complete — chat is live from allowlisted origins — but **acceptance is
|
|
209
|
+
pending** on three items: the sandbox **origin allowlist**, rendering of **`cta` /
|
|
210
|
+
`cited_slides`**, and the **absent CRM lead sink**. The 2026-08-26 action-row changes are
|
|
211
|
+
uncommitted working tree.
|
|
173
212
|
|
|
174
213
|
## Change history
|
|
214
|
+
- 2026-08-26 — The drawer now renders **both** mockup actions: a new **"Call me now"**
|
|
215
|
+
button (via `ChatPanel`'s new `onCall` prop) above "Book a meeting" — **reversing** the
|
|
216
|
+
2026-08-24 decision not to render it, which only held while the destination was the
|
|
217
|
+
parked studio call screen (see [landing-call-flow.md](landing-call-flow.md) for the new
|
|
218
|
+
screens). FIXED "Book a meeting" rendering too large (`<a>` inherits body size, sibling
|
|
219
|
+
`<button>`s use the ~13.3px browser default; both pinned in `.chatactions .btn`).
|
|
220
|
+
CORRECTED the styling note — the security mockup was re-added to the repo on 2026-08-26,
|
|
221
|
+
so `security.css` is only the *shipped* copy, not the *only* copy. Re-affirmed the
|
|
222
|
+
TRUE-80692 "keep the disposable bridge shell" decision and recorded the drafted (not yet
|
|
223
|
+
sent) note to apeterson, including the new design divergence — action buttons + cobalt
|
|
224
|
+
theme — that must be reconciled as blox props/theme vs. app-side. (tcox)
|
|
175
225
|
- 2026-08-25 — Verified the origin gate with browser-shaped probes: `localhost:3000` is
|
|
176
226
|
allowed and returns live replies end-to-end; `bdr.dev.sandbox.togatech.com` gets 403 at
|
|
177
227
|
session mint (not allowlisted → deployed drawer runs on scripted fallback). Recorded the
|
|
@@ -6,7 +6,7 @@ project: AI-BDR
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-08-26
|
|
10
10
|
owners: [tcox]
|
|
11
11
|
files:
|
|
12
12
|
- bdr/src/server/vapiCall.ts
|
|
@@ -24,6 +24,7 @@ files:
|
|
|
24
24
|
- bdr/src/content/default.ts
|
|
25
25
|
- bdr/src/proxy.ts
|
|
26
26
|
related:
|
|
27
|
+
- landing-call-flow.md
|
|
27
28
|
- web-funnel-app.md
|
|
28
29
|
- vapi-integration.md
|
|
29
30
|
- call-orchestration.md
|
|
@@ -144,6 +145,13 @@ rules that are specific to this feature:
|
|
|
144
145
|
looks fake in this environment" is an **env** symptom, not a code bug.
|
|
145
146
|
|
|
146
147
|
## Change history
|
|
148
|
+
- 2026-08-26 — **Second consumer:** `/landing`'s new `SecurityCalling` screen reuses this
|
|
149
|
+
pipeline unchanged (`useCallStatusViewModel` + `/api/call-status` + the same
|
|
150
|
+
purpose-scoped status token), fed by the new `POST /api/landing-call` for anonymous
|
|
151
|
+
visitors. Two behavior notes specific to it: it shows **no "End call" button on the live
|
|
152
|
+
path**, and an **unavailable** status **holds at dialing** rather than faking progress.
|
|
153
|
+
The route is env-gated and currently dormant — see
|
|
154
|
+
[landing-call-flow.md](landing-call-flow.md). No change to this feature's own code. (tcox)
|
|
147
155
|
- 2026-07-29 — Created. BUILT the live call-status pipeline: `src/server/vapiCall.ts` reads
|
|
148
156
|
`GET https://api.vapi.ai/call/{id}` with the worker's Vapi bearer credential and drives a real
|
|
149
157
|
dialing / active / ended / not-answered lifecycle on the Success screen (replacing the ~5s
|