@shulex/cxclawctl 0.0.0-stage → 0.1.2

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 (209) hide show
  1. package/README.md +228 -2
  2. package/bin/cxclawctl.mjs +11 -0
  3. package/capabilities.lock.json +898 -0
  4. package/dist/cli.js +363 -0
  5. package/dist/cli.js.map +1 -0
  6. package/package.json +33 -4
  7. package/requirements-cli.txt +2 -0
  8. package/skills/__lib__/__init__.py +0 -0
  9. package/skills/__lib__/skill-runtime/bootstrap.py +134 -0
  10. package/skills/__lib__/solvea-api/__init__.py +0 -0
  11. package/skills/__lib__/solvea-api/client.py +1944 -0
  12. package/skills/__lib__/solvea-api/dashboard.py +100 -0
  13. package/skills/__lib__/solvea-api/solvea_api.py +1341 -0
  14. package/skills/agent-analytics/SKILL.md +74 -0
  15. package/skills/agent-analytics/references/chat-report-fields.md +36 -0
  16. package/skills/agent-analytics/references/report-fields.md +130 -0
  17. package/skills/agent-analytics/references/transcript.md +110 -0
  18. package/skills/agent-builder/SKILL.md +42 -0
  19. package/skills/agent-builder/interfaces/fallback-chatflow.md +47 -0
  20. package/skills/agent-builder/references/chatflow/graph-spec.md +259 -0
  21. package/skills/agent-builder/references/chatflow/node-selection.md +89 -0
  22. package/skills/agent-builder/references/chatflow/nodes/agent.md +350 -0
  23. package/skills/agent-builder/references/chatflow/nodes/business-track.md +43 -0
  24. package/skills/agent-builder/references/chatflow/nodes/code.md +157 -0
  25. package/skills/agent-builder/references/chatflow/nodes/collect-order.md +47 -0
  26. package/skills/agent-builder/references/chatflow/nodes/finish.md +34 -0
  27. package/skills/agent-builder/references/chatflow/nodes/human-review.md +60 -0
  28. package/skills/agent-builder/references/chatflow/nodes/if-else.md +100 -0
  29. package/skills/agent-builder/references/chatflow/nodes/intent.md +79 -0
  30. package/skills/agent-builder/references/chatflow/nodes/knowledge-reply.md +43 -0
  31. package/skills/agent-builder/references/chatflow/nodes/llm.md +117 -0
  32. package/skills/agent-builder/references/chatflow/nodes/note.md +38 -0
  33. package/skills/agent-builder/references/chatflow/nodes/question.md +97 -0
  34. package/skills/agent-builder/references/chatflow/nodes/rag-reply.md +68 -0
  35. package/skills/agent-builder/references/chatflow/nodes/reply.md +97 -0
  36. package/skills/agent-builder/references/chatflow/nodes/start.md +35 -0
  37. package/skills/agent-builder/references/chatflow/nodes/sub-graph.md +48 -0
  38. package/skills/agent-builder/references/chatflow/nodes/tool.md +138 -0
  39. package/skills/agent-builder/references/chatflow/nodes/transfer-to-human.md +63 -0
  40. package/skills/agent-builder/references/chatflow/nodes/variable-assignment.md +52 -0
  41. package/skills/agent-builder/references/chatflow/nodes/variable-extract.md +50 -0
  42. package/skills/agent-builder/references/chatflow/nodes/variable-process.md +70 -0
  43. package/skills/agent-builder/references/chatflow/overview.md +53 -0
  44. package/skills/agent-builder/references/chatflow/visual-translation.md +64 -0
  45. package/skills/agent-builder/references/chatflow/workflow.md +134 -0
  46. package/skills/agent-builder/references/multi-agent/create-update.md +185 -0
  47. package/skills/agent-builder/references/multi-agent/graph-spec.md +246 -0
  48. package/skills/agent-builder/references/multi-agent/graph-structure.md +12 -0
  49. package/skills/agent-builder/references/multi-agent/migrate-to-chatflow.md +13 -0
  50. package/skills/agent-builder/references/multi-agent/migration/large.md +554 -0
  51. package/skills/agent-builder/references/multi-agent/migration/principles.md +446 -0
  52. package/skills/agent-builder/references/multi-agent/migration/small.md +225 -0
  53. package/skills/agent-builder/references/multi-agent/orchestration.md +11 -0
  54. package/skills/agent-builder/references/multi-agent/overview.md +48 -0
  55. package/skills/agent-builder/references/multi-agent/update.md +128 -0
  56. package/skills/agent-builder/references/multi-agent/workflow.md +87 -0
  57. package/skills/agent-builder/references/single-agent/overview.md +48 -0
  58. package/skills/agent-builder/references/single-agent/prompt.md +16 -0
  59. package/skills/agent-builder/references/single-agent/tools.md +36 -0
  60. package/skills/agent-builder/references/single-agent/voice.md +46 -0
  61. package/skills/agent-builder/references/tools/http-tool.md +315 -0
  62. package/skills/agent-builder/references/tools/mock-tool.md +279 -0
  63. package/skills/agent-builder/references/tools/overview.md +46 -0
  64. package/skills/agent-builder/references/tools/tool-config.md +43 -0
  65. package/skills/agent-builder/references/tools/x-token.md +34 -0
  66. package/skills/agent-builder/scripts/chatflow/chatflow_builder.py +3458 -0
  67. package/skills/agent-builder/scripts/chatflow/validate_graph.py +1697 -0
  68. package/skills/agent-builder/scripts/copy_marketplace_agent.py +61 -0
  69. package/skills/agent-builder/scripts/multi-agent/legacy_graph_analyzer.py +5974 -0
  70. package/skills/agent-builder/scripts/multi-agent/replay_compare.py +679 -0
  71. package/skills/agent-builder/scripts/patch_agent_tools.py +135 -0
  72. package/skills/agent-builder/scripts/tools/validate_tool_config.py +384 -0
  73. package/skills/agent-tester/SKILL.md +35 -0
  74. package/skills/agent-tester/batch-test/overview.md +86 -0
  75. package/skills/agent-tester/scripts/batch_test_sheet.py +213 -0
  76. package/skills/agent-tester/single-test/badcase-retest.md +85 -0
  77. package/skills/agent-tester/single-test/direct-test.md +24 -0
  78. package/skills/agent-tester/single-test/overview.md +142 -0
  79. package/skills/delivery/SKILL.md +112 -0
  80. package/skills/delivery/interfaces/classify.md +38 -0
  81. package/skills/delivery/interfaces/create-agents.md +89 -0
  82. package/skills/delivery/interfaces/delivery-test.md +67 -0
  83. package/skills/delivery/references/flow.md +40 -0
  84. package/skills/delivery/references/validation.md +15 -0
  85. package/skills/delivery/scripts/batch_test.py +704 -0
  86. package/skills/delivery/scripts/classify.py +22 -0
  87. package/skills/delivery/scripts/create_agents.py +695 -0
  88. package/skills/delivery/scripts/gen_delivery_report.py +335 -0
  89. package/skills/delivery/scripts/gen_excel.py +234 -0
  90. package/skills/delivery/scripts/gen_report.py +208 -0
  91. package/skills/delivery/scripts/plan.py +163 -0
  92. package/skills/delivery/scripts/render.py +713 -0
  93. package/skills/delivery/scripts/render_summary.py +38 -0
  94. package/skills/delivery/scripts/sample_history_cases.py +109 -0
  95. package/skills/kb-builder/SKILL.md +80 -0
  96. package/skills/kb-builder/interfaces/qa-upload.md +55 -0
  97. package/skills/kb-builder/interfaces/web-kb.md +84 -0
  98. package/skills/kb-builder/references/directory.md +373 -0
  99. package/skills/kb-builder/references/export.md +282 -0
  100. package/skills/kb-builder/references/isolation-keywords.md +113 -0
  101. package/skills/kb-builder/references/knowledge.md +153 -0
  102. package/skills/kb-builder/references/product.md +52 -0
  103. package/skills/kb-builder/references/qa-from-help-center.md +58 -0
  104. package/skills/kb-builder/references/qa-from-pdf.md +94 -0
  105. package/skills/kb-builder/references/qa-from-tickets.md +168 -0
  106. package/skills/kb-builder/references/qa-from-web.md +98 -0
  107. package/skills/kb-builder/references/qa-quality.md +73 -0
  108. package/skills/kb-builder/references/reply-template.md +237 -0
  109. package/skills/kb-builder/references/tag-isolation-migration.md +61 -0
  110. package/skills/kb-builder/references/tags.md +45 -0
  111. package/skills/kb-builder/references/upload.md +130 -0
  112. package/skills/kb-builder/scripts/batch_extract_ticket_qa.py +333 -0
  113. package/skills/kb-builder/scripts/build_directory_plan.py +144 -0
  114. package/skills/kb-builder/scripts/crawl_site.py +433 -0
  115. package/skills/kb-builder/scripts/create_dirs.py +146 -0
  116. package/skills/kb-builder/scripts/export_knowledge.py +454 -0
  117. package/skills/kb-builder/scripts/extract_ticket_qa.py +441 -0
  118. package/skills/kb-builder/scripts/extract_web_qa.py +118 -0
  119. package/skills/kb-builder/scripts/import_qa.py +219 -0
  120. package/skills/kb-builder/scripts/import_source_documents.py +143 -0
  121. package/skills/kb-builder/scripts/import_templates.py +409 -0
  122. package/skills/poc/SKILL.md +125 -0
  123. package/skills/poc/assets/default_manual.css +279 -0
  124. package/skills/poc/assets/notice_escalate.png +0 -0
  125. package/skills/poc/assets/notice_escalate2.png +0 -0
  126. package/skills/poc/assets/notice_new_chat.png +0 -0
  127. package/skills/poc/assets/poc_test_template.html +645 -0
  128. package/skills/poc/assets/poc_test_template.md +104 -0
  129. package/skills/poc/assets/step3_preview.png +0 -0
  130. package/skills/poc/assets/step4_order_note.png +0 -0
  131. package/skills/poc/interfaces/classify.md +38 -0
  132. package/skills/poc/interfaces/create-agents.md +108 -0
  133. package/skills/poc/interfaces/poc-test.md +94 -0
  134. package/skills/poc/references/flow.md +47 -0
  135. package/skills/poc/references/kb-directory.md +22 -0
  136. package/skills/poc/references/validation.md +17 -0
  137. package/skills/poc/scripts/classify.py +22 -0
  138. package/skills/poc/scripts/create_agents.py +818 -0
  139. package/skills/poc/scripts/gen_excel.py +234 -0
  140. package/skills/poc/scripts/gen_poc_manual.py +249 -0
  141. package/skills/poc/scripts/gen_report.py +208 -0
  142. package/skills/poc/scripts/inspect_chatflow.py +209 -0
  143. package/skills/poc/scripts/plan.py +178 -0
  144. package/skills/poc/scripts/poc_test.py +495 -0
  145. package/skills/poc/scripts/render.py +713 -0
  146. package/skills/poc/scripts/render_summary.py +41 -0
  147. package/skills/runtime-inspector/SKILL.md +51 -0
  148. package/skills/runtime-inspector/references/frontend-rendering.md +126 -0
  149. package/skills/runtime-inspector/references/model.md +59 -0
  150. package/skills/runtime-inspector/references/sandbox.md +131 -0
  151. package/skills/runtime-inspector/scripts/model_inspect.py +68 -0
  152. package/skills/runtime-inspector/scripts/prepare.py +98 -0
  153. package/skills/runtime-inspector/scripts/process.py +84 -0
  154. package/skills/runtime-inspector/scripts/read_state.py +32 -0
  155. package/skills/runtime-inspector/scripts/sandbox_inspect.py +463 -0
  156. package/skills/runtime-inspector/scripts/test_claude_proxy.py +49 -0
  157. package/skills/runtime-inspector/scripts/test_openai_proxy.py +41 -0
  158. package/skills/runtime-inspector/scripts/verify_persistence.py +101 -0
  159. package/skills/runtime-inspector/scripts/write_test.py +41 -0
  160. package/skills/setup/SKILL.md +17 -0
  161. package/skills/shulex-support/SKILL.md +39 -0
  162. package/skills/shulex-support/references/badcase-analysis/overview.md +34 -0
  163. package/skills/shulex-support/references/badcase-analysis/text/conversation-record.md +105 -0
  164. package/skills/shulex-support/references/badcase-analysis/text/core-flow.md +89 -0
  165. package/skills/shulex-support/references/badcase-analysis/text/log-query-rules.md +36 -0
  166. package/skills/shulex-support/references/badcase-analysis/text/m1-entry.md +53 -0
  167. package/skills/shulex-support/references/badcase-analysis/text/m10-chatflow-service.md +259 -0
  168. package/skills/shulex-support/references/badcase-analysis/text/m11-algorithm-service.md +148 -0
  169. package/skills/shulex-support/references/badcase-analysis/text/m12-quality-inspection.md +57 -0
  170. package/skills/shulex-support/references/badcase-analysis/text/m2-intent.md +120 -0
  171. package/skills/shulex-support/references/badcase-analysis/text/m3-strategy.md +126 -0
  172. package/skills/shulex-support/references/badcase-analysis/text/m4-aifield.md +121 -0
  173. package/skills/shulex-support/references/badcase-analysis/text/m5-agent.md +208 -0
  174. package/skills/shulex-support/references/badcase-analysis/text/m6-tool.md +115 -0
  175. package/skills/shulex-support/references/badcase-analysis/text/m7-reply.md +72 -0
  176. package/skills/shulex-support/references/badcase-analysis/text/m8-error.md +92 -0
  177. package/skills/shulex-support/references/badcase-analysis/text/m9-knowledge-search.md +147 -0
  178. package/skills/shulex-support/references/badcase-analysis/text/sls-query.md +283 -0
  179. package/skills/shulex-support/references/badcase-analysis/voice/core-flow.md +77 -0
  180. package/skills/shulex-support/references/badcase-analysis/voice/dependencies.md +19 -0
  181. package/skills/shulex-support/references/badcase-analysis/voice/platform-api.md +23 -0
  182. package/skills/shulex-support/references/badcase-analysis/voice/sls-playbook.md +31 -0
  183. package/skills/shulex-support/references/product-docs.md +70 -0
  184. package/skills/shulex-support/references/tickets.md +184 -0
  185. package/skills/shulex-support/scripts/support.py +427 -0
  186. package/skills/solvea-desk/SKILL.md +39 -0
  187. package/skills/solvea-desk/references/report/fields.md +82 -0
  188. package/skills/solvea-desk/references/report/report-design.md +200 -0
  189. package/skills/solvea-desk/references/report/report-template.html +44 -0
  190. package/skills/solvea-desk/references/report/report.css +517 -0
  191. package/skills/solvea-desk/references/report/workflow.md +464 -0
  192. package/skills/solvea-desk/references/tickets/query.md +103 -0
  193. package/skills/solvea-desk/references/tickets/tags.md +83 -0
  194. package/skills/solvea-desk/scripts/report/fetch_sources.py +580 -0
  195. package/skills/solvea-desk/scripts/report/render_report.py +763 -0
  196. package/skills/solvea-desk/scripts/tickets/desk_api.py +73 -0
  197. package/skills/solvea-desk/scripts/tickets/fetch_tickets.py +403 -0
  198. package/skills/solvea-desk/scripts/tickets/ticket_tags.py +305 -0
  199. package/skills/solvea-kit/SKILL.md +84 -0
  200. package/skills/solvea-kit/references/agent.md +145 -0
  201. package/skills/solvea-kit/references/chat.md +244 -0
  202. package/skills/solvea-kit/references/intent.md +69 -0
  203. package/skills/solvea-kit/references/plugin.md +53 -0
  204. package/skills/solvea-kit/references/quality-inspection.md +75 -0
  205. package/skills/solvea-kit/references/strategy.md +62 -0
  206. package/skills/solvea-kit/references/tagging.md +231 -0
  207. package/skills/solvea-kit/references/variables.md +53 -0
  208. package/skills/solvea-kit/scripts/generate_label_import_file.py +151 -0
  209. package/skills/solvea-kit/scripts/intent_classify.py +843 -0
@@ -0,0 +1,208 @@
1
+ """
2
+ HTML report generator for delivery skill.
3
+
4
+ Reads classification results (workspace/state/delivery/progress.json -> results_path) and
5
+ renders a polished HTML report under workspace/outputs/delivery/.
6
+
7
+ Usage:
8
+ python3 scripts/gen_report.py [--brand "Acme"]
9
+ """
10
+
11
+ import os, sys
12
+ sys.path.insert(0, os.path.join(os.environ.get("SOLVEA_LIB_DIR", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "__lib__"))), "skill-runtime"))
13
+ from bootstrap import *
14
+
15
+ import json, argparse
16
+ from uuid import uuid4
17
+
18
+
19
+ def _read_state() -> dict:
20
+ _sf = STATE_DIR / "progress.json"
21
+ if _sf.exists():
22
+ try:
23
+ return json.loads(_sf.read_text(encoding="utf-8"))
24
+ except Exception:
25
+ pass
26
+ return {}
27
+
28
+
29
+ def _write_state(data: dict) -> None:
30
+ _sf = STATE_DIR / "progress.json"
31
+ _sf.parent.mkdir(parents=True, exist_ok=True)
32
+ _sf.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
33
+
34
+ from render import render_html
35
+
36
+ COLOR_PALETTE = [
37
+ "#ef4444", "#f97316", "#eab308", "#22c55e",
38
+ "#14b8a6", "#3b82f6", "#8b5cf6", "#ec4899",
39
+ "#06b6d4", "#84cc16",
40
+ ]
41
+
42
+ CIRCLE_NUMS = "①②③④⑤⑥⑦⑧⑨⑩"
43
+
44
+
45
+ def build_config_from_results(results, brand_name=""):
46
+ def uid():
47
+ return uuid4().hex
48
+
49
+ agents = []
50
+ all_intents = results.get("intents", []) + results.get("new_intents", [])
51
+ meta = results.get("meta", {})
52
+ total = meta.get("total", 0)
53
+
54
+ for i, intent in enumerate(all_intents):
55
+ sub_intents = intent.get("sub_intents", [])
56
+ is_new = intent.get("is_new", False)
57
+ agent_id = f"intent-{i}"
58
+
59
+ l2_nodes = []
60
+ for j, s in enumerate(sub_intents):
61
+ sample_preview = s["samples"][0][:50] + "..." if s.get("samples") else ""
62
+ l2_nodes.append({
63
+ "id": f"n2-{uid()[:8]}",
64
+ "name": s["name"],
65
+ "desc": f"{s['pct']:.1f}% — {sample_preview}" if sample_preview else f"{s['pct']:.1f}%",
66
+ "count": s["count"],
67
+ "definition": s.get("definition", ""),
68
+ "is_shared": False,
69
+ "shared_agents": [],
70
+ "samples": s.get("samples", []),
71
+ })
72
+
73
+ nodes_ac = []
74
+ for s in sub_intents[:3]:
75
+ nodes_ac.append({
76
+ "name": f"节点 · {s['name']}",
77
+ "trigger": f"客户投诉:{s['name']}",
78
+ "action": f"<code>LLM</code> 处理 {s['name']},引导解决",
79
+ })
80
+
81
+ agents.append({
82
+ "id": agent_id,
83
+ "name": f"{CIRCLE_NUMS[i % len(CIRCLE_NUMS)]} {intent['name']}",
84
+ "color": COLOR_PALETTE[i % len(COLOR_PALETTE)],
85
+ "l1_intents": [intent["name"]],
86
+ "total_count": intent["count"],
87
+ "total_pct": intent["pct"],
88
+ "is_new": is_new,
89
+ "intent_config": {
90
+ "definition": intent.get("definition", ""),
91
+ "excludes": [],
92
+ "examples": intent.get("samples", [])[:3],
93
+ "covers": [s["name"] for s in sub_intents],
94
+ },
95
+ "agent_config": {
96
+ "type": "chatflow",
97
+ "nodes": nodes_ac,
98
+ "footer": f"<b>结束:</b>解决{intent['name']}问题 / 转人工",
99
+ },
100
+ "l2_nodes": l2_nodes,
101
+ })
102
+
103
+ edges = []
104
+ for a in agents:
105
+ ag_html_id = f"ag-{a['id'].replace('_', '-')}"
106
+ color = a.get("color", "#6b7280")
107
+ for node in a["l2_nodes"]:
108
+ count = node.get("count", 0)
109
+ if count > 0:
110
+ edges.append({"from": ag_html_id, "to": node["id"], "count": count, "color": color})
111
+
112
+ total_nodes = sum(len(a["l2_nodes"]) for a in agents)
113
+ n_known = meta.get("n_known", len(results.get("intents", [])))
114
+ n_new = meta.get("n_new", len(results.get("new_intents", [])))
115
+
116
+ rows = []
117
+ for a in agents:
118
+ n = len(a["l2_nodes"])
119
+ badge = f"{n} 节点 ✓" if n <= 2 else (f"{n} 节点 ⚠️" if n <= 3 else f"{n} 节点 ❌")
120
+ rows.append({
121
+ "agent_name": a["name"],
122
+ "color": a["color"],
123
+ "node_count": n,
124
+ "badge": badge,
125
+ })
126
+
127
+ l2_total = sum(s["count"] for intent in all_intents for s in intent.get("sub_intents", []))
128
+ opt_notes = (
129
+ f"✅ 已知意图 {n_known} 个,新发现意图 {n_new} 个,共 {n_known + n_new} 个 L1 意图\n\n"
130
+ f"📊 数据基础:{total:,} 条对话 / L2 分类:{l2_total:,} 条"
131
+ )
132
+ if n_new > 0:
133
+ new_names = [i["name"] for i in results.get("new_intents", [])]
134
+ opt_notes += f"\n\n⚡ 新发现意图:{' · '.join(new_names)}"
135
+
136
+ return {
137
+ "agents": agents,
138
+ "filter_block": {"types": ["未分类投诉"], "count": 0},
139
+ "node_summary": {
140
+ "rows": rows,
141
+ "total_nodes": total_nodes,
142
+ "optimization_notes": opt_notes,
143
+ },
144
+ "shared_nodes": [],
145
+ "edges": edges,
146
+ }
147
+
148
+
149
+ def _resolve_results_path(state: dict) -> str:
150
+ path = state.get("results_path")
151
+ if not path:
152
+ raise FileNotFoundError(
153
+ "No results_path in state. Run classify.py first."
154
+ )
155
+ if not os.path.exists(path):
156
+ raise FileNotFoundError(
157
+ f"Results file missing: {path}. Run classify.py first."
158
+ )
159
+ return path
160
+
161
+
162
+ def main():
163
+ parser = argparse.ArgumentParser(description="Generate delivery intent classification HTML report")
164
+ parser.add_argument("--brand", default="")
165
+ parser.add_argument("--results", default=None,
166
+ help="Override results JSON path (default: read from progress.json)")
167
+ args = parser.parse_args()
168
+
169
+ if args.results:
170
+ results_path = args.results
171
+ if not os.path.exists(results_path):
172
+ raise FileNotFoundError(f"Results file missing: {results_path}")
173
+ else:
174
+ state = _read_state()
175
+ results_path = _resolve_results_path(state)
176
+
177
+ with open(results_path) as f:
178
+ results = json.load(f)
179
+
180
+ config = build_config_from_results(results, brand_name=args.brand)
181
+
182
+ meta = results.get("meta", {})
183
+ data_compat = {
184
+ "_meta": {"total": meta.get("total", 0), "brand": args.brand},
185
+ "tree": {},
186
+ "turn_distribution": {},
187
+ "fuzzy_samples": {},
188
+ }
189
+
190
+ project_id = os.environ.get("SOLVEA_PROJECT_ID", "")
191
+ html = render_html(config, data_compat, brand=args.brand, project_id=project_id)
192
+
193
+ OUTPUTS_DIR.mkdir(parents=True, exist_ok=True)
194
+ report_path = str(OUTPUTS_DIR / f"report_{timestamp()}.html")
195
+ with open(report_path, "w", encoding="utf-8") as f:
196
+ f.write(html)
197
+ print(f"[gen_report] Report -> {report_path}")
198
+
199
+ state = _read_state()
200
+ state["report_path"] = report_path
201
+ state["updated_at"] = timestamp()
202
+ _write_state(state)
203
+
204
+ print(f"[gen_report] Done.")
205
+
206
+
207
+ if __name__ == "__main__":
208
+ main()
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env python3
2
+ """Delivery 顶层状态规划器。
3
+
4
+ 只维护 `/workspace/state/delivery/progress.json`,不执行 intent / KB / batch
5
+ 等底层能力。其他 skill 的状态只通过路径引用记录在本文件中。
6
+ """
7
+ import os, sys
8
+ sys.path.insert(0, os.path.join(os.environ.get("SOLVEA_LIB_DIR", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "__lib__"))), "skill-runtime"))
9
+ from bootstrap import * # noqa: F401,F403
10
+
11
+ import argparse
12
+ import json
13
+ from datetime import datetime, timezone
14
+ from typing import Any
15
+
16
+
17
+ STATE_FILE = STATE_DIR / "progress.json"
18
+
19
+ STATUS_ORDER = [
20
+ "empty",
21
+ "classified",
22
+ "agents_created",
23
+ "kb_done",
24
+ "regression_done",
25
+ "done",
26
+ ]
27
+
28
+
29
+ def default_state() -> dict[str, Any]:
30
+ return {
31
+ "status": "empty",
32
+ "tenant": SOLVEA_TENANT_ID or "",
33
+ "brand_name": "",
34
+ "intent": {
35
+ "progress_path": "/workspace/state/delivery/progress.json",
36
+ "classification_report_path": "",
37
+ "classification_excel_path": "",
38
+ "created_log_path": "",
39
+ },
40
+ "kb": {
41
+ "required": True,
42
+ "source_url": "",
43
+ "directory_plan_path": "",
44
+ "qa_plan_path": "",
45
+ "upload_result_path": "",
46
+ },
47
+ "regression": {
48
+ "input_path": "",
49
+ "touch_point": "LIVE_CHAT",
50
+ "results_path": "",
51
+ "report_html": "",
52
+ "report_md": "",
53
+ },
54
+ "updated_at": "",
55
+ }
56
+
57
+
58
+ def merge_dict(base: dict[str, Any], patch: dict[str, Any]) -> None:
59
+ for key, value in patch.items():
60
+ if isinstance(value, dict) and isinstance(base.get(key), dict):
61
+ merge_dict(base[key], value)
62
+ else:
63
+ base[key] = value
64
+
65
+
66
+ def read_state() -> dict[str, Any]:
67
+ if not STATE_FILE.exists():
68
+ return default_state()
69
+ try:
70
+ loaded = json.loads(STATE_FILE.read_text(encoding="utf-8"))
71
+ except Exception:
72
+ return default_state()
73
+ state = default_state()
74
+ merge_dict(state, loaded)
75
+ return state
76
+
77
+
78
+ def write_state(state: dict[str, Any]) -> None:
79
+ state["updated_at"] = datetime.now(timezone.utc).isoformat()
80
+ STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
81
+ STATE_FILE.write_text(json.dumps(state, ensure_ascii=False, indent=2), encoding="utf-8")
82
+
83
+
84
+ def set_path(state: dict[str, Any], dotted_key: str, raw_value: str) -> None:
85
+ parts = [p for p in dotted_key.split(".") if p]
86
+ if not parts:
87
+ raise ValueError("empty key")
88
+ cur = state
89
+ for part in parts[:-1]:
90
+ nxt = cur.get(part)
91
+ if not isinstance(nxt, dict):
92
+ nxt = {}
93
+ cur[part] = nxt
94
+ cur = nxt
95
+ value: Any = raw_value
96
+ if raw_value.lower() == "true":
97
+ value = True
98
+ elif raw_value.lower() == "false":
99
+ value = False
100
+ elif raw_value.startswith("[") or raw_value.startswith("{"):
101
+ value = json.loads(raw_value)
102
+ cur[parts[-1]] = value
103
+
104
+
105
+ def recommendation(status: str) -> str:
106
+ return {
107
+ "empty": "下一步:读取 delivery/interfaces/classify.md,确认从 xlsx 分类还是固定 7 意图开始。",
108
+ "classified": "下一步:展示分类结果并等待用户确认;确认后调用 /skills/delivery/scripts/create_agents.py --mode delivery。",
109
+ "agents_created": "下一步:进入 KB 阶段;如用户声称已有 KB,先 list-directories / list-knowledge 验证。",
110
+ "kb_done": "下一步:读取 delivery/interfaces/delivery-test.md,确认回归数据来源和 touch-point。",
111
+ "regression_done": "下一步:汇总 KPI 报告和失败明细,确认交付完成。",
112
+ "done": "当前 Delivery 已完成;如需重跑,请明确要重跑的阶段。",
113
+ }.get(status, "状态未知:请检查 progress.json。")
114
+
115
+
116
+ def main() -> None:
117
+ parser = argparse.ArgumentParser(description="Delivery top-level progress planner")
118
+ parser.add_argument("--init", action="store_true", help="Create progress.json if missing")
119
+ parser.add_argument("--status", choices=STATUS_ORDER, help="Update top-level status")
120
+ parser.add_argument("--tenant", default=None)
121
+ parser.add_argument("--brand-name", default=None)
122
+ parser.add_argument("--set", action="append", default=[], metavar="KEY=VALUE",
123
+ help="Set a dotted state path, e.g. intent.created_log_path=/workspace/...")
124
+ parser.add_argument("--json", action="store_true", help="Print JSON only")
125
+ args = parser.parse_args()
126
+
127
+ existed = STATE_FILE.exists()
128
+ state = read_state()
129
+ changed = False
130
+
131
+ if args.init and not existed:
132
+ changed = True
133
+ if args.status:
134
+ state["status"] = args.status
135
+ changed = True
136
+ if args.tenant is not None:
137
+ state["tenant"] = args.tenant
138
+ changed = True
139
+ if args.brand_name is not None:
140
+ state["brand_name"] = args.brand_name
141
+ changed = True
142
+ for item in args.set:
143
+ if "=" not in item:
144
+ raise SystemExit(f"--set expects KEY=VALUE, got: {item}")
145
+ key, value = item.split("=", 1)
146
+ set_path(state, key, value)
147
+ changed = True
148
+
149
+ if changed:
150
+ write_state(state)
151
+
152
+ if args.json:
153
+ print(json.dumps(state, ensure_ascii=False, indent=2))
154
+ return
155
+
156
+ print(f"Delivery progress: {STATE_FILE}")
157
+ print(f"status={state.get('status')} tenant={state.get('tenant') or '-'} brand={state.get('brand_name') or '-'}")
158
+ print(recommendation(str(state.get("status") or "")))
159
+ print(json.dumps(state, ensure_ascii=False, indent=2))
160
+
161
+
162
+ if __name__ == "__main__":
163
+ main()