From 9a0fc44f061b022c95b507965b25ce9c941d56a2 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 4 Jan 2026 18:32:39 -0500 Subject: [PATCH 1/3] Fix profile goal to only show for paid members and handle personal workspaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add is_member to PlannerContext to track membership status - Add is_personal_workspace to company context to identify auto-generated workspaces - Profile completion goal now only shows for paid members (profiles are only visible to members) - Personal workspaces show as "your account" instead of "User's Workspace" - Remove legacy addie_goal fallback in admin-users.html (planner is now primary) - Update all PlannerContext construction sites with new required fields 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .changeset/soft-apples-play.md | 4 +++ server/public/admin-users.html | 13 --------- server/src/addie/services/outbound-planner.ts | 7 +++-- .../src/addie/services/proactive-outreach.ts | 13 +++++++-- .../src/addie/services/rehearsal-service.ts | 1 + server/src/addie/types.ts | 4 +++ server/src/routes/admin-outbound.ts | 28 +++++++++++++------ server/src/routes/admin.ts | 9 +++++- 8 files changed, 51 insertions(+), 28 deletions(-) create mode 100644 .changeset/soft-apples-play.md diff --git a/.changeset/soft-apples-play.md b/.changeset/soft-apples-play.md new file mode 100644 index 0000000000..6bf8a359b8 --- /dev/null +++ b/.changeset/soft-apples-play.md @@ -0,0 +1,4 @@ +--- +--- + +Fix profile completion goal to only show for paid members, handle personal workspaces diff --git a/server/public/admin-users.html b/server/public/admin-users.html index 32a725bab8..cf2dc29a2a 100644 --- a/server/public/admin-users.html +++ b/server/public/admin-users.html @@ -1826,19 +1826,6 @@

Member Context

html += '
No action recommended at this time.
'; } html += ''; - } else if (context.addie_goal) { - // Fallback to legacy addie_goal if no planner data - html += '
'; - html += '

Addie\'s Goal

'; - html += `
- - ${escapeHtml(context.addie_goal.goal_name || formatGoalKey(context.addie_goal.goal_key))} - -
`; - if (context.addie_goal.reasoning) { - html += `

${escapeHtml(context.addie_goal.reasoning)}

`; - } - html += '
'; } // Capabilities Section - What has this member done? diff --git a/server/src/addie/services/outbound-planner.ts b/server/src/addie/services/outbound-planner.ts index 7ca4174cab..9d3fe2eec8 100644 --- a/server/src/addie/services/outbound-planner.ts +++ b/server/src/addie/services/outbound-planner.ts @@ -210,15 +210,16 @@ export class OutboundPlanner { } } - // PRIORITY 2: Profile completion (critical setup step) - if (caps && !caps.profile_complete) { + // PRIORITY 2: Profile completion (only for paid members - profiles are only visible to members) + // Skip for personal workspaces since those aren't real company profiles + if (caps && !caps.profile_complete && ctx.user.is_member && !ctx.company?.is_personal_workspace) { const profileGoal = goals.find(g => g.name.toLowerCase().includes('profile') && g.category === 'admin' ); if (profileGoal) { return { goal: profileGoal, - reason: 'Profile not complete - key capability to unlock', + reason: 'Profile not complete - visible to other members once set up', priority_score: 85, alternative_goals: goals.filter(g => g.id !== profileGoal.id).slice(0, 3), decision_method: 'rule_match', diff --git a/server/src/addie/services/proactive-outreach.ts b/server/src/addie/services/proactive-outreach.ts index f0f8d5fc6a..365f900c16 100644 --- a/server/src/addie/services/proactive-outreach.ts +++ b/server/src/addie/services/proactive-outreach.ts @@ -172,14 +172,16 @@ async function buildPlannerContext(candidate: OutreachCandidate): Promise( - `SELECT o.name, o.company_types + `SELECT o.name, o.company_types, o.subscription_status FROM organization_memberships om JOIN organizations o ON o.workos_organization_id = om.workos_organization_id WHERE om.workos_user_id = $1 @@ -188,10 +190,14 @@ async function buildPlannerContext(candidate: OutreachCandidate): Promise ({ type: i.insight_type_name ?? 'unknown', diff --git a/server/src/addie/services/rehearsal-service.ts b/server/src/addie/services/rehearsal-service.ts index 64e75a5569..5b7dd90c7c 100644 --- a/server/src/addie/services/rehearsal-service.ts +++ b/server/src/addie/services/rehearsal-service.ts @@ -333,6 +333,7 @@ export class RehearsalService { slack_user_id: `rehearsal_${persona.name?.replace(/\s+/g, '_').toLowerCase() ?? 'user'}`, display_name: persona.name, is_mapped: isMapped, + is_member: true, // Rehearsals assume member context engagement_score: persona.engagement_score ?? 50, insights: (persona.existing_insights ?? []).map(i => ({ type: i.type, diff --git a/server/src/addie/types.ts b/server/src/addie/types.ts index f1c90516d5..871638c35e 100644 --- a/server/src/addie/types.ts +++ b/server/src/addie/types.ts @@ -363,6 +363,8 @@ export interface PlannerContext { workos_user_id?: string; display_name?: string; is_mapped: boolean; + /** Whether the user's org has an active AgenticAdvertising.org membership */ + is_member: boolean; engagement_score: number; insights: Array<{ type: string; @@ -375,6 +377,8 @@ export interface PlannerContext { type: string; size?: string; offerings?: string[]; + /** Whether this is a real company org vs an auto-generated personal workspace */ + is_personal_workspace?: boolean; }; /** What capabilities has/hasn't this member unlocked? */ capabilities?: MemberCapabilities; diff --git a/server/src/routes/admin-outbound.ts b/server/src/routes/admin-outbound.ts index d38e2d75ee..a31747fafe 100644 --- a/server/src/routes/admin-outbound.ts +++ b/server/src/routes/admin-outbound.ts @@ -444,6 +444,7 @@ export function createAdminOutboundRouter(): { pageRouter: Router; apiRouter: Ro workos_user_id: memberContext?.workos_user?.workos_user_id, display_name: memberContext?.slack_user?.display_name ?? undefined, is_mapped: !!memberContext?.is_mapped, + is_member: memberContext?.is_member ?? false, engagement_score: 0, // Could compute from activity later insights: insights.map(i => ({ type: i.insight_type_name ?? 'unknown', @@ -451,10 +452,15 @@ export function createAdminOutboundRouter(): { pageRouter: Router; apiRouter: Ro confidence: i.confidence, })), }, - company: memberContext?.organization ? { - name: memberContext.organization.name, - type: 'unknown', // Would need to look up company_type from organization - } : undefined, + company: memberContext?.organization ? (() => { + const orgName = memberContext.organization!.name; + const isPersonalWorkspace = orgName.toLowerCase().endsWith("'s workspace"); + return { + name: isPersonalWorkspace ? 'your account' : orgName, + type: 'unknown', // Would need to look up company_type from organization + is_personal_workspace: isPersonalWorkspace, + }; + })() : undefined, history, contact_eligibility: { can_contact: contactEligibility.canContact, @@ -531,6 +537,7 @@ export function createAdminOutboundRouter(): { pageRouter: Router; apiRouter: Ro workos_user_id: workosUserId, display_name: memberContext?.slack_user?.display_name ?? undefined, is_mapped: !!memberContext?.is_mapped, + is_member: memberContext?.is_member ?? false, engagement_score: capabilities.slack_message_count_30d > 10 ? 75 : capabilities.slack_message_count_30d > 5 ? 50 : capabilities.slack_message_count_30d > 0 ? 25 : 0, @@ -540,10 +547,15 @@ export function createAdminOutboundRouter(): { pageRouter: Router; apiRouter: Ro confidence: i.confidence, })), }, - company: memberContext?.organization ? { - name: memberContext.organization.name, - type: 'unknown', - } : undefined, + company: memberContext?.organization ? (() => { + const orgName = memberContext.organization!.name; + const isPersonalWorkspace = orgName.toLowerCase().endsWith("'s workspace"); + return { + name: isPersonalWorkspace ? 'your account' : orgName, + type: 'unknown', + is_personal_workspace: isPersonalWorkspace, + }; + })() : undefined, capabilities, history, contact_eligibility: { diff --git a/server/src/routes/admin.ts b/server/src/routes/admin.ts index a0052ce663..9ad1d01efd 100644 --- a/server/src/routes/admin.ts +++ b/server/src/routes/admin.ts @@ -260,12 +260,18 @@ export function createAdminRouter(): { pageRouter: Router; apiRouter: Router } { canContactUser(slackUserId), ]); + // Check if this is a personal workspace (auto-generated "User's Workspace" name) + const orgName = context.organization?.name ?? ''; + const isPersonalWorkspace = orgName.toLowerCase().endsWith("'s workspace") || + orgName.toLowerCase().endsWith("'s workspace"); + const plannerCtx: PlannerContext = { user: { slack_user_id: slackUserId, workos_user_id: workosUserId, display_name: context.slack_user?.display_name ?? undefined, is_mapped: !!workosUserId, + is_member: context.is_member ?? false, engagement_score: capabilities.slack_message_count_30d > 10 ? 75 : capabilities.slack_message_count_30d > 5 ? 50 : capabilities.slack_message_count_30d > 0 ? 25 : 0, @@ -276,8 +282,9 @@ export function createAdminRouter(): { pageRouter: Router; apiRouter: Router } { })), }, company: context.organization ? { - name: context.organization.name, + name: isPersonalWorkspace ? 'your account' : context.organization.name, type: 'unknown', + is_personal_workspace: isPersonalWorkspace, } : undefined, capabilities, history, From 401e1fc318163332205589fb057e5a90ef1f5f97 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 4 Jan 2026 19:42:09 -0500 Subject: [PATCH 2/3] Add admin collaboration features for outreach MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add insight button to admin user detail panel - Update year references (2025 -> 2026 cleanup) via migration 132 - Add vendor membership goal for tech companies via migration 133 - Add admin nudge/override for outreach direction - Fix message personalization to use first name instead of full name - Fix input validation for goal_id in admin insights endpoint - Fix event listener accumulation in nudge modal - Fix personal workspace detection consistency across files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .changeset/wicked-memes-act.md | 4 + .gitignore | 1 + server/public/admin-users.html | 429 +++++++++++++++++- server/src/addie/services/outbound-planner.ts | 29 +- .../src/addie/services/proactive-outreach.ts | 164 +++++++ server/src/addie/types.ts | 2 +- server/src/db/insights-db.ts | 11 + .../132_cleanup_year_references.sql | 37 ++ .../migrations/133_vendor_visibility_goal.sql | 81 ++++ server/src/routes/admin-insights.ts | 49 ++ server/src/routes/admin-outbound.ts | 8 +- 11 files changed, 802 insertions(+), 13 deletions(-) create mode 100644 .changeset/wicked-memes-act.md create mode 100644 server/src/db/migrations/132_cleanup_year_references.sql create mode 100644 server/src/db/migrations/133_vendor_visibility_goal.sql diff --git a/.changeset/wicked-memes-act.md b/.changeset/wicked-memes-act.md new file mode 100644 index 0000000000..7596795353 --- /dev/null +++ b/.changeset/wicked-memes-act.md @@ -0,0 +1,4 @@ +--- +--- + +Admin collaboration features for outreach: add insight button, year reference cleanup, vendor membership goal, admin nudge/override diff --git a/.gitignore b/.gitignore index a9d73295d5..b99871c163 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ server/node_modules # Misc .DS_Store +cookies.txt .conductor_port .env.local .env.development.local diff --git a/server/public/admin-users.html b/server/public/admin-users.html index cf2dc29a2a..8392505901 100644 --- a/server/public/admin-users.html +++ b/server/public/admin-users.html @@ -895,6 +895,83 @@

Member Context

+ + + + + +