diff --git a/.agents/skills/inkreach-teable/SKILL.md b/.agents/skills/inkreach-teable/SKILL.md index 5ea9cc3..6874546 100644 --- a/.agents/skills/inkreach-teable/SKILL.md +++ b/.agents/skills/inkreach-teable/SKILL.md @@ -125,7 +125,7 @@ These differ from official teable and from the (stale) `docs/automation/triggers 1. **Use `inkreach-teable` CLI, never `teable` CLI for automations** — official CLI uses incompatible contract (filter/watchFieldIds/recordMatchesConditions) and will 404 or corrupt config. 2. **Read [trigger-types.md](api-reference/trigger-types.md) before configuring a trigger** — contains exact config keys per trigger; guessing leads to silent misfires. 3. **Build the full chain then activate** — `workflow create` → `trigger set` → `action add` → `activate`. Editing nodes after activation requires re-`activate` to snapshot. -4. **For `buttonClick`, ensure the field is a Button field bound to this workflow** — create via `field create --type button` with `options.workflowId`, or it won't render/click. Verify `workflow.isActive` (the field options capture a stale `isActive` snapshot at creation). +4. **For `buttonClick`, ensure the field is a Button field bound to this workflow** — create via `field create --type button` with `options.workflow.id` (the `workflow` value is an **object** `{id, name?}`, NOT a bare `workflowId` string — matching the DB shape), or the field won't render/click. Note: field `options` captures a stale `isActive` snapshot at creation; the API validates the DB `isActive` at click time instead, so a deactivated workflow returns 400. 5. **Read [action-types.md](api-reference/action-types.md) before writing scripts** — covers sandbox globals, variable references `{{nodeIndex.path}}`, and the `_accessToken`/`_apiBaseUrl` back-channel. ## 5. Common Errors & Recovery diff --git a/.agents/skills/inkreach-teable/api-reference/trigger-types.md b/.agents/skills/inkreach-teable/api-reference/trigger-types.md index 20d823f..c90d417 100644 --- a/.agents/skills/inkreach-teable/api-reference/trigger-types.md +++ b/.agents/skills/inkreach-teable/api-reference/trigger-types.md @@ -176,12 +176,21 @@ inkreach-teable trigger set --type buttonClick --table-id tblXXX --field- ```json { "tableId": "tblXXX", "fieldIds": ["fldGo"] } ``` -- The field **must be a Button-type field** whose `options.workflowId` points to this workflow. -- Create it: `inkreach-teable field create --table-id tblXXX --name "Go" --type button --options '{"workflowId":"wflXXX","label":"Go"}'` +- The field **must be a Button-type field** whose `options.workflow.id` points to this workflow. +- Create it: `inkreach-teable field create --table-id tblXXX --name "Go" --type button --options '{"workflow":{"id":"wflXXX"},"label":"Go"}'` **Triggering:** the click comes from `POST /api/table/{tableId}/record/{recordId}/{fieldId}/button-click` (the web UI calls this; CLI tests via `fire button-click`). -**Output:** `record.id`, `record.fields.*`, `fieldId`, `user`, `runId`, `name`, `tableId`, `workflowId`. +**Output:** `record.id`, `record.name` (primary field), `record.fields.` (**only the clicked button field's count, NOT all fields**), `fieldId`, `user`, `runId`, `name`, `tableId`, `workflowId`. + +⚠️ **`record.fields` contains ONLY the button field itself** (e.g. `{fldXxx:{count:1}}`), not the record's other fields. To read other fields (e.g. an "订单号" text field), use the back-channel API inside a script: +```js +const rec = await fetch(`${triggerInput._apiBaseUrl}/api/record/${triggerInput.record.id}`, { + headers: { Authorization: 'Bearer ' + triggerInput._accessToken } +}).then(r => r.json()); +return { orderId: rec.fields.fldXxx }; +``` +`record.name` (primary field) and `record.id` ARE directly available. ⚠️ The Button field's `options` captures a stale `isActive` snapshot at creation. After deactivating, the API validates the DB `isActive` instead — so a deactivated workflow returns 400 "not active" on click. Re-`activate` to fix. diff --git a/.agents/skills/inkreach-teable/evals/evals.json b/.agents/skills/inkreach-teable/evals/evals.json new file mode 100644 index 0000000..02d86c5 --- /dev/null +++ b/.agents/skills/inkreach-teable/evals/evals.json @@ -0,0 +1,33 @@ +{ + "skill_name": "inkreach-teable", + "evals": [ + { + "id": 1, + "name": "button-click-automation", + "prompt": "帮我在 order_logs 表里加一个按钮字段叫「催单」,点这个按钮的时候触发一个自动化,自动化里跑一段脚本,脚本内容是返回当前记录的订单号字段。然后激活它,点一下按钮测试看看执行结果。", + "expected_output": "应创建 button 字段(options.workflow.id 指向新 workflow)、创建 workflow、设 buttonClick 触发器、加 script action(读取 triggerInput.record.fields)、激活、fire button-click 测试、runs list 看到 succeeded。关键:button options 用 workflow 对象结构而非 workflowId 字符串。", + "files": [] + }, + { + "id": 2, + "name": "scheduled-digest", + "prompt": "我想每天定时跑一次自动化,去查一下 order_logs 表里所有记录,把数量统计出来。帮我配一个定时自动化。", + "expected_output": "应创建 workflow、设 scheduledTime 触发器(timing.type + interval,不配后端忽略的字段如 triggerTime/weekdays)、加 getRecords action、加 script action 统计、激活。关键:知道 scheduledTime 后端只认 type+interval。", + "files": [] + }, + { + "id": 3, + "name": "record-condition-trigger", + "prompt": "当 order_logs 表里某条记录的「结果」字段变成「失败」的时候,自动发一封邮件通知到 ops@xingchen.tech。帮我配这个自动化。", + "expected_output": "应创建 workflow、设 recordCondition 触发器(用 conditions 而非 filter,category 是 recordCondition 而非 recordMatchesConditions)、加 sendEmail action、激活。关键:用正确的自研契约键名 conditions。", + "files": [] + }, + { + "id": 4, + "name": "debug-failed-run", + "prompt": "我有个自动化 wfl5F8dQRDloo28w909,最近好像没正常跑,帮我看看是什么情况。", + "expected_output": "应先 workflow get 查 isActive、再 runs list --status failed 查失败记录、runs get 看具体错误。关键:用排查流程而非盲目重新激活。", + "files": [] + } + ] +} diff --git a/.agents/skills/inkreach-teable/guides/automation-guide.md b/.agents/skills/inkreach-teable/guides/automation-guide.md index fdf172c..0e4d078 100644 --- a/.agents/skills/inkreach-teable/guides/automation-guide.md +++ b/.agents/skills/inkreach-teable/guides/automation-guide.md @@ -147,6 +147,19 @@ inkreach-teable runs get wflXXX inkreach-teable runs rerun wflXXX ``` +### `workflow test` vs real fire + +`workflow test ` runs actions synchronously with **mock input** (a fake +`{record:{id:'rec_test',fields:{}}, user:{...}}`). It does NOT fire the +trigger or use the queue. Two caveats: + +- **Upstream outputs are absent in test mode** — if a script reads + `triggerInput.record.fields.`, it gets `{}` because the mock record has + no fields. Test only validates that the script *runs without throwing*, not + that it produces correct data. +- For data-dependent validation, do a **real fire** (button-click / webhook) + or perform the real triggering action, then check `runs list`. + ## Variable References (cross-node data) An action can reference upstream nodes' output in string fields via `{{nodeIndex.path}}`: diff --git a/dist/commands/workflow.js b/dist/commands/workflow.js index 25dbb06..2581999 100644 --- a/dist/commands/workflow.js +++ b/dist/commands/workflow.js @@ -466,11 +466,15 @@ function readFile(p) { /** Build action config from granular flags. */ function buildActionConfig(opts) { const type = opts.type; + // Helper: commander stores --kebab-case flags as camelCase in opts (e.g. + // --target-table -> opts.targetTable). Accept both forms for safety. + const pick = (k) => (opts[k] ?? opts[toCamel(k)]); const config = {}; switch (type) { case 'script': { - if (opts['code-file']) - config.code = readFile(opts['code-file']); + const codeFile = pick('code-file'); + if (codeFile) + config.code = readFile(codeFile); else if (opts.code) config.code = opts.code; if (opts.timeout) @@ -493,21 +497,24 @@ function buildActionConfig(opts) { config.method = opts.method; if (opts.headers) config.headers = JSON.parse(opts.headers); - if (opts['req-body']) - config.body = JSON.parse(opts['req-body']); + const reqBody = pick('req-body'); + if (reqBody) + config.body = JSON.parse(reqBody); break; } case 'createRecord': case 'updateRecord': { - if (opts['target-table']) - config.tableId = opts['target-table']; + const tt = pick('target-table'); + if (tt) + config.tableId = tt; if (opts.records) config.records = JSON.parse(opts.records); break; } case 'getRecords': { - if (opts['target-table']) - config.tableId = opts['target-table']; + const tt = pick('target-table'); + if (tt) + config.tableId = tt; if (opts.query) config.query = JSON.parse(opts.query); break; @@ -518,3 +525,6 @@ function buildActionConfig(opts) { return config; } exports.buildActionConfig = buildActionConfig; +function toCamel(s) { + return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase()); +} diff --git a/src/commands/workflow.ts b/src/commands/workflow.ts index d06042d..9c1d94e 100644 --- a/src/commands/workflow.ts +++ b/src/commands/workflow.ts @@ -501,10 +501,14 @@ function readFile(p: string): string { /** Build action config from granular flags. */ export function buildActionConfig(opts: Record): Record { const type = opts.type; + // Helper: commander stores --kebab-case flags as camelCase in opts (e.g. + // --target-table -> opts.targetTable). Accept both forms for safety. + const pick = (k: string): string | undefined => (opts[k] ?? opts[toCamel(k)]) as string | undefined; const config: Record = {}; switch (type) { case 'script': { - if (opts['code-file']) config.code = readFile(opts['code-file']); + const codeFile = pick('code-file'); + if (codeFile) config.code = readFile(codeFile); else if (opts.code) config.code = opts.code; if (opts.timeout) config.timeout = Number(opts.timeout); break; @@ -519,17 +523,20 @@ export function buildActionConfig(opts: Record): Record): Record c.toUpperCase()); +}