关于 conversation-inbox-triage
这是一个通过 hub-cli 管理 HubSpot 对话收件箱的分流技能,面向收件箱运营人员。它强调完整分页、批量操作安全,以及归档前审批。
主要功能
- 01发现收件箱和渠道
- 02查找未分配及滞留的开放会话
- 03将会话分配给操作者
- 04预览并经保护流程归档已解决会话
- 05完整分页获取结果并计数
- 06汇总收件箱会话状态
适用场景
- 01识别未分配或超时的收件箱会话
- 02将开放会话分配给团队成员
- 03归档前审核已解决会话
- 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开始。