@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.
Files changed (208) hide show
  1. package/README.md +25 -22
  2. package/dist/cli.js +2202 -127
  3. package/dist/cli.js.map +1 -1
  4. package/package.json +19 -6
  5. package/capabilities.lock.json +0 -898
  6. package/requirements-cli.txt +0 -2
  7. package/skills/__lib__/__init__.py +0 -0
  8. package/skills/__lib__/skill-runtime/bootstrap.py +0 -134
  9. package/skills/__lib__/solvea-api/__init__.py +0 -0
  10. package/skills/__lib__/solvea-api/client.py +0 -1944
  11. package/skills/__lib__/solvea-api/dashboard.py +0 -100
  12. package/skills/__lib__/solvea-api/solvea_api.py +0 -1341
  13. package/skills/agent-analytics/SKILL.md +0 -74
  14. package/skills/agent-analytics/references/chat-report-fields.md +0 -36
  15. package/skills/agent-analytics/references/report-fields.md +0 -130
  16. package/skills/agent-analytics/references/transcript.md +0 -110
  17. package/skills/agent-builder/SKILL.md +0 -42
  18. package/skills/agent-builder/interfaces/fallback-chatflow.md +0 -47
  19. package/skills/agent-builder/references/chatflow/graph-spec.md +0 -259
  20. package/skills/agent-builder/references/chatflow/node-selection.md +0 -89
  21. package/skills/agent-builder/references/chatflow/nodes/agent.md +0 -350
  22. package/skills/agent-builder/references/chatflow/nodes/business-track.md +0 -43
  23. package/skills/agent-builder/references/chatflow/nodes/code.md +0 -157
  24. package/skills/agent-builder/references/chatflow/nodes/collect-order.md +0 -47
  25. package/skills/agent-builder/references/chatflow/nodes/finish.md +0 -34
  26. package/skills/agent-builder/references/chatflow/nodes/human-review.md +0 -60
  27. package/skills/agent-builder/references/chatflow/nodes/if-else.md +0 -100
  28. package/skills/agent-builder/references/chatflow/nodes/intent.md +0 -79
  29. package/skills/agent-builder/references/chatflow/nodes/knowledge-reply.md +0 -43
  30. package/skills/agent-builder/references/chatflow/nodes/llm.md +0 -117
  31. package/skills/agent-builder/references/chatflow/nodes/note.md +0 -38
  32. package/skills/agent-builder/references/chatflow/nodes/question.md +0 -97
  33. package/skills/agent-builder/references/chatflow/nodes/rag-reply.md +0 -68
  34. package/skills/agent-builder/references/chatflow/nodes/reply.md +0 -97
  35. package/skills/agent-builder/references/chatflow/nodes/start.md +0 -35
  36. package/skills/agent-builder/references/chatflow/nodes/sub-graph.md +0 -48
  37. package/skills/agent-builder/references/chatflow/nodes/tool.md +0 -138
  38. package/skills/agent-builder/references/chatflow/nodes/transfer-to-human.md +0 -63
  39. package/skills/agent-builder/references/chatflow/nodes/variable-assignment.md +0 -52
  40. package/skills/agent-builder/references/chatflow/nodes/variable-extract.md +0 -50
  41. package/skills/agent-builder/references/chatflow/nodes/variable-process.md +0 -70
  42. package/skills/agent-builder/references/chatflow/overview.md +0 -53
  43. package/skills/agent-builder/references/chatflow/visual-translation.md +0 -64
  44. package/skills/agent-builder/references/chatflow/workflow.md +0 -134
  45. package/skills/agent-builder/references/multi-agent/create-update.md +0 -185
  46. package/skills/agent-builder/references/multi-agent/graph-spec.md +0 -246
  47. package/skills/agent-builder/references/multi-agent/graph-structure.md +0 -12
  48. package/skills/agent-builder/references/multi-agent/migrate-to-chatflow.md +0 -13
  49. package/skills/agent-builder/references/multi-agent/migration/large.md +0 -554
  50. package/skills/agent-builder/references/multi-agent/migration/principles.md +0 -446
  51. package/skills/agent-builder/references/multi-agent/migration/small.md +0 -225
  52. package/skills/agent-builder/references/multi-agent/orchestration.md +0 -11
  53. package/skills/agent-builder/references/multi-agent/overview.md +0 -48
  54. package/skills/agent-builder/references/multi-agent/update.md +0 -128
  55. package/skills/agent-builder/references/multi-agent/workflow.md +0 -87
  56. package/skills/agent-builder/references/single-agent/overview.md +0 -48
  57. package/skills/agent-builder/references/single-agent/prompt.md +0 -16
  58. package/skills/agent-builder/references/single-agent/tools.md +0 -36
  59. package/skills/agent-builder/references/single-agent/voice.md +0 -46
  60. package/skills/agent-builder/references/tools/http-tool.md +0 -315
  61. package/skills/agent-builder/references/tools/mock-tool.md +0 -279
  62. package/skills/agent-builder/references/tools/overview.md +0 -46
  63. package/skills/agent-builder/references/tools/tool-config.md +0 -43
  64. package/skills/agent-builder/references/tools/x-token.md +0 -34
  65. package/skills/agent-builder/scripts/chatflow/chatflow_builder.py +0 -3458
  66. package/skills/agent-builder/scripts/chatflow/validate_graph.py +0 -1697
  67. package/skills/agent-builder/scripts/copy_marketplace_agent.py +0 -61
  68. package/skills/agent-builder/scripts/multi-agent/legacy_graph_analyzer.py +0 -5974
  69. package/skills/agent-builder/scripts/multi-agent/replay_compare.py +0 -679
  70. package/skills/agent-builder/scripts/patch_agent_tools.py +0 -135
  71. package/skills/agent-builder/scripts/tools/validate_tool_config.py +0 -384
  72. package/skills/agent-tester/SKILL.md +0 -35
  73. package/skills/agent-tester/batch-test/overview.md +0 -86
  74. package/skills/agent-tester/scripts/batch_test_sheet.py +0 -213
  75. package/skills/agent-tester/single-test/badcase-retest.md +0 -85
  76. package/skills/agent-tester/single-test/direct-test.md +0 -24
  77. package/skills/agent-tester/single-test/overview.md +0 -142
  78. package/skills/delivery/SKILL.md +0 -112
  79. package/skills/delivery/interfaces/classify.md +0 -38
  80. package/skills/delivery/interfaces/create-agents.md +0 -89
  81. package/skills/delivery/interfaces/delivery-test.md +0 -67
  82. package/skills/delivery/references/flow.md +0 -40
  83. package/skills/delivery/references/validation.md +0 -15
  84. package/skills/delivery/scripts/batch_test.py +0 -704
  85. package/skills/delivery/scripts/classify.py +0 -22
  86. package/skills/delivery/scripts/create_agents.py +0 -695
  87. package/skills/delivery/scripts/gen_delivery_report.py +0 -335
  88. package/skills/delivery/scripts/gen_excel.py +0 -234
  89. package/skills/delivery/scripts/gen_report.py +0 -208
  90. package/skills/delivery/scripts/plan.py +0 -163
  91. package/skills/delivery/scripts/render.py +0 -713
  92. package/skills/delivery/scripts/render_summary.py +0 -38
  93. package/skills/delivery/scripts/sample_history_cases.py +0 -109
  94. package/skills/kb-builder/SKILL.md +0 -80
  95. package/skills/kb-builder/interfaces/qa-upload.md +0 -55
  96. package/skills/kb-builder/interfaces/web-kb.md +0 -84
  97. package/skills/kb-builder/references/directory.md +0 -373
  98. package/skills/kb-builder/references/export.md +0 -282
  99. package/skills/kb-builder/references/isolation-keywords.md +0 -113
  100. package/skills/kb-builder/references/knowledge.md +0 -153
  101. package/skills/kb-builder/references/product.md +0 -52
  102. package/skills/kb-builder/references/qa-from-help-center.md +0 -58
  103. package/skills/kb-builder/references/qa-from-pdf.md +0 -94
  104. package/skills/kb-builder/references/qa-from-tickets.md +0 -168
  105. package/skills/kb-builder/references/qa-from-web.md +0 -98
  106. package/skills/kb-builder/references/qa-quality.md +0 -73
  107. package/skills/kb-builder/references/reply-template.md +0 -237
  108. package/skills/kb-builder/references/tag-isolation-migration.md +0 -61
  109. package/skills/kb-builder/references/tags.md +0 -45
  110. package/skills/kb-builder/references/upload.md +0 -130
  111. package/skills/kb-builder/scripts/batch_extract_ticket_qa.py +0 -333
  112. package/skills/kb-builder/scripts/build_directory_plan.py +0 -144
  113. package/skills/kb-builder/scripts/crawl_site.py +0 -433
  114. package/skills/kb-builder/scripts/create_dirs.py +0 -146
  115. package/skills/kb-builder/scripts/export_knowledge.py +0 -454
  116. package/skills/kb-builder/scripts/extract_ticket_qa.py +0 -441
  117. package/skills/kb-builder/scripts/extract_web_qa.py +0 -118
  118. package/skills/kb-builder/scripts/import_qa.py +0 -219
  119. package/skills/kb-builder/scripts/import_source_documents.py +0 -143
  120. package/skills/kb-builder/scripts/import_templates.py +0 -409
  121. package/skills/poc/SKILL.md +0 -125
  122. package/skills/poc/assets/default_manual.css +0 -279
  123. package/skills/poc/assets/notice_escalate.png +0 -0
  124. package/skills/poc/assets/notice_escalate2.png +0 -0
  125. package/skills/poc/assets/notice_new_chat.png +0 -0
  126. package/skills/poc/assets/poc_test_template.html +0 -645
  127. package/skills/poc/assets/poc_test_template.md +0 -104
  128. package/skills/poc/assets/step3_preview.png +0 -0
  129. package/skills/poc/assets/step4_order_note.png +0 -0
  130. package/skills/poc/interfaces/classify.md +0 -38
  131. package/skills/poc/interfaces/create-agents.md +0 -108
  132. package/skills/poc/interfaces/poc-test.md +0 -94
  133. package/skills/poc/references/flow.md +0 -47
  134. package/skills/poc/references/kb-directory.md +0 -22
  135. package/skills/poc/references/validation.md +0 -17
  136. package/skills/poc/scripts/classify.py +0 -22
  137. package/skills/poc/scripts/create_agents.py +0 -818
  138. package/skills/poc/scripts/gen_excel.py +0 -234
  139. package/skills/poc/scripts/gen_poc_manual.py +0 -249
  140. package/skills/poc/scripts/gen_report.py +0 -208
  141. package/skills/poc/scripts/inspect_chatflow.py +0 -209
  142. package/skills/poc/scripts/plan.py +0 -178
  143. package/skills/poc/scripts/poc_test.py +0 -495
  144. package/skills/poc/scripts/render.py +0 -713
  145. package/skills/poc/scripts/render_summary.py +0 -41
  146. package/skills/runtime-inspector/SKILL.md +0 -51
  147. package/skills/runtime-inspector/references/frontend-rendering.md +0 -126
  148. package/skills/runtime-inspector/references/model.md +0 -59
  149. package/skills/runtime-inspector/references/sandbox.md +0 -131
  150. package/skills/runtime-inspector/scripts/model_inspect.py +0 -68
  151. package/skills/runtime-inspector/scripts/prepare.py +0 -98
  152. package/skills/runtime-inspector/scripts/process.py +0 -84
  153. package/skills/runtime-inspector/scripts/read_state.py +0 -32
  154. package/skills/runtime-inspector/scripts/sandbox_inspect.py +0 -463
  155. package/skills/runtime-inspector/scripts/test_claude_proxy.py +0 -49
  156. package/skills/runtime-inspector/scripts/test_openai_proxy.py +0 -41
  157. package/skills/runtime-inspector/scripts/verify_persistence.py +0 -101
  158. package/skills/runtime-inspector/scripts/write_test.py +0 -41
  159. package/skills/setup/SKILL.md +0 -17
  160. package/skills/shulex-support/SKILL.md +0 -39
  161. package/skills/shulex-support/references/badcase-analysis/overview.md +0 -34
  162. package/skills/shulex-support/references/badcase-analysis/text/conversation-record.md +0 -105
  163. package/skills/shulex-support/references/badcase-analysis/text/core-flow.md +0 -89
  164. package/skills/shulex-support/references/badcase-analysis/text/log-query-rules.md +0 -36
  165. package/skills/shulex-support/references/badcase-analysis/text/m1-entry.md +0 -53
  166. package/skills/shulex-support/references/badcase-analysis/text/m10-chatflow-service.md +0 -259
  167. package/skills/shulex-support/references/badcase-analysis/text/m11-algorithm-service.md +0 -148
  168. package/skills/shulex-support/references/badcase-analysis/text/m12-quality-inspection.md +0 -57
  169. package/skills/shulex-support/references/badcase-analysis/text/m2-intent.md +0 -120
  170. package/skills/shulex-support/references/badcase-analysis/text/m3-strategy.md +0 -126
  171. package/skills/shulex-support/references/badcase-analysis/text/m4-aifield.md +0 -121
  172. package/skills/shulex-support/references/badcase-analysis/text/m5-agent.md +0 -208
  173. package/skills/shulex-support/references/badcase-analysis/text/m6-tool.md +0 -115
  174. package/skills/shulex-support/references/badcase-analysis/text/m7-reply.md +0 -72
  175. package/skills/shulex-support/references/badcase-analysis/text/m8-error.md +0 -92
  176. package/skills/shulex-support/references/badcase-analysis/text/m9-knowledge-search.md +0 -147
  177. package/skills/shulex-support/references/badcase-analysis/text/sls-query.md +0 -283
  178. package/skills/shulex-support/references/badcase-analysis/voice/core-flow.md +0 -77
  179. package/skills/shulex-support/references/badcase-analysis/voice/dependencies.md +0 -19
  180. package/skills/shulex-support/references/badcase-analysis/voice/platform-api.md +0 -23
  181. package/skills/shulex-support/references/badcase-analysis/voice/sls-playbook.md +0 -31
  182. package/skills/shulex-support/references/product-docs.md +0 -70
  183. package/skills/shulex-support/references/tickets.md +0 -184
  184. package/skills/shulex-support/scripts/support.py +0 -427
  185. package/skills/solvea-desk/SKILL.md +0 -39
  186. package/skills/solvea-desk/references/report/fields.md +0 -82
  187. package/skills/solvea-desk/references/report/report-design.md +0 -200
  188. package/skills/solvea-desk/references/report/report-template.html +0 -44
  189. package/skills/solvea-desk/references/report/report.css +0 -517
  190. package/skills/solvea-desk/references/report/workflow.md +0 -464
  191. package/skills/solvea-desk/references/tickets/query.md +0 -103
  192. package/skills/solvea-desk/references/tickets/tags.md +0 -83
  193. package/skills/solvea-desk/scripts/report/fetch_sources.py +0 -580
  194. package/skills/solvea-desk/scripts/report/render_report.py +0 -763
  195. package/skills/solvea-desk/scripts/tickets/desk_api.py +0 -73
  196. package/skills/solvea-desk/scripts/tickets/fetch_tickets.py +0 -403
  197. package/skills/solvea-desk/scripts/tickets/ticket_tags.py +0 -305
  198. package/skills/solvea-kit/SKILL.md +0 -84
  199. package/skills/solvea-kit/references/agent.md +0 -145
  200. package/skills/solvea-kit/references/chat.md +0 -244
  201. package/skills/solvea-kit/references/intent.md +0 -69
  202. package/skills/solvea-kit/references/plugin.md +0 -53
  203. package/skills/solvea-kit/references/quality-inspection.md +0 -75
  204. package/skills/solvea-kit/references/strategy.md +0 -62
  205. package/skills/solvea-kit/references/tagging.md +0 -231
  206. package/skills/solvea-kit/references/variables.md +0 -53
  207. package/skills/solvea-kit/scripts/generate_label_import_file.py +0 -151
  208. 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)