From 80801346b5e22f31f152bad22be56785405db23b Mon Sep 17 00:00:00 2001 From: yeuimu <2197651308@qq.com> Date: Mon, 17 Aug 2026 10:11:25 +0000 Subject: [PATCH] fix(workflow): sendNotification skip-on-empty-toUserId + error precision + doc unification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - toUserId 解析为空时静默跳过 (succeeded, sentCount=0, skipped:true) 而非 failed → 支持'仅有重复订单时才通知'的条件通知编排 - 必填校验只列出实际缺失字段 (message),不再误报已解析的字段 - 文档统一变量格式: {{nodeId.path}} 为主,{{index.path}} 兼容 - spec 6/6,线上实测: 空收件人 skip + 非空正常发送均验证 --- .agents/skills/inkreach-teable/SKILL.md | 2 +- .../api-reference/action-types.md | 29 +++++++++++-------- .../guides/automation-guide.md | 14 +++++---- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/.agents/skills/inkreach-teable/SKILL.md b/.agents/skills/inkreach-teable/SKILL.md index 725d375..5b45ee8 100644 --- a/.agents/skills/inkreach-teable/SKILL.md +++ b/.agents/skills/inkreach-teable/SKILL.md @@ -132,7 +132,7 @@ These differ from official teable and from the (stale) `docs/automation/triggers 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.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. **Read [action-types.md](api-reference/action-types.md) before writing scripts** — covers sandbox globals, variable references `{{nodeId.path}}`, and the `_accessToken`/`_apiBaseUrl` back-channel. ## 5. Common Errors & Recovery diff --git a/.agents/skills/inkreach-teable/api-reference/action-types.md b/.agents/skills/inkreach-teable/api-reference/action-types.md index 25aafba..f38e01f 100644 --- a/.agents/skills/inkreach-teable/api-reference/action-types.md +++ b/.agents/skills/inkreach-teable/api-reference/action-types.md @@ -132,7 +132,7 @@ inkreach-teable action add --type sendEmail \ **Output:** `{ "sent": true, "to": "..." }` -Variable reference: `--body '{{1.record.fields.Name}}'` injects upstream data. +Variable reference: `--body '{{n0.record.fields.Name}}'` injects upstream data. --- @@ -180,14 +180,16 @@ inkreach-teable action add --type sendNotification \ "severity": "info" } ``` -- `toUserId`: recipient user ID (required). Can reference the trigger user with `{{n0.user.id}}` (submitter/operator — only populated on user-initiated triggers; scheduled/webhook resolve to empty). -- `title` + `message`: notification text; both support `{{nodeId.path}}` variables. If title set, rendered as `title\nmessage`. +- `toUserId`: recipient user ID. Can reference the trigger user with `{{n0.user.id}}` (submitter/operator — only populated on user-initiated triggers; scheduled/webhook resolve to empty). +- `title` + `message`: notification text; both support `{{nodeId.path}}` variables (or `{{index.path}}` — both formats resolve identically). If title set, rendered as `title\nmessage`. - `path` (optional): click-through URL in the notification center. - `severity`: `info` (default) | `warning` | `critical`. +**Empty-value semantics**: `toUserId` is required, but if it resolves to an **empty string** the node **silently skips** — status `succeeded`, `sentCount: 0`, output `{ sentCount: 0, invalidUserIds: [], toUserId: '', skipped: true, skipReason: 'toUserId is empty' }`, and `sendCommonNotify` is NOT called. This enables conditional-notify patterns: a script action that returns an empty recipient implements "notify only when X". An empty `message` still fails the node with `Missing required fields: message`. + **IMPORTANT**: this is a **system in-app notification** (bell badge + notification center + realtime socket push). It calls `NotificationService.sendCommonNotify` with `type=system` and **no email config — no email is sent**. Prefer it over `sendEmail` unless an actual email is required. -**Output:** `{ "sentCount": 1, "invalidUserIds": [], "toUserId": "usr..." }` +**Output:** `{ "sentCount": 1, "invalidUserIds": [], "toUserId": "usr..." }` (skipped case: see empty-value semantics above) --- @@ -196,7 +198,7 @@ inkreach-teable action add --type sendNotification \ ```bash inkreach-teable action add --type createRecord \ --target-table tblXXX \ - --records '[{"fields":{"Name":"New","Source":"{{1.record.id}}"}}]' + --records '[{"fields":{"Name":"New","Source":"{{n0.record.id}}"}}]' ``` **Config:** @@ -264,17 +266,20 @@ inkreach-teable action add --type getRecords \ ## Variable References -In any action's **string** config field, use `{{nodeIndex.path}}` to inject upstream output: +In any action's **string** config field, use `{{nodeId.path}}` to inject upstream output. The positional `{{index.path}}` form is also accepted — both resolve identically: | Reference | Resolves to | |-----------|-------------| -| `{{1.record.id}}` | trigger record id (trigger = node 1) | -| `{{1.record.fields.fldStatus}}` | trigger record's Status field | -| `{{2.result}}` | script action's return value (node 2) | -| `{{3.records}}` | getRecords output array (node 3) | -| `{{2.statusCode}}` | httpRequest status (node 2) | +| `{{n0.record.id}}` (≡ `{{1.record.id}}`) | trigger record id (trigger = node 1) | +| `{{n0.record.fields.fldStatus}}` | trigger record's Status field | +| `{{n2.result}}` (≡ `{{2.result}}`) | script action's return value | +| `{{n3.records}}` (≡ `{{3.records}}`) | getRecords output array | +| `{{n2.statusCode}}` (≡ `{{2.statusCode}}`) | httpRequest status | -**Node index** = topological execution order (trigger is always 1, then actions in chain order). +- **`{{nodeId.path}}`** — node ID form (teable generates ids like `n0`, `n1`, `n2`…). Preferred for clarity. +- **`{{index.path}}`** — numeric form; **index = topological execution order** (trigger is always 1, then actions in chain order). + +**Empty resolution**: when a referenced value is missing/undefined, the variable resolves to `''` (see sendNotification §4 for skip semantics). Script actions can also read everything via `triggerInput` (the merged upstream output): ```js diff --git a/.agents/skills/inkreach-teable/guides/automation-guide.md b/.agents/skills/inkreach-teable/guides/automation-guide.md index d7f23b0..ee8f956 100644 --- a/.agents/skills/inkreach-teable/guides/automation-guide.md +++ b/.agents/skills/inkreach-teable/guides/automation-guide.md @@ -163,13 +163,15 @@ trigger or use the queue. Two caveats: ## Variable References (cross-node data) -An action can reference upstream nodes' output in string fields via `{{nodeIndex.path}}`: +An action can reference upstream nodes' output in string fields via `{{nodeId.path}}` (or the compatible `{{index.path}}` positional form): -- `{{1.record.id}}` — trigger record's id (trigger = node 1) -- `{{1.record.fields.Status}}` — trigger record's Status field -- `{{2.records}}` — output of the first action (node 2) +- `{{n0.record.id}}` — trigger record's id (n0 = trigger node) +- `{{n0.record.fields.Status}}` — trigger record's Status field +- `{{2.result}}` — output of the first action (numeric index form; index = topological execution order, trigger is always 1) -Node index follows **topological execution order** (trigger is always 1). Script actions can also read the merged upstream output directly via `triggerInput`. +Both forms resolve to the same value — `{{n0.record.id}}` ≡ `{{1.record.id}}`. Use `{{nodeId.path}}` in new workflows for clarity. + +> **Empty-value semantics**: if a variable resolves to an empty string, the node sees the literal empty value. For `sendNotification`, an empty `toUserId` makes the node **silently skip** (status `succeeded`, `sentCount: 0`) instead of failing — so a script action that returns an empty recipient implements "notify only when X" conditional patterns. An empty `message` still fails with `Missing required fields: message`. ## Common Patterns @@ -179,7 +181,7 @@ inkreach-teable workflow create --name "Status→email" inkreach-teable trigger set wflXXX --type recordUpdated --table-id tblXXX --field-ids '["fldStatus"]' inkreach-teable action add wflXXX --type sendEmail \ --to ops@x.com --subject 'Status changed' \ - --body '{{1.record.fields.Status}}' + --body '{{n0.record.fields.Status}}' inkreach-teable workflow activate wflXXX ```