Twenty Partners 手动工作流运行手册:为每个 Twenty 工作区重建 Cron「每日摘要」工作流
导读:Twenty Partners 应用把 CRM 变成了 Twenty 合作伙伴计划的运营系统(应用概览见 Twenty Partners README),但它有一个极其特殊的基础设施约束——工作流(workflow)是工作区级元数据,无法随应用清单发布。本文即官方运行手册
workflows/README.md的完整展开:你会理解为什么 SDK 没有defineWorkflow、为什么每个安装该应用的 workspace 都必须由管理员手工重建 cron 工作流,并得到一份可直接照做的「Daily Digest 每日摘要」五步搭建指南、本地邮件测试方案与逐工作区核对清单。
为什么这需要一份「手动」运行手册
Twenty Partners 应用的核心业务逻辑都以 logic function(逻辑函数) 的形式随应用清单发布:例如 on-opportunity-partner-won 监听数据库事件、apply-to-brief 响应命令菜单调用。但工作流(Workflow)不在这一体系内——它不是可以在清单里声明的实体,而是由运营人员在 Twenty 工作区 UI 中手工绘制的可视化流程(触发器 → 查找记录 → 迭代 → 分支 → 发邮件)。
这一点与 Twenty SDK 的实体定义清单相互印证:翻阅 twenty-sdk 的 define 目录,可以看到 defineApplication、defineRole、defineField、defineCommandMenuItem、defineFrontComponent、defineLogicFunction 等生成器,但没有任何 defineWorkflow。整个仓库范围内也检索不到该 API。因此:
- 工作流不属于应用清单,不会随
deploy/install一起迁移到目标工作区; - 每一个安装了 Twenty Partners 的工作区(本地 bundle、staging、生产环境)都必须执行一次本手册的重建流程;
- 执行
yarn twenty install之后,生产工作区在工作流重建完成前是「空跑」状态——自动提醒不会发出。
这正是 src/workflows/README.md(即本手册原文,位于 workflows 目录)存在的原因:把「需要人工完成的搭建步骤」文档化,作为版本化的配置资产随仓库一起维护。
前置条件(Prerequisites)
开始重建前,先确认以下两点:
- 应用已安装并同步:工作区已安装 Twenty Partners 应用,并通过
yarn twenty apply完成同步(安装与部署命令实现见 install.ts 与 deploy.ts)。 - 不再需要 WORKFLOWS 权限标记:曾经的
WORKFLOWSpermission flag 是手工开关;现在 apply(提交申请)路径已经作为**命令菜单项(command menu item)**直接随应用清单发布,管理员无需再单独授予该标记。对应的清单实体可以在源码中看到,例如 apply-to-brief.command-menu-item.ts,它定义了当用户在命令菜单中触发「Apply to brief」时应用如何响应。
版本前提:应用声明需要
twenty >= 2.31.0(见 twenty-partners 的 package.json),请确保目标工作区 / CLI 版本满足要求后再执行重建。
赢家分配:这一步不需要工作流
在进入 cron 工作流之前,先澄清一个常见误解:合作伙伴赢家(Winner)的分配不需要任何工作流。
它的运转方式是纯「数据变更 + 逻辑函数」的链路:
- 管理员直接在关联的 Opportunity(商机)记录上设置 Partner 字段;
- 该写入触发数据库更新事件
opportunity.updated; - 应用清单中的逻辑函数
on-opportunity-partner-won被激活。
从 on-opportunity-partner-won.logic-function.ts 的源码可以看到它的精确行为:处理器先检查 updatedFields 中是否包含 partnerId,只有字段确实发生变化时才继续;随后调用 syncApplicationOutcomes 去级联更新该商机下所有 Application 的状态。
级联规则在 sync-application-outcomes.service.ts 中一目了然:
- 分配赢家时:
partnerId与赢家匹配的 Application →WON;其余竞争者 →DECLINED(视为落败); - 撤销分配时:仅将原本为
WON的 Application 重新打开为APPLIED; - 两个豁免状态:
BACKUP是介绍前就设定的常备候选名单,因此不随决策及其逆转而变动;DECLINED是终态,因为管理员也可能手工设置它,自动重开会撤销那次人工操作; - 整个过程以**应用身份(app identity)**运行,绕过合作伙伴的 RLS 锁。
结论:赢家分配步骤没有需要构建或重建的工作流。真正需要手工搭建的只有下面这一条 cron 工作流。
第 1 步:Daily Digest(每日摘要)cron 工作流
这条工作流的作用是:每当过去一天出现新 brief 时,向已通过验证(VALIDATED)的合作伙伴发送一次每日提醒邮件——只包含新增数量 + 链接,不含 brief 细节;如果没有新 brief(计数为 0),则不发送邮件。
因为工作流是工作区元数据、不随应用旅行,所以它必须在每个工作区手工构建。以下是 2026-08-25 在本地环境验证过的标准结构:
节点 1:触发器 —— Cron,每日 07:00
- 新建工作流,选择 Cron 触发器,频率设置为每日 07:00。
- 测试阶段可以暂时使用**手动触发器(manual trigger)**来反复触发验证;但在正式依赖它之前,必须切回 cron 触发器。
节点 2:查找记录 Search new opp —— 找出新 brief
目标对象为 Opportunity,过滤条件组合:
| 字段 | 操作符 | 取值 |
|---|---|---|
Creation date(创建日期) |
相对时间 | PAST_1_DAY(过去一天内) |
isListed |
等于 | true |
其中 isListed 是「是否上架到市场」的布尔标记,其字段定义见 opportunity-is-listed.field.ts。从应用源码看,这一标记在整条匹配链路中被反复使用(submit-client-brief 提交时置 true、简介发送后 on-opportunity-intro-sent 将其置回 false),因此「过去一天新上架的 brief」用它做过滤是最准确的信号。
⚠️ 务必把记录上限(record limit)调高——编辑器默认值只有 1,若保持默认,工作流每次都只会看到一条新 brief,计数会严重失真。
节点 3:查找记录 Search Partners —— 找出所有已验证合作伙伴
- 目标对象:Partner
- 过滤条件:
Validation Stage(验证阶段)为VALIDATED - 同样把 record limit 调高,确保不会漏掉任何一个应收到提醒的合作伙伴。
节点 4:迭代器(Iterator)+ 循环体
对节点 3 返回的每个合作伙伴执行循环,循环体内包含三个子节点:
- 查找记录
Search people partner:查找 Person,其关联字段Partner → Id等于{{currentItem.id}}(即当前迭代到的合作伙伴)。原理解释:Twenty 工作流引擎不会自动加载对象间的关联(relations),所以无法直接从 Partner 记录上读出联系邮箱——这条额外的查找就是用来把合作伙伴的联系人邮箱解析出来。 - If/Else 分支:仅当上一步的 Person 查找返回了邮箱时才继续;没有邮箱的合作伙伴直接跳过,避免发送失败或产生无效收件人。
- 发送邮件(Send Email):发件邮箱使用工作区已连接的邮箱账户(connected mailbox),收件人为上一步解析出的 Person 邮箱;邮件的主题和正文携带来自节点 2 的新 brief 计数以及生产环境市场(marketplace)链接。
节点 5:发布并确认状态为 ACTIVE
保存后点击发布,然后在版本列表中确认该工作流版本显示为 ACTIVE,才算真正生效。
生产环境前置要求
在正式工作区中,Daily Digest 依赖两样东西:
- 一个已连接的邮箱:Settings → Accounts 中完成连接(对应 SMTP 发信与 IMAP 收信);
- 应用本身已安装(保证 Partner / Opportunity / Person 等对象与字段就绪)。
本地邮件测试方案(严禁在生产环境使用)
本地联调时不要真发邮件。官方推荐的方案是使用 smtp4dev 起一个本地假 SMTP/IMAP 服务器,把邮件「投递」进浏览器界面:
docker run --rm -d --name smtp4dev -p 8090:80 -p 2525:25 -p 1143:143 rnwood/smtp4dev
随后按以下顺序完成配置:
- 关闭外发安全模式:把配置变量
OUTBOUND_HTTP_SAFE_MODE_ENABLED设为false(位置:Settings → Admin Panel → Config Variables)。原理解释:safe mode 会有意拦截发往内网/私有主机的 IMAP/SMTP 流量,host.docker.internal这类本地地址正是它的拦截对象。 - 连接一个 IMAP/SMTP 账户,参数如下:
| 参数 | 值 |
|---|---|
| Host | host.docker.internal |
| SMTP 端口 | 2525 |
| IMAP 端口 | 1143 |
| TLS | 关闭 |
| 凭据 | 任意(smtp4dev 不做鉴权) |
- 邮件会落在 http://localhost:8090 的 Web 界面里,可以直接查看收件箱、正文与附件渲染。
⚠️ 注意不要混淆两套邮件通道:
EMAIL_DRIVER相关环境变量属于 Twenty 服务器的系统邮件发送器(system mailer),与工作流「Send Email」节点所使用的工作区连接邮箱无关——本地测试期间保持它们原样即可,不要去改动。
每个工作区的核对清单
完成重建后,用这张清单做最终验收。每一条都应当在工作流编辑器中逐一核对:
| 项目 | 验收标准 |
|---|---|
| 触发器 | cron,每日(daily)触发 |
| 动作 | 通过 Person 查找,向每个 VALIDATED 合作伙伴发送一封邮件 |
| 发布标签 | 发布的工作流版本标签为 Daily Digest |
| 执行身份 | 以**应用角色(application role)**运行——该应用角色的 RLS 谓词定义见 partner.role.ts,并由 configure-partner-rls 脚本按角色标签定位后写入目标工作区 |
在每次 install 之后,对每一个目标工作区重复执行以上全部步骤。 工作流是工作区元数据——它们不会随 deploy / install 迁移;漏掉任何一个工作区(尤其是新接入的 staging / prod 环境),就意味着该环境永远不会发出每日提醒。
延伸阅读:本运行手册涉及的仓库资源
- 运行手册原文:src/workflows/README.md
- 应用整体介绍与 matchStatus 全流程:Twenty Partners README
- 应用清单配置(声明 per-workspace 应用变量):application-config.ts
- 赢家分配监听器(机会
partnerId变更 → 级联):on-opportunity-partner-won.logic-function.ts - Application 状态级联实现:sync-application-outcomes.service.ts
isListed市场标记字段定义:opportunity-is-listed.field.ts- apply 命令菜单项(清单随应用发布的示例):apply-to-brief.command-menu-item.ts
- Twenty SDK 的 define 实体目录(确认无 workflow 生成器):twenty-sdk/src/sdk/define
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00