首页
/ Twenty 应用扩展开发规范:以 Last Contact 为例拆解 UUID v4、dev:add 脚手架与布局陷阱

Twenty 应用扩展开发规范:以 Last Contact 为例拆解 UUID v4、dev:add 脚手架与布局陷阱

2026-09-07 14:21:12作者:盛欣凯Ernestine

在 Twenty 平台上,任何自定义应用(App)都通过一组可同步的"实体"(Entity)与引擎协作。仓库中的 Last Contact AGENTS 文档 正是面向 AI 编码代理(Agent)的二十扩展开发规范,它浓缩了 yarn twenty dev:add 脚手架用法、实体目录约定、UUID v4 硬性约束与两类高频陷阱。本文以仓库内的 Last ContactPostcard 富示例 应用为实证,逐条拆解这份规范背后的源码依据,帮助你(或你正在驱动的 Agent)在 Twenty 上高效、合规地开发 CRM 扩展应用。

Last Contact 应用在 People 列表上提供的 Last contact / Last contact by / Last contact item 实时列

一、AGENTS.md 的定位:给 Agent 的"扩展开发知识索引"

AGENTS.md 通常作为仓库级 Agent 指引存在,而这份文档把 Twenty App 开发者的完整知识体系分成七大类,每类都对应源码中的具体落点:

知识域 关注内容 仓库内对应实证
Getting started 快速上手、核心概念、项目结构、本地服务器、脚手架、故障排查 Twenty SDK CLI 提供的 dev / dev:add / typecheck 命令族
Config 应用声明、角色、安装钩子、公共资源 application-config.tsdefault-role.ts
Data 对象、扩展对象、关系 src/fields/ 下的字段定义,如 last-contact-by.field.ts
Logic 逻辑函数、技能与 Agent、连接 src/logic-functions/,如 on-email-interaction.ts
Layout 视图、导航项、页面布局、前端组件、命令菜单项 Twenty 前端渲染器 与各应用的 src/views/src/navigation-menu-items/
Operations CLI、测试、发布 SDK 的 deploy / install / publish 命令及 src/__tests__/src/**/__tests__/ 测试目录
富示例 完整可运行的参考应用 postcard 示例(被文档直接推荐为 rich app example)

对开发者的启示是:遇到不确定的实体定义方式时,优先查看本地 src/ 目录结构与已发布应用(public 目录下每个子目录就是一个真实 App)的既有写法,而不是凭空猜测。

二、UUID 硬性约束:所有实体 ID 必须是 UUID v4

文档明确要求:"All generated UUIDs must be valid UUID v4."。在 Twenty 的同步模型里,universalIdentifier 是应用实体跨环境稳定引用的主键——同一实体的字段、对象、视图都需要靠它完成与引擎元数据的映射,因此"生成合法 UUID v4"是硬性约束而非建议。

从 Last Contact 的实现可以反推这一规范为何被单列为一条:

  • 应用、角色、各逻辑函数、各字段的 universalIdentifier 全部集中在 universal-identifiers.ts 一个文件中,例如应用 66a504cc-0a75-410e-a43f-cdeae1db1522、邮件交互函数 87c8926b-15e3-43ed-a303-60f6d072f351。它们均为 36 位标准 v4 格式(含连字符,版本位为 4)。
  • 定义字段时,defineFielduniversalIdentifierrelationTargetFieldMetadataUniversalIdentifier 都要引用该常量文件,避免字符串散落造成不一致(见 last-contact-by.field.ts)。

手动复制 UUID 极易出错(多一个字符、非 v4、重复引用),这正是下一节 dev:add 会成为"最佳实践"的根本原因。

三、最佳实践:用 yarn twenty dev:add 生成实体

文档的核心建议是:创建新实体时优先使用 yarn twenty dev:add,让脚手架替你生成合法 ID 与规范模板。命令的实体清单及产物如下:

实体类型 命令 生成文件
Object yarn twenty dev:add object src/objects/<name>.ts
Field yarn twenty dev:add field src/fields/<name>.ts
Logic function yarn twenty dev:add logicFunction src/logic-functions/<name>.ts
Front component yarn twenty dev:add frontComponent src/front-components/<name>.tsx
Role yarn twenty dev:add role src/roles/<name>.ts
Skill yarn twenty dev:add skill src/skills/<name>.ts
Agent yarn twenty dev:add agent src/agents/<name>.ts
View yarn twenty dev:add view src/views/<name>.ts
Navigation menu item yarn twenty dev:add navigationMenuItem src/navigation-menu-items/<name>.ts
Page layout yarn twenty dev:add pageLayout src/page-layouts/<name>.ts

注意:命令实际支持的实体类型比表格更多。在 CLI 实现 add.ts 中,EntityAddCommand.execute 还覆盖了 viewFieldpageLayoutTabcommandMenuItemtimelineActivityTypeconnectionProvider,并且生成目录统一取实体的 kebab-case 复数形式(src/${kebabCase(entity)}s/),命名风格与 Last Contact 的 src/fields/src/logic-functions/ 完全一致。

3.1 脚手架自动生成 UUID v4

这是"省心"的关键来源:add.tsgetEntityData 分支中,Object 和 View 的 universalIdentifier 直接通过 v4()uuid 包)现场生成:

  • 创建 Object 时自动生成 objectUniversalIdentifiernameFieldUniversalIdentifier 两个 v4(代码约在 add.ts 的 getObjectData 返回后);
  • 创建 View 时需要传入 applicationUniversalIdentifier 派生相关 ID。

也就是说,规范表里"Object / View"等实体的 ID 均由脚手架内置的 v4() 填充,天然满足"UUID v4"约束,开发者无需手抄 UUID。

3.2 Object 的配套实体联动生成

add.ts 的另一个亮点是 promptAndCreateObjectCompanions:创建 Object 后,命令行会询问是否同时生成配套的 View(all-<object>.ts)、Navigation menu item、Record page layout,三者的 universalIdentifier 同样用 v4() 分配。这与文档 "Common Pitfalls" 第一条(见下节)互相印证:Twenty 的对象默认就应配套视图、导航与记录页布局,避免产生"孤儿视图"。

3.3 交互式校验规则

脚手架大量使用 inquirer 交互,并对命名做了即时校验:

  • Object 的名称单复数不可相同(如 company / companies);
  • Field 需要指定归属对象的 universalIdentifier,关系字段还额外要求目标对象/目标字段的 ID、关系类型(RelationType)与删除行为(RelationOnDeleteAction,默认 CASCADE);
  • View 必须绑定到某个对象(objectUniversalIdentifier,默认 fill-later,需要后续回填)。

这些交互把文档中关于 Config / Data / Layout 的抽象规则,落地成了"输错就提示、缺参就占位"的可执行流程。

四、Common Pitfalls 详解:两条最容易被 Agent 踩中的坑

文档只列了两条高频陷阱,但每条背后都有明确的工程原因。

4.1 视图必须绑定导航项,否则会"裸奔"进左侧边栏

原文要点:"Creating a view without a navigationMenuItem associated. This will make the view available on the left sidebar."

即:如果你创建了一个 View,却没有同时创建与之关联的 Navigation menu item,该视图会被引擎直接暴露到左侧边栏,污染全局导航。这正是上一节对象脚手架要连带创建 navigation menu item 的原因——getNavigationMenuItemBaseFile 通过 targetObjectUniversalIdentifier 把导航项指向对象,View 才拥有正确的"入口"而非游离在侧边栏。

从数据关系看,Navigation menu item 与 View/Object 之间是强绑定:菜单项必须携带目标(对象或视图)的引用。因此规范建议在 navigation-menu-items 实体族中"先建目标、后建入口",或在创建对象时直接用配套生成避免遗漏。

4.2 前端组件必须自适应,而不是自带滚动条

原文要点:"Creating a front-end component that has a scroll instead of being responsive to its fixed widget height and width, unless it is specifically meant to be used in a canvas tab."

Twenty 的 Front component 会被嵌入固定宽高的 Widget 中渲染,其宿主是 twenty-front-component-renderer(提供 polyfills、host 与 remote 运行时)。若组件内部用 overflow: scroll 之类的方式"假装"容纳超量内容,而不是根据 widget 的固定尺寸做响应式布局,在常规 Tab 中就会表现为双滚动条或内容被裁切——唯一例外是明确用于 canvas tab 的场景。

从渲染架构可以推断,front component 运行在受限的 remote 沙箱里,高度、宽度由宿主注入。因此开发者应让内容自适应 widget height/width,把可滚动、可交互的复杂界面保留给 canvas tab。这条对"用 Agent 生成界面组件"尤其重要,因为 LLM 生成的组件最容易随手加一个外层 div + overflow: auto

五、把规范落到 Last Contact:一份真实应用如何组织

Last Contact 应用是这份 AGENTS.md 的最佳注脚——它展示了规范描述的全部要素如何协同:

  • 目录即实体类型src/fields/ 存放 20+ 个字段定义(last-contact-atlast-outbound-atlast-email、按对象 × 事件类型拆分的 *-for-*-on-message/calendar-event 等);src/logic-functions/ 存放事件与定时触发的处理函数;src/constants/ 统一管理所有 universal identifier。
  • 字段定义契合 Config/Data 规范last-contact-by.field.ts 展示了关系字段的完整写法:类型 RELATION、目标 workspaceMember、反向字段 UUID、many-to-oneonDelete: SET_NULLjoinColumnName 以及 isUIEditable: false(防止用户手工编辑计算值)。
  • 角色即最小权限default-role.ts 遵循"最少权限"原则:对 person/company/opportunity 允许读写;对 message/messageParticipant/calendarEvent 及其参与者只读;canReadAllObjectRecordscanBeAssignedToUsers/ApiKeys 等全局开关全部为 false
  • 逻辑函数双触发模型:邮件侧用数据库事件触发(messageParticipant.updated 且更新字段含 personId,见 on-email-interaction.ts),日历侧则用定时任务 cron 触发(每 */N * * * * 扫描刚结束的会议,见 on-calendar-event-started.ts),两者都在 60 秒超时内用 CoreApiClient 完成分页查询与回写。
  • 测试与发布:每个函数/工具目录下都有 __tests__,外加 集成测试,支撑文档 Operations 域的 testing/publishing 章节。

六、写给 Agent 与开发者的执行清单

把 AGENTS.md 压缩成可执行的规则,推荐在 yarn twenty dev:add 工作流中遵守:

  1. 新增对象yarn twenty dev:add object,并接受"同时创建 View、导航项、记录页布局"的建议,避免孤儿视图。
  2. 新增字段yarn twenty dev:add field,关系字段必须一次性填对目标对象/目标字段的 universalIdentifierRelationType
  3. 新增逻辑yarn twenty dev:add logicFunction,按需选择事件触发器或 cron 触发器,并把常量的 UUID 收口到 src/constants/universal-identifiers.ts 统一维护。
  4. 新增界面yarn twenty dev:add frontComponent,遵守"自适应固定 widget 尺寸、不内置滚动条"原则;普通 tab 与 canvas tab 的使用场景要分开设计。
  5. 权限边界:用 role 显式声明每个对象记录的读写能力,默认关闭全局读写与人员/API Key 委派。
  6. 兜底校验:任何手写的 universalIdentifier 都必须复核为合法 UUID v4,切勿从旧文件复制粘贴。
  7. 参考实现:复杂需求先参考 postcard 富示例Last Contact 源码,确认 CLI 命令与既有写法后再动手。

这套流程同时是"喂给 AI Agent 的提示词模板"——当你在其他 Twenty 扩展仓库里维护代码时,直接把以上清单连同 AGENTS.md 原文交给编码代理,即可显著减少因 UUID 非法、视图缺导航、组件带滚动条导致的无效改动。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388