Commit Graph
21 Commits
Author SHA1 Message Date
yeuimu dc92b4cc8f fix(workflow): suppress self-echo trigger duplication on workflow-written records
One user edit cascaded into 3-5 workflow executions and hourly sync jobs
generated 1000+ wasted runs/week (7d: 15.7k of 18k executions of the
ticket-status workflow ended in skipped no-op guards). Root cause: workflow
scripts write records via REST with a runner-signed token, and record-event
triggers could not distinguish those writes from human edits — every script
PATCH re-triggered its own workflow.

Attribution chain (new):
- WorkflowTokenService signs {workflowId, executionId} into the runner token
- jwt.strategy lifts it into CLS workflowContext (User-type branch)
- all 13 automation.record.* emit sites tag payloads with writeSource
- the 4 record triggers drop SELF echoes (writeSource.workflowId === own id)
  unconditionally; cross-workflow writes keep firing by default (chaining is
  load-bearing: order-sync建档 -> status workflow stamps 待处理, ~142 real
  actions/week). Opt-in stronger dedup via trigger config
  skipWorkflowWrites: true.

Verified live: human edit (处理反馈=正在核实) -> exactly 1 execution, script
flipped 流转状态 待处理->处理中, zero follow-up echoes; restore writes likewise
1 execution each with skipped no-ops. Unit: 14 new tests, 318 adjacent green.
2026-09-18 02:28:11 +00:00
yeuimu 1df830adc4 chore: cli subscription command registration + env and deploy docs
- inkreach-cli: subscription command in bin, built dist
- env.development: public origin, plugin service registry, cache
  provider=redis, cold retention days
- skill docs: subscription quick reference, deploy and ops notes
2026-08-22 02:44:16 +00:00
yeuimu 19a9a9012f feat(field): lastModifiedTime expression field references
Parse LAST_MODIFIED_TIME([Name]/{fldId}) references into trackedFieldIds so
the modification time only resets when a referenced field changes.

- core: relax expression from z.literal('LAST_MODIFIED_TIME()') to z.string(),
  add parseLastModifiedTimeReferences() + spec
- v1 field-supplement.service: resolve trackedFieldIds from expression on
  create/convert (incl. same-type merge), 400 on unknown field name
- v2 field-open-api-v2.service: resolve trackedFieldIds from expression on
  create/createFields/convert
- docs: field-types.md tracked-field reference section
2026-08-20 14:31:20 +00:00
yeuimu 92e73f7baa fix(automation): fire record triggers on all grid write paths (paste/clear/duplicate)
Investigation of the 'grid edits bypass the REST layer' report found the
ShareDB socket submit path is dead code (adapter db.commit() throws and no
frontend submitOp callsites exist) — grid cell edits already flow through
record.updateCell -> REST updateRecord and DO fire triggers. The real
coverage gaps were the selection-layer writes that call the open-api
SERVICES below the controller where the manual emits live:

- v1 selection paste/paste-by-id, clear/clear-by-id (grid paste, Delete-key
  clear): emitted nothing. Now emit automation.record.create/update with
  full post-write records + changedFieldIds/oldFields diffs (best-effort —
  emission failure logs but never fails the write).
- v1 record duplicate: emitted nothing. Now emits automation.record.create.
- v2 paste endpoints (range paste + paste-stream + paste-by-id-stream):
  returned only counts. PasteResult/PasteStreamDoneEvent now carry per-record
  update diffs and created-record values through to the nest layer, which
  emits create/update events (full record + diff metadata).
- v2 record duplicate: now emits automation.record.create.

Form submissions intentionally remain formSubmitted-only (independent
channel); deletions have no trigger category. Docs corrected: grid cell
edits fire triggers (they are REST), coverage table updated.

Verified live: duplicate / range paste / stream paste on a v2 base each
produced a workflow execution with full triggerInput; selection e2e
114/114, PasteHandler specs 30/30, selection unit 31/31.
2026-08-18 18:54:52 +00:00
yeuimu 7281a4f2e6 docs: workspace packages must be rebuilt before the backend (silent stale-dist trap)
The backend never compiles @teable/* sources — it loads packages/*/dist via
node_modules symlinks (@teable/v2-core is a webpack external, read at runtime
from packages/v2/core/dist/index.cjs). Editing a package's src and only
rebuilding apps/nestjs-backend ships nothing: no error, old behavior online.
Documented the correct build order + verification greps in AGENTS.md and the
skill's development guide (found while shipping the defaultValue cascade fix).
2026-08-18 09:06:24 +00:00
yeuimu 4176165ec7 docs(skill): oldFields is top-level, not nested in record
E2E feedback: script following the documented 'record.oldFields.*'
nesting read undefined. Actual payload shape (verified in
workflow_execution.trigger_input + trigger node output):
triggerInput.oldFields = { fldXXX: value } at TOP level, sibling of
record. Align all trigger output docs (recordUpdated §2, recordCondition
§3, recordCreatedOrUpdated §4) + add explicit access examples
(triggerInput.oldFields?.['fldXXX'], {{n0.oldFields.fldXXX}}).
2026-08-18 08:22:45 +00:00
yeuimu 9ec7dcb15a docs(skill): warn --config must be flat action config
Wrapping --config in {"config":{...}} deploys a silent empty script
(succeeds in ~1ms, result {}). Document the trap + verification recipe
(workflow get --json, nodes[].config.code non-empty).
2026-08-18 07:49:51 +00:00
yeuimu 70f8372fbc fix(workflow): deliver changedFieldIds + oldFields to recordUpdated trigger
Engine gap: the 'automation.record.update' event carries changedFieldIds
(and now oldFields), but RecordUpdatedTrigger.accumulate() dropped both —
scripts had no way to see what changed or pre-update values, despite the
documented trigger output (trigger-types.md).

- record-updated/record-condition/record-created-or-updated triggers:
  pass changedFieldIds + oldFields through to accumulate()
- batch-buffer TriggerEvent += oldFields; batch flush records[] entries
  now carry changedFieldIds + oldFields per record
- v2 updateRecord/updateRecords: emit oldFields (pre-update snapshot)
- v1 controller updateRecord/updateRecords: snapshot before-fields via
  getRecordsById, compute changedFieldIds (deep-equal diff, same
  semantics as v2) instead of hardcoded [] — v1/v2 parity
- new spec record-updated.trigger.spec.ts (4 tests, passthrough +
  watch-filter + table isolation); workflow suite 16 files / 99 tests
- trigger-types.md: document semantics + honest scope (REST API writes;
  ShareDB grid edits do not fire this trigger)

Live-verified: PATCH 流转角色 → trigger_input.changedFieldIds=[fld...],
oldFields=[售前客服] → record=[技术]; script reads oldRole via
triggerInput; fieldIds watch-filter blocks non-watched fields.
2026-08-18 07:49:51 +00:00
yeuimu 80801346b5 fix(workflow): sendNotification skip-on-empty-toUserId + error precision + doc unification
- toUserId 解析为空时静默跳过 (succeeded, sentCount=0, skipped:true) 而非 failed
  → 支持'仅有重复订单时才通知'的条件通知编排
- 必填校验只列出实际缺失字段 (message),不再误报已解析的字段
- 文档统一变量格式: {{nodeId.path}} 为主,{{index.path}} 兼容
- spec 6/6,线上实测: 空收件人 skip + 非空正常发送均验证
2026-08-17 10:11:25 +00:00
yeuimu 146d62c80b docs(skill): document sendNotification action (7 actions total) + CLI support
- skill: SKILL.md (7 actions in description + routing table entry), action-types.md
  (new section #4 sendNotification: config keys, variables, system-in-app semantics),
  automation-guide.md/cli-reference.md (--to-user/--title/--message/--severity/--path),
  development.md (7 actions), metadata.json abstract
- cli: action add --type sendNotification now builds config from granular flags
  (toUserId/title/message/severity/path); ACTION_TYPES includes sendNotification;
  live-verified via workflow create + action add + delete
2026-08-17 07:50:15 +00:00
yeuimu e1a91dfa84 feat(cli+skill): make documented field UX real + fix skill doc drift
CLI (packages/inkreach-cli):
- new src/lib/field-normalize.ts: type aliases (sel/num/text/mul/…),
  select string-choices -> {name}, compact --fields 'Name:type:choices'
  expansion, smart type inference from --options shape / --name
  (--type now optional), name->ID resolution for foreignTableName /
  linkFieldName / lookupFieldName (incl. link-field foreignTableId
  backfill + unknown-key stripping), rating/number/button required-
  option defaults
- field create: +--description/--lookup-options/--is-lookup
- field update --options + table create --fields route through the
  normalizer; DEFAULT_ENDPOINT :3000 -> :3003
- 20 unit tests (field-normalize.spec.ts, vitest); live-verified:
  string choices, alias, foreignTableName, lookup-by-names, compact
  format, inference (priority->singleSelect, max:5->rating)

Skill docs (.agents/skills/inkreach-teable):
- ports :3000 -> :3003 everywhere (backend real port; :3000 is an
  unrelated container on this host)
- remove dead hr_admin account; note throwaway signup + real admin
- plugin register: actual URL-based flags; document plugin csp
  list/set; view/dashboard install requires config enable first
- field-types.md marked verified-2026-08 (aliases/inference/name
  resolution now implemented + tested)
- development.md: webpack dist/index.js cache trap; PGPASSWORD
  placeholder instead of wrong literal
2026-08-16 05:58:01 +00:00
yeuimu 5a984eb5f8 docs(skill): update inkreach-teable skill for fixed field routes + select-choice dependency
- cli-reference.md: field get/update/delete now <tableId> <fieldId>;
  new field visible-choices command; note --options goes via convert API
- tables-fields.md: correct update/delete signatures; options-rewrite
  semantics (convert API, whole-replace not merge); new section on
  select-choice visibilityFilter dependency (read/write/evaluate,
  backend 400 guards, write-time enforcement message)
2026-08-16 05:35:23 +00:00
yeuimu bc8c76851d docs(skill): add plugin module to SKILL.md + routing rules + description
- Module map: new Plugins row (list/install/uninstall, dev allow/revoke)
- Routing rules: 3 new plugin intents (custom UI, allow devs, external API)
- Description: mention dynamic plugins + dev whitelist + CSP
2026-08-13 15:39:56 +00:00
yeuimu 49836d2655 docs: add plugin CLI commands to skill reference 2026-08-13 15:29:41 +00:00
yeuimu 73998173a8 docs(skill): update formSubmitted trigger docs for multi-form support
Reflect the formId(formId) -> formIds[] change across skill + CLI + docs:
- skill api-reference/trigger-types.md: formIds array config + legacy note
- skill guides (automation-guide.md, cli-reference.md): --form-ids <csv>
- inkreach-cli: add --form-ids option (csv parse), keep --form-id legacy
- docs/automation/triggers.md: P1 formSubmitted marked done
- docs/automation/README.md: formSubmitted config fields updated
2026-08-12 03:59:12 +00:00
yeuimu ef013690f3 feat(workflow): expose trigger user role to script actions
Scripts can now branch on WHO triggered the workflow and their role in the
base, without an extra API call (the workflow token has no role-read scope).

New triggerInput fields (user-initiated triggers only: buttonClick /
recordCreated / recordUpdated / recordCondition / formSubmitted):
- _triggerUserRoles: string[]  (enabled role display names in this base)
- _triggerUserIsAdmin: boolean (space owner / AM admin / AM-disabled)

Absent for scheduledTime / webhook (no user context).

Implementation:
- trigger-user-info.ts: resolveTriggerUserInfo(prisma, baseId, userId) —
  standalone, unit-tested (8 tests). Mirrors the admin logic in
  AuthorityMatrixPermissionService but returns only role names + bool.
- workflow-runner.service.ts: resolve once at first action node, merge into
  enrichedInput (both execute() and test() paths).
- skill action-types.md: document the fields + script branching example.
2026-08-11 10:08:12 +00:00
yeuimu cf25c8ff93 feat(cli): support --dependencies for script actions
- action add/update: new --dependencies <json> option
- parseDependencies accepts JSON, name@version shorthand, or bare names
- update is incremental; --dependencies replaces the whole array
- docs: deployment/systemd.md + skill SKILL/action-types/development
  document the npm-deps feature and the service-PATH requirement
  (spawn npm ENOENT without nvm bin dir on PATH)
2026-08-11 05:51:27 +00:00
yeuimu e5975fe5bc docs(skill): add filter operator reference by field type
Add comprehensive operator-to-field-type mapping table covering:
- text/number/boolean/date base operators
- select/link/user field-specific operators (isAnyOf, hasAnyOf, etc.)
- date sub-operators (today, pastWeek, exactDate, etc.)
- conjunction (and/or) + nesting
- Warning: isAnyOf not valid for text fields — use OR conjunction
2026-08-07 18:00:37 +00:00
yeuimu 584b75ba69 fix(skill): trim inkreach-teable description under 1024-char limit
The skill description was 1033 chars, exceeding pi's 1024-char limit and
causing a 'Skill conflicts: description exceeds 1024 characters' warning,
which prevents the skill from loading/triggering reliably.

Shortened to 990 chars while preserving all trigger-relevant keywords
(inkreach-teable/Teable, ID patterns bseXXX/tblXXX/..., configure
tables/automations, dev/deploy, systemd). Also fixed YAML folded-scalar
artifacts that injected stray spaces after slashes
('/ formSubmitted', '/ httpRequest') by avoiding line breaks after '/'.
2026-08-05 09:22:26 +00:00
yeuimu 3e06a10fe0 skill(iter-1): fix CLI getRecords flag + docs from eval feedback
Ran 4 end-to-end evals (skill-creator flow): all 23 assertions passed (100%).
The evals exposed 3 issues, now fixed:

1. CLI bug: getRecords/createRecord/updateRecord --target-table flag was
   read as opts['target-table'] but commander stores it camelCase
   (opts.targetTable). Added a pick() helper accepting both forms.
2. trigger-types.md: buttonClick output said record.fields.* implying all
   fields; actually record.fields only contains the button field's count.
   Documented the back-channel API fetch pattern for other fields.
3. automation-guide.md: documented that 'workflow test' uses mock input
   with empty fields, so upstream outputs are absent in test mode — use
   real fire for data-dependent validation.

Also: corrected button field options (workflow.id object, not workflowId).
2026-08-05 10:48:33 +08:00
yeuimu d3790ab856 refactor: rename skill 'inkreach-teable-ops' -> 'inkreach-teable'
Rename the skill directory and update all references (SKILL.md frontmatter
name, AGENTS.md, design doc, install.sh). Git tracks this as a rename so
history is preserved.
2026-08-05 10:35:04 +08:00