mcpField
C
conversation-inbox-triage

conversation-inbox-triage

github.com/hubspot其他28194 次安装今天更新
代码仓库

关于 conversation-inbox-triage

这是一个通过 hub-cli 管理 HubSpot 对话收件箱的分流技能,面向收件箱运营人员。它强调完整分页、批量操作安全,以及归档前审批。

主要功能

  1. 01发现收件箱和渠道
  2. 02查找未分配及滞留的开放会话
  3. 03将会话分配给操作者
  4. 04预览并经保护流程归档已解决会话
  5. 05完整分页获取结果并计数
  6. 06汇总收件箱会话状态

适用场景

  1. 01识别未分配或超时的收件箱会话
  2. 02将开放会话分配给团队成员
  3. 03归档前审核已解决会话
  4. 04完整统计多个收件箱的会话

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/hubspot/agent-cli-skills --skill conversation-inbox-triage

安装后在 Claude Code 中输入/conversation-inbox-triage确认已加载。

详细介绍

需要 hub-cli PR #251 中的 conversations 命令组。hubspot conversations --help 是权威说明。归档采用 dry-run/digest/confirm 防护模式——请严格遵循。

分页:切勿根据 --format jsonl 重定向的结果进行分流

所有 conversations list 命令默认使用 --limit 100,并返回下一页的游标。--format jsonl 只会打印数据行——只有 --format json 会在 .meta.next 中显示游标。 因此,threads list --format jsonl > file 会悄无声息地在 100 个会话线程处停止:在繁忙的收件箱中,操作人员看到的是一次顺利完成的运行,但第一页之后的所有会话线程都没有被分配、没有被归档,也没有计入统计。提高 --limit 也无法解决问题——该端点会限制每页大小。

所有批量操作或计数操作都使用此辅助脚本:

# conv_page <output-file> <conversations list args...>
# Pages a conversations list endpoint to exhaustion, writing one JSON object
# per line to <output-file>.
conv_page() {
  out=$1; shift
  after=""
  : > "$out"
  while :; do
    if [ -n "$after" ]; then
      page=$(hubspot "$@" --format json --after "$after") || return 1
    else
      page=$(hubspot "$@" --format json) || return 1
    fi
    printf '%s' "$page" | jq -c '.data[]' >> "$out"
    after=$(printf '%s' "$page" | jq -r '.meta.next // empty')
    [ -n "$after" ] || break
  done
}

步骤 2–4 中直接使用 --format jsonl 的调用适合交互式查看第一页。步骤 6–8 会修改数据或计数,因此需要分页。

1. 查找收件箱

收件箱 ID 因门户而异。每次会话开始时都要枚举,切勿硬编码。

conv_page /tmp/inboxes.jsonl conversations inboxes list
# {"id":"123","name":"Support Inbox","createdAt":"...","updatedAt":"..."}
# {"id":"124","name":"Sales Chat","createdAt":"...","updatedAt":"..."}

查看收件箱中的渠道(电子邮件、在线聊天、表单等):

conv_page /tmp/channels.jsonl conversations channels list --inbox-id 123
# {"id":"456","name":"Support Email","type":"EMAIL","inboxId":"123"}

2. 列出未结会话线程

threads list 需要 --inbox-id。在客户端按 .status 过滤,以便聚焦查看:

# all threads in an inbox
hubspot conversations threads list --inbox-id 123 --format jsonl

# only OPEN threads
hubspot conversations threads list --inbox-id 123 --format jsonl \
| jq -c 'select(.status == "OPEN")'

每行的输出格式:

{"id":"111","status":"OPEN","createdAt":"2026-07-28T10:02:00Z","latestMessageTimestamp":"2026-07-30T14:22:00Z","assignedTo":"A-78901","inboxId":"123","originalChannelId":"456","originalChannelAccountId":"148662","associatedContactId":"73235","spam":false,"archived":false}

会话线程没有 channelType——它通过 originalChannelId 中的 ID 指明来源渠道,需要与 channels list(步骤 8)的结果关联。subject 也不一定存在;读取时应使用 // "(no subject)" 作为备用值。

3. 查找未分配的会话线程

未分配会话线程的 assignedTo 为 null 或不存在:

hubspot conversations threads list --inbox-id 123 --format jsonl \
| jq -c 'select(.status == "OPEN") | select(.assignedTo == null or .assignedTo == "")'

4. 查找过期会话线程(N 小时内没有回复)

latestMessageTimestamp 为 ISO 8601 格式,可按字典序比较。

# macOS: threads with no activity in 24 hours
CUTOFF=$(date -u -v-24H +%Y-%m-%dT%H:%M:%SZ)
# Linux: CUTOFF=$(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ)

hubspot conversations threads list --inbox-id 123 --format jsonl \
| jq -c --arg cutoff "$CUTOFF" \
    'select(.status == "OPEN") | select(.latestMessageTimestamp < $cutoff)'

要查看 48 小时和 72 小时的情况,将 -v-24H 改为 -v-48H / -v-72H。

5. 查询操作者 ID

assign 接受操作者 ID。可通过团队成员的 Agent ID 或电子邮件获取其操作者 ID。HubSpot 操作者 ID 使用 A- 前缀:

hubspot conversations actors get --id A-123456 --format jsonl

要将所有者 ID 映射为操作者 ID,请交叉参考 hubspot owners list --format jsonl(会返回 .email 和 .id),并以 A-<ownerId> 格式的操作者 ID 作为起点——使用 actors get 验证该 ID 是否有效。

owners list 需要服务密钥。使用 OAuth 登录时,该命令会返回 403 This endpoint does not support user-level OAuth tokens,因此在查询所有者到操作者的映射之前,请将 HUBSPOT_ACCESS_TOKEN 设置为服务密钥。conversations 命令本身在这两种身份验证模式下都能使用。

6. 分配会话线程

hubspot conversations threads assign --id 111 --actor-id A-123456
# {"id":"111","ok":true}

批量分配(将所有未分配的未结会话线程分配给一名操作者)。这里有两点需要注意:从文件读取 ID 列表,而不是使用管道——由管道输入的 while 循环会在子 shell 中运行,因此计数器和失败列表会在循环结束时丢失;并且最后要返回非零状态码,以免将部分运行误认为已全部完成。

ACTOR=A-123456
conv_page /tmp/threads.jsonl conversations threads list --inbox-id 123

jq -r 'select(.status == "OPEN")
       | select(.assignedTo == null or .assignedTo == "")
       | .id' /tmp/threads.jsonl > /tmp/assign_ids.txt

assign_threads() {
  actor=$1; ids=$2
  assigned=0
  : > /tmp/assign_failed.txt

  while read -r tid; do
    if out=$(hubspot conversations threads assign --id "$tid" --actor-id "$actor" 2>&1); then
      assigned=$((assigned + 1))
    else
      printf '%s\t%s\n' "$tid" "$out" >> /tmp/assign_failed.txt
    fi
  done < "$ids"

  failed=$(wc -l < /tmp/assign_failed.txt)
  echo "Assigned $assigned of $(wc -l < "$ids"); $failed failed"
  [ "$failed" -eq 0 ] || { echo "Failures:"; cat /tmp/assign_failed.txt; return 1; }
}

assign_threads "$ACTOR" /tmp/assign_ids.txt

7. 归档已解决的会话线程

归档操作受保护:先运行 --dry-run 获取摘要,然后使用 --digest 和 --confirm(值 = 会话线程 ID)重新运行。

# Single thread — two steps
hubspot conversations threads archive --id 111 --dry-run
# {"id":"111","digest":"blast-29cfdd48b583","ok":true,"executed":false,"dry_run":true}

hubspot conversations threads archive --id 111 --digest blast-29cfdd48b583 --confirm 111
# {"id":"111","ok":true}
批量归档——先预览,再执行

批量归档分为两个独立阶段,中间需要人工决策。不要将两个阶段合并为一个循环,逐个对会话进行试运行并归档:这样会在任何人看到完整候选列表之前就开始销毁会话,而且整个操作也无法作为一个整体被拒绝。

阶段 1——预览。只读;不会修改任何内容。

INBOX=123
conv_page /tmp/threads.jsonl conversations threads list --inbox-id "$INBOX"

jq -c 'select(.status == "CLOSED")
       | {id, last_activity: .latestMessageTimestamp, assigned_to: .assignedTo}' \
  /tmp/threads.jsonl > /tmp/archive_candidates.jsonl

echo "Candidates for archive: $(wc -l < /tmp/archive_candidates.jsonl)"
jq -r '"\(.id)  \(.last_activity // "?")  \(.assigned_to // "unassigned")"' \
  /tmp/archive_candidates.jsonl

在此处停止并获得候选列表的批准。 你可以随时重新运行阶段 1;它不会更改任何内容。

保存候选项的 ID,不要保存摘要。摘要的有效期只有 5 分钟(src/hub/guard.rs 中的 DIGEST_TTL_SECONDS),因此,在预览期间生成的摘要经过实际审核所需的暂停时间后会变成 digest_expired。因此,阶段 2 会在每次使用摘要前立即为其生成摘要。

阶段 2——执行。只有在上面的列表获批后才能执行。

jq -r '.id' /tmp/archive_candidates.jsonl > /tmp/archive_ids.txt

archive_threads() {
  ids=$1
  archived=0
  : > /tmp/archive_failed.txt

  while read -r tid; do
    preview=$(hubspot conversations threads archive --id "$tid" --dry-run 2>&1)
    digest=$(printf '%s' "$preview" | jq -r '.digest // empty' 2>/dev/null)
    if [ -z "$digest" ]; then
      printf '%s\tdry-run\t%s\n' "$tid" "$preview" >> /tmp/archive_failed.txt
      continue
    fi

    if out=$(hubspot conversations threads archive --id "$tid" --digest "$digest" --confirm "$tid" 2>&1); then
      archived=$((archived + 1))
    else
      printf '%s\tarchive\t%s\n' "$tid" "$out" >> /tmp/archive_failed.txt
    fi
  done < "$ids"

  failed=$(wc -l < /tmp/archive_failed.txt)
  echo "Archived $archived of $(wc -l < "$ids"); $failed failed"
  [ "$failed" -eq 0 ] || { echo "Failures (id / phase / error):"; cat /tmp/archive_failed.txt; return 1; }
}

archive_threads /tmp/archive_ids.txt

失败的会话会被记录,运行也会继续,但函数会返回非零状态并打印所有失败项,因此,部分清空的收件箱绝不会被报告为运行成功。对同一个 ID 文件重新运行阶段 2 是安全的——已归档的会话只会失败并列出。

要撤销,请使用 restore:

hubspot conversations threads restore --id 111

8. 生成分类摘要

统计收件箱中各状态的会话数量:

echo "=== Inbox 123 triage summary ==="
conv_page /tmp/threads.jsonl conversations threads list --inbox-id 123

echo "Total threads:      $(wc -l < /tmp/threads.jsonl)"
echo "Open:               $(jq -c 'select(.status=="OPEN")' /tmp/threads.jsonl | wc -l)"
echo "Closed:             $(jq -c 'select(.status=="CLOSED")' /tmp/threads.jsonl | wc -l)"
echo "Unassigned (open):  $(jq -c 'select(.status=="OPEN") | select(.assignedTo==null or .assignedTo=="")' /tmp/threads.jsonl | wc -l)"

# Breakdown by channel type. Threads carry originalChannelId, not a type name,
# so build a channelId -> type map from `channels list` and join on it.
conv_page /tmp/channels.jsonl conversations channels list --inbox-id 123
CHANNEL_TYPES=$(jq -s 'map({key: (.id|tostring), value: .type}) | from_entries' /tmp/channels.jsonl)

echo ""
echo "=== By channel type ==="
jq -r --argjson types "$CHANNEL_TYPES" \
  '$types[(.originalChannelId | tostring)] // "UNKNOWN"' /tmp/threads.jsonl \
| sort | uniq -c | sort -rn

若要生成多个收件箱的摘要,请遍历步骤 1 中的所有收件箱 ID 并汇总。

已知限制

  • threads list 需要 --inbox-id——无法列出整个门户中的所有会话。
  • 服务端不支持按 status 或 assignedTo 过滤——请使用 jq 在客户端过滤。
  • 每条 list 命令最多分页返回 100 行,且只有 --format json 会公开游标(.meta.next)。批量操作和计数步骤必须使用 conv_page;直接重定向 --format jsonl 的输出会静默截断,让部分运行看起来像是完整的。
  • 会话不包含 channelType——请将 originalChannelId 与 channels list 的结果联接,以获取类型。会话可能在对话过程中切换频道,因此这描述的是它开始时所在的频道。
  • 批量归档不能通过 --digest/--confirm 合并为单条管道;每个会话都必须通过各自的两步流程单独归档,并且必须在执行任何归档操作前预览并批准候选列表。
  • 摘要会在生成它的 --dry-run 之后 5 分钟过期,因此不能预先收集后再应用——每次使用前都要立即生成。
  • 批量循环应由文件驱动,而不是由管道驱动:通过管道馈入的 while 循环体会在子 Shell 中运行,因此循环退出时,其中收集的失败计数会丢失。
  • actors get 只能按 ID 查询单个参与者;没有 actors 列表命令。要映射所有者与参与者,请从 hubspot owners list 开始。

评论

支持 Markdown