@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,209 @@
1
+ """DefaultRestKbClient — call external FAQ / search API per business_contract default contract.
2
+
3
+ Corresponding contracts:
4
+ - POST /faq/search faq.search
5
+ - GET /faq faq.list
6
+ - POST /faq faq.upsert
7
+ - DELETE /faq/{entry_id} faq.delete
8
+
9
+ Environment variables:
10
+ - KB_REST_BASE_URL FAQ service base URL
11
+ - KB_REST_TOKEN Bearer Token (optional)
12
+ - KB_REST_TIMEOUT_MS Timeout (default 5000)
13
+
14
+ Security: consistent with human-handoff default_rest — only https / localhost; reject private networks.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import logging
19
+ import os
20
+ import re
21
+ from typing import List, Optional
22
+ from urllib.parse import urlparse
23
+
24
+ try:
25
+ import requests # type: ignore
26
+ except ImportError: # pragma: no cover
27
+ requests = None # type: ignore
28
+
29
+ from ..core.models import FaqEntry, KbStats, SearchHit
30
+ from ..ports.kb_client import KnowledgeBaseClient
31
+
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ _PRIVATE_PATTERNS = [
37
+ re.compile(r"^9\."),
38
+ re.compile(r"^10\."),
39
+ re.compile(r"^11\."),
40
+ re.compile(r"^21\."),
41
+ re.compile(r"^30\."),
42
+ re.compile(r"^169\.254\."),
43
+ re.compile(r"^172\.(1[6-9]|2[0-9]|3[01])\."),
44
+ re.compile(r"^192\.168\."),
45
+ ]
46
+
47
+
48
+ def _is_localhost(host: str) -> bool:
49
+ return host in {"localhost", "127.0.0.1", "::1"}
50
+
51
+
52
+ def _is_private(host: str) -> bool:
53
+ return any(p.match(host) for p in _PRIVATE_PATTERNS)
54
+
55
+
56
+ def _validate_base_url(url: str) -> str:
57
+ parsed = urlparse(url)
58
+ if parsed.scheme not in {"http", "https"}:
59
+ raise ValueError(f"unsupported scheme: {parsed.scheme}")
60
+ host = parsed.hostname or ""
61
+ if not host:
62
+ raise ValueError("empty host in KB_REST_BASE_URL")
63
+ if parsed.scheme == "http" and not _is_localhost(host):
64
+ raise ValueError(
65
+ "non-HTTPS KB_REST_BASE_URL only allowed for localhost"
66
+ )
67
+ if _is_private(host):
68
+ raise ValueError(
69
+ f"access to private network host '{host}' is denied"
70
+ )
71
+ return url.rstrip("/")
72
+
73
+
74
+ class DefaultRestKbClient(KnowledgeBaseClient):
75
+ """Call external FAQ service per default REST contract."""
76
+
77
+ def __init__(
78
+ self,
79
+ *,
80
+ base_url: str,
81
+ token: Optional[str] = None,
82
+ timeout_ms: int = 5000,
83
+ ) -> None:
84
+ if requests is None:
85
+ raise RuntimeError(
86
+ "requests library is required for DefaultRestKbClient"
87
+ )
88
+ self._base = _validate_base_url(base_url)
89
+ self._token = token
90
+ self._timeout = max(0.5, timeout_ms / 1000.0)
91
+ self._session = requests.Session()
92
+
93
+ # ------------------------------------------------------------------
94
+ def search(
95
+ self,
96
+ query: str,
97
+ *,
98
+ top_k: Optional[int] = None,
99
+ min_score: Optional[float] = None,
100
+ ) -> List[SearchHit]:
101
+ if not query or not query.strip():
102
+ return []
103
+ payload: dict = {"query": query}
104
+ if top_k is not None:
105
+ payload["top_k"] = int(top_k)
106
+ if min_score is not None:
107
+ payload["min_score"] = float(min_score)
108
+ data = self._post("/faq/search", payload)
109
+ items = data if isinstance(data, list) else data.get("hits", [])
110
+ hits: List[SearchHit] = []
111
+ for it in items or []:
112
+ entry = it.get("entry") if isinstance(it, dict) else None
113
+ if entry is None and isinstance(it, dict) and "question" in it:
114
+ entry = it
115
+ score = float(it.get("score", 0.0))
116
+ else:
117
+ score = float(it.get("score", 0.0)) if isinstance(it, dict) else 0.0
118
+ if not entry:
119
+ continue
120
+ hits.append(
121
+ SearchHit(
122
+ entry=FaqEntry.from_dict({**entry, "source": "remote_api"}),
123
+ score=score,
124
+ )
125
+ )
126
+ return hits
127
+
128
+ def list_all(self) -> List[FaqEntry]:
129
+ data = self._get("/faq")
130
+ items = data if isinstance(data, list) else data.get("items", [])
131
+ return [FaqEntry.from_dict({**it, "source": "remote_api"}) for it in items]
132
+
133
+ def upsert(self, entry: FaqEntry) -> FaqEntry:
134
+ if not entry.id or not entry.question:
135
+ raise ValueError("id and question are required")
136
+ data = self._post("/faq", entry.to_dict())
137
+ return FaqEntry.from_dict({**data, "source": "remote_api"})
138
+
139
+ def delete(self, entry_id: str) -> bool:
140
+ url = self._base + f"/faq/{entry_id}"
141
+ resp = self._session.delete(
142
+ url, headers=self._headers(), timeout=self._timeout
143
+ )
144
+ if resp.status_code == 404:
145
+ return False
146
+ if resp.status_code >= 400:
147
+ raise RuntimeError(
148
+ f"remote kb service returned HTTP {resp.status_code}"
149
+ )
150
+ return True
151
+
152
+ def stats(self) -> KbStats:
153
+ try:
154
+ items = self.list_all()
155
+ return KbStats(
156
+ backend="remote_api",
157
+ entry_count=len(items),
158
+ data_source=self._base,
159
+ )
160
+ except Exception: # noqa: BLE001
161
+ return KbStats(backend="remote_api", entry_count=-1, data_source=self._base)
162
+
163
+ # ------------------------------------------------------------------
164
+ def _headers(self) -> dict:
165
+ h = {"Content-Type": "application/json"}
166
+ if self._token:
167
+ h["Authorization"] = f"Bearer {self._token}"
168
+ return h
169
+
170
+ def _get(self, path: str):
171
+ resp = self._session.get(
172
+ self._base + path, headers=self._headers(), timeout=self._timeout
173
+ )
174
+ return self._handle(resp)
175
+
176
+ def _post(self, path: str, payload: dict):
177
+ resp = self._session.post(
178
+ self._base + path,
179
+ json=payload,
180
+ headers=self._headers(),
181
+ timeout=self._timeout,
182
+ )
183
+ return self._handle(resp)
184
+
185
+ @staticmethod
186
+ def _handle(resp):
187
+ if resp.status_code >= 400:
188
+ raise RuntimeError(
189
+ f"remote kb service returned HTTP {resp.status_code}"
190
+ )
191
+ try:
192
+ data = resp.json()
193
+ except ValueError as exc:
194
+ raise RuntimeError("remote kb service returned non-JSON") from exc
195
+ if isinstance(data, dict) and "data" in data and isinstance(data["data"], (dict, list)):
196
+ return data["data"]
197
+ return data
198
+
199
+
200
+ # ---------------------------------------------------------------------------
201
+ def from_env() -> Optional[DefaultRestKbClient]:
202
+ base = os.getenv("KB_REST_BASE_URL")
203
+ if not base:
204
+ return None
205
+ return DefaultRestKbClient(
206
+ base_url=base,
207
+ token=os.getenv("KB_REST_TOKEN"),
208
+ timeout_ms=int(os.getenv("KB_REST_TIMEOUT_MS", "5000")),
209
+ )
@@ -0,0 +1,86 @@
1
+ """adapter factory: selects KnowledgeBaseClient implementation based on environment variable.
2
+
3
+ Environment variable `KB_ADAPTER`:
4
+ local_json Default local JSON file search (production-ready, zero dependencies)
5
+ mock Built-in demo FAQ (for Recipe video recording)
6
+ default_rest Call remote FAQ service per business_contract default contract
7
+ user_custom User integration wizard generated implementation
8
+
9
+ When not set or invalid, fall back to local_json (keeps behavior compatibility with Phase 2).
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import logging
14
+ import os
15
+ from typing import Optional
16
+
17
+ from ..ports.kb_client import KnowledgeBaseClient
18
+
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ _VALID = ("local_json", "mock", "default_rest", "user_custom")
24
+
25
+
26
+ def _build(name: str) -> Optional[KnowledgeBaseClient]:
27
+ if name == "local_json":
28
+ from .local_json import from_env as build_local
29
+ return build_local()
30
+ if name == "mock":
31
+ from .mock import from_env as build_mock
32
+ return build_mock()
33
+ if name == "default_rest":
34
+ from .default_rest import from_env as build_rest
35
+ c = build_rest()
36
+ if c is None:
37
+ logger.warning(
38
+ "KB_ADAPTER=default_rest but KB_REST_BASE_URL is empty; "
39
+ "falling back to local_json"
40
+ )
41
+ return c
42
+ if name == "user_custom":
43
+ try:
44
+ from .user_custom import from_env as build_custom # type: ignore
45
+ except ImportError:
46
+ logger.warning(
47
+ "KB_ADAPTER=user_custom but src/adapters/user_custom.py is missing; "
48
+ "run scripts/contract-adapt.py knowledge-base to generate it"
49
+ )
50
+ return None
51
+ return build_custom()
52
+ return None
53
+
54
+
55
+ def build_default() -> KnowledgeBaseClient:
56
+ name = (os.getenv("KB_ADAPTER") or "local_json").strip().lower()
57
+ if name not in _VALID:
58
+ logger.warning("KB_ADAPTER=%s is not recognised; using local_json", name)
59
+ name = "local_json"
60
+ client = _build(name)
61
+ if client is None:
62
+ from .local_json import from_env as build_local
63
+ client = build_local()
64
+ return client
65
+
66
+
67
+ # ---------------------------------------------------------------------------
68
+ _singleton: Optional[KnowledgeBaseClient] = None
69
+
70
+
71
+ def get_client() -> KnowledgeBaseClient:
72
+ global _singleton
73
+ if _singleton is None:
74
+ _singleton = build_default()
75
+ return _singleton
76
+
77
+
78
+ def set_client(client: KnowledgeBaseClient) -> None:
79
+ """For testing only: inject a custom client."""
80
+ global _singleton
81
+ _singleton = client
82
+
83
+
84
+ def reset_client() -> None:
85
+ global _singleton
86
+ _singleton = None
@@ -0,0 +1,172 @@
1
+ """LocalJsonKbClient — default local implementation.
2
+
3
+ Zero external dependencies. Reads FAQ from JSON file + TF-IDF scoring. Migrated from the original retriever.py implementation as the "default out-of-the-box" version.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import json
8
+ import os
9
+ import threading
10
+ import time
11
+ from pathlib import Path
12
+ from typing import Dict, List, Optional
13
+
14
+ from ..core.models import FaqEntry, KbStats, SearchHit
15
+ from ..core.scoring import build_df, tfidf_score, tokenize
16
+ from ..ports.kb_client import KnowledgeBaseClient
17
+
18
+
19
+ class LocalJsonKbClient(KnowledgeBaseClient):
20
+ """FAQ retriever based on local JSON file."""
21
+
22
+ def __init__(
23
+ self,
24
+ data_file: Optional[str | Path] = None,
25
+ *,
26
+ min_score: float = 0.1,
27
+ top_k: int = 3,
28
+ ) -> None:
29
+ self._lock = threading.RLock()
30
+ self._entries: List[FaqEntry] = []
31
+ self._df: Dict[str, int] = {}
32
+ self._min_score = float(min_score)
33
+ self._top_k = int(top_k)
34
+ self._data_file: Optional[Path] = (
35
+ Path(data_file) if data_file else None
36
+ )
37
+ self._loaded_at: Optional[float] = None
38
+ if self._data_file and self._data_file.exists():
39
+ self.reload()
40
+
41
+ # ------------------------------------------------------------------
42
+ # KnowledgeBaseClient required implementations
43
+ # ------------------------------------------------------------------
44
+ def search(
45
+ self,
46
+ query: str,
47
+ *,
48
+ top_k: Optional[int] = None,
49
+ min_score: Optional[float] = None,
50
+ ) -> List[SearchHit]:
51
+ if not query or not query.strip():
52
+ return []
53
+ k = top_k or self._top_k
54
+ threshold = float(min_score) if min_score is not None else self._min_score
55
+ q_tokens = tokenize(query)
56
+ if not q_tokens:
57
+ return []
58
+ with self._lock:
59
+ n = max(1, len(self._entries))
60
+ hits: List[SearchHit] = []
61
+ for entry in self._entries:
62
+ score = tfidf_score(entry, q_tokens, df=self._df, n_docs=n)
63
+ if score >= threshold:
64
+ hits.append(SearchHit(entry=entry, score=score))
65
+ hits.sort(key=lambda h: h.score, reverse=True)
66
+ return hits[:k]
67
+
68
+ def list_all(self) -> List[FaqEntry]:
69
+ with self._lock:
70
+ return [
71
+ FaqEntry(
72
+ id=e.id,
73
+ question=e.question,
74
+ answer=e.answer,
75
+ keywords=list(e.keywords),
76
+ source=e.source or "local_json",
77
+ )
78
+ for e in self._entries
79
+ ]
80
+
81
+ def upsert(self, entry: FaqEntry) -> FaqEntry:
82
+ if not entry.id or not entry.question:
83
+ raise ValueError("id and question are required")
84
+ with self._lock:
85
+ for i, e in enumerate(self._entries):
86
+ if e.id == entry.id:
87
+ self._entries[i] = entry
88
+ self._rebuild_df()
89
+ self._persist()
90
+ return entry
91
+ self._entries.append(entry)
92
+ self._rebuild_df()
93
+ self._persist()
94
+ return entry
95
+
96
+ def delete(self, entry_id: str) -> bool:
97
+ with self._lock:
98
+ before = len(self._entries)
99
+ self._entries = [e for e in self._entries if e.id != entry_id]
100
+ removed = before != len(self._entries)
101
+ if removed:
102
+ self._rebuild_df()
103
+ self._persist()
104
+ return removed
105
+
106
+ def stats(self) -> KbStats:
107
+ with self._lock:
108
+ return KbStats(
109
+ backend="local_json",
110
+ entry_count=len(self._entries),
111
+ loaded_at=self._loaded_at,
112
+ data_source=str(self._data_file) if self._data_file else None,
113
+ )
114
+
115
+ def reload(self) -> int:
116
+ if not self._data_file or not self._data_file.exists():
117
+ return 0
118
+ raw = json.loads(self._data_file.read_text(encoding="utf-8"))
119
+ with self._lock:
120
+ self._entries = [FaqEntry.from_dict(item) for item in raw]
121
+ for e in self._entries:
122
+ e.source = e.source or "local_json"
123
+ self._rebuild_df()
124
+ self._loaded_at = time.time()
125
+ return len(self._entries)
126
+
127
+ # ------------------------------------------------------------------
128
+ @property
129
+ def data_file(self) -> Optional[Path]:
130
+ return self._data_file
131
+
132
+ @property
133
+ def min_score(self) -> float:
134
+ return self._min_score
135
+
136
+ @property
137
+ def top_k(self) -> int:
138
+ return self._top_k
139
+
140
+ # ------------------------------------------------------------------
141
+ # Internal
142
+ # ------------------------------------------------------------------
143
+ def _rebuild_df(self) -> None:
144
+ self._df = build_df(self._entries)
145
+
146
+ def _persist(self) -> None:
147
+ if not self._data_file:
148
+ return
149
+ # Ensure directory exists
150
+ self._data_file.parent.mkdir(parents=True, exist_ok=True)
151
+ tmp = self._data_file.with_suffix(self._data_file.suffix + ".tmp")
152
+ tmp.write_text(
153
+ json.dumps(
154
+ [e.to_dict() for e in self._entries],
155
+ ensure_ascii=False,
156
+ indent=2,
157
+ ),
158
+ encoding="utf-8",
159
+ )
160
+ os.replace(tmp, self._data_file)
161
+
162
+
163
+ # ---------------------------------------------------------------------------
164
+ # Factory
165
+ # ---------------------------------------------------------------------------
166
+ def from_env() -> LocalJsonKbClient:
167
+ default_file = Path(__file__).resolve().parent.parent.parent / "data" / "faq.json"
168
+ return LocalJsonKbClient(
169
+ data_file=os.getenv("KB_DATA_FILE", str(default_file)),
170
+ min_score=float(os.getenv("KB_MIN_SCORE", "0.1")),
171
+ top_k=int(os.getenv("KB_TOP_K", "3")),
172
+ )
@@ -0,0 +1,91 @@
1
+ """MockKbClient — mock implementation for Recipe demo recording.
2
+
3
+ Inherits LocalJsonKbClient; seeds embedded demo FAQ into memory on construction (no disk file dependency).
4
+
5
+ Applicable scenarios:
6
+ - User's first Recipe launch works out of the box even before data/faq.json has been filled in
7
+ - Stable data for video demos, won't break due to external file changes
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ from typing import List
13
+
14
+ from ..core.models import FaqEntry
15
+ from .local_json import LocalJsonKbClient
16
+
17
+
18
+ _DEMO_FAQ: List[dict] = [
19
+ {
20
+ "id": "demo_refund",
21
+ "question": "How do I request a refund?",
22
+ "answer": "Go to My Orders, pick the item, tap Request refund and fill in the reason. Once approved, the amount returns to your original payment within 1-3 business days.",
23
+ "keywords": ["refund", "money back", "return", "cancel order"],
24
+ "source": "demo_seed",
25
+ },
26
+ {
27
+ "id": "demo_logistics",
28
+ "question": "How long does shipping take?",
29
+ "answer": "Standard items ship within 48 hours. Major cities receive in 3-5 days; remote areas 5-7 days. You can track shipments in real time under My Orders.",
30
+ "keywords": ["shipping", "delivery", "tracking", "where is my package", "logistics"],
31
+ "source": "demo_seed",
32
+ },
33
+ {
34
+ "id": "demo_invoice",
35
+ "question": "How can I get an invoice?",
36
+ "answer": "Tick \"Need invoice\" at checkout and fill in your billing details. For existing orders, open the order in My Orders and tap Request invoice. E-invoices are emailed within 24 hours.",
37
+ "keywords": ["invoice", "receipt", "billing", "tax"],
38
+ "source": "demo_seed",
39
+ },
40
+ {
41
+ "id": "demo_size",
42
+ "question": "What if the size doesn't fit?",
43
+ "answer": "We offer 7-day free size exchange — return shipping is on us. Open My Orders, tap Exchange size and follow the instructions. If your size is out of stock you can request a refund and re-order.",
44
+ "keywords": ["size", "exchange", "doesn't fit", "wrong size"],
45
+ "source": "demo_seed",
46
+ },
47
+ {
48
+ "id": "demo_after_sale",
49
+ "question": "What is the warranty period?",
50
+ "answer": "All products carry a 1-year warranty. Electronics may be replaced within 30 days; after 30 days the manufacturer's warranty policy applies. Provide your order ID and a description of the issue and we'll open a ticket for you.",
51
+ "keywords": ["warranty", "after-sale", "repair", "broken", "defective"],
52
+ "source": "demo_seed",
53
+ },
54
+ ]
55
+
56
+
57
+ class MockKbClient(LocalJsonKbClient):
58
+ """Mock implementation for demos."""
59
+
60
+ is_mock = True
61
+
62
+ def __init__(
63
+ self,
64
+ *,
65
+ min_score: float = 0.05, # More lenient threshold for demos
66
+ top_k: int = 3,
67
+ seed_demo_data: bool = True,
68
+ ) -> None:
69
+ # No data_file specified — data is entirely memory-resident
70
+ super().__init__(
71
+ data_file=None,
72
+ min_score=min_score,
73
+ top_k=top_k,
74
+ )
75
+ if seed_demo_data:
76
+ self._seed()
77
+
78
+ def _seed(self) -> None:
79
+ for raw in _DEMO_FAQ:
80
+ entry = FaqEntry.from_dict(raw)
81
+ self._entries.append(entry)
82
+ self._rebuild_df()
83
+
84
+
85
+ # ---------------------------------------------------------------------------
86
+ def from_env() -> MockKbClient:
87
+ return MockKbClient(
88
+ min_score=float(os.getenv("KB_MIN_SCORE", "0.05")),
89
+ top_k=int(os.getenv("KB_TOP_K", "3")),
90
+ seed_demo_data=os.getenv("KB_MOCK_SEED", "1") not in ("0", "false", "False"),
91
+ )
@@ -0,0 +1,12 @@
1
+ """knowledge-base core module."""
2
+ from .models import FaqEntry, KbStats, SearchHit
3
+ from .service import KbService, get_default_service, reset_default_service
4
+
5
+ __all__ = [
6
+ "FaqEntry",
7
+ "KbService",
8
+ "KbStats",
9
+ "SearchHit",
10
+ "get_default_service",
11
+ "reset_default_service",
12
+ ]
@@ -0,0 +1,77 @@
1
+ """knowledge-base core models.
2
+
3
+ Defines unified domain models:
4
+ - FaqEntry Knowledge entry (id / question / answer / keywords / source)
5
+ - SearchHit Search hit (with score)
6
+ - KbStats Knowledge base statistics (entry count / data source type / load time)
7
+
8
+ All adapters must use this module's data structures as transport objects.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass, field
13
+ from typing import List, Optional
14
+
15
+
16
+ @dataclass
17
+ class FaqEntry:
18
+ """A single FAQ knowledge entry."""
19
+
20
+ id: str
21
+ question: str
22
+ answer: str
23
+ keywords: List[str] = field(default_factory=list)
24
+ # Optional: annotate entry source (local_json / remote_api / user_uploaded etc.), useful for dashboard display
25
+ source: Optional[str] = None
26
+
27
+ def to_dict(self) -> dict:
28
+ return {
29
+ "id": self.id,
30
+ "question": self.question,
31
+ "answer": self.answer,
32
+ "keywords": list(self.keywords),
33
+ **({"source": self.source} if self.source else {}),
34
+ }
35
+
36
+ @classmethod
37
+ def from_dict(cls, raw: dict) -> "FaqEntry":
38
+ return cls(
39
+ id=str(raw.get("id") or raw.get("question", ""))[:64] or "auto",
40
+ question=str(raw.get("question", "")).strip(),
41
+ answer=str(raw.get("answer", "")).strip(),
42
+ keywords=[
43
+ str(k).strip()
44
+ for k in (raw.get("keywords") or [])
45
+ if str(k).strip()
46
+ ],
47
+ source=raw.get("source"),
48
+ )
49
+
50
+
51
+ @dataclass
52
+ class SearchHit:
53
+ """Search hit."""
54
+
55
+ entry: FaqEntry
56
+ score: float
57
+
58
+ def to_dict(self) -> dict:
59
+ return {"entry": self.entry.to_dict(), "score": round(float(self.score), 4)}
60
+
61
+
62
+ @dataclass
63
+ class KbStats:
64
+ """Knowledge base statistics (dashboard use)."""
65
+
66
+ backend: str # "local_json" / "remote_api" / "mock" / "user_custom"
67
+ entry_count: int
68
+ loaded_at: Optional[float] = None
69
+ data_source: Optional[str] = None
70
+
71
+ def to_dict(self) -> dict:
72
+ return {
73
+ "backend": self.backend,
74
+ "entry_count": self.entry_count,
75
+ "loaded_at": self.loaded_at,
76
+ "data_source": self.data_source,
77
+ }