@shulex/cxclawctl 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -22
- package/dist/cli.js +2202 -127
- package/dist/cli.js.map +1 -1
- package/package.json +19 -6
- package/capabilities.lock.json +0 -898
- package/requirements-cli.txt +0 -2
- package/skills/__lib__/__init__.py +0 -0
- package/skills/__lib__/skill-runtime/bootstrap.py +0 -134
- package/skills/__lib__/solvea-api/__init__.py +0 -0
- package/skills/__lib__/solvea-api/client.py +0 -1944
- package/skills/__lib__/solvea-api/dashboard.py +0 -100
- package/skills/__lib__/solvea-api/solvea_api.py +0 -1341
- package/skills/agent-analytics/SKILL.md +0 -74
- package/skills/agent-analytics/references/chat-report-fields.md +0 -36
- package/skills/agent-analytics/references/report-fields.md +0 -130
- package/skills/agent-analytics/references/transcript.md +0 -110
- package/skills/agent-builder/SKILL.md +0 -42
- package/skills/agent-builder/interfaces/fallback-chatflow.md +0 -47
- package/skills/agent-builder/references/chatflow/graph-spec.md +0 -259
- package/skills/agent-builder/references/chatflow/node-selection.md +0 -89
- package/skills/agent-builder/references/chatflow/nodes/agent.md +0 -350
- package/skills/agent-builder/references/chatflow/nodes/business-track.md +0 -43
- package/skills/agent-builder/references/chatflow/nodes/code.md +0 -157
- package/skills/agent-builder/references/chatflow/nodes/collect-order.md +0 -47
- package/skills/agent-builder/references/chatflow/nodes/finish.md +0 -34
- package/skills/agent-builder/references/chatflow/nodes/human-review.md +0 -60
- package/skills/agent-builder/references/chatflow/nodes/if-else.md +0 -100
- package/skills/agent-builder/references/chatflow/nodes/intent.md +0 -79
- package/skills/agent-builder/references/chatflow/nodes/knowledge-reply.md +0 -43
- package/skills/agent-builder/references/chatflow/nodes/llm.md +0 -117
- package/skills/agent-builder/references/chatflow/nodes/note.md +0 -38
- package/skills/agent-builder/references/chatflow/nodes/question.md +0 -97
- package/skills/agent-builder/references/chatflow/nodes/rag-reply.md +0 -68
- package/skills/agent-builder/references/chatflow/nodes/reply.md +0 -97
- package/skills/agent-builder/references/chatflow/nodes/start.md +0 -35
- package/skills/agent-builder/references/chatflow/nodes/sub-graph.md +0 -48
- package/skills/agent-builder/references/chatflow/nodes/tool.md +0 -138
- package/skills/agent-builder/references/chatflow/nodes/transfer-to-human.md +0 -63
- package/skills/agent-builder/references/chatflow/nodes/variable-assignment.md +0 -52
- package/skills/agent-builder/references/chatflow/nodes/variable-extract.md +0 -50
- package/skills/agent-builder/references/chatflow/nodes/variable-process.md +0 -70
- package/skills/agent-builder/references/chatflow/overview.md +0 -53
- package/skills/agent-builder/references/chatflow/visual-translation.md +0 -64
- package/skills/agent-builder/references/chatflow/workflow.md +0 -134
- package/skills/agent-builder/references/multi-agent/create-update.md +0 -185
- package/skills/agent-builder/references/multi-agent/graph-spec.md +0 -246
- package/skills/agent-builder/references/multi-agent/graph-structure.md +0 -12
- package/skills/agent-builder/references/multi-agent/migrate-to-chatflow.md +0 -13
- package/skills/agent-builder/references/multi-agent/migration/large.md +0 -554
- package/skills/agent-builder/references/multi-agent/migration/principles.md +0 -446
- package/skills/agent-builder/references/multi-agent/migration/small.md +0 -225
- package/skills/agent-builder/references/multi-agent/orchestration.md +0 -11
- package/skills/agent-builder/references/multi-agent/overview.md +0 -48
- package/skills/agent-builder/references/multi-agent/update.md +0 -128
- package/skills/agent-builder/references/multi-agent/workflow.md +0 -87
- package/skills/agent-builder/references/single-agent/overview.md +0 -48
- package/skills/agent-builder/references/single-agent/prompt.md +0 -16
- package/skills/agent-builder/references/single-agent/tools.md +0 -36
- package/skills/agent-builder/references/single-agent/voice.md +0 -46
- package/skills/agent-builder/references/tools/http-tool.md +0 -315
- package/skills/agent-builder/references/tools/mock-tool.md +0 -279
- package/skills/agent-builder/references/tools/overview.md +0 -46
- package/skills/agent-builder/references/tools/tool-config.md +0 -43
- package/skills/agent-builder/references/tools/x-token.md +0 -34
- package/skills/agent-builder/scripts/chatflow/chatflow_builder.py +0 -3458
- package/skills/agent-builder/scripts/chatflow/validate_graph.py +0 -1697
- package/skills/agent-builder/scripts/copy_marketplace_agent.py +0 -61
- package/skills/agent-builder/scripts/multi-agent/legacy_graph_analyzer.py +0 -5974
- package/skills/agent-builder/scripts/multi-agent/replay_compare.py +0 -679
- package/skills/agent-builder/scripts/patch_agent_tools.py +0 -135
- package/skills/agent-builder/scripts/tools/validate_tool_config.py +0 -384
- package/skills/agent-tester/SKILL.md +0 -35
- package/skills/agent-tester/batch-test/overview.md +0 -86
- package/skills/agent-tester/scripts/batch_test_sheet.py +0 -213
- package/skills/agent-tester/single-test/badcase-retest.md +0 -85
- package/skills/agent-tester/single-test/direct-test.md +0 -24
- package/skills/agent-tester/single-test/overview.md +0 -142
- package/skills/delivery/SKILL.md +0 -112
- package/skills/delivery/interfaces/classify.md +0 -38
- package/skills/delivery/interfaces/create-agents.md +0 -89
- package/skills/delivery/interfaces/delivery-test.md +0 -67
- package/skills/delivery/references/flow.md +0 -40
- package/skills/delivery/references/validation.md +0 -15
- package/skills/delivery/scripts/batch_test.py +0 -704
- package/skills/delivery/scripts/classify.py +0 -22
- package/skills/delivery/scripts/create_agents.py +0 -695
- package/skills/delivery/scripts/gen_delivery_report.py +0 -335
- package/skills/delivery/scripts/gen_excel.py +0 -234
- package/skills/delivery/scripts/gen_report.py +0 -208
- package/skills/delivery/scripts/plan.py +0 -163
- package/skills/delivery/scripts/render.py +0 -713
- package/skills/delivery/scripts/render_summary.py +0 -38
- package/skills/delivery/scripts/sample_history_cases.py +0 -109
- package/skills/kb-builder/SKILL.md +0 -80
- package/skills/kb-builder/interfaces/qa-upload.md +0 -55
- package/skills/kb-builder/interfaces/web-kb.md +0 -84
- package/skills/kb-builder/references/directory.md +0 -373
- package/skills/kb-builder/references/export.md +0 -282
- package/skills/kb-builder/references/isolation-keywords.md +0 -113
- package/skills/kb-builder/references/knowledge.md +0 -153
- package/skills/kb-builder/references/product.md +0 -52
- package/skills/kb-builder/references/qa-from-help-center.md +0 -58
- package/skills/kb-builder/references/qa-from-pdf.md +0 -94
- package/skills/kb-builder/references/qa-from-tickets.md +0 -168
- package/skills/kb-builder/references/qa-from-web.md +0 -98
- package/skills/kb-builder/references/qa-quality.md +0 -73
- package/skills/kb-builder/references/reply-template.md +0 -237
- package/skills/kb-builder/references/tag-isolation-migration.md +0 -61
- package/skills/kb-builder/references/tags.md +0 -45
- package/skills/kb-builder/references/upload.md +0 -130
- package/skills/kb-builder/scripts/batch_extract_ticket_qa.py +0 -333
- package/skills/kb-builder/scripts/build_directory_plan.py +0 -144
- package/skills/kb-builder/scripts/crawl_site.py +0 -433
- package/skills/kb-builder/scripts/create_dirs.py +0 -146
- package/skills/kb-builder/scripts/export_knowledge.py +0 -454
- package/skills/kb-builder/scripts/extract_ticket_qa.py +0 -441
- package/skills/kb-builder/scripts/extract_web_qa.py +0 -118
- package/skills/kb-builder/scripts/import_qa.py +0 -219
- package/skills/kb-builder/scripts/import_source_documents.py +0 -143
- package/skills/kb-builder/scripts/import_templates.py +0 -409
- package/skills/poc/SKILL.md +0 -125
- package/skills/poc/assets/default_manual.css +0 -279
- package/skills/poc/assets/notice_escalate.png +0 -0
- package/skills/poc/assets/notice_escalate2.png +0 -0
- package/skills/poc/assets/notice_new_chat.png +0 -0
- package/skills/poc/assets/poc_test_template.html +0 -645
- package/skills/poc/assets/poc_test_template.md +0 -104
- package/skills/poc/assets/step3_preview.png +0 -0
- package/skills/poc/assets/step4_order_note.png +0 -0
- package/skills/poc/interfaces/classify.md +0 -38
- package/skills/poc/interfaces/create-agents.md +0 -108
- package/skills/poc/interfaces/poc-test.md +0 -94
- package/skills/poc/references/flow.md +0 -47
- package/skills/poc/references/kb-directory.md +0 -22
- package/skills/poc/references/validation.md +0 -17
- package/skills/poc/scripts/classify.py +0 -22
- package/skills/poc/scripts/create_agents.py +0 -818
- package/skills/poc/scripts/gen_excel.py +0 -234
- package/skills/poc/scripts/gen_poc_manual.py +0 -249
- package/skills/poc/scripts/gen_report.py +0 -208
- package/skills/poc/scripts/inspect_chatflow.py +0 -209
- package/skills/poc/scripts/plan.py +0 -178
- package/skills/poc/scripts/poc_test.py +0 -495
- package/skills/poc/scripts/render.py +0 -713
- package/skills/poc/scripts/render_summary.py +0 -41
- package/skills/runtime-inspector/SKILL.md +0 -51
- package/skills/runtime-inspector/references/frontend-rendering.md +0 -126
- package/skills/runtime-inspector/references/model.md +0 -59
- package/skills/runtime-inspector/references/sandbox.md +0 -131
- package/skills/runtime-inspector/scripts/model_inspect.py +0 -68
- package/skills/runtime-inspector/scripts/prepare.py +0 -98
- package/skills/runtime-inspector/scripts/process.py +0 -84
- package/skills/runtime-inspector/scripts/read_state.py +0 -32
- package/skills/runtime-inspector/scripts/sandbox_inspect.py +0 -463
- package/skills/runtime-inspector/scripts/test_claude_proxy.py +0 -49
- package/skills/runtime-inspector/scripts/test_openai_proxy.py +0 -41
- package/skills/runtime-inspector/scripts/verify_persistence.py +0 -101
- package/skills/runtime-inspector/scripts/write_test.py +0 -41
- package/skills/setup/SKILL.md +0 -17
- package/skills/shulex-support/SKILL.md +0 -39
- package/skills/shulex-support/references/badcase-analysis/overview.md +0 -34
- package/skills/shulex-support/references/badcase-analysis/text/conversation-record.md +0 -105
- package/skills/shulex-support/references/badcase-analysis/text/core-flow.md +0 -89
- package/skills/shulex-support/references/badcase-analysis/text/log-query-rules.md +0 -36
- package/skills/shulex-support/references/badcase-analysis/text/m1-entry.md +0 -53
- package/skills/shulex-support/references/badcase-analysis/text/m10-chatflow-service.md +0 -259
- package/skills/shulex-support/references/badcase-analysis/text/m11-algorithm-service.md +0 -148
- package/skills/shulex-support/references/badcase-analysis/text/m12-quality-inspection.md +0 -57
- package/skills/shulex-support/references/badcase-analysis/text/m2-intent.md +0 -120
- package/skills/shulex-support/references/badcase-analysis/text/m3-strategy.md +0 -126
- package/skills/shulex-support/references/badcase-analysis/text/m4-aifield.md +0 -121
- package/skills/shulex-support/references/badcase-analysis/text/m5-agent.md +0 -208
- package/skills/shulex-support/references/badcase-analysis/text/m6-tool.md +0 -115
- package/skills/shulex-support/references/badcase-analysis/text/m7-reply.md +0 -72
- package/skills/shulex-support/references/badcase-analysis/text/m8-error.md +0 -92
- package/skills/shulex-support/references/badcase-analysis/text/m9-knowledge-search.md +0 -147
- package/skills/shulex-support/references/badcase-analysis/text/sls-query.md +0 -283
- package/skills/shulex-support/references/badcase-analysis/voice/core-flow.md +0 -77
- package/skills/shulex-support/references/badcase-analysis/voice/dependencies.md +0 -19
- package/skills/shulex-support/references/badcase-analysis/voice/platform-api.md +0 -23
- package/skills/shulex-support/references/badcase-analysis/voice/sls-playbook.md +0 -31
- package/skills/shulex-support/references/product-docs.md +0 -70
- package/skills/shulex-support/references/tickets.md +0 -184
- package/skills/shulex-support/scripts/support.py +0 -427
- package/skills/solvea-desk/SKILL.md +0 -39
- package/skills/solvea-desk/references/report/fields.md +0 -82
- package/skills/solvea-desk/references/report/report-design.md +0 -200
- package/skills/solvea-desk/references/report/report-template.html +0 -44
- package/skills/solvea-desk/references/report/report.css +0 -517
- package/skills/solvea-desk/references/report/workflow.md +0 -464
- package/skills/solvea-desk/references/tickets/query.md +0 -103
- package/skills/solvea-desk/references/tickets/tags.md +0 -83
- package/skills/solvea-desk/scripts/report/fetch_sources.py +0 -580
- package/skills/solvea-desk/scripts/report/render_report.py +0 -763
- package/skills/solvea-desk/scripts/tickets/desk_api.py +0 -73
- package/skills/solvea-desk/scripts/tickets/fetch_tickets.py +0 -403
- package/skills/solvea-desk/scripts/tickets/ticket_tags.py +0 -305
- package/skills/solvea-kit/SKILL.md +0 -84
- package/skills/solvea-kit/references/agent.md +0 -145
- package/skills/solvea-kit/references/chat.md +0 -244
- package/skills/solvea-kit/references/intent.md +0 -69
- package/skills/solvea-kit/references/plugin.md +0 -53
- package/skills/solvea-kit/references/quality-inspection.md +0 -75
- package/skills/solvea-kit/references/strategy.md +0 -62
- package/skills/solvea-kit/references/tagging.md +0 -231
- package/skills/solvea-kit/references/variables.md +0 -53
- package/skills/solvea-kit/scripts/generate_label_import_file.py +0 -151
- package/skills/solvea-kit/scripts/intent_classify.py +0 -843
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
# 产品文档查询
|
|
2
|
-
|
|
3
|
-
产品使用、配置、排障、功能说明类问题,只使用产品文档查询命令,不再调用旧的 GPT 售后知识库查询链路。
|
|
4
|
-
|
|
5
|
-
### 搜索 query 写法
|
|
6
|
-
|
|
7
|
-
文档搜索同时看语义和关键词。调用 `search-docs` 时,query 要像用户真实问题,但保留核心产品词、功能词和对象名:
|
|
8
|
-
|
|
9
|
-
- 推荐:`我该怎么使用人机协同节点`、`Human in Loop 人机协同节点怎么配置`
|
|
10
|
-
- 不推荐:只堆叠 2-3 个短关键词,尤其包含“使用”“设置”“功能”等泛词时,容易带出无关结果
|
|
11
|
-
|
|
12
|
-
查看搜索结果时,优先采用标题、页面路径、内容片段都命中核心概念的结果;只因为泛词命中的结果不要引用。
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
npx --yes @shulex/cxclawctl@latest script skills/shulex-support/scripts/support.py search-docs \
|
|
16
|
-
--query "<用户问题>" \
|
|
17
|
-
--pretty
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
`search-docs` 用于搜索产品文档,返回结构化结果:
|
|
21
|
-
|
|
22
|
-
```json
|
|
23
|
-
{
|
|
24
|
-
"query": "<用户问题>",
|
|
25
|
-
"results": [
|
|
26
|
-
{
|
|
27
|
-
"title": "<文档标题>",
|
|
28
|
-
"url": "<文档链接>",
|
|
29
|
-
"page": "<页面路径>",
|
|
30
|
-
"content": "<相关片段>"
|
|
31
|
-
}
|
|
32
|
-
]
|
|
33
|
-
}
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
当需要进一步定位或读取文档文件时,使用文件查询命令:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
npx --yes @shulex/cxclawctl@latest script skills/shulex-support/scripts/support.py query-docs-filesystem \
|
|
40
|
-
--command 'rg -il "<关键词>" / | head -10' \
|
|
41
|
-
--pretty
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
常用只读命令示例:
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
# 找相关文件
|
|
48
|
-
npx --yes @shulex/cxclawctl@latest script skills/shulex-support/scripts/support.py query-docs-filesystem \
|
|
49
|
-
--command 'rg -il "变量" / | head -10' \
|
|
50
|
-
--pretty
|
|
51
|
-
|
|
52
|
-
# 读取某个文档开头
|
|
53
|
-
npx --yes @shulex/cxclawctl@latest script skills/shulex-support/scripts/support.py query-docs-filesystem \
|
|
54
|
-
--command 'sed -n "1,160p" /solvea/bot_cn/function-manual/agents/variables/variable-definition.mdx' \
|
|
55
|
-
--pretty
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
基于文档回答时:
|
|
59
|
-
|
|
60
|
-
- 只使用 `search-docs` / `query-docs-filesystem` 返回的内容,不要编造文档里没有的信息。
|
|
61
|
-
- 如果文档结果不足以回答,直接说明文档中没有找到确定依据,再引导用户补充信息或提交工单。
|
|
62
|
-
- 回复末尾必须列出「参考文档」,使用命令返回的 `title` 和 `url`;没有 `url` 时只列 `page`,不要自行拼链接。
|
|
63
|
-
- 有 `url` 时使用 Markdown 链接格式,避免写成 `标题:URL`。
|
|
64
|
-
|
|
65
|
-
参考文档格式:
|
|
66
|
-
|
|
67
|
-
```text
|
|
68
|
-
参考文档:
|
|
69
|
-
- [<title>](<url>)
|
|
70
|
-
```
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
# 售后工单
|
|
2
|
-
|
|
3
|
-
所有创建动作都遵循“先确认后提交”:先向用户展示整理后的工单明细,获得明确确认后才能调用创建接口。
|
|
4
|
-
|
|
5
|
-
## CLI 入口
|
|
6
|
-
|
|
7
|
-
工单创建、列表查询、详情查询、追加消息,继续通过本 skill 的 CLI 调用 GPT support API:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npx --yes @shulex/cxclawctl@latest script skills/shulex-support/scripts/support.py <command> --pretty
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
可用命令:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
# 创建工单(基本)
|
|
17
|
-
npx --yes @shulex/cxclawctl@latest script skills/shulex-support/scripts/support.py create-ticket \
|
|
18
|
-
--content "<问题描述>" \
|
|
19
|
-
--brand "<brand,可选>" \
|
|
20
|
-
--issue-type "<工单类型 code,见下方问题分类表>" \
|
|
21
|
-
--ext '<{"chatId":"<chatId>"}>' \
|
|
22
|
-
--subject "<工单标题,可选>" \
|
|
23
|
-
--contact-email "<邮箱,可选>" \
|
|
24
|
-
--pretty
|
|
25
|
-
|
|
26
|
-
# 创建工单(带附件,可重复 --attachment)
|
|
27
|
-
npx --yes @shulex/cxclawctl@latest script skills/shulex-support/scripts/support.py create-ticket \
|
|
28
|
-
--content "<问题描述>" \
|
|
29
|
-
--issue-type "<工单类型 code>" \
|
|
30
|
-
--attachment "<沙箱内文件路径>" \
|
|
31
|
-
--attachment "<另一个文件路径(可选)>" \
|
|
32
|
-
--pretty
|
|
33
|
-
|
|
34
|
-
# 分页查询工单列表
|
|
35
|
-
npx --yes @shulex/cxclawctl@latest script skills/shulex-support/scripts/support.py list-tickets \
|
|
36
|
-
--page 1 \
|
|
37
|
-
--page-size 20 \
|
|
38
|
-
--pretty
|
|
39
|
-
|
|
40
|
-
# 查询工单详情和消息
|
|
41
|
-
npx --yes @shulex/cxclawctl@latest script skills/shulex-support/scripts/support.py get-ticket \
|
|
42
|
-
--ticket-number "<工单号>" \
|
|
43
|
-
--pretty
|
|
44
|
-
|
|
45
|
-
# 追加客户消息
|
|
46
|
-
npx --yes @shulex/cxclawctl@latest script skills/shulex-support/scripts/support.py append-message \
|
|
47
|
-
--ticket-number "<工单号>" \
|
|
48
|
-
--content "<用户补充描述>" \
|
|
49
|
-
--pretty
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## 提交工单信息
|
|
53
|
-
|
|
54
|
-
创建工单前尽量整理以下信息:
|
|
55
|
-
|
|
56
|
-
| 字段 | 说明 |
|
|
57
|
-
|---|---|
|
|
58
|
-
| 问题描述 | 用户遇到的问题、期望结果、实际结果 |
|
|
59
|
-
| 问题分类 | 见下方「问题分类」表,选对应 code |
|
|
60
|
-
| 扩展字段 | 按分类填写必要字段(见下方「按分类必填」) |
|
|
61
|
-
| 附件 | 用户提供的截图/日志等文件,通过 `--attachment` 上传 |
|
|
62
|
-
| 来源信息 | 当前会话或页面上下文中可用的 sourceType/sourceId |
|
|
63
|
-
| 品牌 | 如果上下文能拿到 brand 则带上;拿不到不要让用户反复补 |
|
|
64
|
-
| 联系邮箱 | 可选,用于后续通知 |
|
|
65
|
-
|
|
66
|
-
### 问题描述:使用纯文本
|
|
67
|
-
|
|
68
|
-
`--content` 里的工单描述使用纯文本,不使用 Markdown 标题、列表、表格或 HTML。内容要准确、精准、客观,方便客服和研发直接阅读、定位和复制处理。建议包含:
|
|
69
|
-
|
|
70
|
-
- 问题现象
|
|
71
|
-
- 期望结果和实际结果
|
|
72
|
-
- 已尝试的排查步骤
|
|
73
|
-
- 相关 ID、时间点、截图或日志说明
|
|
74
|
-
|
|
75
|
-
不要写 `# 标题`、`- 列表项`、Markdown 表格、HTML 标签或 `\n` 这种字面量转义。需要分段时,直接使用正常换行。
|
|
76
|
-
|
|
77
|
-
### 问题分类
|
|
78
|
-
|
|
79
|
-
| code | 中文含义 |
|
|
80
|
-
|---|---|
|
|
81
|
-
| `agent_badcase` | AI 客服异常/Bug |
|
|
82
|
-
| `consult` | 产品咨询 |
|
|
83
|
-
| `agent_feature` | AI 客服功能需求 |
|
|
84
|
-
| `integration_feature` | 集成需求 |
|
|
85
|
-
| `ticket_feature` | 工单功能需求 |
|
|
86
|
-
| `ticket_bug` | 工单 Bug |
|
|
87
|
-
| `other` | 其他 |
|
|
88
|
-
|
|
89
|
-
### 按分类必填(放 `--ext`)
|
|
90
|
-
|
|
91
|
-
| 分类 code | 必填 ext 字段 | 说明 |
|
|
92
|
-
|---|---|---|
|
|
93
|
-
| `agent_badcase` | `chatId` | 异常对话的 Chat ID,用于定位问题 |
|
|
94
|
-
| `ticket_bug` | `relatedTicketId` | 相关工单 ID,便于复现和定位 |
|
|
95
|
-
|
|
96
|
-
其余分类无强制 ext 字段;`--ext` 可省略。
|
|
97
|
-
|
|
98
|
-
### 附件上传说明
|
|
99
|
-
|
|
100
|
-
用户上传的截图、日志等文件,脚本内部会在建单前自动先将每个附件 POST 到上传端点取得 URL,再随建单请求一并提交,整个过程对 agent 透明。调用方式:
|
|
101
|
-
|
|
102
|
-
```bash
|
|
103
|
-
--attachment <沙箱内文件路径>
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
- 每个 `--attachment` 指定一个文件,可重复使用以上传多个文件。
|
|
107
|
-
- 正文里只写图片说明;真实截图、日志等文件通过 `--attachment` 上传。
|
|
108
|
-
|
|
109
|
-
### 问题描述:准确、精准地整理
|
|
110
|
-
|
|
111
|
-
`--content` 不是简单复制用户某一句话,而是结合整段对话上下文,**站在用户立场**重新整理,准确表达用户当前真正的诉求:
|
|
112
|
-
|
|
113
|
-
- **准确、精准、客观**:用专业清晰的描述表达问题,不要太口语化、情绪化或闲聊化;可以保留必要的第一人称,但不要写成第三人称客服转述(不要"该用户反馈……")。
|
|
114
|
-
- **整合上下文**:把多轮对话里零散的信息(现象、已尝试的操作、期望结果、实际结果、相关 ID/截图说明)汇总成一段完整、自洽的描述。
|
|
115
|
-
- **准确不夸大**:只写对话里真实出现的信息,不要替用户编造未确认的细节、原因或诉求;用户表达模糊时如实保留,不要脑补。
|
|
116
|
-
- **聚焦真实诉求**:先点明用户现在最想解决/想要的是什么,再补背景,避免被中间的闲聊带偏。
|
|
117
|
-
|
|
118
|
-
### 创建前必须先确认(强制)
|
|
119
|
-
|
|
120
|
-
整理好工单明细后,**先把明细完整列给用户确认,得到明确同意才调用 `create-ticket`**。不要跳过确认直接创建。
|
|
121
|
-
|
|
122
|
-
向用户展示的明细至少包含:
|
|
123
|
-
|
|
124
|
-
```text
|
|
125
|
-
即将为你创建售后工单,请确认以下内容:
|
|
126
|
-
|
|
127
|
-
- 问题描述:<以用户口吻整理后的完整描述>
|
|
128
|
-
- 问题分类:<分类中文名>
|
|
129
|
-
- 扩展字段:<如 chatId、relatedTicketId 等,无则省略>
|
|
130
|
-
- 附件:<文件名列表,无则省略>
|
|
131
|
-
- 工单标题:<subject,可选>
|
|
132
|
-
- 联系邮箱:<contactEmail,可选>
|
|
133
|
-
|
|
134
|
-
确认无误我就提交;需要修改的话告诉我要改哪里。
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
- 用户确认后再执行 `create-ticket`,并把确认后的描述作为 `--content`。
|
|
138
|
-
- 用户要求修改时,按反馈改完重新展示明细,再次确认后才提交。
|
|
139
|
-
- 用户没有明确确认前,不要调用创建接口。
|
|
140
|
-
|
|
141
|
-
创建后必须根据接口返回的 `status` 判断结果,并且只向用户展示客户可见工单号 `ticketNumber`。不要向用户解释"本地工单""同步""Tars""内部客服系统"等实现细节:
|
|
142
|
-
|
|
143
|
-
- `status == "created"`:明确告知工单创建成功,给出工单号,并把工单号渲染成可点击的 Markdown 链接,链接使用脚本返回的 `ticketDetailUrl`;同时按下方话术规范给出预估跟进时效与后续方式。
|
|
144
|
-
- `status == "syncing"`:说明已收到并正在受理工单,可给工单号;同样把工单号渲染成可点击的 Markdown 链接,链接使用脚本返回的 `ticketDetailUrl`;用"正在受理"措辞,不要承诺客服已接单,跟进时效可照常给出。
|
|
145
|
-
- `status == "sync_failed"`:说明工单暂时提交失败,致歉并建议稍后重试或联系 Shulex 客服;不要说"创建成功",也不要给跟进时效承诺。
|
|
146
|
-
- `tarsTicketNumber` / `shulexTicketNumber` 是内部字段,不能展示给用户,不能拿它替代 `ticketNumber`。
|
|
147
|
-
|
|
148
|
-
### 售后客服话术规范(创建成功后)
|
|
149
|
-
|
|
150
|
-
用专业、有温度的售后客服语气回复,包含以下要素(按需裁剪,不要机械堆砌):
|
|
151
|
-
|
|
152
|
-
1. **确认受理 + 适度共情**:先告诉用户问题已收到、工单已建好;若用户明显受困扰,简短致歉("给你带来不便很抱歉"),不要过度道歉。
|
|
153
|
-
2. **工单号**:先直接展示 `ticketNumber`,再用脚本返回的 `ticketDetailUrl` 拼出“工单详情页”的 Markdown 链接,方便后续追踪。
|
|
154
|
-
3. **预估时效**:给出明确的跟进时效,默认 **售后团队会在 2–5 个工作日内跟进处理(节假日顺延)**;可按紧急度调整措辞(严重问题会优先处理),但**只承诺"跟进 / 回复"的时效,不承诺具体解决时间或结果**。
|
|
155
|
-
4. **后续跟进方式**:说明会有售后人员跟进;用户可凭工单号在「售后工单」列表查看进度、随时把工单号发给我继续补充信息 / 上传截图日志;若填写了联系邮箱,进展也会通过邮件通知。
|
|
156
|
-
5. **礼貌收尾**:感谢用户的耐心与反馈。
|
|
157
|
-
|
|
158
|
-
约束:
|
|
159
|
-
|
|
160
|
-
- 不透露任何内部实现(Tars、本地工单、同步、内部客服系统等)。
|
|
161
|
-
- 不编造未发生的事(如"客服已接单""预计今天解决");时效只说预估,不打包票。
|
|
162
|
-
- 时效与 `status` 匹配:仅 `created` / `syncing` 给跟进时效;`sync_failed` 不给时效,改为致歉 + 重试 / 联系客服建议。
|
|
163
|
-
|
|
164
|
-
成功话术示例:
|
|
165
|
-
|
|
166
|
-
```text
|
|
167
|
-
你好,你的售后工单已经创建好了。
|
|
168
|
-
|
|
169
|
-
工单号:CX-1
|
|
170
|
-
你可以点击跳转至[工单详情页](https://ai-staging.solvea.cx/chatbot#/support/tickets/CX-1)查看此工单进展,建议留存方便后续追踪。
|
|
171
|
-
|
|
172
|
-
我们已经收到你反馈的问题,售后团队会在 2–5 个工作日内跟进处理(节假日顺延),有进展会第一时间同步给你。处理期间你可以:
|
|
173
|
-
- 在「售后工单」列表凭工单号查看最新进度;
|
|
174
|
-
- 直接把工单号发给我,继续补充信息或上传截图 / 日志,我会同步到这条工单;
|
|
175
|
-
- 若你留了联系邮箱,后续进展也会通过邮件通知你。
|
|
176
|
-
|
|
177
|
-
给你带来不便很抱歉,感谢你的耐心与反馈!
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
## 查询与追加
|
|
181
|
-
|
|
182
|
-
- 查询时回答当前状态、最新一条人工回复、下一步建议。
|
|
183
|
-
- 追加时先确认目标工单号,再把用户的新描述作为客户补充消息追加到该工单。
|
|
184
|
-
- 如果用户在当前对话里直接问"我那个工单怎么样了",但没有可定位的工单号,先让用户提供工单号。
|
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""CLI for Shulex product docs lookup and support ticket APIs.
|
|
3
|
-
|
|
4
|
-
Usage:
|
|
5
|
-
python3 /skills/shulex-support/scripts/support.py search-docs --query "how to connect" --pretty
|
|
6
|
-
python3 /skills/shulex-support/scripts/support.py query-docs-filesystem --command 'rg -il "变量" / | head -10' --pretty
|
|
7
|
-
python3 /skills/shulex-support/scripts/support.py create-ticket --content "help me" --pretty
|
|
8
|
-
python3 /skills/shulex-support/scripts/support.py list-tickets --page 1 --page-size 20 --pretty
|
|
9
|
-
python3 /skills/shulex-support/scripts/support.py get-ticket --ticket-number CX-1 --pretty
|
|
10
|
-
python3 /skills/shulex-support/scripts/support.py append-message --ticket-number CX-1 --content "more details" --pretty
|
|
11
|
-
"""
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
import argparse
|
|
15
|
-
import asyncio
|
|
16
|
-
import json
|
|
17
|
-
import mimetypes
|
|
18
|
-
import os
|
|
19
|
-
import re
|
|
20
|
-
import sys
|
|
21
|
-
import uuid
|
|
22
|
-
from typing import Any
|
|
23
|
-
from urllib import error, parse, request
|
|
24
|
-
|
|
25
|
-
sys.path.insert(0, os.path.join(os.environ["SOLVEA_LIB_DIR"], "skill-runtime"))
|
|
26
|
-
from bootstrap import * # noqa: F401,F403
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def _base_url() -> str:
|
|
30
|
-
env = os.environ.get("SOLVEA_ENV", "prod")
|
|
31
|
-
if env == "prod":
|
|
32
|
-
base_url = os.environ.get("SOLVEA_PROD_URL", "https://apps.voc.ai").strip()
|
|
33
|
-
else:
|
|
34
|
-
base_url = os.environ.get("SOLVEA_BASE_URL", "https://apps-staging.voc.ai").strip()
|
|
35
|
-
return f"{base_url.rstrip('/')}/api_v2/gpt"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def _support_ticket_origin() -> str:
|
|
39
|
-
env = os.environ.get("SOLVEA_ENV", "prod")
|
|
40
|
-
if env == "prod":
|
|
41
|
-
return os.environ.get("SOLVEA_SUPPORT_PORTAL_URL", "https://ai.solvea.cx").rstrip("/")
|
|
42
|
-
return os.environ.get("SOLVEA_SUPPORT_PORTAL_URL_STAGING", "https://ai-staging.solvea.cx").rstrip("/")
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def _headers() -> dict[str, str]:
|
|
46
|
-
headers = {"Content-Type": "application/json"}
|
|
47
|
-
env = os.environ.get("SOLVEA_ENV", "prod")
|
|
48
|
-
if env == "prod":
|
|
49
|
-
token = os.environ.get("SOLVEA_PROD_TOKEN", "").strip() or os.environ.get("SOLVEA_TOKEN", "").strip()
|
|
50
|
-
cookie_name = "_token"
|
|
51
|
-
else:
|
|
52
|
-
token = os.environ.get("SOLVEA_TOKEN", "").strip()
|
|
53
|
-
cookie_name = "_token_staging"
|
|
54
|
-
if token:
|
|
55
|
-
headers["Cookie"] = f"{cookie_name}={token}"
|
|
56
|
-
# 透传当前请求的 trace,让 gpt/ARMS 续上同一条链路(SLS 可按一个 trace 串到底);env 没有就不带,不影响独立运行
|
|
57
|
-
traceparent = os.environ.get("TRACEPARENT", "").strip()
|
|
58
|
-
if traceparent:
|
|
59
|
-
headers["traceparent"] = traceparent
|
|
60
|
-
return headers
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def _request(
|
|
64
|
-
method: str,
|
|
65
|
-
path: str,
|
|
66
|
-
payload: dict[str, Any] | bytes | None = None,
|
|
67
|
-
headers: dict[str, str] | None = None,
|
|
68
|
-
) -> dict[str, Any]:
|
|
69
|
-
url = f"{_base_url()}{path}"
|
|
70
|
-
if payload is None:
|
|
71
|
-
data = None
|
|
72
|
-
elif isinstance(payload, bytes):
|
|
73
|
-
data = payload
|
|
74
|
-
else:
|
|
75
|
-
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
|
76
|
-
req = request.Request(url, data=data, method=method.upper(), headers=headers or _headers())
|
|
77
|
-
try:
|
|
78
|
-
with request.urlopen(req, timeout=30) as resp:
|
|
79
|
-
body = resp.read().decode("utf-8")
|
|
80
|
-
except error.HTTPError as exc:
|
|
81
|
-
body = exc.read().decode("utf-8", errors="replace")
|
|
82
|
-
raise RuntimeError(f"HTTP {exc.code} {method.upper()} {url}: {body}") from exc
|
|
83
|
-
except error.URLError as exc:
|
|
84
|
-
raise RuntimeError(f"HTTP request failed {method.upper()} {url}: {exc.reason}") from exc
|
|
85
|
-
return json.loads(body) if body else {}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def _unwrap_result(payload: dict[str, Any]) -> Any:
|
|
89
|
-
if payload.get("code") != 200:
|
|
90
|
-
raise RuntimeError(payload.get("message") or "support ticket API failed")
|
|
91
|
-
return payload.get("data")
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
_DOCS_SERVICE_URL = "https://solvea-39f11a36.mintlify.app/mcp"
|
|
95
|
-
_DOCS_PREVIEW_URL_PREFIX = "https://solvea-39f11a36.mintlify.app/"
|
|
96
|
-
_PUBLIC_DOCS_URL_PREFIX = "https://www.voc.ai/cn/docs/"
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def _tool_text_blocks(result: Any) -> list[str]:
|
|
100
|
-
blocks = []
|
|
101
|
-
for item in getattr(result, "content", None) or []:
|
|
102
|
-
text = getattr(item, "text", None)
|
|
103
|
-
if isinstance(text, str):
|
|
104
|
-
blocks.append(text)
|
|
105
|
-
elif isinstance(item, dict) and isinstance(item.get("text"), str):
|
|
106
|
-
blocks.append(item["text"])
|
|
107
|
-
return blocks
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def _call_docs_tool(tool_name: str, arguments: dict[str, Any]) -> list[str]:
|
|
111
|
-
"""Call the product docs service and return text content blocks."""
|
|
112
|
-
|
|
113
|
-
timeout = float(os.environ.get("SOLVEA_DOCS_MCP_TIMEOUT_SECONDS", "30"))
|
|
114
|
-
|
|
115
|
-
async def _call() -> list[str]:
|
|
116
|
-
from mcp import ClientSession
|
|
117
|
-
from mcp.client.streamable_http import streamablehttp_client
|
|
118
|
-
|
|
119
|
-
async with streamablehttp_client(_DOCS_SERVICE_URL, timeout=timeout, sse_read_timeout=timeout) as (
|
|
120
|
-
read,
|
|
121
|
-
write,
|
|
122
|
-
_get_session_id,
|
|
123
|
-
):
|
|
124
|
-
async with ClientSession(read, write) as session:
|
|
125
|
-
await session.initialize()
|
|
126
|
-
result = await session.call_tool(tool_name, arguments)
|
|
127
|
-
|
|
128
|
-
if getattr(result, "isError", False):
|
|
129
|
-
text = "\n".join(_tool_text_blocks(result)).strip()
|
|
130
|
-
raise RuntimeError(text or f"Product docs query failed: {tool_name}")
|
|
131
|
-
return _tool_text_blocks(result)
|
|
132
|
-
|
|
133
|
-
return asyncio.run(asyncio.wait_for(_call(), timeout=timeout + 5))
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def _parse_search_result_block(text: str) -> dict[str, Any]:
|
|
137
|
-
lines = text.splitlines()
|
|
138
|
-
result: dict[str, Any] = {}
|
|
139
|
-
content_lines: list[str] = []
|
|
140
|
-
in_content = False
|
|
141
|
-
for line in lines:
|
|
142
|
-
if in_content:
|
|
143
|
-
content_lines.append(line)
|
|
144
|
-
continue
|
|
145
|
-
if line.startswith("Title: "):
|
|
146
|
-
result["title"] = line[len("Title: "):].strip()
|
|
147
|
-
elif line.startswith("Link: "):
|
|
148
|
-
result["url"] = line[len("Link: "):].strip()
|
|
149
|
-
elif line.startswith("Page: "):
|
|
150
|
-
result["page"] = line[len("Page: "):].strip()
|
|
151
|
-
elif line.startswith("Content:"):
|
|
152
|
-
first_line = line[len("Content:"):].lstrip()
|
|
153
|
-
if first_line:
|
|
154
|
-
content_lines.append(first_line)
|
|
155
|
-
in_content = True
|
|
156
|
-
content = "\n".join(content_lines).strip()
|
|
157
|
-
if content:
|
|
158
|
-
result["content"] = content
|
|
159
|
-
if not result:
|
|
160
|
-
result["content"] = text
|
|
161
|
-
return result
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def _public_docs_url(url: str) -> str:
|
|
165
|
-
if not url.startswith(_DOCS_PREVIEW_URL_PREFIX):
|
|
166
|
-
return url
|
|
167
|
-
path = url[len(_DOCS_PREVIEW_URL_PREFIX):]
|
|
168
|
-
fragment = ""
|
|
169
|
-
if "#" in path:
|
|
170
|
-
path, fragment = path.split("#", 1)
|
|
171
|
-
fragment = f"#{fragment}"
|
|
172
|
-
if path.startswith("solvea/bot_cn/"):
|
|
173
|
-
return _PUBLIC_DOCS_URL_PREFIX + "bot/" + path.rsplit("/", 1)[-1] + fragment
|
|
174
|
-
if path.startswith("updates/log_cn/log/"):
|
|
175
|
-
version = path.rsplit("/", 1)[-1]
|
|
176
|
-
version = re.sub(r"^(V|v)(\d+)-(\d+)-(\d+)$", r"\1\2.\3.\4", version)
|
|
177
|
-
version = re.sub(r"^(version\d+)-(\d+)-(\d+)$", r"\1.\2.\3", version)
|
|
178
|
-
return _PUBLIC_DOCS_URL_PREFIX + "log/" + version + fragment
|
|
179
|
-
return _PUBLIC_DOCS_URL_PREFIX + path + fragment
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def _cmd_search_docs(args: argparse.Namespace) -> Any:
|
|
183
|
-
blocks = _call_docs_tool("search_solvea", {"query": args.query})
|
|
184
|
-
results = [_parse_search_result_block(block) for block in blocks]
|
|
185
|
-
for result in results:
|
|
186
|
-
url = result.get("url")
|
|
187
|
-
if isinstance(url, str):
|
|
188
|
-
result["url"] = _public_docs_url(url)
|
|
189
|
-
return {
|
|
190
|
-
"query": args.query,
|
|
191
|
-
"results": results,
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def _cmd_query_docs_filesystem(args: argparse.Namespace) -> Any:
|
|
196
|
-
blocks = _call_docs_tool("query_docs_filesystem_solvea", {"command": args.command})
|
|
197
|
-
return {
|
|
198
|
-
"command": args.command,
|
|
199
|
-
"output": blocks,
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def _normalize_create_ticket_result(ticket: Any) -> Any:
|
|
204
|
-
if not isinstance(ticket, dict):
|
|
205
|
-
return ticket
|
|
206
|
-
# 只返回客户相关字段;内部字段(sourceType/sourceId、tars/shulex 工单号、同步时间戳等)一律不透出
|
|
207
|
-
ticket_number = ticket.get("ticketNumber")
|
|
208
|
-
result = {
|
|
209
|
-
"ticketNumber": ticket_number,
|
|
210
|
-
"ticketDetailUrl": (
|
|
211
|
-
f"{_support_ticket_origin()}/chatbot#/support/tickets/{ticket_number}"
|
|
212
|
-
if ticket_number
|
|
213
|
-
else None
|
|
214
|
-
),
|
|
215
|
-
"status": ticket.get("status"),
|
|
216
|
-
"subject": ticket.get("subject"),
|
|
217
|
-
"issueType": ticket.get("issueType"),
|
|
218
|
-
"createdAt": ticket.get("createdAt"),
|
|
219
|
-
}
|
|
220
|
-
return {k: v for k, v in result.items() if v is not None}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
def _upload_attachment(path: str) -> dict[str, Any]:
|
|
224
|
-
"""上传本地文件到附件端点,返回 {name, url, mimeType}。
|
|
225
|
-
|
|
226
|
-
用 stdlib urllib 手工构造 multipart/form-data body,字段名 file。
|
|
227
|
-
保留 token cookie,Content-Type 换成 multipart/form-data; boundary=...。
|
|
228
|
-
"""
|
|
229
|
-
filename = os.path.basename(path)
|
|
230
|
-
mime_type = mimetypes.guess_type(filename)[0] or "application/octet-stream"
|
|
231
|
-
|
|
232
|
-
with open(path, "rb") as f:
|
|
233
|
-
file_bytes = f.read()
|
|
234
|
-
|
|
235
|
-
boundary = uuid.uuid4().hex
|
|
236
|
-
|
|
237
|
-
def _field_bytes(name: str, data: bytes, fname: str, content_type: str) -> bytes:
|
|
238
|
-
header = (
|
|
239
|
-
f"--{boundary}\r\n"
|
|
240
|
-
f'Content-Disposition: form-data; name="{name}"; filename="{fname}"\r\n'
|
|
241
|
-
f"Content-Type: {content_type}\r\n"
|
|
242
|
-
f"\r\n"
|
|
243
|
-
).encode("utf-8")
|
|
244
|
-
return header + data + b"\r\n"
|
|
245
|
-
|
|
246
|
-
body = _field_bytes("file", file_bytes, filename, mime_type)
|
|
247
|
-
body += f"--{boundary}--\r\n".encode("utf-8")
|
|
248
|
-
|
|
249
|
-
hdrs = _headers()
|
|
250
|
-
hdrs["Content-Type"] = f"multipart/form-data; boundary={boundary}"
|
|
251
|
-
|
|
252
|
-
return _unwrap_result(_request("POST", "/support/tickets/attachments/upload", body, hdrs))
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
def _report_ticket_created(result: Any, source_id: str) -> None:
|
|
256
|
-
"""非阻塞上报“创建了工单”到 support_skill_metrics;失败只打日志,绝不影响建单。
|
|
257
|
-
|
|
258
|
-
带当前请求 token,tenantId 由 gpt 从 token 解析。
|
|
259
|
-
"""
|
|
260
|
-
ticket_number = result.get("ticketNumber") if isinstance(result, dict) else None
|
|
261
|
-
try:
|
|
262
|
-
_request(
|
|
263
|
-
"POST",
|
|
264
|
-
"/support/skill-metrics",
|
|
265
|
-
{
|
|
266
|
-
"chatId": source_id,
|
|
267
|
-
"ticketNumber": ticket_number,
|
|
268
|
-
},
|
|
269
|
-
_headers(),
|
|
270
|
-
)
|
|
271
|
-
except Exception as exc: # noqa: BLE001 上报失败不影响建单
|
|
272
|
-
print(
|
|
273
|
-
f"support_skill_metrics ticket report failed chatId={source_id} error={exc}",
|
|
274
|
-
file=sys.stderr,
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
def _cmd_create_ticket(args: argparse.Namespace) -> Any:
|
|
279
|
-
source_id = os.environ.get("CHAT_ID", "").strip()
|
|
280
|
-
if not source_id:
|
|
281
|
-
raise RuntimeError("CHAT_ID is not configured")
|
|
282
|
-
|
|
283
|
-
# 先上传附件,收集 attachments 列表
|
|
284
|
-
attachments = []
|
|
285
|
-
for att_path in getattr(args, "attachment", None) or []:
|
|
286
|
-
att = _upload_attachment(att_path)
|
|
287
|
-
attachments.append(att)
|
|
288
|
-
|
|
289
|
-
# 工单类型:--issue-type 优先,没有时回退到 --category(兼容别名)
|
|
290
|
-
issue_type = getattr(args, "issue_type", None) or getattr(args, "category", None)
|
|
291
|
-
|
|
292
|
-
# 扩展字段
|
|
293
|
-
ext_fields: dict[str, Any] | None = None
|
|
294
|
-
ext_str = getattr(args, "ext", None)
|
|
295
|
-
if ext_str:
|
|
296
|
-
ext_fields = json.loads(ext_str)
|
|
297
|
-
|
|
298
|
-
payload: dict[str, Any] = {
|
|
299
|
-
"content": args.content,
|
|
300
|
-
"sourceType": "cxclaw",
|
|
301
|
-
"sourceId": source_id,
|
|
302
|
-
"brand": args.brand,
|
|
303
|
-
"issueType": issue_type,
|
|
304
|
-
"subject": args.subject,
|
|
305
|
-
"contactEmail": args.contact_email,
|
|
306
|
-
}
|
|
307
|
-
if ext_fields is not None:
|
|
308
|
-
payload["extFields"] = ext_fields
|
|
309
|
-
if attachments:
|
|
310
|
-
payload["attachments"] = attachments
|
|
311
|
-
|
|
312
|
-
result = _unwrap_result(
|
|
313
|
-
_request(
|
|
314
|
-
"POST",
|
|
315
|
-
"/support/tickets/create",
|
|
316
|
-
payload,
|
|
317
|
-
_headers(),
|
|
318
|
-
)
|
|
319
|
-
)
|
|
320
|
-
_report_ticket_created(result, source_id)
|
|
321
|
-
return _normalize_create_ticket_result(result)
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
def _cmd_list_tickets(args: argparse.Namespace) -> Any:
|
|
325
|
-
return _unwrap_result(
|
|
326
|
-
_request(
|
|
327
|
-
"POST",
|
|
328
|
-
"/support/tickets",
|
|
329
|
-
{"page": args.page, "pageSize": args.page_size},
|
|
330
|
-
_headers(),
|
|
331
|
-
)
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
def _cmd_get_ticket(args: argparse.Namespace) -> Any:
|
|
336
|
-
ticket_number = parse.quote(args.ticket_number, safe="")
|
|
337
|
-
return _unwrap_result(
|
|
338
|
-
_request(
|
|
339
|
-
"GET",
|
|
340
|
-
f"/support/tickets/{ticket_number}",
|
|
341
|
-
None,
|
|
342
|
-
_headers(),
|
|
343
|
-
)
|
|
344
|
-
)
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
def _cmd_append_message(args: argparse.Namespace) -> Any:
|
|
348
|
-
ticket_number = parse.quote(args.ticket_number, safe="")
|
|
349
|
-
return _unwrap_result(
|
|
350
|
-
_request(
|
|
351
|
-
"POST",
|
|
352
|
-
f"/support/tickets/{ticket_number}/messages",
|
|
353
|
-
{"content": args.content},
|
|
354
|
-
_headers(),
|
|
355
|
-
)
|
|
356
|
-
)
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
def _add_pretty_arg(parser: argparse.ArgumentParser) -> None:
|
|
360
|
-
parser.add_argument("--pretty", action="store_true", help="Pretty-print JSON output.")
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
def _build_arg_parser() -> argparse.ArgumentParser:
|
|
364
|
-
parser = argparse.ArgumentParser(description="Call Shulex docs lookup and support ticket APIs.")
|
|
365
|
-
_add_pretty_arg(parser)
|
|
366
|
-
sub = parser.add_subparsers(dest="command", required=True)
|
|
367
|
-
|
|
368
|
-
search = sub.add_parser("search-docs", help="Search Shulex product docs.")
|
|
369
|
-
_add_pretty_arg(search)
|
|
370
|
-
search.add_argument("--query", required=True)
|
|
371
|
-
search.set_defaults(func=_cmd_search_docs)
|
|
372
|
-
|
|
373
|
-
docs_fs = sub.add_parser("query-docs-filesystem", help="Run a read-only query against product docs files.")
|
|
374
|
-
_add_pretty_arg(docs_fs)
|
|
375
|
-
docs_fs.add_argument("--command", required=True)
|
|
376
|
-
docs_fs.set_defaults(func=_cmd_query_docs_filesystem)
|
|
377
|
-
|
|
378
|
-
create = sub.add_parser("create-ticket", help="Create a support ticket.")
|
|
379
|
-
_add_pretty_arg(create)
|
|
380
|
-
create.add_argument("--content", required=True)
|
|
381
|
-
create.add_argument("--brand", default=None)
|
|
382
|
-
create.add_argument("--issue-type", default=None,
|
|
383
|
-
help="Issue type code: agent_badcase / consult / agent_feature / "
|
|
384
|
-
"integration_feature / ticket_feature / ticket_bug / other")
|
|
385
|
-
create.add_argument("--category", default=None, help="Deprecated alias for --issue-type.")
|
|
386
|
-
create.add_argument("--ext", default=None,
|
|
387
|
-
help="JSON string of extra fields, e.g. '{\"chatId\":\"c1\"}'")
|
|
388
|
-
create.add_argument("--attachment", action="append", default=None,
|
|
389
|
-
help="Path to a local file to attach (may be repeated).")
|
|
390
|
-
create.add_argument("--subject", default=None)
|
|
391
|
-
create.add_argument("--contact-email", default=None)
|
|
392
|
-
create.set_defaults(func=_cmd_create_ticket)
|
|
393
|
-
|
|
394
|
-
list_tickets = sub.add_parser("list-tickets", help="List current tenant support tickets.")
|
|
395
|
-
_add_pretty_arg(list_tickets)
|
|
396
|
-
list_tickets.add_argument("--page", type=int, default=1)
|
|
397
|
-
list_tickets.add_argument("--page-size", type=int, default=20)
|
|
398
|
-
list_tickets.set_defaults(func=_cmd_list_tickets)
|
|
399
|
-
|
|
400
|
-
get = sub.add_parser("get-ticket", help="Get support ticket detail and messages.")
|
|
401
|
-
_add_pretty_arg(get)
|
|
402
|
-
get.add_argument("--ticket-number", required=True)
|
|
403
|
-
get.set_defaults(func=_cmd_get_ticket)
|
|
404
|
-
|
|
405
|
-
append = sub.add_parser("append-message", help="Append a customer message to a support ticket.")
|
|
406
|
-
_add_pretty_arg(append)
|
|
407
|
-
append.add_argument("--ticket-number", required=True)
|
|
408
|
-
append.add_argument("--content", required=True)
|
|
409
|
-
append.set_defaults(func=_cmd_append_message)
|
|
410
|
-
|
|
411
|
-
return parser
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
def main(argv: list[str] | None = None) -> int:
|
|
415
|
-
parser = _build_arg_parser()
|
|
416
|
-
args = parser.parse_args(argv)
|
|
417
|
-
result = args.func(args)
|
|
418
|
-
print(json.dumps(result, ensure_ascii=False, indent=2 if args.pretty else None))
|
|
419
|
-
return 0
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
if __name__ == "__main__":
|
|
423
|
-
try:
|
|
424
|
-
raise SystemExit(main())
|
|
425
|
-
except Exception as exc:
|
|
426
|
-
print(json.dumps({"error": str(exc)}, ensure_ascii=False), file=sys.stderr)
|
|
427
|
-
raise SystemExit(1)
|