@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.
- package/.cursor/rules/main.mdc +12 -0
- package/AGENTS.md +14 -104
- package/CLAUDE.md +15 -127
- package/CODEBUDDY.md +32 -118
- package/README.md +7 -5
- package/README.zh.md +7 -5
- package/bin/cli.js +133 -41
- package/hooks/__pycache__/cursor-adapter.cpython-313.pyc +0 -0
- package/hooks/cursor-adapter.py +45 -18
- package/hooks/hooks-cursor.json +5 -18
- package/hooks/hooks.json +6 -31
- package/knowledge-base/conference/web/index.yaml +143 -0
- package/knowledge-base/platform-slice-template.md +1041 -133
- package/knowledge-base/products.yaml +76 -0
- package/knowledge-base/scenario-spec.md +1316 -115
- package/knowledge-base/scenarios/conference/base/general-conference.md +22 -2
- package/knowledge-base/scenarios/conference/base/webinar-conference.md +14 -0
- package/knowledge-base/scenarios/conference/medical/1v1-video-consultation.md +19 -0
- package/knowledge-base/scenarios/conference/medical/medical-multidoctor-consultation.md +13 -0
- package/knowledge-base/scenarios/live/entertainment-live-room.md +12 -0
- package/knowledge-base/slice-spec.md +2377 -299
- package/knowledge-base/slices/conference/web/integration-audit.md +1 -1
- package/knowledge-base/slices/conference/web/login-auth.md +14 -1
- package/knowledge-base/tooling/aliases.yaml +92 -0
- package/knowledge-base/tooling/intent-signals.yaml +181 -0
- package/knowledge-base/tooling/symptom-keywords.yaml +21 -0
- package/package.json +1 -1
- package/skills/trtc/SKILL.md +202 -245
- package/skills/trtc/hooks/__pycache__/report_prompt.cpython-313.pyc +0 -0
- package/skills/{trtc-topic/guardrails → trtc/hooks}/gate_slice_read.py +12 -8
- package/skills/{trtc-topic/guardrails → trtc/hooks}/gate_slice_write.py +16 -12
- package/skills/{trtc-topic/guardrails → trtc/hooks}/stop_require_apply_evidence.py +22 -14
- package/skills/trtc/hooks/topic_phase_gate.py +161 -0
- package/skills/{trtc-topic → trtc}/runtime/README.md +2 -2
- package/skills/{trtc-onboarding/reference/reporting-protocol.md → trtc/runtime/REPORTING.md} +5 -5
- package/skills/{trtc-topic → trtc}/runtime/RUNTIME.md +5 -5
- package/skills/trtc/runtime/lib/__init__.py +0 -0
- package/skills/{trtc-topic → trtc}/runtime/package-lock.json +2 -2
- package/skills/{trtc-topic → trtc}/runtime/package.json +2 -2
- package/skills/{trtc-topic → trtc}/runtime/telemetry-bridge.mjs +1 -1
- package/skills/{trtc-topic/scripts → trtc/tools}/STATE-MACHINE-GUIDE.md +23 -23
- package/skills/trtc/tools/__init__.py +2 -0
- package/skills/trtc/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/query_classifier.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/reporting.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/search.cpython-313.pyc +0 -0
- package/skills/trtc/tools/__pycache__/session.cpython-313.pyc +0 -0
- package/skills/trtc/tools/apply.py +540 -0
- package/skills/trtc/tools/docs.py +712 -0
- package/skills/trtc/tools/docsbot.py +182 -0
- package/skills/trtc/tools/entry/render_ai_instructions.py +92 -0
- package/skills/trtc/tools/flow.py +1089 -0
- package/skills/{trtc-topic/scripts → trtc/tools}/init_slice_queue.py +5 -4
- package/skills/{trtc-topic/scripts → trtc/tools}/next_slice.py +5 -4
- package/skills/trtc/tools/query_classifier.py +301 -0
- package/skills/trtc/tools/reporting.py +447 -0
- package/skills/trtc/tools/search.py +817 -0
- package/skills/trtc/tools/session.py +1261 -0
- package/skills/trtc/tools/state_machine.py +690 -0
- package/skills/trtc-ai-service/README.md +195 -0
- package/skills/trtc-ai-service/README.zh-CN.md +193 -0
- package/skills/trtc-ai-service/SKILL.md +945 -0
- package/skills/trtc-ai-service/auto_adapters/README.md +40 -0
- package/skills/trtc-ai-service/auto_adapters/frontend-spa/README.md +27 -0
- package/skills/trtc-ai-service/auto_adapters/frontend-spa/angular/voice-agent.component.ts.tpl +131 -0
- package/skills/trtc-ai-service/auto_adapters/frontend-spa/manifest.yaml +57 -0
- package/skills/trtc-ai-service/auto_adapters/frontend-spa/react/VoiceAgent.tsx.tpl +142 -0
- package/skills/trtc-ai-service/auto_adapters/frontend-spa/vue/VoiceAgent.vue.tpl +121 -0
- package/skills/trtc-ai-service/auto_adapters/integration_templates/generic-backend.md +45 -0
- package/skills/trtc-ai-service/auto_adapters/integration_templates/generic-frontend.md +51 -0
- package/skills/trtc-ai-service/auto_adapters/integration_templates/generic-rest-api.md +93 -0
- package/skills/trtc-ai-service/auto_adapters/java-backend/README.md +25 -0
- package/skills/trtc-ai-service/auto_adapters/java-backend/manifest.yaml +30 -0
- package/skills/trtc-ai-service/auto_adapters/java-backend/quarkus/VoiceAgentFilter.java.tpl +64 -0
- package/skills/trtc-ai-service/auto_adapters/java-backend/springboot/VoiceAgentFilter.java.tpl +91 -0
- package/skills/trtc-ai-service/auto_adapters/manifest.yaml +43 -0
- package/skills/trtc-ai-service/auto_adapters/node-backend/README.md +25 -0
- package/skills/trtc-ai-service/auto_adapters/node-backend/express.js.tpl +40 -0
- package/skills/trtc-ai-service/auto_adapters/node-backend/fastify.js.tpl +27 -0
- package/skills/trtc-ai-service/auto_adapters/node-backend/koa.js.tpl +31 -0
- package/skills/trtc-ai-service/auto_adapters/node-backend/manifest.yaml +47 -0
- package/skills/trtc-ai-service/auto_adapters/python-backend/README.md +22 -0
- package/skills/trtc-ai-service/auto_adapters/python-backend/django.py.tpl +32 -0
- package/skills/trtc-ai-service/auto_adapters/python-backend/fastapi.py.tpl +35 -0
- package/skills/trtc-ai-service/auto_adapters/python-backend/flask.py.tpl +31 -0
- package/skills/trtc-ai-service/auto_adapters/python-backend/manifest.yaml +45 -0
- package/skills/trtc-ai-service/capabilities/__init__.py +43 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/.env.example +29 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/INTEGRATION.md +134 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/INTERFACE_ADAPT.md +111 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/QUICK_START.md +62 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/manifest.yaml +250 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/requirements.txt +6 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/__init__.py +10 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/_capability_loader.py +218 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/agent.py +231 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/credentials.py +132 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/health.py +355 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/log_filter.py +76 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/modality.py +109 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/server.py +312 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/trtc_client.py +315 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/src/usersig.py +90 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/tests/test_skeleton.py +216 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/web-demo/README.md +48 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/web-demo/app.js +415 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/web-demo/index.html +68 -0
- package/skills/trtc-ai-service/capabilities/conversation-core/web-demo/styles.css +136 -0
- package/skills/trtc-ai-service/capabilities/digital-human/README.md +31 -0
- package/skills/trtc-ai-service/capabilities/digital-human/manifest.yaml +64 -0
- package/skills/trtc-ai-service/capabilities/digital-human/src/__init__.py +2 -0
- package/skills/trtc-ai-service/capabilities/digital-human/src/router.py +43 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/INTERFACE_ADAPT.md +353 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/README.md +44 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/manifest.yaml +227 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/__init__.py +2 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/__init__.py +9 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/default_rest.py +242 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/factory.py +89 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/local_queue.py +258 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/adapters/mock.py +132 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/core/__init__.py +25 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/core/intent_detector.py +75 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/core/models.py +163 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/core/service.py +192 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/feedback_store.py +54 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/ports/__init__.py +4 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/ports/handoff_client.py +86 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/queue.py +62 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/router.py +201 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/summary_link.py +77 -0
- package/skills/trtc-ai-service/capabilities/human-handoff/src/trigger.py +25 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/INTERFACE_ADAPT.md +297 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/README.md +51 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/data/faq.json +20 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/manifest.yaml +211 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/__init__.py +8 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/__init__.py +9 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/default_rest.py +209 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/factory.py +86 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/local_json.py +172 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/adapters/mock.py +91 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/core/__init__.py +12 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/core/models.py +77 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/core/scoring.py +73 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/core/service.py +78 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/ports/__init__.py +4 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/ports/kb_client.py +61 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/retriever.py +56 -0
- package/skills/trtc-ai-service/capabilities/knowledge-base/src/router.py +85 -0
- package/skills/trtc-ai-service/capabilities/session-summary/INTERFACE_ADAPT.md +99 -0
- package/skills/trtc-ai-service/capabilities/session-summary/README.md +47 -0
- package/skills/trtc-ai-service/capabilities/session-summary/data/test_session.json +18 -0
- package/skills/trtc-ai-service/capabilities/session-summary/manifest.yaml +165 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/__init__.py +2 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/__init__.py +5 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/base.py +31 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/default_rest.py +67 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/factory.py +51 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/local_json.py +42 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/adapters/mock.py +22 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/recorder.py +210 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/router.py +93 -0
- package/skills/trtc-ai-service/capabilities/session-summary/src/summarizer.py +163 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/INTERFACE_ADAPT.md +158 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/README.md +50 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/data/tools.yaml +58 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/examples/__init__.py +1 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/examples/local_tools.py +101 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/manifest.yaml +146 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/src/__init__.py +8 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/src/dispatcher.py +54 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/src/registry.py +219 -0
- package/skills/trtc-ai-service/capabilities/tool-calling/src/router.py +50 -0
- package/skills/trtc-ai-service/references/business-contract-spec.md +263 -0
- package/skills/trtc-ai-service/scenarios/custom-builder/README.md +86 -0
- package/skills/trtc-ai-service/scenarios/custom-builder/output-templates/recipe.yaml.j2 +194 -0
- package/skills/trtc-ai-service/scenarios/custom-builder/prompts/q1-business-scenario.md +43 -0
- package/skills/trtc-ai-service/scenarios/custom-builder/prompts/q2-io-modality.md +57 -0
- package/skills/trtc-ai-service/scenarios/custom-builder/prompts/q3-ui-form.md +55 -0
- package/skills/trtc-ai-service/scenarios/custom-builder/prompts/q4-capabilities.md +78 -0
- package/skills/trtc-ai-service/scenarios/customer-service/README.md +114 -0
- package/skills/trtc-ai-service/scenarios/customer-service/recipe.yaml +154 -0
- package/skills/trtc-ai-service/scenarios/customer-service/sample-data/README.md +32 -0
- package/skills/trtc-ai-service/scenarios/customer-service/sample-data/faq-sample.json +37 -0
- package/skills/trtc-ai-service/scenarios/customer-service/system-prompt.template.md +94 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/admin-board/app.js +347 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/admin-board/index.html +125 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/admin-board/styles.css +487 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/admin-board/tokens.css +71 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/design-system/DESIGN_GUIDELINES.md +370 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/README.md +68 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/app.js +1307 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/data.js +40 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/index.html +233 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/mock-shop.json +21 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/styles.css +603 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/voice-customer-service/tokens.css +71 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/agent-link.js +323 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/app.js +458 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/index.html +109 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/styles.css +489 -0
- package/skills/trtc-ai-service/scenarios/customer-service/ui/widget-floating/tokens.css +59 -0
- package/skills/trtc-ai-service/scripts/add-capability.py +364 -0
- package/skills/trtc-ai-service/scripts/contract-adapt.py +334 -0
- package/skills/trtc-ai-service/scripts/detect-stack.py +40 -0
- package/skills/trtc-ai-service/scripts/lib/__init__.py +20 -0
- package/skills/trtc-ai-service/scripts/lib/adapter_codegen.py +509 -0
- package/skills/trtc-ai-service/scripts/lib/arbitrator.py +152 -0
- package/skills/trtc-ai-service/scripts/lib/contract_resolver.py +519 -0
- package/skills/trtc-ai-service/scripts/lib/credential_validators.py +253 -0
- package/skills/trtc-ai-service/scripts/lib/curl_parser.py +303 -0
- package/skills/trtc-ai-service/scripts/lib/degrader.py +140 -0
- package/skills/trtc-ai-service/scripts/lib/injector.py +347 -0
- package/skills/trtc-ai-service/scripts/lib/manifest_resolver.py +288 -0
- package/skills/trtc-ai-service/scripts/lib/openapi_parser.py +289 -0
- package/skills/trtc-ai-service/scripts/lib/stack_detector.py +159 -0
- package/skills/trtc-ai-service/scripts/lib/tokens_compile.py +204 -0
- package/skills/trtc-ai-service/scripts/post-install-patch.py +225 -0
- package/skills/trtc-ai-service/scripts/setup-credentials.py +393 -0
- package/skills/trtc-ai-service/scripts/verify-credentials.py +108 -0
- package/skills/trtc-ai-service/start.sh +111 -0
- package/skills/trtc-ai-service/tests/__init__.py +1 -0
- package/skills/trtc-ai-service/tests/test_arbitrator.py +64 -0
- package/skills/trtc-ai-service/tests/test_capability_overlay.py +85 -0
- package/skills/trtc-ai-service/tests/test_contract_resolver.py +190 -0
- package/skills/trtc-ai-service/tests/test_handoff_ports.py +195 -0
- package/skills/trtc-ai-service/tests/test_kb_ports.py +195 -0
- package/skills/trtc-ai-service/tests/test_manifest_resolver.py +95 -0
- package/skills/trtc-ai-service/tests/test_recipe_assembly.py +175 -0
- package/skills/trtc-ai-service/tests/test_stack_and_degrader.py +101 -0
- package/skills/trtc-ai-service/tests/test_verify_credentials.py +285 -0
- package/skills/trtc-ai-service/triggers.yaml +29 -0
- package/skills/trtc-conference/SKILL.md +324 -0
- package/skills/trtc-conference/flows/onboarding.md +205 -0
- package/skills/trtc-conference/flows/topic.md +474 -0
- package/skills/trtc-conference/flows/troubleshoot.md +85 -0
- package/skills/trtc-conference/hooks/pretooluse_require_business_decisions.py +213 -0
- package/skills/trtc-conference/playbooks/medical-quickstart.md +84 -0
- package/skills/trtc-conference/playbooks/official-roomkit.md +97 -0
- package/skills/trtc-conference/references/local-usersig/basic-info-config.ts +39 -0
- package/skills/trtc-conference/references/usersig-handling.md +134 -0
- package/skills/trtc-conference/templates/medical-consultation/src/config/lib-generate-test-usersig-es.min.d.ts +4 -0
- package/skills/trtc-conference/templates/medical-consultation/src/config/lib-generate-test-usersig-es.min.js +2 -0
- package/skills/trtc-conference/tests/__pycache__/test_conference_onboarding_contract.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-conference/tests/__pycache__/test_conference_topic_flow_contract.cpython-313-pytest-9.0.2.pyc +0 -0
- package/skills/trtc-conference/tests/test_conference_index_contract.py +43 -0
- package/skills/trtc-conference/tests/test_conference_onboarding_contract.py +103 -0
- package/skills/trtc-conference/tests/test_conference_template_contract.py +25 -0
- package/skills/trtc-conference/tests/test_conference_topic_flow_contract.py +132 -0
- package/skills/trtc-conference/tools/apply_checks.py +328 -0
- package/skills/trtc-conference/verify_lib/__init__.py +0 -0
- package/skills/{trtc-apply/guardrails/apply_lib → trtc-conference/verify_lib}/rule_parser.py +1 -1
- package/skills/trtc-docs/SKILL.md +91 -119
- package/.cursor/rules/ui-mode.mdc +0 -99
- package/ai-instructions/base.md +0 -13
- package/ai-instructions/ui-mode.md +0 -93
- package/knowledge-base/index.yaml +0 -462
- package/skills/trtc/room-builder/SKILL.md +0 -133
- package/skills/trtc/room-builder/templates/scenarios/medical-consultation/README.md +0 -108
- package/skills/trtc/room-builder/tools/render_ai_instructions.py +0 -226
- package/skills/trtc-apply/SKILL.md +0 -97
- package/skills/trtc-onboarding/SKILL.md +0 -841
- package/skills/trtc-onboarding/reference/path-a1-demo.md +0 -103
- package/skills/trtc-onboarding/reference/path-a2-integrate.md +0 -737
- package/skills/trtc-onboarding/reference/path-b-troubleshoot.md +0 -186
- package/skills/trtc-onboarding/reference/path-c-expand.md +0 -43
- package/skills/trtc-onboarding/reference/supported-matrix.md +0 -100
- package/skills/trtc-search/SKILL.md +0 -228
- package/skills/trtc-topic/SKILL.md +0 -622
- package/skills/trtc-topic/scripts/apply.py +0 -581
- package/skills/trtc-topic/scripts/lib/state_machine.py +0 -328
- package/skills/trtc-topic/tests/README.md +0 -70
- package/skills/trtc-topic/tests/conftest.py +0 -72
- package/skills/trtc-topic/tests/test_apply_cli.py +0 -480
- package/skills/trtc-topic/tests/test_end_to_end.py +0 -305
- package/skills/trtc-topic/tests/test_finalize_session.py +0 -51
- package/skills/trtc-topic/tests/test_gates.py +0 -316
- package/skills/trtc-topic/tests/test_session_resolver.py +0 -260
- package/skills/trtc-topic/tests/test_state_machine.py +0 -414
- package/skills/trtc-topic/tests/test_stop_require_apply.py +0 -99
- package/skills/trtc-topic/tests/test_topic_skill_invariants.py +0 -130
- /package/skills/{trtc-topic → trtc}/runtime/lib/platforms.py +0 -0
- /package/skills/{trtc-topic → trtc}/runtime/telemetry_collector.py +0 -0
- /package/skills/{trtc-onboarding/reference → trtc/runtime}/usersig-handling.md +0 -0
- /package/skills/{trtc-topic/scripts → trtc/tools}/finalize_session.py +0 -0
- /package/skills/{trtc-apply/guardrails/apply_lib → trtc-ai-service/capabilities/conversation-core/tests}/__init__.py +0 -0
- /package/skills/{trtc-topic → trtc-conference}/references/execution-units.yaml +0 -0
- /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
- /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
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/docs/backend-contract.zh-CN.md +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/docs/integration.zh-CN.md +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/docs/theme.zh-CN.md +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/index.html +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/package.json +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/postcss.config.js +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/App.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/ConsultationManagePanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/LanguageSwitch.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/LoadingSpinner.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalAlert.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalBusinessPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalButton.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalConfirmDialog.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalDataPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/MedicalRecordPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/components/PrescriptionPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/config/basic-info-config.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/config/runtime-config.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/env.d.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/ConsultationChatPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/ConsultationMembersPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/ConsultationTranscriptionPanel.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/ConsultationVideoStage.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/InviteDoctorDialog.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/components/KickMemberConfirmDialog.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/types.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/useConsultationChat.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/useConsultationDevices.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/useConsultationParticipants.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/useConsultationPermissions.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/features/consultation/utils.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/en-US/index.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/index.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/medicalTranslate.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/state.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/i18n/zh-CN/index.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/main.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/mock/appointments.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/mock/users.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/router/index.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/index.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/integration/appointmentService.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/integration/authService.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/integration/launchContext.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/integration/userService.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/mock/appointmentService.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/mock/authService.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/mock/userService.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/services/adapters/types.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/shared/icons.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/styles/index.css +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/styles/tailwind.css +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/styles/theme.css +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/utils/auth.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/utils/format.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/utils/navigation.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/utils/session.ts +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/DoctorConsultationView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/DoctorDashboardView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/LoginView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/PatientConsultationFinishedView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/PatientConsultationView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/PatientSelectDoctorView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/src/views/PatientWaitingView.vue +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/tsconfig.json +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/tsconfig.node.json +0 -0
- /package/skills/{trtc/room-builder/templates/scenarios → trtc-conference/templates}/medical-consultation/vite.config.ts +0 -0
- /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
- /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)
|