@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,227 @@
|
|
|
1
|
+
# human-handoff capability self-describing manifest
|
|
2
|
+
# Type: capability (optional install)
|
|
3
|
+
|
|
4
|
+
name: "human-handoff"
|
|
5
|
+
version: "1.1.0"
|
|
6
|
+
type: "capability"
|
|
7
|
+
description: "Semantic trigger for human handoff + ticket lifecycle (Phase 3 refactored to ports/adapters/core three-layer architecture)"
|
|
8
|
+
|
|
9
|
+
dependencies:
|
|
10
|
+
- name: "conversation-core"
|
|
11
|
+
version: ">=1.0.0,<2.0.0"
|
|
12
|
+
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
# Injection points (maintains full Phase 2 compatibility; trigger.py / queue.py are now facades)
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
extensions:
|
|
17
|
+
# Detect whether to trigger "handoff" before text injection; if matched, route to human channel
|
|
18
|
+
- inject_at: "agent.before_push_text"
|
|
19
|
+
inline_code: |
|
|
20
|
+
# [human-handoff] semantic trigger detection
|
|
21
|
+
from ._capability_loader import try_load_capability
|
|
22
|
+
_hh_trigger = try_load_capability("human-handoff", "src/trigger.py")
|
|
23
|
+
if _hh_trigger is not None:
|
|
24
|
+
_hand = _hh_trigger.maybe_handoff(session_id, text)
|
|
25
|
+
if _hand is not None:
|
|
26
|
+
text = _hand # Replace original text with queue/connect messaging to prevent LLM self-answering
|
|
27
|
+
# Release queue slot after agent stops
|
|
28
|
+
- inject_at: "agent.after_start"
|
|
29
|
+
inline_code: |
|
|
30
|
+
# [human-handoff] register session for handoff state machine
|
|
31
|
+
from ._capability_loader import try_load_capability
|
|
32
|
+
_hh_queue = try_load_capability("human-handoff", "src/queue.py")
|
|
33
|
+
if _hh_queue is not None:
|
|
34
|
+
_hh_queue.attach_session(session_id, info=info if 'info' in locals() else None)
|
|
35
|
+
- inject_at: "server.router_extension"
|
|
36
|
+
inline_code: |
|
|
37
|
+
# [human-handoff] mount sub-router
|
|
38
|
+
from ._capability_loader import try_load_capability as _try_load_capability
|
|
39
|
+
_hh_router_mod = _try_load_capability("human-handoff", "src/router.py")
|
|
40
|
+
if _hh_router_mod is not None and hasattr(_hh_router_mod, "router"):
|
|
41
|
+
app.include_router(
|
|
42
|
+
_hh_router_mod.router, prefix="/api/v1/handoff", tags=["human-handoff"]
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# Configuration
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
config:
|
|
49
|
+
triggers:
|
|
50
|
+
description: "Strong trigger keyword list (exact match / case-insensitive)"
|
|
51
|
+
default:
|
|
52
|
+
- "talk to agent"
|
|
53
|
+
- "human support"
|
|
54
|
+
- "real person"
|
|
55
|
+
- "talk to agent"
|
|
56
|
+
- "speak to a human"
|
|
57
|
+
intent_keywords:
|
|
58
|
+
description: "Weak trigger keywords (subtracted from negative context)"
|
|
59
|
+
default:
|
|
60
|
+
- "complain"
|
|
61
|
+
- "manager"
|
|
62
|
+
- "supervisor"
|
|
63
|
+
- "not working"
|
|
64
|
+
queue_capacity:
|
|
65
|
+
description: "Local queue capacity (effective only for local_queue / mock adapter)"
|
|
66
|
+
default: 50
|
|
67
|
+
agent_pool_size:
|
|
68
|
+
description: "Number of available agents (auto-connect when >0)"
|
|
69
|
+
default: 1
|
|
70
|
+
estimated_wait_seconds_per_slot:
|
|
71
|
+
default: 30
|
|
72
|
+
retention_seconds:
|
|
73
|
+
description: "Retention duration for completed sessions (used for summary queries)"
|
|
74
|
+
default: 600
|
|
75
|
+
adapter:
|
|
76
|
+
description: "Which HandoffClient implementation to use: local_queue | mock | default_rest | user_custom"
|
|
77
|
+
default: "local_queue"
|
|
78
|
+
env: "HH_ADAPTER"
|
|
79
|
+
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
# API endpoints
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
endpoints:
|
|
84
|
+
- method: GET
|
|
85
|
+
path: /api/v1/handoff/status
|
|
86
|
+
description: Query overall queue status
|
|
87
|
+
- method: GET
|
|
88
|
+
path: /api/v1/handoff/{session_id}
|
|
89
|
+
description: Query handoff status for a single session (legacy field format, backward compatible)
|
|
90
|
+
- method: POST
|
|
91
|
+
path: /api/v1/handoff/request
|
|
92
|
+
description: "Explicit handoff request submission (body shape: {session_id, reason?})"
|
|
93
|
+
- method: POST
|
|
94
|
+
path: /api/v1/handoff/connect
|
|
95
|
+
description: "Simulate agent connection (body shape: {session_id, agent_id})"
|
|
96
|
+
- method: POST
|
|
97
|
+
path: /api/v1/handoff/cancel
|
|
98
|
+
description: "Cancel queue (body shape: {session_id})"
|
|
99
|
+
# ----- Phase 3 new: Ticket agent dashboard endpoints -----
|
|
100
|
+
- method: GET
|
|
101
|
+
path: /api/v1/handoff/admin/tickets
|
|
102
|
+
description: "Ticket list (supports ?status=&limit= filtering)"
|
|
103
|
+
- method: GET
|
|
104
|
+
path: /api/v1/handoff/admin/tickets/{ticket_id}
|
|
105
|
+
description: "Ticket details"
|
|
106
|
+
- method: POST
|
|
107
|
+
path: /api/v1/handoff/admin/tickets/{ticket_id}/status
|
|
108
|
+
description: "Agent manually switches ticket status (pending|processing|closed|canceled|timeout)"
|
|
109
|
+
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
# Business contract (Phase 3 new; follows references/business-contract-spec.md v1.0)
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
business_contract:
|
|
114
|
+
port_class: "src.ports.handoff_client.HandoffClient"
|
|
115
|
+
default_adapter: "src.adapters.local_queue.LocalQueueHandoffClient"
|
|
116
|
+
mock_adapter: "src.adapters.mock.MockHandoffClient"
|
|
117
|
+
customization_sop: "INTERFACE_ADAPT.md"
|
|
118
|
+
external_apis:
|
|
119
|
+
- name: ticket.create
|
|
120
|
+
direction: outbound
|
|
121
|
+
method: POST
|
|
122
|
+
path: /tickets
|
|
123
|
+
description: "Create a new ticket in the ticketing system when user triggers handoff"
|
|
124
|
+
request_schema:
|
|
125
|
+
user_id: string
|
|
126
|
+
subject: string
|
|
127
|
+
description: string
|
|
128
|
+
priority: enum[low, normal, high, urgent]
|
|
129
|
+
transcript: string[]
|
|
130
|
+
response_schema:
|
|
131
|
+
ticket_id: string
|
|
132
|
+
queue_position: int
|
|
133
|
+
eta_seconds: int
|
|
134
|
+
adapter_slots:
|
|
135
|
+
- request.subject
|
|
136
|
+
- request.priority
|
|
137
|
+
- response.ticket_id
|
|
138
|
+
- response.queue_position
|
|
139
|
+
- response.eta_seconds
|
|
140
|
+
auth:
|
|
141
|
+
type: bearer
|
|
142
|
+
location: header
|
|
143
|
+
name: Authorization
|
|
144
|
+
timeout_ms: 5000
|
|
145
|
+
|
|
146
|
+
- name: ticket.status_query
|
|
147
|
+
direction: outbound
|
|
148
|
+
method: GET
|
|
149
|
+
path: /tickets/{ticket_id}
|
|
150
|
+
description: "Poll ticket status for queue progress updates"
|
|
151
|
+
request_schema:
|
|
152
|
+
ticket_id: string
|
|
153
|
+
response_schema:
|
|
154
|
+
ticket_id: string
|
|
155
|
+
status: enum[pending, processing, closed, canceled]
|
|
156
|
+
agent_id: string
|
|
157
|
+
updated_at: int
|
|
158
|
+
adapter_slots:
|
|
159
|
+
- response.status
|
|
160
|
+
- response.agent_id
|
|
161
|
+
timeout_ms: 3000
|
|
162
|
+
|
|
163
|
+
- name: ticket.cancel
|
|
164
|
+
direction: outbound
|
|
165
|
+
method: POST
|
|
166
|
+
path: /tickets/{ticket_id}/cancel
|
|
167
|
+
description: "Notify ticketing system when user cancels handoff"
|
|
168
|
+
request_schema:
|
|
169
|
+
ticket_id: string
|
|
170
|
+
reason: string
|
|
171
|
+
response_schema:
|
|
172
|
+
ticket_id: string
|
|
173
|
+
canceled: bool
|
|
174
|
+
adapter_slots:
|
|
175
|
+
- request.reason
|
|
176
|
+
timeout_ms: 3000
|
|
177
|
+
|
|
178
|
+
- name: ticket.status_callback
|
|
179
|
+
direction: inbound
|
|
180
|
+
method: POST
|
|
181
|
+
path: /api/v1/handoff/callback/ticket-status
|
|
182
|
+
description: "Callback from ticketing system to notify status changes (optional; when disabled, status_query polling is used instead)"
|
|
183
|
+
request_schema:
|
|
184
|
+
ticket_id: string
|
|
185
|
+
status: enum[pending, processing, closed, canceled]
|
|
186
|
+
agent_id: string
|
|
187
|
+
response_schema:
|
|
188
|
+
code: int
|
|
189
|
+
message: string
|
|
190
|
+
adapter_slots:
|
|
191
|
+
- request.status
|
|
192
|
+
- request.agent_id
|
|
193
|
+
|
|
194
|
+
# ---------------------------------------------------------------------------
|
|
195
|
+
# Integration
|
|
196
|
+
# ---------------------------------------------------------------------------
|
|
197
|
+
integration:
|
|
198
|
+
mode: "auto"
|
|
199
|
+
auto_adapters:
|
|
200
|
+
- tech_stack: ["react", "vue", "angular"]
|
|
201
|
+
adapter: "frontend-spa"
|
|
202
|
+
description: "Append queue status indicator component to SPA"
|
|
203
|
+
- tech_stack: ["express", "koa", "fastify", "next"]
|
|
204
|
+
adapter: "node-backend"
|
|
205
|
+
- tech_stack: ["flask", "fastapi", "django"]
|
|
206
|
+
adapter: "python-backend"
|
|
207
|
+
- tech_stack: ["spring-boot", "quarkus"]
|
|
208
|
+
adapter: "java-backend"
|
|
209
|
+
fallback:
|
|
210
|
+
guided_templates:
|
|
211
|
+
- "../../auto_adapters/integration_templates/generic-frontend.md"
|
|
212
|
+
manual_api:
|
|
213
|
+
rest_endpoint: "/api/v1/handoff"
|
|
214
|
+
|
|
215
|
+
security:
|
|
216
|
+
log_redaction:
|
|
217
|
+
enabled: true
|
|
218
|
+
patterns: ["phone", "mobile", "user_id", "credential", "authorization"]
|
|
219
|
+
network:
|
|
220
|
+
enforce_https: true # default_rest adapter enforces HTTPS for non-localhost
|
|
221
|
+
|
|
222
|
+
acceptance:
|
|
223
|
+
- "Keyword match immediately enters ticket flow, status pollable by frontend"
|
|
224
|
+
- "Connects instantly when agent pool > 0; returns estimated wait time when = 0"
|
|
225
|
+
- "Correctly releases slot when session is canceled / connected"
|
|
226
|
+
- "Switching HH_ADAPTER requires no business code changes (local_queue / mock / default_rest / user_custom)"
|
|
227
|
+
- "default_rest adapter rejects private network addresses (except localhost)"
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""DefaultRestHandoffClient — call external ticketing system per business_contract default contract.
|
|
2
|
+
|
|
3
|
+
Corresponding contracts:
|
|
4
|
+
- POST /tickets ticket.create
|
|
5
|
+
- GET /tickets/{ticket_id} ticket.status_query
|
|
6
|
+
- POST /tickets/{ticket_id}/cancel ticket.cancel
|
|
7
|
+
|
|
8
|
+
Environment variables:
|
|
9
|
+
- HH_REST_BASE_URL Ticketing system base URL (required; must not point to private network, see §Security)
|
|
10
|
+
- HH_REST_TOKEN Bearer Token (optional)
|
|
11
|
+
- HH_REST_TIMEOUT_MS Timeout (default 5000)
|
|
12
|
+
|
|
13
|
+
Security constraints (aligned with project security_rules):
|
|
14
|
+
- Only allow https:// or http://localhost / 127.0.0.1
|
|
15
|
+
- Default reject common private network ranges (9.* / 10.* / 11.* / 21.* / 30.* / 169.254.* / 172.16-31.* / 192.168.*)
|
|
16
|
+
- Log redaction auto-masks Authorization
|
|
17
|
+
|
|
18
|
+
Dependency: only requests (already in conversation-core/requirements.txt), no new deps.
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import logging
|
|
23
|
+
import os
|
|
24
|
+
import re
|
|
25
|
+
from typing import List, Optional
|
|
26
|
+
from urllib.parse import urlparse
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
import requests # type: ignore
|
|
30
|
+
except ImportError: # pragma: no cover
|
|
31
|
+
requests = None # type: ignore
|
|
32
|
+
|
|
33
|
+
from ..core.models import (
|
|
34
|
+
OverallStatus,
|
|
35
|
+
Ticket,
|
|
36
|
+
TicketStatus,
|
|
37
|
+
TicketStatusEnum,
|
|
38
|
+
now_ts,
|
|
39
|
+
)
|
|
40
|
+
from ..ports.handoff_client import HandoffClient
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
logger = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
# Security: private network and loopback check
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
_PRIVATE_PATTERNS = [
|
|
50
|
+
re.compile(r"^9\."),
|
|
51
|
+
re.compile(r"^10\."),
|
|
52
|
+
re.compile(r"^11\."),
|
|
53
|
+
re.compile(r"^21\."),
|
|
54
|
+
re.compile(r"^30\."),
|
|
55
|
+
re.compile(r"^169\.254\."),
|
|
56
|
+
re.compile(r"^172\.(1[6-9]|2[0-9]|3[01])\."),
|
|
57
|
+
re.compile(r"^192\.168\."),
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _is_localhost(host: str) -> bool:
|
|
62
|
+
return host in {"localhost", "127.0.0.1", "::1"}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _is_private(host: str) -> bool:
|
|
66
|
+
return any(p.match(host) for p in _PRIVATE_PATTERNS)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _validate_base_url(url: str) -> str:
|
|
70
|
+
parsed = urlparse(url)
|
|
71
|
+
if parsed.scheme not in {"http", "https"}:
|
|
72
|
+
raise ValueError(f"unsupported scheme: {parsed.scheme}")
|
|
73
|
+
host = parsed.hostname or ""
|
|
74
|
+
if not host:
|
|
75
|
+
raise ValueError("empty host in HH_REST_BASE_URL")
|
|
76
|
+
# Non-HTTPS only allowed for localhost
|
|
77
|
+
if parsed.scheme == "http" and not _is_localhost(host):
|
|
78
|
+
raise ValueError(
|
|
79
|
+
"non-HTTPS HH_REST_BASE_URL only allowed for localhost"
|
|
80
|
+
)
|
|
81
|
+
# Reject private network ranges (prevent SSRF)
|
|
82
|
+
if _is_private(host):
|
|
83
|
+
raise ValueError(
|
|
84
|
+
f"access to private network host '{host}' is denied; "
|
|
85
|
+
"set HH_REST_ALLOW_PRIVATE=1 to override (not recommended)"
|
|
86
|
+
)
|
|
87
|
+
return url.rstrip("/")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
# Client implementation
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
class DefaultRestHandoffClient(HandoffClient):
|
|
94
|
+
"""Call external ticketing system per default REST contract."""
|
|
95
|
+
|
|
96
|
+
def __init__(
|
|
97
|
+
self,
|
|
98
|
+
*,
|
|
99
|
+
base_url: str,
|
|
100
|
+
token: Optional[str] = None,
|
|
101
|
+
timeout_ms: int = 5000,
|
|
102
|
+
) -> None:
|
|
103
|
+
if requests is None:
|
|
104
|
+
raise RuntimeError(
|
|
105
|
+
"requests library is required for DefaultRestHandoffClient"
|
|
106
|
+
)
|
|
107
|
+
self._base = _validate_base_url(base_url)
|
|
108
|
+
self._token = token
|
|
109
|
+
self._timeout = max(0.5, timeout_ms / 1000.0)
|
|
110
|
+
self._session = requests.Session()
|
|
111
|
+
|
|
112
|
+
# ------------------------------------------------------------------
|
|
113
|
+
# HandoffClient required implementations
|
|
114
|
+
# ------------------------------------------------------------------
|
|
115
|
+
def create_ticket(
|
|
116
|
+
self,
|
|
117
|
+
*,
|
|
118
|
+
user_id: str,
|
|
119
|
+
subject: str = "",
|
|
120
|
+
description: str = "",
|
|
121
|
+
priority: str = "normal",
|
|
122
|
+
transcript: Optional[List[str]] = None,
|
|
123
|
+
) -> Ticket:
|
|
124
|
+
payload = {
|
|
125
|
+
"user_id": user_id,
|
|
126
|
+
"subject": subject,
|
|
127
|
+
"description": description,
|
|
128
|
+
"priority": priority or "normal",
|
|
129
|
+
"transcript": list(transcript or []),
|
|
130
|
+
}
|
|
131
|
+
data = self._post("/tickets", payload)
|
|
132
|
+
ticket_id = str(data.get("ticket_id") or "").strip()
|
|
133
|
+
if not ticket_id:
|
|
134
|
+
raise RuntimeError("remote ticket service did not return ticket_id")
|
|
135
|
+
return Ticket(
|
|
136
|
+
ticket_id=ticket_id,
|
|
137
|
+
user_id=user_id,
|
|
138
|
+
subject=subject,
|
|
139
|
+
description=description,
|
|
140
|
+
priority=priority or "normal",
|
|
141
|
+
status=TicketStatusEnum.PENDING.value,
|
|
142
|
+
queue_position=int(data.get("queue_position") or 0),
|
|
143
|
+
eta_seconds=int(data.get("eta_seconds") or 0),
|
|
144
|
+
transcript=list(transcript or []),
|
|
145
|
+
reason=description[:128] if description else "",
|
|
146
|
+
created_at=now_ts(),
|
|
147
|
+
updated_at=now_ts(),
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def query_status(self, ticket_id: str) -> Optional[TicketStatus]:
|
|
151
|
+
data = self._get(f"/tickets/{ticket_id}", optional=True)
|
|
152
|
+
if data is None:
|
|
153
|
+
return None
|
|
154
|
+
return TicketStatus(
|
|
155
|
+
ticket_id=str(data.get("ticket_id") or ticket_id),
|
|
156
|
+
status=str(data.get("status") or TicketStatusEnum.PENDING.value),
|
|
157
|
+
agent_id=data.get("agent_id"),
|
|
158
|
+
updated_at=float(data.get("updated_at") or 0.0) or None,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
def cancel_ticket(self, ticket_id: str, reason: str = "") -> Optional[Ticket]:
|
|
162
|
+
data = self._post(
|
|
163
|
+
f"/tickets/{ticket_id}/cancel",
|
|
164
|
+
{"ticket_id": ticket_id, "reason": reason},
|
|
165
|
+
optional=True,
|
|
166
|
+
)
|
|
167
|
+
if data is None:
|
|
168
|
+
return None
|
|
169
|
+
return Ticket(
|
|
170
|
+
ticket_id=ticket_id,
|
|
171
|
+
user_id="", # remote may not return user_id
|
|
172
|
+
status=TicketStatusEnum.CANCELED.value,
|
|
173
|
+
reason=reason,
|
|
174
|
+
updated_at=now_ts(),
|
|
175
|
+
closed_at=now_ts(),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def overall_status(self) -> OverallStatus:
|
|
179
|
+
# Remote backend does not expose overall status; return placeholder (for /api/v1/handoff/status compatibility)
|
|
180
|
+
return OverallStatus(
|
|
181
|
+
agent_pool_size=-1,
|
|
182
|
+
available_agents=-1,
|
|
183
|
+
waiting=-1,
|
|
184
|
+
connected=-1,
|
|
185
|
+
capacity=-1,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# ------------------------------------------------------------------
|
|
189
|
+
# Internal:HTTP
|
|
190
|
+
# ------------------------------------------------------------------
|
|
191
|
+
def _headers(self) -> dict:
|
|
192
|
+
h = {"Content-Type": "application/json"}
|
|
193
|
+
if self._token:
|
|
194
|
+
h["Authorization"] = f"Bearer {self._token}"
|
|
195
|
+
return h
|
|
196
|
+
|
|
197
|
+
def _get(self, path: str, *, optional: bool = False):
|
|
198
|
+
url = self._base + path
|
|
199
|
+
resp = self._session.get(url, headers=self._headers(), timeout=self._timeout)
|
|
200
|
+
return self._handle(resp, optional=optional)
|
|
201
|
+
|
|
202
|
+
def _post(self, path: str, payload: dict, *, optional: bool = False):
|
|
203
|
+
url = self._base + path
|
|
204
|
+
resp = self._session.post(
|
|
205
|
+
url,
|
|
206
|
+
json=payload,
|
|
207
|
+
headers=self._headers(),
|
|
208
|
+
timeout=self._timeout,
|
|
209
|
+
)
|
|
210
|
+
return self._handle(resp, optional=optional)
|
|
211
|
+
|
|
212
|
+
@staticmethod
|
|
213
|
+
def _handle(resp, *, optional: bool):
|
|
214
|
+
if resp.status_code == 404 and optional:
|
|
215
|
+
return None
|
|
216
|
+
if resp.status_code >= 400:
|
|
217
|
+
# Do not print response body (may contain sensitive info)
|
|
218
|
+
raise RuntimeError(
|
|
219
|
+
f"remote ticket service returned HTTP {resp.status_code}"
|
|
220
|
+
)
|
|
221
|
+
try:
|
|
222
|
+
data = resp.json()
|
|
223
|
+
except ValueError as exc:
|
|
224
|
+
raise RuntimeError("remote ticket service returned non-JSON") from exc
|
|
225
|
+
# Response may be {"data": {...}} or flat {...}
|
|
226
|
+
if isinstance(data, dict) and "data" in data and isinstance(data["data"], dict):
|
|
227
|
+
return data["data"]
|
|
228
|
+
return data
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# ---------------------------------------------------------------------------
|
|
232
|
+
# Factory
|
|
233
|
+
# ---------------------------------------------------------------------------
|
|
234
|
+
def from_env() -> Optional[DefaultRestHandoffClient]:
|
|
235
|
+
base = os.getenv("HH_REST_BASE_URL")
|
|
236
|
+
if not base:
|
|
237
|
+
return None
|
|
238
|
+
return DefaultRestHandoffClient(
|
|
239
|
+
base_url=base,
|
|
240
|
+
token=os.getenv("HH_REST_TOKEN"),
|
|
241
|
+
timeout_ms=int(os.getenv("HH_REST_TIMEOUT_MS", "5000")),
|
|
242
|
+
)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""adapter factory: selects HandoffClient implementation based on environment variable.
|
|
2
|
+
|
|
3
|
+
Environment variable `HH_ADAPTER`:
|
|
4
|
+
local_queue Default local in-memory queue (production-ready, zero dependencies)
|
|
5
|
+
mock Demo data (includes several preset tickets for video recording)
|
|
6
|
+
default_rest Call remote ticketing system per business_contract default contract
|
|
7
|
+
user_custom User integration wizard (contract-adapt.py) generated implementation
|
|
8
|
+
|
|
9
|
+
When not set or invalid, fall back to local_queue (keeps behavior compatibility with Phase 2).
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import os
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
from ..ports.handoff_client import HandoffClient
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
_VALID = ("local_queue", "mock", "default_rest", "user_custom")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _build(name: str) -> Optional[HandoffClient]:
|
|
27
|
+
if name == "local_queue":
|
|
28
|
+
from .local_queue import from_env as build_local
|
|
29
|
+
return build_local()
|
|
30
|
+
if name == "mock":
|
|
31
|
+
from .mock import from_env as build_mock
|
|
32
|
+
return build_mock()
|
|
33
|
+
if name == "default_rest":
|
|
34
|
+
from .default_rest import from_env as build_rest
|
|
35
|
+
c = build_rest()
|
|
36
|
+
if c is None:
|
|
37
|
+
logger.warning(
|
|
38
|
+
"HH_ADAPTER=default_rest but HH_REST_BASE_URL is empty; "
|
|
39
|
+
"falling back to local_queue"
|
|
40
|
+
)
|
|
41
|
+
return c
|
|
42
|
+
if name == "user_custom":
|
|
43
|
+
try:
|
|
44
|
+
from .user_custom import from_env as build_custom # type: ignore
|
|
45
|
+
except ImportError:
|
|
46
|
+
logger.warning(
|
|
47
|
+
"HH_ADAPTER=user_custom but src/adapters/user_custom.py is missing; "
|
|
48
|
+
"run scripts/contract-adapt.py human-handoff to generate it"
|
|
49
|
+
)
|
|
50
|
+
return None
|
|
51
|
+
return build_custom()
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def build_default() -> HandoffClient:
|
|
56
|
+
"""Build default client from environment variables; invalid config falls back to local_queue."""
|
|
57
|
+
name = (os.getenv("HH_ADAPTER") or "local_queue").strip().lower()
|
|
58
|
+
if name not in _VALID:
|
|
59
|
+
logger.warning("HH_ADAPTER=%s is not recognised; using local_queue", name)
|
|
60
|
+
name = "local_queue"
|
|
61
|
+
client = _build(name)
|
|
62
|
+
if client is None:
|
|
63
|
+
from .local_queue import from_env as build_local
|
|
64
|
+
client = build_local()
|
|
65
|
+
return client
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
# Global singleton
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
_singleton: Optional[HandoffClient] = None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_client() -> HandoffClient:
|
|
75
|
+
global _singleton
|
|
76
|
+
if _singleton is None:
|
|
77
|
+
_singleton = build_default()
|
|
78
|
+
return _singleton
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def set_client(client: HandoffClient) -> None:
|
|
82
|
+
"""For testing only: inject a custom client."""
|
|
83
|
+
global _singleton
|
|
84
|
+
_singleton = client
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def reset_client() -> None:
|
|
88
|
+
global _singleton
|
|
89
|
+
_singleton = None
|