Flow 4: Studio API
Lambda: studio-api(studio-website-monorepo) · 触发: 员工 UI 操作 · 状态: 部分实施(2026-05-04)
::: details 实施溯源 + 落地状态(2026-05-04) 历史 doc 写"DEFERRED — studio-api 当前只连 DynamoDB,不连 Neon"已过时。当前状态:
- 已 Neon-connected:用
@neondatabase/serverlessHTTP driver 直接读写 Neon(raw SQL,不用 Drizzle helper) - 已实施 4a Close:UPDATE tasks 关闭 + 写
task_events审计表 - 未实施 4a 完整原子 batch:不写
contact_timeline、不更新contacts.lastActivityAt、不发 SQS 给 contacts-analyzer - 未实施 4b Postpone:暂无 postpone 端点
- 额外端点:reopen(重开 task)、note(改备注)、events、communications、list(读取)
Studio-api 额外写入:PUT /v3/calls/:id/staff 修正 staff 归属时,写 calls.original_staff_name + calls.amendment_history(详见 per-call-analysis-writes.md Step ①.5)。
:::
数据流概览
不调用 AI,所有值来自员工 UI 操作或系统硬编码。
4a. Close(关闭 Task)— ✅ 已实施
员工打完电话后从 UI 关闭 task,填 closeResult + closeNote。
端点:PATCH /v2/tasks/close?taskId=<uuid>
输入
Query:taskId(UUID)
Body(zod-validated):
权限校验
- 校验 user 对该 store 有访问权(
not_found/forbidden/authorized) - 取
store.providerAccountId作account_id过滤 - 取该 store 拥有的电话号码列表,用于 store-level WHERE filter
- 拼接
WHERE store_phone IN (...) OR store_id = ?— 防止跨 store 改 task(retaintive/studio-website-monorepo#214修复)
Step ①:UPDATE tasks(原子)
WHERE 条件:task_id = $taskId AND account_id = $providerAccountId AND status = 'pending' AND <store-level filter>。
响应处理:
- 0 行 → 检查 task 是否存在:不存在 →
404 NotFoundError,存在但已关 →409 ConflictError - 1 行 → 200 + 返回
{taskId, status, closedAt}
Step ②:INSERT task_events(审计表)
⚠️ 当前不是原子 batch:UPDATE tasks 和 INSERT task_events 是两个独立 SQL 调用。前者成功后者失败 → task 已关但无 audit。后续若需原子化,改用
db.batch()模式。
未实施的部分(gap)
上述 gap 不阻塞功能(任务可关闭),但跟设计意图(原子 batch + 立即 re-analyze)有差距。
4b. Postpone(推迟 dueAt)— ❌ 未实施
当前没有 postpone 端点,以下是终态设计。
设计意图(待实施)
客户要求改约时间(如"周四再打给我"),员工推迟 dueAt,必须填 reason。
Step ①:UPDATE tasks
API 层校验 status='pending',closed task 不可 postpone。
Step ②:INSERT contact_timeline
公共字段(contactPhone/franchiseId/accountId/storeId/eventCategory='task'/occurredAt=NOW()/actorType='staff')外:
Step ③:不更新 contacts
推迟 ≠ 互动,lastActivityAt 不变。
Step ④:不触发 Contact Analysis
推迟不改变客户状态,不产生 Contact Analysis 需要的新信息。