toga-ai 1.0.285 → 1.0.287

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.
@@ -5,4 +5,5 @@
5
5
  | [AI-BDR Architecture](architecture.md) | **AI-BDR** is TOGA's outbound AI sales-development representative. | ai-bdr/README.md, ai-bdr/requirements.txt, ai-bdr/.env.example, 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/vapi/templates/system-prompt.template.md, ai-bdr/vapi/templates/assistant.template.json, 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/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 |
6
6
  | [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 |
7
7
  | [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 |
8
+ | [Web Funnel — Next.js UI + Config-Driven Campaign Content](features/web-funnel-content-model.md) | The **public-facing web funnel** is the new UI / lead-capture front door of the **same AI-BDR product** documented in `../architecture.md`. | bdr/src/content/schema.ts, bdr/src/content/hubspotProvider.ts, bdr/src/content/useCampaign.ts, bdr/src/content/default.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 |
8
9
  | [New-Campaign Onboarding (6-phase runbook)](workflows/new-campaign-onboarding.md) | The end-to-end procedure for launching a new outbound BDR campaign (industry, offer, target persona, geography). | ai-bdr/docs/client-onboarding-sop.md, ai-bdr/docs/calcom-new-campaign-event-guide.md, ai-bdr/prompts/templates/campaign-template.md, ai-bdr/scripts/update_assistant.py, ai-bdr/scripts/update_system_prompt.py |
@@ -6,8 +6,8 @@ project: AI-BDR
6
6
  client: shared
7
7
  type: architecture
8
8
  status: active
9
- updated: 2026-06-16
10
- owners: [akhokhani]
9
+ updated: 2026-07-08
10
+ owners: [akhokhani, tcox]
11
11
  files:
12
12
  - ai-bdr/README.md
13
13
  - ai-bdr/requirements.txt
@@ -30,6 +30,7 @@ files:
30
30
  related:
31
31
  - features/vapi-integration.md
32
32
  - features/call-orchestration.md
33
+ - features/web-funnel-content-model.md
33
34
  - workflows/new-campaign-onboarding.md
34
35
  ---
35
36
 
@@ -175,6 +176,19 @@ authored here, then pushed to Vapi by `scripts/update_system_prompt.py`.
175
176
  - **No Langfuse / OTEL** — there is no TOGA-side LLM call to trace; the
176
177
  LLM call happens inside Vapi.
177
178
 
179
+ ## Public web funnel (UI / lead-capture front door)
180
+
181
+ The AI-BDR product now has a **public-facing web funnel** — a **Next.js (App Router)**
182
+ app in its own code repo, **`bdr`** — as its UI and lead-capture front door. It **reuses
183
+ this system's existing calls** (HubSpot contact fetch, Toga upsert + campaign link,
184
+ `requestCall` callback — see *End-to-end call flow* and `features/call-orchestration.md`),
185
+ ported from the external `info` repo behind `LeadSink` / `CallbackService` adapters so all
186
+ secret-bearing calls stay server-side. On top of that reuse it adds a **HubSpot-owned,
187
+ config-driven campaign content layer**: marketing authors campaign content in HubSpot,
188
+ fetched by slug at runtime and mapped to a serializable `CampaignBundle` (config-driven
189
+ components, `DEFAULT` fallback — no `if(campaign===x)`; a copy change ships with no
190
+ redeploy). Full detail: **`features/web-funnel-content-model.md`**.
191
+
178
192
  ## Key decisions
179
193
 
180
194
  - **Single shared structured-output schema** across all assistants — keeps
@@ -195,3 +209,7 @@ authored here, then pushed to Vapi by `scripts/update_system_prompt.py`.
195
209
 
196
210
  ## Change history
197
211
  - 2026-06-16 — Initial architecture doc for AI-BDR. (akhokhani)
212
+ - 2026-07-08 — Additive: recorded the public Next.js web funnel (code repo `bdr`) as the
213
+ product's UI / lead-capture front door — reuses this system's existing HubSpot/Toga/callback
214
+ calls and adds a HubSpot-owned, config-driven campaign content layer. See
215
+ `features/web-funnel-content-model.md`. (tcox)
@@ -0,0 +1,148 @@
1
+ ---
2
+ title: Web Funnel — Next.js UI + Config-Driven Campaign Content
3
+ framework: "2.0"
4
+ repo: ai-bdr
5
+ project: AI-BDR
6
+ client: shared
7
+ type: feature
8
+ status: draft
9
+ updated: 2026-07-08
10
+ owners: [tcox]
11
+ files:
12
+ - bdr/src/content/schema.ts
13
+ - bdr/src/content/hubspotProvider.ts
14
+ - bdr/src/content/useCampaign.ts
15
+ - bdr/src/content/default.ts
16
+ - bdr/src/server/leadSink.ts
17
+ - bdr/src/server/callbackService.ts
18
+ - bdr/src/app/api/call-now/route.ts
19
+ - bdr/src/app/api/call-later/route.ts
20
+ related:
21
+ - ../architecture.md
22
+ - call-orchestration.md
23
+ ---
24
+
25
+ ## What this is
26
+
27
+ The **public-facing web funnel** is the new UI / lead-capture front door of the **same
28
+ AI-BDR product** documented in `../architecture.md`. It is a **Next.js (App Router) +
29
+ TypeScript** app living in its own code repo, `bdr` (registered separately because it is a
30
+ distinct codebase). Only the UI/content layer is new — the funnel **reuses this AI-BDR
31
+ system's existing backend calls** (see "Backend reuse" below); the API/backend contracts
32
+ are already documented and are **not** restated here.
33
+
34
+ The UI is built pixel-perfect from the mockup at `BDR/mockup`: a **7-screen "Agent Studio"
35
+ flow** — Welcome → AutoBuild → Capabilities → Call Now / Schedule → Success. The
36
+ agent-identity model is **form × slot × accent × voice × name** (form Human/Owl/Robot ×
37
+ slot 0–8 × one of 5 TOGA accents → `--accent` × voice × name). Two shipped-copy rules from
38
+ the mockup are load-bearing: a **single `--accent` source** (every accent-tinted element
39
+ routes through `var(--accent)` + derived `--accent-fill`; never hardcode an accent) and
40
+ **no em dashes** in any shipped copy.
41
+
42
+ Full implementation plan: `BDR/PLAN.md`.
43
+
44
+ ## Config-driven campaign content (HubSpot-owned, fetched by slug at runtime)
45
+
46
+ Every campaign is a **serializable data bundle** (`CampaignBundle`) keyed by a stable
47
+ `slug`. **Campaign content is authored by the marketing team in HubSpot** and fetched **by
48
+ slug at runtime, server-side**, then mapped to a `CampaignBundle`. Content is **not** stored
49
+ in the repo — a new campaign or a copy edit ships with **no developer and no redeploy**,
50
+ only a HubSpot edit.
51
+
52
+ Config-driven, per TOGA precedent (`toga25-supply`'s `useClientFields`,
53
+ `toga2-commerce`'s Cart C1–C7): components are **pure renderers of a bundle**; there is
54
+ **no `if (campaign === 'x')` branching** anywhere in the screens. The repo holds only the
55
+ **schema** (`CampaignBundle` types), the **HubSpot→bundle mapping** (provider), and a
56
+ minimal safety-net **`DEFAULT`** bundle (an engineering fallback, not authored copy).
57
+
58
+ - **Provider seam.** `CampaignContentProvider.resolve(slug)` returns a `CampaignBundle` (or
59
+ `DEFAULT` on an unknown/unreachable slug). `HubSpotContentProvider` runs **server-side**
60
+ (page server component / route handler), maps HubSpot content to a `CampaignBundle`,
61
+ caches it (Next revalidation), and applies the no-em-dash normalization pass. Client
62
+ components receive the resolved bundle as props / via `useCampaign()` and never see
63
+ HubSpot.
64
+ - **Serializable-only bundles.** Non-serializable behavior (a CTA action, an icon) is
65
+ referenced by an **enum key** and hydrated at the view-model layer through a
66
+ `Record<key, fn|Component>` registry — HubSpot only ever stores plain strings/values.
67
+ - **Campaign selection is URL-driven, per-request.** `resolveCampaignId()`, first match
68
+ wins: `?campaign=<slug>` → mapped `?hsCampaignId=<id>` (id→slug lookup so existing
69
+ outreach/CRM links keep working) → `DEFAULT`. Query-param entry ships first (matches how
70
+ leads arrive and how the ported backend already reads `?hsCampaignId=`); optional
71
+ `/c/[slug]` path later. No global "current campaign" state — selection is derived from
72
+ each visitor's URL, so any number of campaigns run **simultaneously**.
73
+ - **Attribution travels in the bundle.** The resolved bundle carries `hsCampaignId` and
74
+ `togaCampaignUuid`, used when the lead is upserted/linked and for per-campaign GA4.
75
+
76
+ ### CampaignBundle shape (contract)
77
+
78
+ Keyed by `slug` (`id`). Carries `agent: AgentIdentity` (form × slot × accent → `--accent` ×
79
+ voice × name; the default flow does not let the user pick, so agent identity is effectively
80
+ campaign config), plus `welcome`, optional `landing`, `capabilities` (skill cards +
81
+ `anchor`/`pool`/`sets` Q&A bank + CTAs), `call`, `schedule`, `success` (`callSummaries`),
82
+ optional `build` (AutoBuild beats), `analytics`, and `features[]` capability flags.
83
+ `RichLine` supports the accent-inked span the mockup uses (e.g. `"<agent> is ready to
84
+ work."`). The mapping layer is the seam between HubSpot storage and this contract.
85
+
86
+ ## Backend reuse (from the `info` repo; existing AI-BDR calls)
87
+
88
+ The funnel's Call Now / Schedule screens submit to a real backend **ported from the
89
+ external `info` repo** (github.com/agilantsolutions/info — a Next.js app whose `/bdr` route
90
+ already runs these calls in production). `info` is an **external reference repo, not a TOGA
91
+ registry repo** — port its logic, do not register or rebuild it. The ported logic drives
92
+ **this AI-BDR system's existing backend calls** (HubSpot contact fetch, Toga upsert +
93
+ campaign link, `requestCall` callback) — those contracts are already documented in
94
+ `call-orchestration.md` and `../architecture.md`; do **not** restate them.
95
+
96
+ Because BDR is Next.js there is no separate backend to host — all secret-bearing calls run
97
+ server-side (route handlers / server actions in `app/api/*`), wrapped behind two adapters
98
+ so only the impl changes later:
99
+
100
+ - `LeadSink.upsertLead(input)` → Toga upsert-by-email + campaign link + HubSpot.
101
+ - `CallbackService.requestCall(ref, whenOrNow, phone)` → Toga `requestCall`, immediate vs.
102
+ scheduled mapped to `CONTACT_CALL_TYPE_UUID`.
103
+
104
+ Note on "campaign": `info` uses HubSpot **only to fetch the contact**; the URL's
105
+ `hsCampaignId` is passed to Toga as the attribution campaign UUID
106
+ (`addContactToCampaign`) — attribution-only in `info`. **Fetching marketing content by
107
+ slug from HubSpot is net-new** to this funnel (above). The content slug and the attribution
108
+ UUID are related but distinct; the `CampaignBundle` carries the UUID so a slug maps to the
109
+ right campaign for logging.
110
+
111
+ ### Environment (values live in env / Amplify config, never in the repo)
112
+
113
+ - `HUBSPOT_ACCESS_TOKEN`
114
+ - `TOGA_API_BASE_URL`, `TOGA_CLIENT_ID`, `TOGA_CLIENT_API_UUID`, `TOGA_CLIENT_API_SECRET`
115
+ - `NEXT_PUBLIC_GA_MEASUREMENT_ID`
116
+
117
+ ## Gotchas
118
+
119
+ - **HubSpot content mechanism is an OPEN question that gates the content-layer phase.**
120
+ `info` gives no precedent (it only *reads contacts*). Candidate homes: **HubDB** (CMS-Hub
121
+ table — one row per campaign, `slug` column + content columns, API-fetchable; likely fit)
122
+ vs. a **custom object** vs. properties on the Campaigns object. The hard part is the
123
+ **nested content** — Q&A pool + rotation sets, call summaries, accent `RichLine`s — which
124
+ flat columns can't hold cleanly, so it needs child rows or a structured (JSON) field.
125
+ Settle with the HubSpot owner before building the content layer.
126
+ - **Keep secrets server-side.** `TOGA_CLIENT_API_SECRET` / `HUBSPOT_ACCESS_TOKEN` and the
127
+ token exchange must live in server components / route handlers, never in a `"use client"`
128
+ component or a `NEXT_PUBLIC_` env var (those ship to the browser). The interactive Agent
129
+ Studio screens are client components, so this boundary is load-bearing.
130
+ - **Do NOT port `info`'s debug `console.log`s** — several log PII (email/phone). Log
131
+ identifiers only.
132
+ - **Do not port `info`'s hero/curiosity marketing UI** — it is a different, older funnel.
133
+ - **`DEFAULT` must always render.** An unknown/absent slug or a brief HubSpot outage falls
134
+ back to the built-in safety net — never a 404 funnel.
135
+ - **Runtime fetch adds latency + an availability dependency.** Cache server-side with short
136
+ revalidation; mind HubSpot API rate limits.
137
+ - **No em dashes in any shipped copy** (mockup rule) — can't be linted on HubSpot-authored
138
+ copy, so enforce via marketing author guidance + the normalization pass; in-repo strings
139
+ get an ESLint no-em-dash check.
140
+ - **Single accent source** (mockup rule): every accent-tinted element routes through
141
+ `var(--accent)` (+ derived `--accent-fill`); never hardcode an accent value.
142
+
143
+ ## Change history
144
+ - 2026-07-08 — Initial doc: Next.js App Router web funnel as the AI-BDR product's UI /
145
+ lead-capture front door; config-driven campaign bundle authored in HubSpot and fetched by
146
+ slug server-side at runtime (`DEFAULT` fallback, URL-driven per-request selection); backend
147
+ ported from the external `info` repo behind LeadSink/CallbackService adapters, reusing this
148
+ AI-BDR system's existing calls. HubSpot content mechanism still open. (tcox)
@@ -6,7 +6,7 @@ project: Worker
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-23
9
+ updated: 2026-07-08
10
10
  owners: ["jcardinal"]
11
11
  files:
12
12
  - worker2/Worker/Clickup.php
@@ -69,8 +69,26 @@ None — internal team/sprint tooling, uniform across clients.
69
69
  - **No PHPUnit harness** in worker2. The regression test
70
70
  `Tests/Worker/ClickupWorkTypeTest.php` is a plain-PHP script (reflection on
71
71
  `isStatusComplete`) run with `php Tests/Worker/ClickupWorkTypeTest.php`.
72
+ - **ClickUp dropdown custom-field `value` is the option ORDERINDEX, not its name.** The
73
+ webhook payload for a dropdown/labels custom field delivers the selected option's integer
74
+ `orderindex`, *not* the option's display name. Any name-keyed lookup (e.g.
75
+ `$lookupPreviousSprintWorkTypeByName`) will silently never match if fed the raw `value`.
76
+ Resolve the name first via the field's own `type_config->options` — build a map keyed by
77
+ `orderindex` (`$workTypesByIndex[$opt->orderindex] = $opt`) and read `->name` from it. This
78
+ is the proven pattern in `_Worker_Team_Sprint::SprintLock` (`Worker/Team/Sprint.php`, the
79
+ Work Type resolve ~L3894 and the POST ~L3971). Guard the lookup with
80
+ `isset($workTypesByIndex[$value])` so a stale/deleted option orderindex doesn't warn.
72
81
 
73
82
  ## Change history
83
+ - 2026-07-08 — Fixed **Previous Sprint Work Type** never being set on tasks created after a
84
+ sprint launched (`taskCreated` case, `Worker/Clickup.php` ~L715-751). Root cause: the handler
85
+ assigned `$workType = $customField->value` (the ClickUp option orderindex, an int) then looked
86
+ it up in a name-keyed map, so the `isset()` guard never matched and the POST silently never
87
+ fired. Now resolves orderindex→name via the WORKTYPE field's `type_config->options`
88
+ (`$workTypesByIndex`), matching the `SprintLock` convention, with an added
89
+ `isset($workTypesByIndex[$value])` guard. Also added: empty Work Type now defaults to
90
+ `Unplanned` before the Previous Sprint Work Type lookup so newly-created tasks still get a
91
+ value. `php -l` clean; php-reviewer approved. (jcardinal)
74
92
  - 2026-06-23 — Fixed dependents staying Conditional after a blocker completed: completion check
75
93
  now treats `closed` as terminal alongside `done`, and `taskStatusUpdated` re-evaluates
76
94
  dependents (gated to terminal statuses). Added `COMPLETE_STATUS_TYPES`, `isStatusComplete()`,
@@ -27,10 +27,11 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
27
27
  - **toga2-hub** (TOGa Hub) — 2 doc(s) → [2.0/apps/toga2-hub/INDEX.md](2.0/apps/toga2-hub/INDEX.md)
28
28
  - **talos** (TOGa IQ) — 7 doc(s) → [2.0/apps/talos/INDEX.md](2.0/apps/talos/INDEX.md)
29
29
  - **voice-to-voice** (TOGa Voice) — 4 doc(s) → [2.0/apps/voice-to-voice/INDEX.md](2.0/apps/voice-to-voice/INDEX.md)
30
- - **ai-bdr** (AI-BDR) — 4 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
30
+ - **ai-bdr** (AI-BDR) — 5 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
31
31
  - **toga2-commerce** (TOGa Commerce) — 7 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
32
32
  - **toga25-supply** (TOGa 2.5 Supply) — 8 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
33
33
  - **toga-blox** (TOGa Blox) — 7 doc(s) → [2.0/apps/toga-blox/INDEX.md](2.0/apps/toga-blox/INDEX.md)
34
+ - **bdr** (BDR) — 0 doc(s) → [2.0/apps/bdr/INDEX.md](2.0/apps/bdr/INDEX.md)
34
35
 
35
36
  ## standalone framework
36
37
 
@@ -210,5 +210,14 @@
210
210
  "dependsOn": [
211
211
  "library"
212
212
  ]
213
+ },
214
+ {
215
+ "repo": "bdr",
216
+ "project": "BDR",
217
+ "framework": "2.0",
218
+ "role": "app",
219
+ "dependsOn": [
220
+ "api2"
221
+ ]
213
222
  }
214
223
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.285",
3
+ "version": "1.0.287",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",