feat(discord): thread interaction, slash commands, and mention-free replies#26
Open
YoYuTW wants to merge 10 commits into
Open
feat(discord): thread interaction, slash commands, and mention-free replies#26YoYuTW wants to merge 10 commits into
YoYuTW wants to merge 10 commits into
Conversation
…umbers Thread names now show user and prompt summary (🤖 user · prompt) instead of hardcoded "🤖 Response". Thread body includes a header embed with execution context, sequential step numbers on thinking/tool messages, and an enhanced summary footer with steps breakdown and duration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Track thread IDs created by the bot in ownThreads set. Messages in these threads skip the @mention check. Register thread alias via handler.registerThreadAlias so replies resolve to the original session. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…oubleshooting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ive restarts Saves thread IDs to own-threads.json in workingDir. Loads on startup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…plies threadChannelId was null for in-thread follow-ups, causing respondInThread() to buffer messages indefinitely since nested threads can't be created. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When an event triggers the agent, there is no real Discord message to reply to. Using postReply with a synthetic ts (channelId) fails silently because no such message exists. Instead, use postMessage to post directly to the channel when isEvent is true.
… + slash-handler.sh
Resolve the Discord context merge conflict against main. Carry forward the thread alias/session routing helpers required by the PR. Fix the usage summary footer newline regression that broke lint/build.
geminixiang
reviewed
Apr 27, 2026
Comment on lines
+417
to
+418
| const commandDefPath = join(skillDir, "slash-command.json"); | ||
| const handlerPath = join(skillDir, "slash-handler.sh"); |
Owner
There was a problem hiding this comment.
這兩個是什麼新玩意兒,我原本以為是去掃描 SKILL.md
Author
There was a problem hiding this comment.
記得是繞過 agent 直接註冊 slash command 的功能來著,就不會花 api credit 還啥的
媽呀 pr 開著就忘了
Author
There was a problem hiding this comment.
#!/bin/bash
# /order slash command handler
# Args: $1 = subcommand
# Env: OPTION_ID, OPTION_STATUS, OPTION_SHIP_DATE, DISCORD_USER_ID, etc.
SUBCOMMAND="$1"
# Helper: call admin status update API
update_status() {
local id="$1"
local status="$2"
local ship_date="$3"
if [ -n "$ship_date" ]; then
BODY="{\"status\":\"$status\",\"expected_ship_date\":\"$ship_date\"}"
else
BODY="{\"status\":\"$status\"}"
fi
RESPONSE=$(curl -s -w "\n%{http_code}" -X PATCH \
"$WORKER_URL/admin/order/$id/status" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "$BODY")
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY_RESP=$(echo "$RESPONSE" | head -1)
if [ "$HTTP_CODE" = "200" ]; then
echo "訂單 #$id 已更新為 $status"
else
echo "錯誤 ($HTTP_CODE): $BODY_RESP"
exit 1
fi
}
# Helper: format order row
format_order() {
local json="$1"
echo "$json" | python3 -c "
import json, sys
o = json.load(sys.stdin)
status_label = {
'pending_payment': '待付款',
'payment_submitted': '已付款待確認',
'confirmed': '已確認待出貨',
'shipped': '已出貨',
'completed': '已完成',
'payment_issue': '付款異常',
'cancelled': '已取消',
}
def fmt(o):
s = status_label.get(o.get('status',''), o.get('status',''))
items = json.loads(o.get('items','[]')) if isinstance(o.get('items'), str) else o.get('items',[])
item_str = ', '.join(f\"{i['spec']} x{i['quantity']}\" for i in items)
line = f\"#{o['id']} {o['name']} [{s}] {item_str}\"
if o.get('note'):
line += f\" 備註:{o['note']}\"
return line
data = json.load(open('/dev/stdin')) if False else o
if isinstance(data, list):
for row in data:
print(fmt(row))
else:
o2 = data
s = status_label.get(o2.get('status',''), o2.get('status',''))
items = json.loads(o2.get('items','[]')) if isinstance(o2.get('items'), str) else o2.get('items',[])
item_str = '\n'.join(f\" {i['spec']} x{i['quantity']}\" for i in items)
print(f\"訂單 #{o2['id']}\")
print(f\"姓名:{o2.get('name','')}\")
print(f\"電話:{o2.get('phone','')}\")
print(f\"地址:{o2.get('address','')}\")
print(f\"品項:\n{item_str}\")
print(f\"狀態:{s}\")
if o2.get('transfer_last5'): print(f\"匯款末五碼:{o2['transfer_last5']}\")
if o2.get('expected_ship_date'): print(f\"預計出貨:{o2['expected_ship_date']}\")
if o2.get('shipped_at'): print(f\"出貨時間:{o2['shipped_at']}\")
if o2.get('note'): print(f\"備註:{o2['note']}\")
print(f\"下單時間:{o2.get('created_at','')}\")
"
}
case "$SUBCOMMAND" in
list)
if [ -n "$OPTION_STATUS" ]; then
WHERE="WHERE status='$OPTION_STATUS'"
else
WHERE=""
fi
RESULT=$(wrangler d1 execute x --remote \
--command="SELECT id, name, items, status, note, created_at FROM orders $WHERE ORDER BY created_at DESC" \
--json 2>/dev/null | python3 -c "
import json, sys
data = json.load(sys.stdin)
rows = data[0].get('results', []) if data else []
if not rows:
print('沒有訂單')
sys.exit(0)
status_label = {
'pending_payment': '待付款',
'payment_submitted': '已付款待確認',
'confirmed': '已確認待出貨',
'shipped': '已出貨',
'completed': '已完成',
'payment_issue': '付款異常',
'cancelled': '已取消',
}
lines = []
for o in rows:
s = status_label.get(o.get('status',''), o.get('status',''))
try:
items = json.loads(o.get('items','[]'))
item_str = ', '.join(f\"{i['spec']} x{i['quantity']}\" for i in items)
except:
item_str = o.get('items','')
line = f\"#{o['id']} {o['name']} [{s}] {item_str}\"
if o.get('note'):
line += f\" 備:{o['note']}\"
lines.append(line)
print('\n'.join(lines))
")
echo "$RESULT"
;;
view)
ID="${OPTION_ID}"
if [ -z "$ID" ]; then echo "缺少 id"; exit 1; fi
wrangler d1 execute x --remote \
--command="SELECT * FROM orders WHERE id=$ID" \
--json 2>/dev/null | python3 -c "
import json, sys
data = json.load(sys.stdin)
rows = data[0].get('results', []) if data else []
if not rows:
print('找不到訂單')
sys.exit(0)
o = rows[0]
status_label = {
'pending_payment': '待付款',
'payment_submitted': '已付款待確認',
'confirmed': '已確認待出貨',
'shipped': '已出貨',
'completed': '已完成',
'payment_issue': '付款異常',
'cancelled': '已取消',
}
s = status_label.get(o.get('status',''), o.get('status',''))
try:
items = json.loads(o.get('items','[]'))
item_str = '\n'.join(f\" {i['spec']} x{i['quantity']}\" for i in items)
except:
item_str = o.get('items','')
print(f\"訂單 #{o['id']}\")
print(f\"姓名:{o.get('name','')}\")
print(f\"電話:{o.get('phone','')}\")
print(f\"Email:{o.get('email','')}\")
print(f\"地址:{o.get('address','')}\")
print(f\"品項:\n{item_str}\")
print(f\"狀態:{s}\")
if o.get('transfer_last5'): print(f\"匯款末五碼:{o['transfer_last5']}\")
if o.get('expected_ship_date'): print(f\"預計出貨:{o['expected_ship_date']}\")
if o.get('shipped_at'): print(f\"出貨時間:{o['shipped_at']}\")
if o.get('note'): print(f\"備註:{o['note']}\")
print(f\"下單時間:{o.get('created_at','')}\")
"
;;
confirm)
update_status "${OPTION_ID}" "confirmed" "${OPTION_SHIP_DATE:-}"
;;
ship)
update_status "${OPTION_ID}" "shipped"
;;
done)
update_status "${OPTION_ID}" "completed"
;;
issue)
update_status "${OPTION_ID}" "payment_issue"
;;
cancel)
update_status "${OPTION_ID}" "cancelled"
;;
*)
echo "未知的 subcommand: $SUBCOMMAND"
exit 1
;;
esac{
"name": "order",
"description": "訂單管理",
"options": [
{
"type": 1,
"name": "list",
"description": "列出訂單",
"options": [
{
"type": 3,
"name": "status",
"description": "篩選狀態",
"required": false,
"choices": [
{ "name": "待付款", "value": "pending_payment" },
{ "name": "已提交付款", "value": "payment_submitted" },
{ "name": "已確認待出貨", "value": "confirmed" },
{ "name": "已出貨", "value": "shipped" },
{ "name": "已完成", "value": "completed" },
{ "name": "付款異常", "value": "payment_issue" },
{ "name": "已取消", "value": "cancelled" }
]
}
]
},
{
"type": 1,
"name": "view",
"description": "查看單筆訂單",
"options": [
{
"type": 4,
"name": "id",
"description": "訂單 ID",
"required": true
}
]
},
{
"type": 1,
"name": "confirm",
"description": "確認付款",
"options": [
{
"type": 4,
"name": "id",
"description": "訂單 ID",
"required": true
},
{
"type": 3,
"name": "ship_date",
"description": "預計出貨日 (YYYY-MM-DD)",
"required": false
}
]
},
{
"type": 1,
"name": "ship",
"description": "標記出貨",
"options": [
{
"type": 4,
"name": "id",
"description": "訂單 ID",
"required": true
}
]
},
{
"type": 1,
"name": "done",
"description": "標記完成",
"options": [
{
"type": 4,
"name": "id",
"description": "訂單 ID",
"required": true
}
]
},
{
"type": 1,
"name": "issue",
"description": "標記付款異常",
"options": [
{
"type": 4,
"name": "id",
"description": "訂單 ID",
"required": true
}
]
},
{
"type": 1,
"name": "cancel",
"description": "取消訂單",
"options": [
{
"type": 4,
"name": "id",
"description": "訂單 ID",
"required": true
}
]
}
]
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
slash-command.json+slash-handler.sh🤖 Generated with Claude Code