@tencent-rtc/trtc-agent-skills 0.1.3 → 0.1.4

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 (360) hide show
  1. package/.cursor/rules/main.mdc +12 -0
  2. package/AGENTS.md +14 -104
  3. package/CLAUDE.md +15 -127
  4. package/CODEBUDDY.md +32 -118
  5. package/README.md +7 -5
  6. package/README.zh.md +7 -5
  7. package/bin/cli.js +133 -41
  8. package/hooks/__pycache__/cursor-adapter.cpython-313.pyc +0 -0
  9. package/hooks/cursor-adapter.py +45 -18
  10. package/hooks/hooks-cursor.json +5 -18
  11. package/hooks/hooks.json +6 -31
  12. package/knowledge-base/conference/web/index.yaml +143 -0
  13. package/knowledge-base/platform-slice-template.md +1041 -133
  14. package/knowledge-base/products.yaml +76 -0
  15. package/knowledge-base/scenario-spec.md +1316 -115
  16. package/knowledge-base/scenarios/conference/base/general-conference.md +22 -2
  17. package/knowledge-base/scenarios/conference/base/webinar-conference.md +14 -0
  18. package/knowledge-base/scenarios/conference/medical/1v1-video-consultation.md +19 -0
  19. package/knowledge-base/scenarios/conference/medical/medical-multidoctor-consultation.md +13 -0
  20. package/knowledge-base/scenarios/live/entertainment-live-room.md +12 -0
  21. package/knowledge-base/slice-spec.md +2377 -299
  22. package/knowledge-base/slices/conference/web/integration-audit.md +1 -1
  23. package/knowledge-base/slices/conference/web/login-auth.md +14 -1
  24. package/knowledge-base/tooling/aliases.yaml +92 -0
  25. package/knowledge-base/tooling/intent-signals.yaml +181 -0
  26. package/knowledge-base/tooling/symptom-keywords.yaml +21 -0
  27. package/package.json +1 -1
  28. package/skills/trtc/SKILL.md +202 -245
  29. package/skills/trtc/hooks/__pycache__/report_prompt.cpython-313.pyc +0 -0
  30. package/skills/{trtc-topic/guardrails → trtc/hooks}/gate_slice_read.py +12 -8
  31. package/skills/{trtc-topic/guardrails → trtc/hooks}/gate_slice_write.py +16 -12
  32. package/skills/{trtc-topic/guardrails → trtc/hooks}/stop_require_apply_evidence.py +22 -14
  33. package/skills/trtc/hooks/topic_phase_gate.py +161 -0
  34. package/skills/{trtc-topic → trtc}/runtime/README.md +2 -2
  35. package/skills/{trtc-onboarding/reference/reporting-protocol.md → trtc/runtime/REPORTING.md} +5 -5
  36. package/skills/{trtc-topic → trtc}/runtime/RUNTIME.md +5 -5
  37. package/skills/trtc/runtime/lib/__init__.py +0 -0
  38. package/skills/{trtc-topic → trtc}/runtime/package-lock.json +2 -2
  39. package/skills/{trtc-topic → trtc}/runtime/package.json +2 -2
  40. package/skills/{trtc-topic → trtc}/runtime/telemetry-bridge.mjs +1 -1
  41. package/skills/{trtc-topic/scripts → trtc/tools}/STATE-MACHINE-GUIDE.md +23 -23
  42. package/skills/trtc/tools/__init__.py +2 -0
  43. package/skills/trtc/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  44. package/skills/trtc/tools/__pycache__/query_classifier.cpython-313.pyc +0 -0
  45. package/skills/trtc/tools/__pycache__/reporting.cpython-313.pyc +0 -0
  46. package/skills/trtc/tools/__pycache__/search.cpython-313.pyc +0 -0
  47. package/skills/trtc/tools/__pycache__/session.cpython-313.pyc +0 -0
  48. package/skills/trtc/tools/apply.py +540 -0
  49. package/skills/trtc/tools/docs.py +712 -0
  50. package/skills/trtc/tools/docsbot.py +182 -0
  51. package/skills/trtc/tools/entry/render_ai_instructions.py +92 -0
  52. package/skills/trtc/tools/flow.py +1089 -0
  53. package/skills/{trtc-topic/scripts → trtc/tools}/init_slice_queue.py +5 -4
  54. package/skills/{trtc-topic/scripts → trtc/tools}/next_slice.py +5 -4
  55. package/skills/trtc/tools/query_classifier.py +301 -0
  56. package/skills/trtc/tools/reporting.py +447 -0
  57. package/skills/trtc/tools/search.py +817 -0
  58. package/skills/trtc/tools/session.py +1261 -0
  59. package/skills/trtc/tools/state_machine.py +690 -0
  60. package/skills/trtc-ai-service/README.md +195 -0
  61. package/skills/trtc-ai-service/README.zh-CN.md +193 -0
  62. package/skills/trtc-ai-service/SKILL.md +945 -0
  63. package/skills/trtc-ai-service/auto_adapters/README.md +40 -0
  64. package/skills/trtc-ai-service/auto_adapters/frontend-spa/README.md +27 -0
  65. package/skills/trtc-ai-service/auto_adapters/frontend-spa/angular/voice-agent.component.ts.tpl +131 -0
  66. package/skills/trtc-ai-service/auto_adapters/frontend-spa/manifest.yaml +57 -0
  67. package/skills/trtc-ai-service/auto_adapters/frontend-spa/react/VoiceAgent.tsx.tpl +142 -0
  68. package/skills/trtc-ai-service/auto_adapters/frontend-spa/vue/VoiceAgent.vue.tpl +121 -0
  69. package/skills/trtc-ai-service/auto_adapters/integration_templates/generic-backend.md +45 -0
  70. package/skills/trtc-ai-service/auto_adapters/integration_templates/generic-frontend.md +51 -0
  71. package/skills/trtc-ai-service/auto_adapters/integration_templates/generic-rest-api.md +93 -0
  72. package/skills/trtc-ai-service/auto_adapters/java-backend/README.md +25 -0
  73. package/skills/trtc-ai-service/auto_adapters/java-backend/manifest.yaml +30 -0
  74. package/skills/trtc-ai-service/auto_adapters/java-backend/quarkus/VoiceAgentFilter.java.tpl +64 -0
  75. package/skills/trtc-ai-service/auto_adapters/java-backend/springboot/VoiceAgentFilter.java.tpl +91 -0
  76. package/skills/trtc-ai-service/auto_adapters/manifest.yaml +43 -0
  77. package/skills/trtc-ai-service/auto_adapters/node-backend/README.md +25 -0
  78. package/skills/trtc-ai-service/auto_adapters/node-backend/express.js.tpl +40 -0
  79. package/skills/trtc-ai-service/auto_adapters/node-backend/fastify.js.tpl +27 -0
  80. package/skills/trtc-ai-service/auto_adapters/node-backend/koa.js.tpl +31 -0
  81. package/skills/trtc-ai-service/auto_adapters/node-backend/manifest.yaml +47 -0
  82. package/skills/trtc-ai-service/auto_adapters/python-backend/README.md +22 -0
  83. package/skills/trtc-ai-service/auto_adapters/python-backend/django.py.tpl +32 -0
  84. package/skills/trtc-ai-service/auto_adapters/python-backend/fastapi.py.tpl +35 -0
  85. package/skills/trtc-ai-service/auto_adapters/python-backend/flask.py.tpl +31 -0
  86. package/skills/trtc-ai-service/auto_adapters/python-backend/manifest.yaml +45 -0
  87. package/skills/trtc-ai-service/capabilities/__init__.py +43 -0
  88. package/skills/trtc-ai-service/capabilities/conversation-core/.env.example +29 -0
  89. package/skills/trtc-ai-service/capabilities/conversation-core/INTEGRATION.md +134 -0
  90. package/skills/trtc-ai-service/capabilities/conversation-core/INTERFACE_ADAPT.md +111 -0
  91. package/skills/trtc-ai-service/capabilities/conversation-core/QUICK_START.md +62 -0
  92. package/skills/trtc-ai-service/capabilities/conversation-core/manifest.yaml +250 -0
  93. package/skills/trtc-ai-service/capabilities/conversation-core/requirements.txt +6 -0
  94. package/skills/trtc-ai-service/capabilities/conversation-core/src/__init__.py +10 -0
  95. package/skills/trtc-ai-service/capabilities/conversation-core/src/_capability_loader.py +218 -0
  96. package/skills/trtc-ai-service/capabilities/conversation-core/src/agent.py +231 -0
  97. package/skills/trtc-ai-service/capabilities/conversation-core/src/credentials.py +132 -0
  98. package/skills/trtc-ai-service/capabilities/conversation-core/src/health.py +355 -0
  99. package/skills/trtc-ai-service/capabilities/conversation-core/src/log_filter.py +76 -0
  100. package/skills/trtc-ai-service/capabilities/conversation-core/src/modality.py +109 -0
  101. package/skills/trtc-ai-service/capabilities/conversation-core/src/server.py +312 -0
  102. package/skills/trtc-ai-service/capabilities/conversation-core/src/trtc_client.py +315 -0
  103. package/skills/trtc-ai-service/capabilities/conversation-core/src/usersig.py +90 -0
  104. package/skills/trtc-ai-service/capabilities/conversation-core/tests/test_skeleton.py +216 -0
  105. package/skills/trtc-ai-service/capabilities/conversation-core/web-demo/README.md +48 -0
  106. package/skills/trtc-ai-service/capabilities/conversation-core/web-demo/app.js +415 -0
  107. package/skills/trtc-ai-service/capabilities/conversation-core/web-demo/index.html +68 -0
  108. package/skills/trtc-ai-service/capabilities/conversation-core/web-demo/styles.css +136 -0
  109. package/skills/trtc-ai-service/capabilities/digital-human/README.md +31 -0
  110. package/skills/trtc-ai-service/capabilities/digital-human/manifest.yaml +64 -0
  111. package/skills/trtc-ai-service/capabilities/digital-human/src/__init__.py +2 -0
  112. package/skills/trtc-ai-service/capabilities/digital-human/src/router.py +43 -0
  113. package/skills/trtc-ai-service/capabilities/human-handoff/INTERFACE_ADAPT.md +353 -0
  114. package/skills/trtc-ai-service/capabilities/human-handoff/README.md +44 -0
  115. package/skills/trtc-ai-service/capabilities/human-handoff/manifest.yaml +227 -0
  116. package/skills/trtc-ai-service/capabilities/human-handoff/src/__init__.py +2 -0
  117. package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/__init__.py +9 -0
  118. package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/default_rest.py +242 -0
  119. package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/factory.py +89 -0
  120. package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/local_queue.py +258 -0
  121. package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/mock.py +132 -0
  122. package/skills/trtc-ai-service/capabilities/human-handoff/src/core/__init__.py +25 -0
  123. package/skills/trtc-ai-service/capabilities/human-handoff/src/core/intent_detector.py +75 -0
  124. package/skills/trtc-ai-service/capabilities/human-handoff/src/core/models.py +163 -0
  125. package/skills/trtc-ai-service/capabilities/human-handoff/src/core/service.py +192 -0
  126. package/skills/trtc-ai-service/capabilities/human-handoff/src/feedback_store.py +54 -0
  127. package/skills/trtc-ai-service/capabilities/human-handoff/src/ports/__init__.py +4 -0
  128. package/skills/trtc-ai-service/capabilities/human-handoff/src/ports/handoff_client.py +86 -0
  129. package/skills/trtc-ai-service/capabilities/human-handoff/src/queue.py +62 -0
  130. package/skills/trtc-ai-service/capabilities/human-handoff/src/router.py +201 -0
  131. package/skills/trtc-ai-service/capabilities/human-handoff/src/summary_link.py +77 -0
  132. package/skills/trtc-ai-service/capabilities/human-handoff/src/trigger.py +25 -0
  133. package/skills/trtc-ai-service/capabilities/knowledge-base/INTERFACE_ADAPT.md +297 -0
  134. package/skills/trtc-ai-service/capabilities/knowledge-base/README.md +51 -0
  135. package/skills/trtc-ai-service/capabilities/knowledge-base/data/faq.json +20 -0
  136. package/skills/trtc-ai-service/capabilities/knowledge-base/manifest.yaml +211 -0
  137. package/skills/trtc-ai-service/capabilities/knowledge-base/src/__init__.py +8 -0
  138. package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/__init__.py +9 -0
  139. package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/default_rest.py +209 -0
  140. package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/factory.py +86 -0
  141. package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/local_json.py +172 -0
  142. package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/mock.py +91 -0
  143. package/skills/trtc-ai-service/capabilities/knowledge-base/src/core/__init__.py +12 -0
  144. package/skills/trtc-ai-service/capabilities/knowledge-base/src/core/models.py +77 -0
  145. package/skills/trtc-ai-service/capabilities/knowledge-base/src/core/scoring.py +73 -0
  146. package/skills/trtc-ai-service/capabilities/knowledge-base/src/core/service.py +78 -0
  147. package/skills/trtc-ai-service/capabilities/knowledge-base/src/ports/__init__.py +4 -0
  148. package/skills/trtc-ai-service/capabilities/knowledge-base/src/ports/kb_client.py +61 -0
  149. package/skills/trtc-ai-service/capabilities/knowledge-base/src/retriever.py +56 -0
  150. package/skills/trtc-ai-service/capabilities/knowledge-base/src/router.py +85 -0
  151. package/skills/trtc-ai-service/capabilities/session-summary/INTERFACE_ADAPT.md +99 -0
  152. package/skills/trtc-ai-service/capabilities/session-summary/README.md +47 -0
  153. package/skills/trtc-ai-service/capabilities/session-summary/data/test_session.json +18 -0
  154. package/skills/trtc-ai-service/capabilities/session-summary/manifest.yaml +165 -0
  155. package/skills/trtc-ai-service/capabilities/session-summary/src/__init__.py +2 -0
  156. package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/__init__.py +5 -0
  157. package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/base.py +31 -0
  158. package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/default_rest.py +67 -0
  159. package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/factory.py +51 -0
  160. package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/local_json.py +42 -0
  161. package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/mock.py +22 -0
  162. package/skills/trtc-ai-service/capabilities/session-summary/src/recorder.py +210 -0
  163. package/skills/trtc-ai-service/capabilities/session-summary/src/router.py +93 -0
  164. package/skills/trtc-ai-service/capabilities/session-summary/src/summarizer.py +163 -0
  165. package/skills/trtc-ai-service/capabilities/tool-calling/INTERFACE_ADAPT.md +158 -0
  166. package/skills/trtc-ai-service/capabilities/tool-calling/README.md +50 -0
  167. package/skills/trtc-ai-service/capabilities/tool-calling/data/tools.yaml +58 -0
  168. package/skills/trtc-ai-service/capabilities/tool-calling/examples/__init__.py +1 -0
  169. package/skills/trtc-ai-service/capabilities/tool-calling/examples/local_tools.py +101 -0
  170. package/skills/trtc-ai-service/capabilities/tool-calling/manifest.yaml +146 -0
  171. package/skills/trtc-ai-service/capabilities/tool-calling/src/__init__.py +8 -0
  172. package/skills/trtc-ai-service/capabilities/tool-calling/src/dispatcher.py +54 -0
  173. package/skills/trtc-ai-service/capabilities/tool-calling/src/registry.py +219 -0
  174. package/skills/trtc-ai-service/capabilities/tool-calling/src/router.py +50 -0
  175. package/skills/trtc-ai-service/references/business-contract-spec.md +263 -0
  176. package/skills/trtc-ai-service/scenarios/custom-builder/README.md +86 -0
  177. package/skills/trtc-ai-service/scenarios/custom-builder/output-templates/recipe.yaml.j2 +194 -0
  178. package/skills/trtc-ai-service/scenarios/custom-builder/prompts/q1-business-scenario.md +43 -0
  179. package/skills/trtc-ai-service/scenarios/custom-builder/prompts/q2-io-modality.md +57 -0
  180. package/skills/trtc-ai-service/scenarios/custom-builder/prompts/q3-ui-form.md +55 -0
  181. package/skills/trtc-ai-service/scenarios/custom-builder/prompts/q4-capabilities.md +78 -0
  182. package/skills/trtc-ai-service/scenarios/customer-service/README.md +114 -0
  183. package/skills/trtc-ai-service/scenarios/customer-service/recipe.yaml +154 -0
  184. package/skills/trtc-ai-service/scenarios/customer-service/sample-data/README.md +32 -0
  185. package/skills/trtc-ai-service/scenarios/customer-service/sample-data/faq-sample.json +37 -0
  186. package/skills/trtc-ai-service/scenarios/customer-service/system-prompt.template.md +94 -0
  187. package/skills/trtc-ai-service/scenarios/customer-service/ui/admin-board/app.js +347 -0
  188. package/skills/trtc-ai-service/scenarios/customer-service/ui/admin-board/index.html +125 -0
  189. package/skills/trtc-ai-service/scenarios/customer-service/ui/admin-board/styles.css +487 -0
  190. package/skills/trtc-ai-service/scenarios/customer-service/ui/admin-board/tokens.css +71 -0
  191. package/skills/trtc-ai-service/scenarios/customer-service/ui/design-system/DESIGN_GUIDELINES.md +370 -0
  192. package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/README.md +68 -0
  193. package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/app.js +1307 -0
  194. package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/data.js +40 -0
  195. package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/index.html +233 -0
  196. package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/mock-shop.json +21 -0
  197. package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/styles.css +603 -0
  198. package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/tokens.css +71 -0
  199. package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/agent-link.js +323 -0
  200. package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/app.js +458 -0
  201. package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/index.html +109 -0
  202. package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/styles.css +489 -0
  203. package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/tokens.css +59 -0
  204. package/skills/trtc-ai-service/scripts/add-capability.py +364 -0
  205. package/skills/trtc-ai-service/scripts/contract-adapt.py +334 -0
  206. package/skills/trtc-ai-service/scripts/detect-stack.py +40 -0
  207. package/skills/trtc-ai-service/scripts/lib/__init__.py +20 -0
  208. package/skills/trtc-ai-service/scripts/lib/adapter_codegen.py +509 -0
  209. package/skills/trtc-ai-service/scripts/lib/arbitrator.py +152 -0
  210. package/skills/trtc-ai-service/scripts/lib/contract_resolver.py +519 -0
  211. package/skills/trtc-ai-service/scripts/lib/credential_validators.py +253 -0
  212. package/skills/trtc-ai-service/scripts/lib/curl_parser.py +303 -0
  213. package/skills/trtc-ai-service/scripts/lib/degrader.py +140 -0
  214. package/skills/trtc-ai-service/scripts/lib/injector.py +347 -0
  215. package/skills/trtc-ai-service/scripts/lib/manifest_resolver.py +288 -0
  216. package/skills/trtc-ai-service/scripts/lib/openapi_parser.py +289 -0
  217. package/skills/trtc-ai-service/scripts/lib/stack_detector.py +159 -0
  218. package/skills/trtc-ai-service/scripts/lib/tokens_compile.py +204 -0
  219. package/skills/trtc-ai-service/scripts/post-install-patch.py +225 -0
  220. package/skills/trtc-ai-service/scripts/setup-credentials.py +393 -0
  221. package/skills/trtc-ai-service/scripts/verify-credentials.py +108 -0
  222. package/skills/trtc-ai-service/start.sh +111 -0
  223. package/skills/trtc-ai-service/tests/__init__.py +1 -0
  224. package/skills/trtc-ai-service/tests/test_arbitrator.py +64 -0
  225. package/skills/trtc-ai-service/tests/test_capability_overlay.py +85 -0
  226. package/skills/trtc-ai-service/tests/test_contract_resolver.py +190 -0
  227. package/skills/trtc-ai-service/tests/test_handoff_ports.py +195 -0
  228. package/skills/trtc-ai-service/tests/test_kb_ports.py +195 -0
  229. package/skills/trtc-ai-service/tests/test_manifest_resolver.py +95 -0
  230. package/skills/trtc-ai-service/tests/test_recipe_assembly.py +175 -0
  231. package/skills/trtc-ai-service/tests/test_stack_and_degrader.py +101 -0
  232. package/skills/trtc-ai-service/tests/test_verify_credentials.py +285 -0
  233. package/skills/trtc-ai-service/triggers.yaml +29 -0
  234. package/skills/trtc-conference/SKILL.md +324 -0
  235. package/skills/trtc-conference/flows/onboarding.md +205 -0
  236. package/skills/trtc-conference/flows/topic.md +474 -0
  237. package/skills/trtc-conference/flows/troubleshoot.md +85 -0
  238. package/skills/trtc-conference/hooks/pretooluse_require_business_decisions.py +213 -0
  239. package/skills/trtc-conference/playbooks/medical-quickstart.md +84 -0
  240. package/skills/trtc-conference/playbooks/official-roomkit.md +97 -0
  241. package/skills/trtc-conference/references/local-usersig/basic-info-config.ts +39 -0
  242. package/skills/trtc-conference/references/usersig-handling.md +134 -0
  243. package/skills/trtc-conference/templates/medical-consultation/src/config/lib-generate-test-usersig-es.min.d.ts +4 -0
  244. package/skills/trtc-conference/templates/medical-consultation/src/config/lib-generate-test-usersig-es.min.js +2 -0
  245. package/skills/trtc-conference/tests/__pycache__/test_conference_onboarding_contract.cpython-313-pytest-9.0.2.pyc +0 -0
  246. package/skills/trtc-conference/tests/__pycache__/test_conference_topic_flow_contract.cpython-313-pytest-9.0.2.pyc +0 -0
  247. package/skills/trtc-conference/tests/test_conference_index_contract.py +43 -0
  248. package/skills/trtc-conference/tests/test_conference_onboarding_contract.py +103 -0
  249. package/skills/trtc-conference/tests/test_conference_template_contract.py +25 -0
  250. package/skills/trtc-conference/tests/test_conference_topic_flow_contract.py +132 -0
  251. package/skills/trtc-conference/tools/apply_checks.py +328 -0
  252. package/skills/trtc-conference/verify_lib/__init__.py +0 -0
  253. package/skills/{trtc-apply/guardrails/apply_lib → trtc-conference/verify_lib}/rule_parser.py +1 -1
  254. package/skills/trtc-docs/SKILL.md +91 -119
  255. package/.cursor/rules/ui-mode.mdc +0 -99
  256. package/ai-instructions/base.md +0 -13
  257. package/ai-instructions/ui-mode.md +0 -93
  258. package/knowledge-base/index.yaml +0 -462
  259. package/skills/trtc/room-builder/SKILL.md +0 -133
  260. package/skills/trtc/room-builder/templates/scenarios/medical-consultation/README.md +0 -108
  261. package/skills/trtc/room-builder/tools/render_ai_instructions.py +0 -226
  262. package/skills/trtc-apply/SKILL.md +0 -97
  263. package/skills/trtc-onboarding/SKILL.md +0 -841
  264. package/skills/trtc-onboarding/reference/path-a1-demo.md +0 -103
  265. package/skills/trtc-onboarding/reference/path-a2-integrate.md +0 -737
  266. package/skills/trtc-onboarding/reference/path-b-troubleshoot.md +0 -186
  267. package/skills/trtc-onboarding/reference/path-c-expand.md +0 -43
  268. package/skills/trtc-onboarding/reference/supported-matrix.md +0 -100
  269. package/skills/trtc-search/SKILL.md +0 -228
  270. package/skills/trtc-topic/SKILL.md +0 -622
  271. package/skills/trtc-topic/scripts/apply.py +0 -581
  272. package/skills/trtc-topic/scripts/lib/state_machine.py +0 -328
  273. package/skills/trtc-topic/tests/README.md +0 -70
  274. package/skills/trtc-topic/tests/conftest.py +0 -72
  275. package/skills/trtc-topic/tests/test_apply_cli.py +0 -480
  276. package/skills/trtc-topic/tests/test_end_to_end.py +0 -305
  277. package/skills/trtc-topic/tests/test_finalize_session.py +0 -51
  278. package/skills/trtc-topic/tests/test_gates.py +0 -316
  279. package/skills/trtc-topic/tests/test_session_resolver.py +0 -260
  280. package/skills/trtc-topic/tests/test_state_machine.py +0 -414
  281. package/skills/trtc-topic/tests/test_stop_require_apply.py +0 -99
  282. package/skills/trtc-topic/tests/test_topic_skill_invariants.py +0 -130
  283. /package/skills/{trtc-topic → trtc}/runtime/lib/platforms.py +0 -0
  284. /package/skills/{trtc-topic → trtc}/runtime/telemetry_collector.py +0 -0
  285. /package/skills/{trtc-onboarding/reference → trtc/runtime}/usersig-handling.md +0 -0
  286. /package/skills/{trtc-topic/scripts → trtc/tools}/finalize_session.py +0 -0
  287. /package/skills/{trtc-apply/guardrails/apply_lib → trtc-ai-service/capabilities/conversation-core/tests}/__init__.py +0 -0
  288. /package/skills/{trtc-topic → trtc-conference}/references/execution-units.yaml +0 -0
  289. /package/skills/{trtc/room-builder/templates/scenarios/medical-consultation/src/config → trtc-conference/references/local-usersig}/lib-generate-test-usersig-es.min.d.ts +0 -0
  290. /package/skills/{trtc/room-builder/templates/scenarios/medical-consultation/src/config → trtc-conference/references/local-usersig}/lib-generate-test-usersig-es.min.js +0 -0
  291. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/docs/backend-contract.zh-CN.md +0 -0
  292. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/docs/integration.zh-CN.md +0 -0
  293. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/docs/theme.zh-CN.md +0 -0
  294. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/index.html +0 -0
  295. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/package.json +0 -0
  296. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/postcss.config.js +0 -0
  297. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/App.vue +0 -0
  298. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/ConsultationManagePanel.vue +0 -0
  299. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/LanguageSwitch.vue +0 -0
  300. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/LoadingSpinner.vue +0 -0
  301. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalAlert.vue +0 -0
  302. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalBusinessPanel.vue +0 -0
  303. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalButton.vue +0 -0
  304. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalConfirmDialog.vue +0 -0
  305. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalDataPanel.vue +0 -0
  306. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalRecordPanel.vue +0 -0
  307. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/PrescriptionPanel.vue +0 -0
  308. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/config/basic-info-config.ts +0 -0
  309. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/config/runtime-config.ts +0 -0
  310. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/env.d.ts +0 -0
  311. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/ConsultationChatPanel.vue +0 -0
  312. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/ConsultationMembersPanel.vue +0 -0
  313. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/ConsultationTranscriptionPanel.vue +0 -0
  314. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/ConsultationVideoStage.vue +0 -0
  315. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/InviteDoctorDialog.vue +0 -0
  316. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/KickMemberConfirmDialog.vue +0 -0
  317. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/types.ts +0 -0
  318. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/useConsultationChat.ts +0 -0
  319. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/useConsultationDevices.ts +0 -0
  320. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/useConsultationParticipants.ts +0 -0
  321. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/useConsultationPermissions.ts +0 -0
  322. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/utils.ts +0 -0
  323. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/en-US/index.ts +0 -0
  324. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/index.ts +0 -0
  325. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/medicalTranslate.ts +0 -0
  326. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/state.ts +0 -0
  327. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/zh-CN/index.ts +0 -0
  328. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/main.ts +0 -0
  329. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/mock/appointments.ts +0 -0
  330. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/mock/users.ts +0 -0
  331. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/router/index.ts +0 -0
  332. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/index.ts +0 -0
  333. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/integration/appointmentService.ts +0 -0
  334. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/integration/authService.ts +0 -0
  335. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/integration/launchContext.ts +0 -0
  336. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/integration/userService.ts +0 -0
  337. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/mock/appointmentService.ts +0 -0
  338. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/mock/authService.ts +0 -0
  339. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/mock/userService.ts +0 -0
  340. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/types.ts +0 -0
  341. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/shared/icons.ts +0 -0
  342. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/styles/index.css +0 -0
  343. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/styles/tailwind.css +0 -0
  344. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/styles/theme.css +0 -0
  345. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/utils/auth.ts +0 -0
  346. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/utils/format.ts +0 -0
  347. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/utils/navigation.ts +0 -0
  348. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/utils/session.ts +0 -0
  349. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/DoctorConsultationView.vue +0 -0
  350. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/DoctorDashboardView.vue +0 -0
  351. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/LoginView.vue +0 -0
  352. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/PatientConsultationFinishedView.vue +0 -0
  353. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/PatientConsultationView.vue +0 -0
  354. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/PatientSelectDoctorView.vue +0 -0
  355. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/PatientWaitingView.vue +0 -0
  356. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/tsconfig.json +0 -0
  357. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/tsconfig.node.json +0 -0
  358. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/vite.config.ts +0 -0
  359. /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation//346/216/245/345/205/245/350/257/264/346/230/216.md" +0 -0
  360. /package/skills/{trtc-topic/runtime/lib → trtc-conference/tools}/__init__.py +0 -0
@@ -0,0 +1,195 @@
1
+ """Phase 3 Stage 6: human-handoff capability ports/adapters unit tests.
2
+
3
+ Coverage targets:
4
+ - LocalQueueHandoffClient: FIFO queue + auto-connect + cancel / status transitions
5
+ - MockHandoffClient: pre-seeded demo data (_seed three tickets) + list_tickets
6
+ - DefaultRestHandoffClient: base_url security validation (private network denied) + HTTP path calls + passthrough responses
7
+
8
+ Note: This test file cleans up stale ``src.*`` modules from sys.modules before imports
9
+ to avoid conflicts with the same-named ``src`` package in ``test_kb_ports.py``;
10
+ KB tests likewise self-clean.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import sys
15
+ import time
16
+ import unittest
17
+ from pathlib import Path
18
+ from unittest.mock import MagicMock, patch
19
+
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # Isolated import: clean src.* cache + insert human-handoff's own src parent directory
23
+ # ---------------------------------------------------------------------------
24
+ _ROOT = Path(__file__).resolve().parent.parent
25
+ _HH = _ROOT / "capabilities" / "human-handoff"
26
+
27
+ for _name in [k for k in list(sys.modules) if k == "src" or k.startswith("src.")]:
28
+ del sys.modules[_name]
29
+ sys.path[:] = [p for p in sys.path if "/capabilities/" not in p]
30
+ sys.path.insert(0, str(_HH))
31
+
32
+ # Import directly from core.models module to bypass the circular dependency in src/core/__init__.py
33
+ # (src/core/__init__.py imports service.py, which in turn depends on ports.handoff_client)
34
+ import importlib # noqa: E402
35
+
36
+ _models = importlib.import_module("src.core.models")
37
+ TicketStatusEnum = _models.TicketStatusEnum
38
+
39
+ from src.adapters.local_queue import LocalQueueHandoffClient # noqa: E402
40
+ from src.adapters.mock import MockHandoffClient # noqa: E402
41
+ # Pre-import default_rest (HH version) and pin the reference at module scope
42
+ # to avoid ``src`` being replaced by KB namespace when test_kb_ports runs later
43
+ from src.adapters import default_rest as _hh_default_rest # noqa: E402
44
+
45
+ DefaultRestHandoffClient = _hh_default_rest.DefaultRestHandoffClient
46
+
47
+
48
+ class LocalQueueAdapterTests(unittest.TestCase):
49
+ """Cover LocalQueueHandoffClient core behaviors."""
50
+
51
+ def test_create_then_query_pending(self):
52
+ c = LocalQueueHandoffClient(
53
+ capacity=5, agent_pool_size=0, estimated_wait_per_slot=10
54
+ )
55
+ t = c.create_ticket(
56
+ user_id="u1",
57
+ subject="退款",
58
+ description="3 天未到账",
59
+ priority="normal",
60
+ transcript=["用户:申请退款"],
61
+ )
62
+ # agent_pool_size=0 → no auto-connect; enters queue
63
+ self.assertEqual(t.status, TicketStatusEnum.PENDING.value)
64
+ self.assertEqual(t.queue_position, 1)
65
+ self.assertEqual(t.eta_seconds, 10)
66
+
67
+ status = c.query_status(t.ticket_id)
68
+ self.assertIsNotNone(status)
69
+ self.assertEqual(status.status, TicketStatusEnum.PENDING.value)
70
+
71
+ def test_auto_connect_when_agent_available(self):
72
+ c = LocalQueueHandoffClient(capacity=5, agent_pool_size=1)
73
+ t = c.create_ticket(user_id="u_auto", subject="物流")
74
+ self.assertEqual(t.status, TicketStatusEnum.PROCESSING.value)
75
+ self.assertEqual(t.queue_position, 0)
76
+ self.assertIsNotNone(t.agent_id)
77
+
78
+ def test_cancel_releases_slot(self):
79
+ c = LocalQueueHandoffClient(capacity=5, agent_pool_size=1)
80
+ t = c.create_ticket(user_id="u_cancel", subject="发票")
81
+ canceled = c.cancel_ticket(t.ticket_id, reason="用户主动取消")
82
+ self.assertIsNotNone(canceled)
83
+ self.assertEqual(canceled.status, TicketStatusEnum.CANCELED.value)
84
+ # After cancel, status still queryable (closed ticket), but overall_status.connected = 0
85
+ status = c.overall_status()
86
+ self.assertEqual(status.connected, 0)
87
+
88
+ def test_capacity_full_yields_timeout(self):
89
+ c = LocalQueueHandoffClient(capacity=1, agent_pool_size=0)
90
+ c.create_ticket(user_id="u_fill")
91
+ t2 = c.create_ticket(user_id="u_overflow")
92
+ self.assertEqual(t2.status, TicketStatusEnum.TIMEOUT.value)
93
+ self.assertIsNotNone(t2.closed_at)
94
+
95
+ def test_update_status_manual_close(self):
96
+ c = LocalQueueHandoffClient(capacity=5, agent_pool_size=1)
97
+ t = c.create_ticket(user_id="u_close")
98
+ # auto-connected → close
99
+ closed = c.update_status(t.ticket_id, "closed")
100
+ self.assertEqual(closed.status, TicketStatusEnum.CLOSED.value)
101
+ self.assertIsNotNone(closed.closed_at)
102
+ self.assertEqual(c.overall_status().connected, 0)
103
+
104
+ def test_get_or_attach_returns_active_only(self):
105
+ c = LocalQueueHandoffClient(capacity=5, agent_pool_size=0)
106
+ t = c.create_ticket(user_id="u_query")
107
+ same = c.get_or_attach("u_query")
108
+ self.assertEqual(same.ticket_id, t.ticket_id)
109
+ # After cancel, get_or_attach should return None (only returns active tickets)
110
+ c.cancel_ticket(t.ticket_id)
111
+ self.assertIsNone(c.get_or_attach("u_query"))
112
+
113
+
114
+ class MockAdapterTests(unittest.TestCase):
115
+ """MockHandoffClient demo data should be pre-populated."""
116
+
117
+ def test_seed_three_tickets(self):
118
+ c = MockHandoffClient(seed_demo_data=True, agent_pool_size=2)
119
+ tickets = c.list_tickets(limit=10)
120
+ ids = {t.ticket_id for t in tickets}
121
+ # All three demo tickets present
122
+ self.assertIn("demo_pending_001", ids)
123
+ self.assertIn("demo_processing_001", ids)
124
+ self.assertIn("demo_closed_001", ids)
125
+ statuses = {t.status for t in tickets}
126
+ self.assertIn(TicketStatusEnum.PENDING.value, statuses)
127
+ self.assertIn(TicketStatusEnum.PROCESSING.value, statuses)
128
+ self.assertIn(TicketStatusEnum.CLOSED.value, statuses)
129
+
130
+ def test_seed_can_be_disabled(self):
131
+ c = MockHandoffClient(seed_demo_data=False, agent_pool_size=2)
132
+ self.assertEqual(c.list_tickets(), [])
133
+
134
+ def test_inherits_create_behavior(self):
135
+ c = MockHandoffClient(seed_demo_data=False, agent_pool_size=2)
136
+ t = c.create_ticket(user_id="u_new", subject="测试")
137
+ # agent_pool_size=2 → immediate connect
138
+ self.assertEqual(t.status, TicketStatusEnum.PROCESSING.value)
139
+
140
+
141
+ class DefaultRestAdapterSecurityTests(unittest.TestCase):
142
+ """DefaultRestHandoffClient base_url security validation (no actual HTTP calls)."""
143
+
144
+ def test_https_public_allowed(self):
145
+ # No real HTTP: constructing the object is enough to trigger _validate_base_url
146
+ c = DefaultRestHandoffClient(base_url="https://crm.example.com")
147
+ # Verify the validated url via _base
148
+ self.assertTrue(c._base.startswith("https://"))
149
+
150
+ def test_localhost_http_allowed(self):
151
+ c = DefaultRestHandoffClient(base_url="http://localhost:8080")
152
+ self.assertIn("localhost", c._base)
153
+
154
+ def test_private_network_denied(self):
155
+ for url in (
156
+ "https://10.1.2.3",
157
+ "https://192.168.0.1",
158
+ "https://172.16.0.5",
159
+ "https://9.0.0.1",
160
+ ):
161
+ with self.subTest(url=url):
162
+ with self.assertRaises(ValueError):
163
+ DefaultRestHandoffClient(base_url=url)
164
+
165
+ def test_non_https_remote_denied(self):
166
+ with self.assertRaises(ValueError):
167
+ DefaultRestHandoffClient(base_url="http://crm.example.com")
168
+
169
+ def test_create_ticket_calls_post(self):
170
+ """Simulate a successful create_ticket via requests, verify key payload fields."""
171
+ fake_resp = MagicMock()
172
+ fake_resp.status_code = 200
173
+ fake_resp.json.return_value = {
174
+ "ticket_id": "T-9001",
175
+ "queue_position": 2,
176
+ "eta_seconds": 60,
177
+ }
178
+
179
+ c = DefaultRestHandoffClient(base_url="https://crm.example.com", token="tok")
180
+ with patch.object(c._session, "post", return_value=fake_resp) as p:
181
+ t = c.create_ticket(
182
+ user_id="u9", subject="退款", description="3 天未到账"
183
+ )
184
+ self.assertEqual(t.ticket_id, "T-9001")
185
+ self.assertEqual(t.queue_position, 2)
186
+ # Verify call arguments
187
+ self.assertEqual(p.call_count, 1)
188
+ _, kwargs = p.call_args
189
+ self.assertEqual(kwargs["json"]["user_id"], "u9")
190
+ # Authorization header should carry Bearer
191
+ self.assertIn("Bearer ", kwargs["headers"]["Authorization"])
192
+
193
+
194
+ if __name__ == "__main__":
195
+ unittest.main()
@@ -0,0 +1,195 @@
1
+ """Phase 3 Stage 6: knowledge-base capability ports/adapters unit tests.
2
+
3
+ Coverage targets:
4
+ - LocalJsonKbClient: reload / search (TF-IDF) / upsert / delete + stats
5
+ - MockKbClient: 5 pre-seeded demo FAQ entries + keyword search hits
6
+ - DefaultRestKbClient: base_url security validation + search/list_all via HTTP passthrough
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import sys
12
+ import tempfile
13
+ import unittest
14
+ from pathlib import Path
15
+ from unittest.mock import MagicMock, patch
16
+
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # Isolated import: clean src.* cache (avoid conflict with same-named src in test_handoff_ports)
20
+ # ---------------------------------------------------------------------------
21
+ _ROOT = Path(__file__).resolve().parent.parent
22
+ _KB = _ROOT / "capabilities" / "knowledge-base"
23
+
24
+ for _name in [k for k in list(sys.modules) if k == "src" or k.startswith("src.")]:
25
+ del sys.modules[_name]
26
+ sys.path[:] = [p for p in sys.path if "/capabilities/" not in p]
27
+ sys.path.insert(0, str(_KB))
28
+
29
+ import importlib # noqa: E402
30
+
31
+ _models = importlib.import_module("src.core.models")
32
+ FaqEntry = _models.FaqEntry
33
+
34
+ from src.adapters.local_json import LocalJsonKbClient # noqa: E402
35
+ from src.adapters.mock import MockKbClient # noqa: E402
36
+ # 预先导入 default_rest(KB 版本)并固化引用,
37
+ # 避免后续测试运行时 ``src`` 已被替换为 HH 命名空间
38
+ from src.adapters import default_rest as _kb_default_rest # noqa: E402
39
+
40
+ DefaultRestKbClient = _kb_default_rest.DefaultRestKbClient
41
+
42
+
43
+ class LocalJsonAdapterTests(unittest.TestCase):
44
+
45
+ def setUp(self):
46
+ self._tmp = tempfile.TemporaryDirectory()
47
+ self.data_path = Path(self._tmp.name) / "faq.json"
48
+ self.data_path.write_text(
49
+ json.dumps(
50
+ [
51
+ {
52
+ "id": "kb_refund",
53
+ "question": "退款政策是什么?",
54
+ "answer": "支持 7 天无理由退款",
55
+ "keywords": ["退款", "退货", "refund"],
56
+ },
57
+ {
58
+ "id": "kb_ship",
59
+ "question": "物流多久送达?",
60
+ "answer": "全国 3-5 天送达",
61
+ "keywords": ["物流", "发货"],
62
+ },
63
+ ],
64
+ ensure_ascii=False,
65
+ ),
66
+ encoding="utf-8",
67
+ )
68
+ self.client = LocalJsonKbClient(
69
+ data_file=self.data_path, min_score=0.0, top_k=5
70
+ )
71
+
72
+ def tearDown(self):
73
+ self._tmp.cleanup()
74
+
75
+ def test_reload_loads_entries(self):
76
+ self.assertEqual(len(self.client.list_all()), 2)
77
+ stats = self.client.stats()
78
+ self.assertEqual(stats.backend, "local_json")
79
+ self.assertEqual(stats.entry_count, 2)
80
+ self.assertIsNotNone(stats.loaded_at)
81
+
82
+ def test_search_hits_by_keyword(self):
83
+ hits = self.client.search("退款", top_k=3)
84
+ self.assertTrue(len(hits) >= 1)
85
+ self.assertEqual(hits[0].entry.id, "kb_refund")
86
+ # score should be > 0
87
+ self.assertGreater(hits[0].score, 0.0)
88
+
89
+ def test_search_returns_empty_for_blank_query(self):
90
+ self.assertEqual(self.client.search(""), [])
91
+ self.assertEqual(self.client.search(" "), [])
92
+
93
+ def test_upsert_persists_to_disk(self):
94
+ new_entry = FaqEntry(
95
+ id="kb_invoice",
96
+ question="如何开发票?",
97
+ answer="下单时勾选需要发票",
98
+ keywords=["发票"],
99
+ )
100
+ self.client.upsert(new_entry)
101
+ self.assertEqual(len(self.client.list_all()), 3)
102
+
103
+ # 真正落盘了:重读文件
104
+ on_disk = json.loads(self.data_path.read_text(encoding="utf-8"))
105
+ ids = {e["id"] for e in on_disk}
106
+ self.assertIn("kb_invoice", ids)
107
+
108
+ def test_upsert_updates_existing(self):
109
+ updated = FaqEntry(
110
+ id="kb_refund",
111
+ question="退款政策(已更新)",
112
+ answer="支持 14 天无理由退款",
113
+ keywords=["退款"],
114
+ )
115
+ self.client.upsert(updated)
116
+ items = self.client.list_all()
117
+ self.assertEqual(len(items), 2) # 总数不变
118
+ kept = next(e for e in items if e.id == "kb_refund")
119
+ self.assertIn("已更新", kept.question)
120
+
121
+ def test_delete_removes_entry(self):
122
+ ok = self.client.delete("kb_ship")
123
+ self.assertTrue(ok)
124
+ self.assertEqual(len(self.client.list_all()), 1)
125
+ # Non-existent id returns False
126
+ self.assertFalse(self.client.delete("nonexistent"))
127
+
128
+
129
+ class MockKbAdapterTests(unittest.TestCase):
130
+
131
+ def test_seed_five_demo_entries(self):
132
+ c = MockKbClient(seed_demo_data=True)
133
+ items = c.list_all()
134
+ self.assertGreaterEqual(len(items), 5)
135
+ ids = {e.id for e in items}
136
+ for required in ("demo_refund", "demo_logistics", "demo_invoice"):
137
+ self.assertIn(required, ids)
138
+
139
+ def test_search_hits_chinese_keyword(self):
140
+ c = MockKbClient(seed_demo_data=True)
141
+ hits = c.search("物流")
142
+ self.assertTrue(any(h.entry.id == "demo_logistics" for h in hits))
143
+
144
+ def test_seed_disable(self):
145
+ c = MockKbClient(seed_demo_data=False)
146
+ self.assertEqual(c.list_all(), [])
147
+
148
+
149
+ class DefaultRestKbAdapterSecurityTests(unittest.TestCase):
150
+
151
+ def test_https_public_allowed(self):
152
+ c = DefaultRestKbClient(base_url="https://kb.example.com")
153
+ self.assertTrue(c._base.startswith("https://"))
154
+
155
+ def test_localhost_http_allowed(self):
156
+ c = DefaultRestKbClient(base_url="http://127.0.0.1:9090")
157
+ self.assertIn("127.0.0.1", c._base)
158
+
159
+ def test_private_network_denied(self):
160
+ for url in ("https://10.1.1.1", "https://192.168.1.5"):
161
+ with self.subTest(url=url):
162
+ with self.assertRaises(ValueError):
163
+ DefaultRestKbClient(base_url=url)
164
+
165
+ def test_search_calls_post(self):
166
+ fake_resp = MagicMock()
167
+ fake_resp.status_code = 200
168
+ fake_resp.json.return_value = {
169
+ "hits": [
170
+ {
171
+ "entry": {
172
+ "id": "1",
173
+ "question": "退款?",
174
+ "answer": "7 天无理由",
175
+ "keywords": ["退款"],
176
+ },
177
+ "score": 0.85,
178
+ }
179
+ ]
180
+ }
181
+ c = DefaultRestKbClient(base_url="https://kb.example.com", token="tok")
182
+ with patch.object(c._session, "post", return_value=fake_resp) as p:
183
+ hits = c.search("退款", top_k=3)
184
+
185
+ self.assertEqual(len(hits), 1)
186
+ self.assertEqual(hits[0].entry.id, "1")
187
+ # Request URL path
188
+ args, kwargs = p.call_args
189
+ self.assertTrue(args[0].endswith("/faq/search"))
190
+ self.assertEqual(kwargs["json"]["query"], "退款")
191
+ self.assertEqual(kwargs["json"]["top_k"], 3)
192
+
193
+
194
+ if __name__ == "__main__":
195
+ unittest.main()
@@ -0,0 +1,95 @@
1
+ """Manifest parsing / topological order / circular dependency / version conflict / unknown injection point tests."""
2
+ import os
3
+ import sys
4
+ import unittest
5
+ from pathlib import Path
6
+
7
+ _ROOT = Path(__file__).resolve().parent.parent
8
+ sys.path.insert(0, str(_ROOT))
9
+
10
+ from scripts.lib import manifest_resolver as mr
11
+
12
+
13
+ def _mf(name, deps=(), inj=(), ext=(), typ="capability", version="1.0.0"):
14
+ return mr.Manifest(
15
+ name=name, version=version, type=typ,
16
+ dependencies=[mr.DependencySpec(d if isinstance(d, str) else d[0],
17
+ d[1] if isinstance(d, tuple) else "*") for d in deps],
18
+ injection_points=list(inj),
19
+ extensions=list(ext),
20
+ )
21
+
22
+
23
+ class ManifestResolverTests(unittest.TestCase):
24
+
25
+ def setUp(self):
26
+ self.skeleton = _mf(
27
+ "conversation-core", typ="skeleton",
28
+ inj=[
29
+ {"id": "agent.before_start", "target": "src/agent.py", "position": "before:start_agent"},
30
+ {"id": "agent.after_start", "target": "src/agent.py", "position": "after:start_agent"},
31
+ ],
32
+ )
33
+
34
+ def test_topological_order_with_skeleton_first(self):
35
+ a = _mf("cap-a", deps=["conversation-core"])
36
+ b = _mf("cap-b", deps=["conversation-core", "cap-a"])
37
+ graph = mr.resolve([a, self.skeleton, b])
38
+ self.assertEqual(graph.skeleton.name, "conversation-core")
39
+ self.assertEqual(graph.order[0], "conversation-core")
40
+ # b must come after a
41
+ self.assertGreater(graph.order.index("cap-b"), graph.order.index("cap-a"))
42
+
43
+ def test_circular_dependency_detected(self):
44
+ a = _mf("cap-a", deps=["cap-b", "conversation-core"])
45
+ b = _mf("cap-b", deps=["cap-a", "conversation-core"])
46
+ with self.assertRaises(mr.CircularDependencyError):
47
+ mr.resolve([self.skeleton, a, b])
48
+
49
+ def test_unknown_injection_point_rejected(self):
50
+ bad = _mf(
51
+ "cap-x",
52
+ deps=["conversation-core"],
53
+ ext=[{"inject_at": "no.such.point"}],
54
+ )
55
+ with self.assertRaises(mr.UnknownInjectionPointError):
56
+ mr.resolve([self.skeleton, bad])
57
+
58
+ def test_known_injection_point_accepted(self):
59
+ ok = _mf(
60
+ "cap-y",
61
+ deps=["conversation-core"],
62
+ ext=[{"inject_at": "agent.before_start"}],
63
+ )
64
+ graph = mr.resolve([self.skeleton, ok])
65
+ self.assertIn("cap-y", graph.order)
66
+
67
+ def test_version_conflict_blocked(self):
68
+ strict = _mf("cap-z", deps=[("conversation-core", ">=2.0.0")])
69
+ with self.assertRaises(mr.VersionConflictError):
70
+ mr.resolve([self.skeleton, strict])
71
+
72
+ def test_semver_caret_compatibility(self):
73
+ self.assertTrue(mr.satisfies("1.4.2", "^1.0.0"))
74
+ self.assertFalse(mr.satisfies("2.0.0", "^1.0.0"))
75
+ self.assertTrue(mr.satisfies("1.4.0", ">=1.0.0,<2.0.0"))
76
+
77
+ def test_skeleton_uniqueness(self):
78
+ s2 = _mf("another-skeleton", typ="skeleton")
79
+ with self.assertRaises(mr.ManifestError):
80
+ mr.resolve([self.skeleton, s2])
81
+
82
+ def test_real_project_capabilities_resolve(self):
83
+ """The 6 capabilities in the real repo should all resolve correctly."""
84
+ manifests = mr.discover_manifests(_ROOT / "capabilities")
85
+ self.assertGreaterEqual(len(manifests), 6)
86
+ graph = mr.resolve(manifests)
87
+ self.assertEqual(graph.order[0], "conversation-core")
88
+ for n in ("knowledge-base", "tool-calling", "human-handoff",
89
+ "session-summary", "digital-human"):
90
+ self.assertIn(n, graph.order)
91
+ self.assertGreater(graph.order.index(n), graph.order.index("conversation-core"))
92
+
93
+
94
+ if __name__ == "__main__":
95
+ unittest.main()
@@ -0,0 +1,175 @@
1
+ """Phase 3 Stage 6: scenarios/* recipe static validation + Path B Jinja template rendering tests.
2
+
3
+ Coverage targets:
4
+ - Path A: scenarios/customer-service/recipe.yaml has valid structure and key fields present
5
+ - Path B: output-templates/recipe.yaml.j2 rendered with 4 typical Q1~Q4 answer sets produces valid YAML with correct field linkage
6
+ - ui_overlay referenced subdirectories widget-floating / admin-board all physically exist
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import sys
11
+ import unittest
12
+ from pathlib import Path
13
+
14
+ import yaml
15
+
16
+ _ROOT = Path(__file__).resolve().parent.parent
17
+ sys.path.insert(0, str(_ROOT))
18
+
19
+ _SCEN = _ROOT / "scenarios"
20
+ _RECIPE_A = _SCEN / "customer-service" / "recipe.yaml"
21
+ _TEMPLATE = _SCEN / "custom-builder" / "output-templates" / "recipe.yaml.j2"
22
+
23
+
24
+ class CustomerServiceRecipeTests(unittest.TestCase):
25
+ """Path A default recipe static structure validation."""
26
+
27
+ def setUp(self):
28
+ self.assertTrue(_RECIPE_A.exists(), f"recipe.yaml not found: {_RECIPE_A}")
29
+ self.recipe = yaml.safe_load(_RECIPE_A.read_text(encoding="utf-8"))
30
+
31
+ def test_top_level_kind_and_api_version(self):
32
+ self.assertEqual(self.recipe.get("kind"), "Recipe")
33
+ self.assertEqual(self.recipe.get("apiVersion"), "ai-customer-service/v1")
34
+
35
+ def test_install_capabilities_contain_kb_and_handoff(self):
36
+ installs = self.recipe["capabilities"]["install"]
37
+ names = {c["name"] for c in installs}
38
+ self.assertIn("knowledge-base", names)
39
+ self.assertIn("human-handoff", names)
40
+ # adapter field must be explicitly given (out-of-box experience relies on mock / local_queue)
41
+ for c in installs:
42
+ self.assertIn("adapter", c)
43
+ self.assertIn("env", c)
44
+
45
+ def test_excluded_capability_is_digital_human(self):
46
+ excluded = self.recipe["capabilities"].get("excluded") or []
47
+ names = {c["name"] for c in excluded}
48
+ self.assertIn("digital-human", names)
49
+
50
+ def test_ui_overlay_layers_reference_real_dirs(self):
51
+ layers = self.recipe["ui_overlay"]["layers"]
52
+ ui_root = _SCEN / "customer-service" / "ui"
53
+ for layer in layers:
54
+ sub = ui_root / layer["source"]
55
+ self.assertTrue(sub.is_dir(), f"missing UI overlay source dir: {sub}")
56
+
57
+ def test_post_assembly_health_check_url(self):
58
+ hc = self.recipe["post_assembly"]["health_check"]
59
+ self.assertEqual(hc["url"], "http://localhost:3000/api/v1/health")
60
+
61
+ def test_design_locks_dark_no_emoji(self):
62
+ d = self.recipe["design"]
63
+ self.assertEqual(d["theme"], "dark")
64
+ self.assertFalse(d["emoji_in_ui"])
65
+
66
+
67
+ # ---------------------------------------------------------------------------
68
+ # Path B: Jinja2 template rendering (falls back to substring checks if Jinja2 unavailable)
69
+ # ---------------------------------------------------------------------------
70
+ try:
71
+ import jinja2 # type: ignore
72
+ _HAS_JINJA = True
73
+ except ImportError: # pragma: no cover
74
+ _HAS_JINJA = False
75
+
76
+
77
+ class CustomBuilderTemplateStructureTests(unittest.TestCase):
78
+ """The key Jinja branch literals must exist in the template (does not depend on Jinja2 engine)."""
79
+
80
+ def setUp(self):
81
+ self.assertTrue(_TEMPLATE.exists(), f"template not found: {_TEMPLATE}")
82
+ self.text = _TEMPLATE.read_text(encoding="utf-8")
83
+
84
+ def test_io_modality_branches_present(self):
85
+ for modality in ("text_only", "text_with_tts", "omni", "voice_only"):
86
+ self.assertIn(modality, self.text)
87
+
88
+ def test_ui_form_branches_present(self):
89
+ for form in ("floating", "fullscreen", "headless"):
90
+ self.assertIn(form, self.text)
91
+
92
+ def test_capability_branches_present(self):
93
+ for cap in (
94
+ "knowledge-base",
95
+ "human-handoff",
96
+ "tool-calling",
97
+ "session-summary",
98
+ ):
99
+ self.assertIn(cap, self.text)
100
+
101
+ def test_excludes_digital_human(self):
102
+ self.assertIn("digital-human", self.text)
103
+ self.assertIn("excluded", self.text)
104
+
105
+
106
+ @unittest.skipUnless(_HAS_JINJA, "jinja2 not installed; skip render assertions")
107
+ class CustomBuilderTemplateRenderTests(unittest.TestCase):
108
+ """Jinja2 渲染后的 YAML 应可被 yaml.safe_load 解析,且关键字段按 Q 答联动。"""
109
+
110
+ def _render(self, **ctx) -> str:
111
+ env = jinja2.Environment( # type: ignore[name-defined]
112
+ keep_trailing_newline=True,
113
+ trim_blocks=False,
114
+ lstrip_blocks=False,
115
+ )
116
+ tpl = env.from_string(_TEMPLATE.read_text(encoding="utf-8"))
117
+ return tpl.render(**ctx)
118
+
119
+ def test_render_text_with_tts_floating_kb_only(self):
120
+ out = self._render(
121
+ business_desc="我们是一家咖啡品牌的电商客服",
122
+ business_name="ACME",
123
+ io_modality="text_with_tts",
124
+ ui_form="floating",
125
+ extra_capabilities=["knowledge-base"],
126
+ )
127
+ data = yaml.safe_load(out)
128
+ self.assertEqual(data["apiVersion"], "ai-customer-service/v1")
129
+ # Q2 linkage
130
+ self.assertFalse(data["runtime_modality"]["voice_input"])
131
+ self.assertTrue(data["runtime_modality"]["voice_output"])
132
+ # Q3 联动
133
+ self.assertTrue(data["ui"]["overlay_required"])
134
+ # human-handoff not selected → admin-board layer not present
135
+ layer_sources = [l["source"] for l in data["ui"]["ui_overlay"]["layers"]]
136
+ self.assertIn("widget-floating", layer_sources)
137
+ self.assertNotIn("admin-board", layer_sources)
138
+ # Q4: only knowledge-base installed
139
+ installed_names = {c["name"] for c in data["capabilities"]["install"]}
140
+ self.assertEqual(installed_names, {"knowledge-base"})
141
+
142
+ def test_render_omni_fullscreen_with_handoff(self):
143
+ out = self._render(
144
+ business_desc="车企售后客服",
145
+ business_name="XYZ",
146
+ io_modality="omni",
147
+ ui_form="fullscreen",
148
+ extra_capabilities=["knowledge-base", "human-handoff"],
149
+ )
150
+ data = yaml.safe_load(out)
151
+ self.assertTrue(data["runtime_modality"]["voice_input"])
152
+ self.assertTrue(data["runtime_modality"]["text_input"])
153
+ # human-handoff selected → admin-board layer should exist
154
+ layer_sources = [l["source"] for l in data["ui"]["ui_overlay"]["layers"]]
155
+ self.assertIn("admin-board", layer_sources)
156
+ installed_names = {c["name"] for c in data["capabilities"]["install"]}
157
+ self.assertEqual(installed_names, {"knowledge-base", "human-handoff"})
158
+
159
+ def test_render_headless_no_overlay(self):
160
+ out = self._render(
161
+ business_desc="纯 API 后端客服",
162
+ business_name="BackOnly",
163
+ io_modality="text_only",
164
+ ui_form="headless",
165
+ extra_capabilities=[],
166
+ )
167
+ data = yaml.safe_load(out)
168
+ self.assertFalse(data["ui"]["overlay_required"])
169
+ self.assertIsNone(data["ui"]["ui_overlay"])
170
+ # User selected no capabilities → install list is empty
171
+ self.assertEqual(data["capabilities"]["install"], [])
172
+
173
+
174
+ if __name__ == "__main__":
175
+ unittest.main()