关于 fcode-factorial-actions
fcode-factorial-actions 是用于将流程配置为 Factorial Actions 的技能,同一实现可由 Factorial 中的多个入口调用。它说明设置契约、调用策略、表单、界面按钮和生命周期行为;Factorial One 工具调用目前尚不可用。
主要功能
- 01在 factorial 设置块中配置操作
- 02通过界面按钮和表单公开操作
- 03在调用前执行 requiredPolicies 检查
- 04设置同步或异步结果处理
- 05说明 Factorial One 工具契约
适用场景
- 01设置可由 Factorial 按钮或表单调用的流程
- 02限制只有满足策略的用户才能执行操作
- 03配置文件上传和公开表单访问
- 04为未来的 Factorial One 支持准备 agentTool 元数据
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ factorialco/ factorial-code-skills --skill fcode-factorial-actions安装后在 Claude Code 中输入/fcode-factorial-actions确认已加载。
详细介绍
Factorial Code — Factorial Actions
Factorial Action 是客户安装 App 后,能够在 Factorial 内部触达的流程,客户无需知道 Factorial Code 的存在。一个流程就是一个 Action,同一个 Action 可以同时通过多个入口公开——Factorial UI 中的按钮、表单、Factorial One 工具、后端任务。无论入口是什么,Factorial 都会向流程传递参数,并在需要时等待结果;流程不知道是哪个入口启动了它,因此一种实现即可服务所有入口。
Action 所需的一切都位于一个设置块 factorial 中:控制台流程页面中的 Trigger from Factorial 部分,或 processes/<slug>/metadata.json 的 factorial 键(可通过 fcode pull / fcode push 往返同步;字段参考见 fcode-cli)。
当前已上线和即将推出的功能
| 目前可用 | 即将推出 |
|---|---|
控制台、metadata.json 中的 factorial 块,以及 API 中的 settings.factorial | Factorial One 工具:助手读取 agentTool 契约并调用流程 |
| Factorial 调用的 Actions API:列出某公司的 Actions、读取 Action 的表单架构、调用 Action、签发上传授权,以及在 App 安装前列出该 App 声明的 Actions | |
每次调用都会强制执行 requiredPolicies,且在任何操作运行之前执行 | |
uiTrigger:按钮根据此契约列出并运行 Action,通过不透明的 action_id 寻址——浏览器不会接触流程 slug | |
form:从 Factorial 打开的表单通过 Factorial 后端提交;后端会携带用户身份并注入可信上下文 | |
除非 form.public 解除限制,否则表单必须进行 Factorial 身份验证;该开关控制表单端点检查的身份验证模式 | |
文件参数(x-fcode-file)通过授权直接上传到 Factorial Code 存储,提交内容携带 fcode.storage:// 路径 | |
uiTrigger.label 在 ?locale= 列表中通过 fcode.i18n("key") 令牌本地化 | |
从流程的 form.appRole 派生 lifecycleRole;若未声明,则从保留 slug 派生,并在控制台中标记 |
因此,当前 uiTrigger、form、requiredPolicies 和 awaitResult 都已生效。agentTool 和 backend 已存储且可访问,但目前还没有助手读取工具契约——现在就填写它们,让 Action 在该功能上线时做好准备。
注意事项
enabled是总开关。 当它为false时,下面的内容都不会暴露出来,无论各入口自己的enabled值是什么。重新启用后,入口会恢复到原来的状态。awaitResult默认为true(同步)。 旧版uiTrigger默认为触发后即返回。镜像会将factorial.awaitResult的值复制到uiTrigger.awaitResult,因此,将按钮移到factorial区块后,会变为同步,除非你写入"awaitResult": false。同步操作的耗时应控制在约 50 秒内(fcode-ui-triggers)。requiredPolicies会被强制执行,但键名是自由文本。 Factorial 会在运行任何操作前检查这些键。它不会检查键是否 存在:不认识的键永远不会被授予,因此包含该键的整个 AND 组都会失败——拼写错误只会关闭操作,不会开放操作。请从 Factorial 的策略目录中选取键;与其使用一个很长的组,不如优先使用多个较小的替代组。- 没有策略时,操作不受限制。 列表为空或未提供列表时,任何能够访问该入口的人都可以运行该操作——此时只剩下入口自身的限制(例如按钮的位置权限、是否安装应用)。非空列表中的空 组 会被丢弃,而不会被视为“允许所有人”,因此
[["a"], []]仍要求满足a。 - 只有
uiTrigger.label可以翻译。 这是用户唯一能看到的文本,因此应使用fcode.i18n("key")令牌(fcode-i18n)。agentTool中的文本供模型使用——它们是单一语言的纯文本,绝不会展示给用户。进程自身的name/description面向用户和市场卡片;该区块本身没有标题。 - 未设置
effect时会被视为DESTRUCTIVE。 在你将其设为READ或WRITE之前,每个工具都会默认拒绝操作(要求审批并显示“无法撤销”警告)。 - 四个生命周期 slug 是保留值。 无论区块如何,
install、settings、uninstall、sync对 Factorial 都有特殊含义。请勿将它们用于普通操作。 - 省略默认值。
fcode pull会省略值为true的awaitResult、空的requiredPolicies、值为false的form.public和form.appTool、未设置的agentTool文本和空列表,以及任何enabled为false的子区块。未向 Factorial 暴露的进程完全不会有factorial键。部分更新会保留未指定字段的原值。 - 旧版
form/uiTrigger键仍可往返保留。 当文件同时包含两者时,fcode push会发送factorial,并让镜像更新其余内容。建议优先编写factorial(见下文)。
factorial 区块,逐字段说明
{
"factorial": {
"enabled": true,
"awaitResult": false,
"requiredPolicies": [["company.manage_timeoff"], ["company.admin"]],
"uiTrigger": { "enabled": true, "locationId": "calendar.header.admin", "label": "fcode.i18n(\"acme.approve.button\")", "icon": "Bell" },
"form": { "enabled": true, "public": false, "appTool": true },
"agentTool": { "enabled": true, "description": "…", "effect": "WRITE" },
"backend": { "enabled": true }
}
}
| 字段 | 类型 / 默认值 | 含义 |
|---|---|---|
enabled | 布尔值,false | 下方所有入口的总开关 |
awaitResult | 布尔值,true | true:Factorial 等待并接收进程的返回值,且不作更改(fcode-ui-triggers)。false:Factorial 只获知执行已启动;进程自行报告结果(FactorialClient,通知)。导入和批量处理请使用 false |
requiredPolicies | string[][],[] | 用户必须持有的 Factorial 策略键的与组之或:[["a","b"],["c"]] 表示 (a 和 b) 或 c。为空 = 任何能访问该入口的人。控制台:每行填写一个备选组,组内的键以逗号分隔 |
uiTrigger.enabled | 布尔值 | 在 Factorial 内将该操作呈现为按钮 |
uiTrigger.locationId | 字符串 | 按钮在 Factorial UI 中呈现的位置(例如 calendar.header.admin)。启用时必填,最多 200 个字符,由负责该页面的 Factorial 团队提供;位置规范见 fcode-ui-triggers |
uiTrigger.label | 字符串 | 按钮文字。唯一的 i18n 字段:纯文本或 fcode.i18n("key") 标记 |
uiTrigger.icon | 字符串 | 允许使用的图标名称(fcode-ui-triggers);若按钮仅显示文字,则省略 |
form.enabled | 布尔值 | 将参数作为表单公开(fcode-forms)。与 Forms 标志共用同一开关和配额 |
form.appTool | 布尔值,false | 在 Factorial 中 App 自己的页面上,向公司的用户提供该表单,列在 App 允许他们运行的功能中,例如“报告同步问题”或“立即同步”表单。如果表单由 App 自行打开,请保持为 false。这是该区块对旧字段 form.appRole: USER_FACING_FORM 的命名;两者会保持同步 |
form.public | 布尔值,false | 将表单开放给任何持有其链接的人,无需 Factorial 账号。此开关对应旧字段 form.authMode,表单端点会强制执行该字段;两者会保持同步:开启时写入 NONE,关闭时写入 FACTORIAL。除非表单面向 Factorial 以外的人群,否则请保持关闭;开启后,调用者不会经过任何身份验证 |
agentTool.enabled | 布尔值 | 允许 Factorial One 助手将该进程作为工具调用。其余子区块定义了工具规范(见下一节) |
backend.enabled | 布尔值 | 允许 Factorial 后端任务在没有用户参与的情况下运行该进程 |
Factorial One 工具规范(agentTool)
参数架构会告诉助手工具接受什么;此区块会告诉助手工具的用途。使用纯文本,不支持 i18n。
| 字段 | 类型 | 对模型的作用 |
|---|---|---|
description | 字符串 | 用一两句话说明工具的功能:指出它操作的对象和预期结果。系统会据此匹配用户请求 |
effect | READ | WRITE | DESTRUCTIVE | READ 无需询问即可运行;WRITE 需要请求批准;DESTRUCTIVE 会请求批准,并警告操作无法撤销。未设置 ⇒ DESTRUCTIVE |
whenToUse | 字符串 | 用用户可能提出请求的方式,描述适用场景 |
whenNotToUse | 字符串[] | 每项描述一种相似但不匹配的场景,并指出应改用的相邻工具 |
preconditions | 字符串[] | 每项说明调用前必须满足的条件;助手会先检查或询问,而不是直接调用后失败 |
doesNotDo | 字符串[] | 每项说明用户可能以为会发生、但该工具实际上不会产生的副作用 |
degradation | 字符串 | 说明部分成功时会发生什么:完成了什么、跳过了什么,以及结果如何说明这些情况 |
simulatesFor | 字符串 | 此工具预览的进程 slug(即该进程的试运行)。纯文本,尚未验证 |
编写优质的契约文本
- 使用通俗语言,不做国际化,只用一种语言。 面向阅读英文的模型编写,
而不是面向屏幕。这里绝不要放入
fcode.i18n(...)标记。 description:对象 + 结果。“批准团队所有待处理的休假申请” 胜过“休假工具”。whenToUse中只描述一种情况。 如果需要用“或”连接,你很可能有两个 工具。用用户会提出的方式来表述。- 在
whenNotToUse中注明相邻工具。“用户想批准单个申请:使用approve-timeoff-request。”每个条目只描述一种情况。 preconditions是可核验的事实(“团队至少有一个待处理申请”), 而不是建议。doesNotDo列出用户可能会以为会发生的事情。 通知、附带写入、撤销—— 明确说明哪些事情不会发生,以便助手提供缺失的步骤。degradation描述部分完成的结果,这样助手就能解释结果,而不是报告失败。- 试运行流程通过
simulatesFor指向对应的真实流程,且自身为READ; 真实流程则为WRITE或DESTRUCTIVE。
保留的生命周期 slug
| Slug | 作用 | 平台执行的操作 |
|---|---|---|
install | 公司安装 App 时 Factorial 显示的表单 | 推导出 lifecycleRole: INSTALL |
settings | 已安装 App 的公司用来配置 App 的表单 | SETTINGS |
uninstall | 公司移除 App 时运行 | UNINSTALL |
sync | integrations-framework App 的同步流程 | SYNC |
lifecycleRole 是只读的:控制台会在流程列表和流程标题中显示带底色的 安装 /
设置 / 卸载 / 同步 标签,CLI 和 API 会报告它,但不会有任何操作写入它(它不在
metadata.json 中,也不包含在内容哈希中)。install、settings 和
uninstall 是 marketplace 生命周期表单;与任何表单一样,它们仍使用
factorial.form.enabled。重新打开的 install/settings 表单所用的预渲染模式见
fcode-forms。
它是根据流程声明的 form.appRole 推导出来的,只有流程未声明该字段时才使用保留
slug。顺序必须如此:marketplace 目录一直根据 appRole 来识别这些表单,而生产
环境中的 App 会将表单命名为 datev-install、net-empregos-install 等,并始终设置
正确的角色。如果仅根据 slug 识别,这些 App 就完全不会有 install、settings 或
uninstall 表单——而等待配置的安装只能看到自己的生命周期表单,因此也无法完成安装。
回退机制将永久保留。 appRole 没有弃用日期,也不要求你执行任何迁移。新流程
使用保留 slug,因为名称更清晰;声明了角色的流程仍会照常工作。
在现有 App 中采用保留 slug
这是可选操作,只有为了使用更清晰的名称时才值得做。fcode push 按 slug
将本地流程与远程流程匹配,因此重命名流程文件夹不会重命名流程:它会创建第二个
流程,并将第一个流程留在工作区中。请在同一次 push 中停用旧流程。
- 创建一个使用保留 slug 的流程,并迁移源文件和
factorial块。 - 在旧流程上设置
factorial.form.enabled: false,并在同一次 push 中 推送。不要让两个流程都保持启用:两个流程争用一个生命周期槽位时,slug 按字典序 排在最前的流程会胜出;胜出的可能是旧名称(datev-install<install), 也可能是新名称(install<net-empregos-install)。冲突会作为duplicate_role_warnings条目报告在 App 的目录条目中。 - 执行
fcode push。
操作 ID 会发生变化,因为它是根据 slug 推导出来的。没有任何持久化内容会引用它, 因此安装及其配置不会受到影响;已加入书签的表单 URL 将无法再解析。
参数:与 Factorial 的契约
无论入口点是什么,Factorial 发送的内容都由流程的
parametersSchema.json(fcode-json-schema)描述。表单会根据它渲染,助手会读取它以了解工具接受哪些参数,Factorial 则会根据它进行验证。为每个参数提供 title 和 description,在 required 中列出必填项,并将任何架构变更视为Factorial 可以向你传入什么内容的变更。UI 触发器注入的可信键(company_id、
triggered_from_location、access_id)详见 fcode-ui-triggers。
从 form 和 uiTrigger 过渡
factorial 块取代 Forms 标志(form)和 UI trigger 按钮(uiTrigger)。目前两者都会保留,平台会让它们保持镜像同步:无论你修改哪一侧,另一侧都会更新,以最后一次写入为准。
写入 factorial…… | ……会更新旧版设置 |
|---|---|
enabled && uiTrigger.enabled | uiTrigger.enabled |
uiTrigger.locationId、uiTrigger.icon、uiTrigger.label | uiTrigger 中的同名键 |
awaitResult | uiTrigger.awaitResult |
enabled && form.enabled | form.enabled |
form.appTool | form.appRole = USER_FACING_FORM |
写入旧版块也会将相同字段镜像回去;只要启用了任一入口点,factorial.enabled
就会开启。
form.appRole 是该块部分管理的唯一旧版字段。它的四个值中,USER_FACING_FORM 对应 form.appTool——开启此标志会占用该值,关闭时仅当该值原本由它占用才会释放;如果写入时未提及 appTool,则角色保持不变。因此,在此标志出现之前写入的文件推送时会保留其角色:CLI 会读取 appRole 并发送相应标志。
INSTALL / SETTINGS / UNINSTALL 则属于另一类:该块没有对应的开关,因此它们仍保留在 form.appRole 中,lifecycleRole 也会优先读取该字段。继续在此处写入这些值——对于完全未声明角色的流程,预留 slug 是另一种选择,并非要求你迁移过去的替代方案。
优先使用 factorial:在新流程中写入它;修改旧的 metadata.json 时,也应将设置迁入其中,而不是编辑 uiTrigger。
示例——SILTRA FIE 导入(试运行 + 应用)
改编自 siltra-integration-app:fie-import 会读取公司从 SILTRA 下载的 .msj 文件,并报告它会在 Factorial 中创建、关联或关闭哪些病假记录,但不会写入任何内容;fie-import-apply 会在后台执行写入。
processes/fie-import/metadata.json:
{
"name": "FIE Import",
"description": "Reads the .msj file the company downloads from SILTRA and shows what importing it would change in Factorial. Writes nothing — the import only happens after confirmation.",
"tags": ["inbound", "siltra", "fie"],
"factorial": {
"enabled": true,
"requiredPolicies": [["company.manage_timeoff"], ["company.admin"]],
"uiTrigger": {
"enabled": true,
"locationId": "calendar.header.admin",
"label": "fcode.i18n(\"calendar.import.fie.button\")",
"icon": "Upload"
},
"form": { "enabled": true },
"agentTool": {
"enabled": true,
"description": "Reads a SILTRA FIE transmission (.msj) and reports which sick leaves it would create, link or close in Factorial, and which events it would leave out and why. It writes nothing.",
"effect": "READ",
"whenToUse": "The user has downloaded a FIE file from SILTRA and wants to know what importing it would change before anything is written.",
"whenNotToUse": [
"The user has already reviewed the report and wants the leaves written: use fie-import-apply.",
"The file is not a SILTRA .msj transmission."
],
"preconditions": ["The .msj file has been uploaded and its storage reference is passed as msj_file."],
"doesNotDo": ["It does not create, change or close any leave.", "It does not notify employees or managers."],
"degradation": "If Factorial cannot be consulted, the report still lists every event in the file, marked as not compared against Factorial.",
"simulatesFor": "fie-import-apply"
}
}
}
processes/fie-import-apply/metadata.json——没有按钮、没有表单、异步执行,并负责这一对流程中的写入:
{
"name": "FIE Import — apply",
"tags": ["inbound", "siltra", "fie"],
"factorial": {
"enabled": true,
"awaitResult": false,
"requiredPolicies": [["company.manage_timeoff"], ["company.admin"]],
"agentTool": {
"enabled": true,
"description": "Writes the sick leaves a SILTRA FIE transmission describes into Factorial: creates, links or closes them.",
"effect": "WRITE",
"whenToUse": "The user has seen the fie-import report for a file and asks to apply it.",
"whenNotToUse": ["The user only wants to know what the file would change: use fie-import."],
"preconditions": ["fie-import has reported on this same file and the user confirmed the result."],
"doesNotDo": ["It does not send SILTRA anything back.", "It does not notify employees."],
"degradation": "Leaves that fail to write are skipped and listed in the result; the rest are applied."
},
"backend": { "enabled": true }
}
}
策略键仅供示例——请从 Factorial 的策略目录中获取实际键。照常读取参数(fcode.context.parameters.msj_file,一个 fcode.storage://… 引用——fcode-forms)。
发布前检查清单
- 启用
factorial.enabled,并且仅启用你打算公开的入口点。 - 明确决定
awaitResult的值:任何耗时操作都设为false;同步操作应返回其运行所在入口点能够读取的数据结构。 requiredPolicies的键应从 Factorial 的目录中复制,而非猜测——也可以有意留空,这会让任何能访问该操作的人都可以使用它。agentTool:设置effect,在whenNotToUse中列出相邻工具;试运行应通过simulatesFor指向实际流程。- 普通操作不得使用
install、settings、uninstall或sync作为 slug;架构中应包含title、description和required。