@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,347 @@
1
+ """Execute / plan code injection per manifest injection point descriptions.
2
+
3
+ The position field uses three unified formats (P2 standardized spec):
4
+ before:<function_name> Insert before the specified function definition
5
+ after:<function_name> Insert after the specified function definition
6
+ replace:<function_name> Replace the specified function
7
+
8
+ For cross-language compatibility, matching uses relaxed "line-level + function keyword" heuristics:
9
+ Python: def func_name(...) / class func_name(
10
+ JS/TS: function func_name(... ) / func_name = (...) =>
11
+ Java: \\bfunc_name\\s*\\(
12
+
13
+ The skeleton conversation-core injection points are currently only declared in .py source,
14
+ so this module's default implementation is Python-focused. Injection for other languages
15
+ only returns "suggested patches" for the Agent to apply.
16
+
17
+ API:
18
+ plan(injection_points, extensions, source_root) -> List[InjectionPlan]
19
+ apply_plans(plans, dry_run=True) -> List[ApplyResult]
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import re
24
+ from dataclasses import dataclass, field
25
+ from pathlib import Path
26
+ from typing import Dict, List, Optional
27
+
28
+
29
+ # ---------------------------------------------------------------------------
30
+ # 数据模型
31
+ # ---------------------------------------------------------------------------
32
+ @dataclass
33
+ class InjectionPoint:
34
+ id: str
35
+ target: str # 相对于骨架根的源文件路径
36
+ position: str # before:xxx / after:xxx / replace:xxx
37
+ description: str = ""
38
+
39
+
40
+ @dataclass
41
+ class Extension:
42
+ inject_at: str # 引用 InjectionPoint.id
43
+ code_template: str = "" # 模板路径,相对能力包根
44
+ inline_code: str = "" # 直接以字符串形式提供的代码
45
+ capability: str = "" # 来源能力包名(拼装日志使用)
46
+
47
+
48
+ @dataclass
49
+ class InjectionPlan:
50
+ point: InjectionPoint
51
+ extension: Extension
52
+ target_abs_path: Path
53
+ op: str # before | after | replace
54
+ anchor: str # 函数 / 类名
55
+ code: str # 真正注入的代码片段
56
+ valid: bool = True
57
+ error: str = ""
58
+
59
+
60
+ @dataclass
61
+ class ApplyResult:
62
+ plan: InjectionPlan
63
+ applied: bool
64
+ dry_run: bool
65
+ diff_preview: str = ""
66
+ error: str = ""
67
+
68
+
69
+ # ---------------------------------------------------------------------------
70
+ # 解析
71
+ # ---------------------------------------------------------------------------
72
+ _POSITION_RE = re.compile(r"^(before|after|replace):(.+)$")
73
+
74
+
75
+ def _parse_position(position: str) -> Optional[tuple]:
76
+ m = _POSITION_RE.match(position.strip())
77
+ if not m:
78
+ return None
79
+ return m.group(1), m.group(2).strip()
80
+
81
+
82
+ def _load_template(capability_root: Path, ext: Extension) -> str:
83
+ if ext.inline_code:
84
+ return ext.inline_code
85
+ if ext.code_template:
86
+ p = capability_root / ext.code_template
87
+ if p.exists():
88
+ return p.read_text(encoding="utf-8")
89
+ return ""
90
+
91
+
92
+ def plan(
93
+ skeleton_root: Path,
94
+ injection_points: List[Dict],
95
+ capabilities: List[tuple],
96
+ ) -> List[InjectionPlan]:
97
+ """构造注入计划。
98
+
99
+ Parameters
100
+ ----------
101
+ skeleton_root
102
+ conversation-core 根目录(用于解析 target 相对路径)。
103
+ injection_points
104
+ 骨架 manifest 声明的 injection_points 列表(dict 形式)。
105
+ capabilities
106
+ list of (capability_root: Path, extensions: List[dict])
107
+ """
108
+ points: Dict[str, InjectionPoint] = {}
109
+ for raw in injection_points:
110
+ if not raw.get("id") or not raw.get("position") or not raw.get("target"):
111
+ continue
112
+ points[raw["id"]] = InjectionPoint(
113
+ id=raw["id"],
114
+ target=raw["target"],
115
+ position=raw["position"],
116
+ description=raw.get("description", ""),
117
+ )
118
+
119
+ plans: List[InjectionPlan] = []
120
+ for cap_root, exts in capabilities:
121
+ for raw in exts or []:
122
+ inject_id = raw.get("inject_at")
123
+ if not inject_id or inject_id not in points:
124
+ # 引用未知点:放一条无效计划,由 resolver 阶段已拦截,这里兜底
125
+ plans.append(
126
+ InjectionPlan(
127
+ point=InjectionPoint(id=inject_id or "?", target="", position=""),
128
+ extension=Extension(
129
+ inject_at=inject_id or "",
130
+ code_template=raw.get("code_template", ""),
131
+ inline_code=raw.get("inline_code", ""),
132
+ capability=str(cap_root.name),
133
+ ),
134
+ target_abs_path=Path("/dev/null"),
135
+ op="",
136
+ anchor="",
137
+ code="",
138
+ valid=False,
139
+ error=f"unknown injection point: {inject_id}",
140
+ )
141
+ )
142
+ continue
143
+ pt = points[inject_id]
144
+ parsed = _parse_position(pt.position)
145
+ if not parsed:
146
+ plans.append(
147
+ InjectionPlan(
148
+ point=pt,
149
+ extension=Extension(inject_at=inject_id, capability=cap_root.name),
150
+ target_abs_path=skeleton_root / pt.target,
151
+ op="",
152
+ anchor="",
153
+ code="",
154
+ valid=False,
155
+ error=f"invalid position format: {pt.position!r}",
156
+ )
157
+ )
158
+ continue
159
+ op, anchor = parsed
160
+ ext = Extension(
161
+ inject_at=inject_id,
162
+ code_template=raw.get("code_template", ""),
163
+ inline_code=raw.get("inline_code", ""),
164
+ capability=cap_root.name,
165
+ )
166
+ code = _load_template(cap_root, ext)
167
+ plans.append(
168
+ InjectionPlan(
169
+ point=pt,
170
+ extension=ext,
171
+ target_abs_path=skeleton_root / pt.target,
172
+ op=op,
173
+ anchor=anchor,
174
+ code=code,
175
+ valid=True,
176
+ )
177
+ )
178
+ return plans
179
+
180
+
181
+ # ---------------------------------------------------------------------------
182
+ # 应用:仅支持 Python 源码定位(骨架现状)
183
+ # ---------------------------------------------------------------------------
184
+ _PY_DEF_RE_TPL = (
185
+ r"^(?P<indent>[ \t]*)(async\s+def|def|class)\s+{name}\b[^\n]*:?\s*$"
186
+ )
187
+
188
+
189
+ def _locate_py_anchor(src: str, anchor: str):
190
+ """First try strict def/class anchor; fallback to 'line-level generic anchor'.
191
+
192
+ Generic anchor applies to plain call statements like ``app.include_router``, ``return foo()``;
193
+ before/after means 'insert before/after that line'; replace means 'replace the entire line'.
194
+ """
195
+ pattern = re.compile(_PY_DEF_RE_TPL.format(name=re.escape(anchor)), re.MULTILINE)
196
+ m = pattern.search(src)
197
+ if m:
198
+ return m, "block"
199
+ # Fallback: capture the entire line containing the anchor
200
+ loose = re.compile(
201
+ r"^(?P<indent>[ \t]*)[^\n]*\b" + re.escape(anchor) + r"\b[^\n]*$",
202
+ re.MULTILINE,
203
+ )
204
+ m2 = loose.search(src)
205
+ if m2:
206
+ return m2, "line"
207
+ return None, None
208
+
209
+
210
+ def _apply_python(plan_item: InjectionPlan, src: str):
211
+ """Execute a single injection on Python source; always returns ``(ApplyResult, new_src_or_none)``.
212
+
213
+ Idempotency Protection
214
+ ----------------------
215
+ If ``plan_item.code`` already exists in the source (checked by ``# [<capability>]`` comment marker,
216
+ or full snippet substring match), this injection is skipped and returns ``applied=False`` with
217
+ ``error="already_injected"``, preventing duplicate inserts on repeated ``add-capability`` runs.
218
+ """
219
+ target = plan_item.target_abs_path
220
+ # Idempotency check: use first comment line in inline_code (containing [cap-name]) as fingerprint.
221
+ # Cannot solely rely on `# [{capability}]` because a single capability may have multiple
222
+ # injection points (e.g., agent.before_push_text + agent.after_start) sharing the same
223
+ # marker, which would cause the second injection to be falsely skipped.
224
+ snippet_stripped = (plan_item.code or "").strip()
225
+ fingerprint = next(
226
+ (ln.strip() for ln in (plan_item.code or "").splitlines() if ln.strip()),
227
+ "",
228
+ )
229
+ cap_marker_loose = f"# [{plan_item.extension.capability}]"
230
+ already = False
231
+ if snippet_stripped and snippet_stripped in src:
232
+ already = True
233
+ elif fingerprint and fingerprint in src:
234
+ already = True
235
+ elif cap_marker_loose in plan_item.code and snippet_stripped in src:
236
+ already = True
237
+ if already:
238
+ return (
239
+ ApplyResult(
240
+ plan=plan_item, applied=False, dry_run=True,
241
+ diff_preview=f"skip (already injected): {plan_item.extension.capability} -> {target.name}",
242
+ error="already_injected",
243
+ ),
244
+ None,
245
+ )
246
+ m, kind = _locate_py_anchor(src, plan_item.anchor)
247
+ if not m:
248
+ return (
249
+ ApplyResult(
250
+ plan=plan_item, applied=False, dry_run=True,
251
+ error=f"anchor '{plan_item.anchor}' not found in {target.name}",
252
+ ),
253
+ None,
254
+ )
255
+ indent = m.group("indent") or ""
256
+ snippet = "\n".join(
257
+ f"{indent}{line}" if line.strip() else line
258
+ for line in plan_item.code.splitlines()
259
+ )
260
+ if kind == "block":
261
+ if plan_item.op == "before":
262
+ new_src = src[: m.start()] + snippet + "\n" + src[m.start():]
263
+ elif plan_item.op == "after":
264
+ rest = src[m.end():]
265
+ end_in_rest = _find_block_end(rest, indent)
266
+ end_pos = m.end() + end_in_rest
267
+ new_src = src[:end_pos] + "\n" + snippet + "\n" + src[end_pos:]
268
+ else: # replace 整个 def/class 块
269
+ rest = src[m.end():]
270
+ end_in_rest = _find_block_end(rest, indent)
271
+ end_pos = m.end() + end_in_rest
272
+ new_src = src[: m.start()] + snippet + "\n" + src[end_pos:]
273
+ else: # 行级 anchor:before/after 在该行前/后插,replace 替换整行
274
+ line_start = m.start()
275
+ line_end = m.end()
276
+ # 行级匹配未含尾随换行,定位到 \n 位置以保证拼接正确
277
+ if line_end < len(src) and src[line_end] == "\n":
278
+ line_end += 1
279
+ if plan_item.op == "before":
280
+ new_src = src[:line_start] + snippet + "\n" + src[line_start:]
281
+ elif plan_item.op == "after":
282
+ new_src = src[:line_end] + snippet + "\n" + src[line_end:]
283
+ else: # replace 整行
284
+ new_src = src[:line_start] + snippet + "\n" + src[line_end:]
285
+ diff = f"{plan_item.op}({kind}) {plan_item.anchor}\n{snippet[:200]}"
286
+ return (
287
+ ApplyResult(plan=plan_item, applied=True, dry_run=True, diff_preview=diff),
288
+ new_src,
289
+ )
290
+
291
+
292
+ def _find_block_end(rest: str, indent: str) -> int:
293
+ """在函数体之后定位下一个同级/更低缩进的非空行起点。"""
294
+ pos = 0
295
+ for line in rest.splitlines(keepends=True):
296
+ if line.strip() == "":
297
+ pos += len(line)
298
+ continue
299
+ line_indent = len(line) - len(line.lstrip(" \t"))
300
+ if line_indent <= len(indent) and line.strip():
301
+ return pos
302
+ pos += len(line)
303
+ return len(rest)
304
+
305
+
306
+ def apply_plans(
307
+ plans: List[InjectionPlan],
308
+ *,
309
+ dry_run: bool = True,
310
+ ) -> List[ApplyResult]:
311
+ """Execute injection plans. dry_run=True only produces diff previews without writing to disk."""
312
+ results: List[ApplyResult] = []
313
+ grouped: Dict[Path, List[InjectionPlan]] = {}
314
+ for p in plans:
315
+ if not p.valid:
316
+ results.append(ApplyResult(plan=p, applied=False, dry_run=dry_run, error=p.error))
317
+ continue
318
+ grouped.setdefault(p.target_abs_path, []).append(p)
319
+
320
+ for path, group in grouped.items():
321
+ if not path.exists():
322
+ for p in group:
323
+ results.append(ApplyResult(
324
+ plan=p, applied=False, dry_run=dry_run,
325
+ error=f"target not found: {path}",
326
+ ))
327
+ continue
328
+ # 仅支持 .py 文件原地注入;其他语言由 Agent 自行处理
329
+ if path.suffix != ".py":
330
+ for p in group:
331
+ results.append(ApplyResult(
332
+ plan=p, applied=False, dry_run=dry_run,
333
+ diff_preview=f"non-python target {path.name}: hand off to adapter",
334
+ ))
335
+ continue
336
+ current = path.read_text(encoding="utf-8")
337
+ # 倒序处理,避免位置偏移
338
+ sorted_group = sorted(group, key=lambda x: x.anchor)
339
+ for p in sorted_group:
340
+ res, new_src = _apply_python(p, current)
341
+ if res.applied and new_src is not None:
342
+ current = new_src
343
+ res.dry_run = dry_run
344
+ results.append(res)
345
+ if not dry_run:
346
+ path.write_text(current, encoding="utf-8")
347
+ return results
@@ -0,0 +1,288 @@
1
+ """Capability manifest loading / dependency graph / topological sort / circular dependency detection.
2
+
3
+ Corresponding risks:
4
+ - P1 manifest dependency resolution boundary not covered: use topological sort to detect circular dependencies;
5
+ block installation on semver major version incompatibility.
6
+
7
+ Conventions:
8
+ - Each capability directory must contain ``manifest.yaml``.
9
+ - ``type`` field is ``skeleton`` | ``capability``; skeleton packages must exist and be unique.
10
+ - ``dependencies`` entries are of the form ``{name: conversation-core, version: ">=1.0.0,<2.0.0"}``.
11
+ - ``injection_points`` and ``extensions[*].inject_at`` are aligned via the ``id`` field.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import re
16
+ from dataclasses import dataclass, field
17
+ from pathlib import Path
18
+ from typing import Dict, Iterable, List, Optional, Sequence, Tuple
19
+
20
+ import yaml
21
+
22
+
23
+ # ---------------------------------------------------------------------------
24
+ # Exceptions
25
+ # ---------------------------------------------------------------------------
26
+ class ManifestError(RuntimeError):
27
+ """Manifest load or validation failure."""
28
+
29
+
30
+ class CircularDependencyError(ManifestError):
31
+ """Circular dependency detected."""
32
+
33
+
34
+ class VersionConflictError(ManifestError):
35
+ """Semver major version incompatible."""
36
+
37
+
38
+ class UnknownInjectionPointError(ManifestError):
39
+ """Capability references an injection point not declared by the skeleton."""
40
+
41
+
42
+ # ---------------------------------------------------------------------------
43
+ # Data models
44
+ # ---------------------------------------------------------------------------
45
+ @dataclass(frozen=True)
46
+ class DependencySpec:
47
+ name: str
48
+ version: str = "*" # semver range, e.g., ">=1.0.0,<2.0.0"
49
+
50
+
51
+ @dataclass
52
+ class Manifest:
53
+ name: str
54
+ version: str
55
+ type: str # skeleton | capability
56
+ description: str = ""
57
+ dependencies: List[DependencySpec] = field(default_factory=list)
58
+ injection_points: List[Dict] = field(default_factory=list)
59
+ extensions: List[Dict] = field(default_factory=list)
60
+ config: Dict = field(default_factory=dict)
61
+ integration: Dict = field(default_factory=dict)
62
+ security: Dict = field(default_factory=dict)
63
+ endpoints: List[Dict] = field(default_factory=list)
64
+ path: Optional[Path] = None # filled in at load time
65
+
66
+ @property
67
+ def is_skeleton(self) -> bool:
68
+ return self.type == "skeleton"
69
+
70
+
71
+ # ---------------------------------------------------------------------------
72
+ # Loading
73
+ # ---------------------------------------------------------------------------
74
+ def _coerce_dep(raw) -> DependencySpec:
75
+ if isinstance(raw, str):
76
+ return DependencySpec(name=raw)
77
+ if isinstance(raw, dict):
78
+ if "name" not in raw:
79
+ raise ManifestError(f"dependency missing 'name': {raw}")
80
+ return DependencySpec(name=raw["name"], version=str(raw.get("version", "*")))
81
+ raise ManifestError(f"unsupported dependency form: {raw!r}")
82
+
83
+
84
+ def load_manifest(manifest_path: Path) -> Manifest:
85
+ if not manifest_path.exists():
86
+ raise ManifestError(f"manifest not found: {manifest_path}")
87
+ with manifest_path.open("r", encoding="utf-8") as f:
88
+ raw = yaml.safe_load(f) or {}
89
+ if not isinstance(raw, dict):
90
+ raise ManifestError(f"{manifest_path}: top-level must be mapping")
91
+ for key in ("name", "version", "type"):
92
+ if key not in raw:
93
+ raise ManifestError(f"{manifest_path}: missing required field '{key}'")
94
+ if raw["type"] not in ("skeleton", "capability"):
95
+ raise ManifestError(
96
+ f"{manifest_path}: invalid type '{raw['type']}', expected skeleton|capability"
97
+ )
98
+ deps = [_coerce_dep(d) for d in (raw.get("dependencies") or [])]
99
+ return Manifest(
100
+ name=str(raw["name"]),
101
+ version=str(raw["version"]),
102
+ type=str(raw["type"]),
103
+ description=str(raw.get("description", "")),
104
+ dependencies=deps,
105
+ injection_points=list(raw.get("injection_points") or []),
106
+ extensions=list(raw.get("extensions") or []),
107
+ config=dict(raw.get("config") or {}),
108
+ integration=dict(raw.get("integration") or {}),
109
+ security=dict(raw.get("security") or {}),
110
+ endpoints=list(raw.get("endpoints") or []),
111
+ path=manifest_path,
112
+ )
113
+
114
+
115
+ def discover_manifests(capabilities_root: Path) -> List[Manifest]:
116
+ """Scan all capability manifests under capabilities/."""
117
+ manifests: List[Manifest] = []
118
+ if not capabilities_root.exists():
119
+ return manifests
120
+ for child in sorted(capabilities_root.iterdir()):
121
+ if not child.is_dir():
122
+ continue
123
+ mf = child / "manifest.yaml"
124
+ if mf.exists():
125
+ manifests.append(load_manifest(mf))
126
+ return manifests
127
+
128
+
129
+ # ---------------------------------------------------------------------------
130
+ # Semver compatibility
131
+ # ---------------------------------------------------------------------------
132
+ _SEMVER_RE = re.compile(r"^(\d+)\.(\d+)\.(\d+)")
133
+
134
+
135
+ def _parse_version(v: str) -> Tuple[int, int, int]:
136
+ m = _SEMVER_RE.match(v.strip())
137
+ if not m:
138
+ raise ManifestError(f"invalid semver: {v!r}")
139
+ return int(m.group(1)), int(m.group(2)), int(m.group(3))
140
+
141
+
142
+ _RANGE_RE = re.compile(r"^\s*(>=|<=|>|<|=|\^|~)?\s*(\d+(?:\.\d+){0,2})\s*$")
143
+
144
+
145
+ def _check_single(actual: Tuple[int, int, int], spec: str) -> bool:
146
+ spec = spec.strip()
147
+ if spec in ("", "*"):
148
+ return True
149
+ m = _RANGE_RE.match(spec)
150
+ if not m:
151
+ # Unparseable range treated as pass-through (conservative strategy, logged by caller)
152
+ return True
153
+ op = m.group(1) or "="
154
+ parts = [int(p) for p in m.group(2).split(".")]
155
+ while len(parts) < 3:
156
+ parts.append(0)
157
+ target = (parts[0], parts[1], parts[2])
158
+ if op == "=":
159
+ return actual == target
160
+ if op == ">":
161
+ return actual > target
162
+ if op == ">=":
163
+ return actual >= target
164
+ if op == "<":
165
+ return actual < target
166
+ if op == "<=":
167
+ return actual <= target
168
+ if op == "^": # major version compatible
169
+ return actual >= target and actual[0] == target[0]
170
+ if op == "~": # minor version compatible
171
+ return actual >= target and actual[:2] == target[:2]
172
+ return True
173
+
174
+
175
+ def satisfies(actual_version: str, spec: str) -> bool:
176
+ """Check whether actual falls within spec (supports ','-joined AND relations)."""
177
+ if not spec or spec == "*":
178
+ return True
179
+ actual = _parse_version(actual_version)
180
+ return all(_check_single(actual, part) for part in spec.split(","))
181
+
182
+
183
+ # ---------------------------------------------------------------------------
184
+ # Topological sort / circular dependency detection
185
+ # ---------------------------------------------------------------------------
186
+ @dataclass
187
+ class ResolvedGraph:
188
+ order: List[str] # Topologically sorted capability names
189
+ manifests: Dict[str, Manifest]
190
+ skeleton: Manifest
191
+
192
+
193
+ def resolve(manifests: Sequence[Manifest]) -> ResolvedGraph:
194
+ """Build dependency graph, validate version compatibility, and return topological order.
195
+
196
+ Raises
197
+ ------
198
+ ManifestError
199
+ No skeleton / multiple skeletons / reference to unknown capability / version incompatible.
200
+ CircularDependencyError
201
+ Cycle detected.
202
+ """
203
+ # 1) Uniqueness + skeleton identification
204
+ by_name: Dict[str, Manifest] = {}
205
+ for m in manifests:
206
+ if m.name in by_name:
207
+ raise ManifestError(f"duplicate capability name: {m.name}")
208
+ by_name[m.name] = m
209
+ skeletons = [m for m in manifests if m.is_skeleton]
210
+ if len(skeletons) == 0:
211
+ raise ManifestError("no skeleton capability found (expected conversation-core)")
212
+ if len(skeletons) > 1:
213
+ raise ManifestError(
214
+ f"multiple skeleton capabilities found: {[s.name for s in skeletons]}"
215
+ )
216
+ skeleton = skeletons[0]
217
+
218
+ # 2) Reference + version validation
219
+ for m in manifests:
220
+ for dep in m.dependencies:
221
+ if dep.name not in by_name:
222
+ raise ManifestError(
223
+ f"{m.name} depends on unknown capability '{dep.name}'"
224
+ )
225
+ target = by_name[dep.name]
226
+ if not satisfies(target.version, dep.version):
227
+ raise VersionConflictError(
228
+ f"{m.name} requires {dep.name}@{dep.version} but found "
229
+ f"{target.version}"
230
+ )
231
+
232
+ # 3) Injection point reference validation (only validates injections into skeleton)
233
+ skeleton_ids = {p.get("id") for p in skeleton.injection_points if p.get("id")}
234
+ for m in manifests:
235
+ for ext in m.extensions:
236
+ inject_id = ext.get("inject_at")
237
+ if not inject_id:
238
+ continue
239
+ # Allow references to injection points exposed by other capabilities (prefix cap:<capability>/<injection_id>)
240
+ if ":" in inject_id:
241
+ continue
242
+ if inject_id not in skeleton_ids:
243
+ raise UnknownInjectionPointError(
244
+ f"{m.name} references unknown injection point '{inject_id}' "
245
+ f"(skeleton declares: {sorted(skeleton_ids)})"
246
+ )
247
+
248
+ # 4) Topological sort (Kahn's algorithm)
249
+ indeg: Dict[str, int] = {n: 0 for n in by_name}
250
+ adj: Dict[str, List[str]] = {n: [] for n in by_name}
251
+ for m in manifests:
252
+ for dep in m.dependencies:
253
+ adj[dep.name].append(m.name)
254
+ indeg[m.name] += 1
255
+ queue = sorted([n for n, d in indeg.items() if d == 0])
256
+ order: List[str] = []
257
+ while queue:
258
+ n = queue.pop(0)
259
+ order.append(n)
260
+ for nxt in sorted(adj[n]):
261
+ indeg[nxt] -= 1
262
+ if indeg[nxt] == 0:
263
+ queue.append(nxt)
264
+ if len(order) != len(by_name):
265
+ remaining = [n for n, d in indeg.items() if d > 0]
266
+ raise CircularDependencyError(
267
+ f"circular dependency detected among: {remaining}"
268
+ )
269
+
270
+ # Skeleton must come first
271
+ if skeleton.name not in order or order[0] != skeleton.name:
272
+ order = [skeleton.name] + [n for n in order if n != skeleton.name]
273
+ return ResolvedGraph(order=order, manifests=by_name, skeleton=skeleton)
274
+
275
+
276
+ # ---------------------------------------------------------------------------
277
+ # Utility: export dependency graph as DOT
278
+ # ---------------------------------------------------------------------------
279
+ def to_dot(graph: ResolvedGraph) -> str:
280
+ lines = ["digraph capabilities {", " rankdir=LR;"]
281
+ for name, mf in graph.manifests.items():
282
+ shape = "box" if mf.is_skeleton else "ellipse"
283
+ lines.append(f' "{name}" [shape={shape}, label="{name}\\n{mf.version}"];')
284
+ for name, mf in graph.manifests.items():
285
+ for dep in mf.dependencies:
286
+ lines.append(f' "{name}" -> "{dep.name}";')
287
+ lines.append("}")
288
+ return "\n".join(lines)