@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,258 @@
|
|
|
1
|
+
"""LocalQueueHandoffClient — default local implementation.
|
|
2
|
+
|
|
3
|
+
Zero external dependencies, in-process queuing + agent allocation. Migrated from the original queue.py implementation as the "default out-of-the-box" version.
|
|
4
|
+
|
|
5
|
+
Implementation notes:
|
|
6
|
+
- user_id also serves as ticket_id (keeping behavior consistent with old session_id)
|
|
7
|
+
- State machine:
|
|
8
|
+
PENDING ──connect──▶ PROCESSING
|
|
9
|
+
│ ▲ │
|
|
10
|
+
│ │ ▼
|
|
11
|
+
cancel/timeout cancel/close
|
|
12
|
+
- Single-process RLock protection; cross-process sync handled by integrator upper layer (e.g. Redis)
|
|
13
|
+
- Capacity and agent count read from environment variables; can be overridden via constructor
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import threading
|
|
19
|
+
from typing import Dict, List, Optional
|
|
20
|
+
|
|
21
|
+
from ..core.models import (
|
|
22
|
+
OverallStatus,
|
|
23
|
+
Ticket,
|
|
24
|
+
TicketStatus,
|
|
25
|
+
TicketStatusEnum,
|
|
26
|
+
now_ts,
|
|
27
|
+
)
|
|
28
|
+
from ..ports.handoff_client import HandoffClient
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class LocalQueueHandoffClient(HandoffClient):
|
|
32
|
+
"""In-process in-memory queuing HandoffClient implementation."""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
*,
|
|
37
|
+
capacity: int = 50,
|
|
38
|
+
agent_pool_size: int = 1,
|
|
39
|
+
estimated_wait_per_slot: int = 30,
|
|
40
|
+
) -> None:
|
|
41
|
+
self._lock = threading.RLock()
|
|
42
|
+
self._tickets: Dict[str, Ticket] = {} # ticket_id -> Ticket
|
|
43
|
+
self._waiting: List[str] = [] # ticket_id list, FIFO
|
|
44
|
+
self._connected: Dict[str, str] = {} # ticket_id -> agent_id
|
|
45
|
+
self._capacity = int(capacity)
|
|
46
|
+
self._pool = int(agent_pool_size)
|
|
47
|
+
self._wait_per_slot = max(1, int(estimated_wait_per_slot))
|
|
48
|
+
|
|
49
|
+
# ------------------------------------------------------------------
|
|
50
|
+
# HandoffClient required implementations
|
|
51
|
+
# ------------------------------------------------------------------
|
|
52
|
+
def create_ticket(
|
|
53
|
+
self,
|
|
54
|
+
*,
|
|
55
|
+
user_id: str,
|
|
56
|
+
subject: str = "",
|
|
57
|
+
description: str = "",
|
|
58
|
+
priority: str = "normal",
|
|
59
|
+
transcript: Optional[List[str]] = None,
|
|
60
|
+
) -> Ticket:
|
|
61
|
+
if not user_id:
|
|
62
|
+
raise ValueError("user_id is required")
|
|
63
|
+
with self._lock:
|
|
64
|
+
# Only one in-progress ticket per user; if exists, refresh position and return
|
|
65
|
+
existing = self._find_active_by_user(user_id)
|
|
66
|
+
if existing is not None:
|
|
67
|
+
if existing.status == TicketStatusEnum.PROCESSING.value:
|
|
68
|
+
return existing
|
|
69
|
+
self._refresh_position(existing)
|
|
70
|
+
return existing
|
|
71
|
+
|
|
72
|
+
ticket_id = user_id # Compatible with old behavior: session_id is ticket_id
|
|
73
|
+
t = Ticket(
|
|
74
|
+
ticket_id=ticket_id,
|
|
75
|
+
user_id=user_id,
|
|
76
|
+
subject=subject,
|
|
77
|
+
description=description,
|
|
78
|
+
priority=priority or "normal",
|
|
79
|
+
transcript=list(transcript or []),
|
|
80
|
+
reason=description[:128] if description else "",
|
|
81
|
+
created_at=now_ts(),
|
|
82
|
+
updated_at=now_ts(),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Queue full and no available agents: mark TIMEOUT
|
|
86
|
+
if (
|
|
87
|
+
len(self._waiting) >= self._capacity
|
|
88
|
+
and self._available_agents() == 0
|
|
89
|
+
):
|
|
90
|
+
t.status = TicketStatusEnum.TIMEOUT.value
|
|
91
|
+
t.closed_at = now_ts()
|
|
92
|
+
self._tickets[ticket_id] = t
|
|
93
|
+
return t
|
|
94
|
+
|
|
95
|
+
t.status = TicketStatusEnum.PENDING.value
|
|
96
|
+
self._tickets[ticket_id] = t
|
|
97
|
+
self._waiting.append(ticket_id)
|
|
98
|
+
|
|
99
|
+
# Auto-connect if an agent is available
|
|
100
|
+
if self._available_agents() > 0:
|
|
101
|
+
self._auto_connect()
|
|
102
|
+
self._refresh_position(t)
|
|
103
|
+
return t
|
|
104
|
+
|
|
105
|
+
def query_status(self, ticket_id: str) -> Optional[TicketStatus]:
|
|
106
|
+
with self._lock:
|
|
107
|
+
t = self._tickets.get(ticket_id)
|
|
108
|
+
if t is None:
|
|
109
|
+
return None
|
|
110
|
+
return TicketStatus.from_ticket(t)
|
|
111
|
+
|
|
112
|
+
def cancel_ticket(self, ticket_id: str, reason: str = "") -> Optional[Ticket]:
|
|
113
|
+
with self._lock:
|
|
114
|
+
t = self._tickets.get(ticket_id)
|
|
115
|
+
if t is None:
|
|
116
|
+
return None
|
|
117
|
+
if t.status == TicketStatusEnum.PROCESSING.value:
|
|
118
|
+
self._connected.pop(ticket_id, None)
|
|
119
|
+
self._waiting = [s for s in self._waiting if s != ticket_id]
|
|
120
|
+
t.status = TicketStatusEnum.CANCELED.value
|
|
121
|
+
t.reason = reason or t.reason
|
|
122
|
+
t.closed_at = now_ts()
|
|
123
|
+
t.updated_at = now_ts()
|
|
124
|
+
self._refresh_all_positions()
|
|
125
|
+
return t
|
|
126
|
+
|
|
127
|
+
def overall_status(self) -> OverallStatus:
|
|
128
|
+
with self._lock:
|
|
129
|
+
return OverallStatus(
|
|
130
|
+
agent_pool_size=self._pool,
|
|
131
|
+
available_agents=self._available_agents(),
|
|
132
|
+
waiting=len(self._waiting),
|
|
133
|
+
connected=len(self._connected),
|
|
134
|
+
capacity=self._capacity,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# ------------------------------------------------------------------
|
|
138
|
+
# Dashboard helper methods
|
|
139
|
+
# ------------------------------------------------------------------
|
|
140
|
+
def list_tickets(
|
|
141
|
+
self,
|
|
142
|
+
*,
|
|
143
|
+
limit: int = 50,
|
|
144
|
+
status: Optional[str] = None,
|
|
145
|
+
) -> List[Ticket]:
|
|
146
|
+
with self._lock:
|
|
147
|
+
items = list(self._tickets.values())
|
|
148
|
+
if status:
|
|
149
|
+
items = [t for t in items if t.status == status]
|
|
150
|
+
items.sort(
|
|
151
|
+
key=lambda x: (x.created_at or 0.0),
|
|
152
|
+
reverse=True,
|
|
153
|
+
)
|
|
154
|
+
return items[: max(1, int(limit))]
|
|
155
|
+
|
|
156
|
+
def update_status(
|
|
157
|
+
self,
|
|
158
|
+
ticket_id: str,
|
|
159
|
+
status: str,
|
|
160
|
+
*,
|
|
161
|
+
agent_id: Optional[str] = None,
|
|
162
|
+
) -> Optional[Ticket]:
|
|
163
|
+
try:
|
|
164
|
+
new_status = TicketStatusEnum(status).value
|
|
165
|
+
except ValueError as exc:
|
|
166
|
+
raise ValueError(f"invalid status: {status}") from exc
|
|
167
|
+
|
|
168
|
+
with self._lock:
|
|
169
|
+
t = self._tickets.get(ticket_id)
|
|
170
|
+
if t is None:
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
old_status = t.status
|
|
174
|
+
t.status = new_status
|
|
175
|
+
t.updated_at = now_ts()
|
|
176
|
+
|
|
177
|
+
if new_status == TicketStatusEnum.PROCESSING.value:
|
|
178
|
+
if old_status != TicketStatusEnum.PROCESSING.value:
|
|
179
|
+
if self._available_agents() <= 0 and ticket_id not in self._connected:
|
|
180
|
+
# Force connect (manual): open new slot outside agent pool
|
|
181
|
+
pass
|
|
182
|
+
t.agent_id = agent_id or t.agent_id or f"agent_{ticket_id[-4:]}"
|
|
183
|
+
self._connected[ticket_id] = t.agent_id
|
|
184
|
+
self._waiting = [s for s in self._waiting if s != ticket_id]
|
|
185
|
+
elif new_status in (
|
|
186
|
+
TicketStatusEnum.CLOSED.value,
|
|
187
|
+
TicketStatusEnum.CANCELED.value,
|
|
188
|
+
TicketStatusEnum.TIMEOUT.value,
|
|
189
|
+
):
|
|
190
|
+
t.closed_at = now_ts()
|
|
191
|
+
self._connected.pop(ticket_id, None)
|
|
192
|
+
self._waiting = [s for s in self._waiting if s != ticket_id]
|
|
193
|
+
elif new_status == TicketStatusEnum.PENDING.value:
|
|
194
|
+
self._connected.pop(ticket_id, None)
|
|
195
|
+
if ticket_id not in self._waiting:
|
|
196
|
+
self._waiting.append(ticket_id)
|
|
197
|
+
|
|
198
|
+
self._refresh_all_positions()
|
|
199
|
+
if self._available_agents() > 0:
|
|
200
|
+
self._auto_connect()
|
|
201
|
+
return t
|
|
202
|
+
|
|
203
|
+
def get_or_attach(self, user_id: str) -> Optional[Ticket]:
|
|
204
|
+
with self._lock:
|
|
205
|
+
return self._find_active_by_user(user_id)
|
|
206
|
+
|
|
207
|
+
# ------------------------------------------------------------------
|
|
208
|
+
# Internal
|
|
209
|
+
# ------------------------------------------------------------------
|
|
210
|
+
def _available_agents(self) -> int:
|
|
211
|
+
return max(0, self._pool - len(self._connected))
|
|
212
|
+
|
|
213
|
+
def _find_active_by_user(self, user_id: str) -> Optional[Ticket]:
|
|
214
|
+
for t in self._tickets.values():
|
|
215
|
+
if t.user_id == user_id and t.status in (
|
|
216
|
+
TicketStatusEnum.PENDING.value,
|
|
217
|
+
TicketStatusEnum.PROCESSING.value,
|
|
218
|
+
):
|
|
219
|
+
return t
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
def _auto_connect(self) -> None:
|
|
223
|
+
while self._waiting and self._available_agents() > 0:
|
|
224
|
+
tid = self._waiting.pop(0)
|
|
225
|
+
t = self._tickets.get(tid)
|
|
226
|
+
if t is None:
|
|
227
|
+
continue
|
|
228
|
+
t.status = TicketStatusEnum.PROCESSING.value
|
|
229
|
+
t.updated_at = now_ts()
|
|
230
|
+
t.agent_id = f"agent_auto_{int(t.updated_at)}"
|
|
231
|
+
self._connected[tid] = t.agent_id
|
|
232
|
+
|
|
233
|
+
def _refresh_position(self, t: Ticket) -> None:
|
|
234
|
+
if (
|
|
235
|
+
t.status == TicketStatusEnum.PENDING.value
|
|
236
|
+
and t.ticket_id in self._waiting
|
|
237
|
+
):
|
|
238
|
+
pos = self._waiting.index(t.ticket_id) + 1
|
|
239
|
+
t.queue_position = pos
|
|
240
|
+
t.eta_seconds = pos * self._wait_per_slot
|
|
241
|
+
else:
|
|
242
|
+
t.queue_position = 0
|
|
243
|
+
t.eta_seconds = 0
|
|
244
|
+
|
|
245
|
+
def _refresh_all_positions(self) -> None:
|
|
246
|
+
for t in self._tickets.values():
|
|
247
|
+
self._refresh_position(t)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# ---------------------------------------------------------------------------
|
|
251
|
+
# Factory: build default parameters from environment variables
|
|
252
|
+
# ---------------------------------------------------------------------------
|
|
253
|
+
def from_env() -> LocalQueueHandoffClient:
|
|
254
|
+
return LocalQueueHandoffClient(
|
|
255
|
+
capacity=int(os.getenv("HH_QUEUE_CAPACITY", "50")),
|
|
256
|
+
agent_pool_size=int(os.getenv("HH_AGENT_POOL_SIZE", "1")),
|
|
257
|
+
estimated_wait_per_slot=int(os.getenv("HH_WAIT_PER_SLOT", "30")),
|
|
258
|
+
)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""MockHandoffClient — mock implementation for Recipe demo recording.
|
|
2
|
+
|
|
3
|
+
Inherits LocalQueueHandoffClient, pre-seeds several sample tickets on startup so the agent dashboard has content immediately.
|
|
4
|
+
|
|
5
|
+
Differences from LocalQueueHandoffClient:
|
|
6
|
+
- Seeds example tickets at construction (one each for pending / processing / closed)
|
|
7
|
+
- Marks `is_mock = True`, making it easy to add a "demo data" watermark on the dashboard
|
|
8
|
+
- Demo data uses stable ticket_id prefix `demo_` for reproducible screenshots
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import List, Optional
|
|
13
|
+
|
|
14
|
+
from ..core.models import Ticket, TicketStatusEnum, now_ts
|
|
15
|
+
from .local_queue import LocalQueueHandoffClient
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MockHandoffClient(LocalQueueHandoffClient):
|
|
19
|
+
"""Mock implementation for demos."""
|
|
20
|
+
|
|
21
|
+
is_mock = True
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
*,
|
|
26
|
+
capacity: int = 50,
|
|
27
|
+
agent_pool_size: int = 2,
|
|
28
|
+
estimated_wait_per_slot: int = 30,
|
|
29
|
+
seed_demo_data: bool = True,
|
|
30
|
+
) -> None:
|
|
31
|
+
super().__init__(
|
|
32
|
+
capacity=capacity,
|
|
33
|
+
agent_pool_size=agent_pool_size,
|
|
34
|
+
estimated_wait_per_slot=estimated_wait_per_slot,
|
|
35
|
+
)
|
|
36
|
+
if seed_demo_data:
|
|
37
|
+
self._seed()
|
|
38
|
+
|
|
39
|
+
def _seed(self) -> None:
|
|
40
|
+
"""Seed demo data. Agent pool occupies 1 slot, leaving 1 free for live connection demos."""
|
|
41
|
+
ts_base = now_ts() - 600
|
|
42
|
+
|
|
43
|
+
# 1) Closed ticket (10 minutes ago)
|
|
44
|
+
closed = Ticket(
|
|
45
|
+
ticket_id="demo_closed_001",
|
|
46
|
+
user_id="demo_user_001",
|
|
47
|
+
subject="Invoice header correction",
|
|
48
|
+
description="Shipped order needs invoice header correction",
|
|
49
|
+
priority="normal",
|
|
50
|
+
status=TicketStatusEnum.CLOSED.value,
|
|
51
|
+
agent_id="agent_alice",
|
|
52
|
+
transcript=[
|
|
53
|
+
"User: Hi, I need to update the invoice header",
|
|
54
|
+
"AI: Which order is this regarding?",
|
|
55
|
+
"User: Order number SO20260601-0042",
|
|
56
|
+
"[handoff] User requested human agent",
|
|
57
|
+
"Agent Alice: Done. New invoice will be issued within 30 minutes.",
|
|
58
|
+
],
|
|
59
|
+
reason="Invoice header correction",
|
|
60
|
+
created_at=ts_base,
|
|
61
|
+
updated_at=ts_base + 120,
|
|
62
|
+
closed_at=ts_base + 240,
|
|
63
|
+
)
|
|
64
|
+
self._tickets[closed.ticket_id] = closed
|
|
65
|
+
|
|
66
|
+
# 2) Processing ticket (occupies 1 agent slot)
|
|
67
|
+
processing = Ticket(
|
|
68
|
+
ticket_id="demo_processing_001",
|
|
69
|
+
user_id="demo_user_002",
|
|
70
|
+
subject="Return logistics issue",
|
|
71
|
+
description="Return not picked up for 5 days",
|
|
72
|
+
priority="high",
|
|
73
|
+
status=TicketStatusEnum.PROCESSING.value,
|
|
74
|
+
agent_id="agent_bob",
|
|
75
|
+
transcript=[
|
|
76
|
+
"User: My return has been requested 5 days and no one has picked it up yet",
|
|
77
|
+
"AI: Let me check the logistics status for you...",
|
|
78
|
+
"AI: Sorry, the logistics API is temporarily unavailable for real-time info",
|
|
79
|
+
"[handoff] Escalating to human agent",
|
|
80
|
+
"Agent Bob: Hello, I am following up on your logistics issue",
|
|
81
|
+
],
|
|
82
|
+
reason="Return logistics issue",
|
|
83
|
+
created_at=ts_base + 300,
|
|
84
|
+
updated_at=ts_base + 320,
|
|
85
|
+
)
|
|
86
|
+
self._tickets[processing.ticket_id] = processing
|
|
87
|
+
self._connected[processing.ticket_id] = processing.agent_id # type: ignore[assignment]
|
|
88
|
+
|
|
89
|
+
# 3) Pending ticket (FIFO queue head)
|
|
90
|
+
pending = Ticket(
|
|
91
|
+
ticket_id="demo_pending_001",
|
|
92
|
+
user_id="demo_user_003",
|
|
93
|
+
subject="Refund progress inquiry",
|
|
94
|
+
description="Refund applied 3 days ago not received",
|
|
95
|
+
priority="normal",
|
|
96
|
+
status=TicketStatusEnum.PENDING.value,
|
|
97
|
+
transcript=[
|
|
98
|
+
"User: My refund from 3 days ago hasn't arrived yet",
|
|
99
|
+
"AI: Please provide your order number for lookup",
|
|
100
|
+
"User: Order SO20260605-0099",
|
|
101
|
+
"[handoff] Transfer to agent",
|
|
102
|
+
],
|
|
103
|
+
reason="Refund progress",
|
|
104
|
+
created_at=ts_base + 540,
|
|
105
|
+
updated_at=ts_base + 540,
|
|
106
|
+
)
|
|
107
|
+
self._tickets[pending.ticket_id] = pending
|
|
108
|
+
self._waiting.append(pending.ticket_id)
|
|
109
|
+
self._refresh_all_positions()
|
|
110
|
+
|
|
111
|
+
def list_tickets(
|
|
112
|
+
self,
|
|
113
|
+
*,
|
|
114
|
+
limit: int = 50,
|
|
115
|
+
status: Optional[str] = None,
|
|
116
|
+
) -> List[Ticket]:
|
|
117
|
+
# Mock mode defaults to reverse chrono by creation time, consistent with LocalQueue behavior
|
|
118
|
+
return super().list_tickets(limit=limit, status=status)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
# Factory
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
def from_env() -> MockHandoffClient:
|
|
125
|
+
import os
|
|
126
|
+
|
|
127
|
+
return MockHandoffClient(
|
|
128
|
+
capacity=int(os.getenv("HH_QUEUE_CAPACITY", "50")),
|
|
129
|
+
agent_pool_size=int(os.getenv("HH_AGENT_POOL_SIZE", "2")),
|
|
130
|
+
estimated_wait_per_slot=int(os.getenv("HH_WAIT_PER_SLOT", "30")),
|
|
131
|
+
seed_demo_data=os.getenv("HH_MOCK_SEED", "1") not in ("0", "false", "False"),
|
|
132
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""human-handoff core module."""
|
|
2
|
+
from .intent_detector import IntentDetector, is_handoff_intent
|
|
3
|
+
from .models import (
|
|
4
|
+
OverallStatus,
|
|
5
|
+
Ticket,
|
|
6
|
+
TicketStatus,
|
|
7
|
+
TicketStatusEnum,
|
|
8
|
+
now_ts,
|
|
9
|
+
to_legacy_state,
|
|
10
|
+
)
|
|
11
|
+
from .service import HandoffService, get_default_service, reset_default_service
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"HandoffService",
|
|
15
|
+
"IntentDetector",
|
|
16
|
+
"OverallStatus",
|
|
17
|
+
"Ticket",
|
|
18
|
+
"TicketStatus",
|
|
19
|
+
"TicketStatusEnum",
|
|
20
|
+
"get_default_service",
|
|
21
|
+
"is_handoff_intent",
|
|
22
|
+
"now_ts",
|
|
23
|
+
"reset_default_service",
|
|
24
|
+
"to_legacy_state",
|
|
25
|
+
]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Handoff intent detection: keyword strong matching + weak intent (with negative context recognition).
|
|
2
|
+
|
|
3
|
+
Migrated from original trigger.py. This module does **not depend** on any adapter or global state; pure functions.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
from typing import List
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
_DEFAULT_TRIGGERS = [
|
|
13
|
+
"agent", "help", "support",
|
|
14
|
+
"real person", "talk to agent", "speak to a human", "human agent",
|
|
15
|
+
]
|
|
16
|
+
_DEFAULT_INTENT = ["complain", "manager", "unsatisfied", "escalate"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _csv_env(key: str, default: List[str]) -> List[str]:
|
|
20
|
+
raw = os.getenv(key)
|
|
21
|
+
if not raw:
|
|
22
|
+
return list(default)
|
|
23
|
+
return [item.strip() for item in raw.split(",") if item.strip()]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class IntentDetector:
|
|
27
|
+
"""Determine whether input text expresses a "handoff" intent using regex."""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
*,
|
|
32
|
+
triggers: List[str] | None = None,
|
|
33
|
+
intent_keywords: List[str] | None = None,
|
|
34
|
+
) -> None:
|
|
35
|
+
self._triggers = triggers if triggers is not None else _csv_env(
|
|
36
|
+
"HH_TRIGGERS", _DEFAULT_TRIGGERS
|
|
37
|
+
)
|
|
38
|
+
self._intent = intent_keywords if intent_keywords is not None else _csv_env(
|
|
39
|
+
"HH_INTENT_KEYWORDS", _DEFAULT_INTENT
|
|
40
|
+
)
|
|
41
|
+
self._triggers_re = re.compile(
|
|
42
|
+
"|".join(re.escape(k) for k in self._triggers), re.IGNORECASE
|
|
43
|
+
)
|
|
44
|
+
self._intent_re = re.compile(
|
|
45
|
+
"|".join(re.escape(k) for k in self._intent), re.IGNORECASE
|
|
46
|
+
)
|
|
47
|
+
self._negative_re = re.compile(
|
|
48
|
+
r"\b(not|don't|do not|no|never)\b", re.IGNORECASE
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def is_handoff_intent(self, text: str) -> bool:
|
|
52
|
+
if not text or len(text) > 4096:
|
|
53
|
+
return False
|
|
54
|
+
if self._triggers_re.search(text):
|
|
55
|
+
return True
|
|
56
|
+
if self._intent_re.search(text) and not self._negative_re.search(text):
|
|
57
|
+
return True
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
# Default singleton (keeps behavior consistent with old trigger.py; tests can manually construct new instances to override)
|
|
63
|
+
# ---------------------------------------------------------------------------
|
|
64
|
+
_default_detector: IntentDetector | None = None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_default_detector() -> IntentDetector:
|
|
68
|
+
global _default_detector
|
|
69
|
+
if _default_detector is None:
|
|
70
|
+
_default_detector = IntentDetector()
|
|
71
|
+
return _default_detector
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def is_handoff_intent(text: str) -> bool:
|
|
75
|
+
return get_default_detector().is_handoff_intent(text)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""human-handoff core models.
|
|
2
|
+
|
|
3
|
+
Defines unified domain models:
|
|
4
|
+
- TicketStatusEnum Ticket status (aligned with business_contract.ticket.status_query.response.status)
|
|
5
|
+
- Ticket Complete ticket record (transport object between adapters)
|
|
6
|
+
- TicketStatus Lightweight status view (returned by status_query)
|
|
7
|
+
- OverallStatus Overall queue status (dashboard use)
|
|
8
|
+
|
|
9
|
+
The core layer does not know any specific backend implementation; all adapters must use this module's data structures.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import time
|
|
14
|
+
import uuid
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from enum import Enum
|
|
17
|
+
from typing import Any, Dict, List, Optional
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TicketStatusEnum(str, Enum):
|
|
21
|
+
"""Ticket status enum.
|
|
22
|
+
|
|
23
|
+
Business semantics mapping:
|
|
24
|
+
- PENDING User has applied; not yet assigned an agent (equivalent to old HandoffState.WAITING)
|
|
25
|
+
- PROCESSING Agent assigned, processing (equivalent to old HandoffState.CONNECTED)
|
|
26
|
+
- CLOSED Agent closed ticket
|
|
27
|
+
- CANCELED User actively canceled
|
|
28
|
+
- TIMEOUT Timeout, no agent connected
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
PENDING = "pending"
|
|
32
|
+
PROCESSING = "processing"
|
|
33
|
+
CLOSED = "closed"
|
|
34
|
+
CANCELED = "canceled"
|
|
35
|
+
TIMEOUT = "timeout"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Status name mapping for old API compatibility (HandoffState era)
|
|
39
|
+
_LEGACY_STATE_MAP = {
|
|
40
|
+
TicketStatusEnum.PENDING.value: "waiting",
|
|
41
|
+
TicketStatusEnum.PROCESSING.value: "connected",
|
|
42
|
+
TicketStatusEnum.CLOSED.value: "closed",
|
|
43
|
+
TicketStatusEnum.CANCELED.value: "canceled",
|
|
44
|
+
TicketStatusEnum.TIMEOUT.value: "timeout",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def to_legacy_state(status: str) -> str:
|
|
49
|
+
"""Convert new TicketStatusEnum value back to old API state name."""
|
|
50
|
+
if not status:
|
|
51
|
+
return "idle"
|
|
52
|
+
return _LEGACY_STATE_MAP.get(status, status)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class Ticket:
|
|
57
|
+
"""Ticket record. Transport object between adapters.
|
|
58
|
+
|
|
59
|
+
user_id and ticket_id default to the same value in LocalQueue implementation (using session_id),
|
|
60
|
+
REST implementation uses ticket_id returned by the business side.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
ticket_id: str
|
|
64
|
+
user_id: str
|
|
65
|
+
subject: str = ""
|
|
66
|
+
description: str = ""
|
|
67
|
+
priority: str = "normal"
|
|
68
|
+
status: str = TicketStatusEnum.PENDING.value
|
|
69
|
+
queue_position: int = 0
|
|
70
|
+
eta_seconds: int = 0
|
|
71
|
+
agent_id: Optional[str] = None
|
|
72
|
+
transcript: List[str] = field(default_factory=list)
|
|
73
|
+
reason: str = "" # Trigger reason summary (compatible with old field)
|
|
74
|
+
created_at: Optional[float] = None
|
|
75
|
+
updated_at: Optional[float] = None
|
|
76
|
+
closed_at: Optional[float] = None
|
|
77
|
+
extra: Dict[str, Any] = field(default_factory=dict)
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def new_id() -> str:
|
|
81
|
+
return f"tk_{uuid.uuid4().hex[:12]}"
|
|
82
|
+
|
|
83
|
+
def to_dict(self) -> dict:
|
|
84
|
+
return {
|
|
85
|
+
"ticket_id": self.ticket_id,
|
|
86
|
+
"session_id": self.user_id,
|
|
87
|
+
"user_id": self.user_id,
|
|
88
|
+
"subject": self.subject,
|
|
89
|
+
"description": self.description,
|
|
90
|
+
"priority": self.priority,
|
|
91
|
+
"status": self.status,
|
|
92
|
+
"queue_position": self.queue_position,
|
|
93
|
+
"eta_seconds": self.eta_seconds,
|
|
94
|
+
"agent_id": self.agent_id,
|
|
95
|
+
"transcript": list(self.transcript),
|
|
96
|
+
"reason": self.reason,
|
|
97
|
+
"created_at": self.created_at,
|
|
98
|
+
"updated_at": self.updated_at,
|
|
99
|
+
"closed_at": self.closed_at,
|
|
100
|
+
# Written at ticket creation by human-handoff - session-summary linkage (None if capability not installed)
|
|
101
|
+
"session_summary": self.extra.get("session_summary"),
|
|
102
|
+
# Written by HandoffService.submit_feedback (None if not yet rated)
|
|
103
|
+
"feedback": self.extra.get("feedback"),
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
def to_legacy_dict(self) -> dict:
|
|
107
|
+
"""Field format returned by old REST API (/api/v1/handoff/*), keeps Web Demo compatibility."""
|
|
108
|
+
return {
|
|
109
|
+
"session_id": self.user_id,
|
|
110
|
+
"state": to_legacy_state(self.status),
|
|
111
|
+
"reason": self.reason,
|
|
112
|
+
"requested_at": self.created_at,
|
|
113
|
+
"connected_at": self.updated_at if self.status == TicketStatusEnum.PROCESSING.value else None,
|
|
114
|
+
"closed_at": self.closed_at,
|
|
115
|
+
"agent_id": self.agent_id,
|
|
116
|
+
"queue_position": self.queue_position,
|
|
117
|
+
"estimated_wait_seconds": self.eta_seconds,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass
|
|
122
|
+
class TicketStatus:
|
|
123
|
+
"""Response model corresponding to business_contract.ticket.status_query."""
|
|
124
|
+
|
|
125
|
+
ticket_id: str
|
|
126
|
+
status: str
|
|
127
|
+
agent_id: Optional[str] = None
|
|
128
|
+
queue_position: int = 0
|
|
129
|
+
eta_seconds: int = 0
|
|
130
|
+
updated_at: Optional[float] = None
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def from_ticket(cls, t: Ticket) -> "TicketStatus":
|
|
134
|
+
return cls(
|
|
135
|
+
ticket_id=t.ticket_id,
|
|
136
|
+
status=t.status,
|
|
137
|
+
agent_id=t.agent_id,
|
|
138
|
+
queue_position=t.queue_position,
|
|
139
|
+
eta_seconds=t.eta_seconds,
|
|
140
|
+
updated_at=t.updated_at or t.created_at,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@dataclass
|
|
145
|
+
class OverallStatus:
|
|
146
|
+
agent_pool_size: int
|
|
147
|
+
available_agents: int
|
|
148
|
+
waiting: int
|
|
149
|
+
connected: int
|
|
150
|
+
capacity: int
|
|
151
|
+
|
|
152
|
+
def to_dict(self) -> dict:
|
|
153
|
+
return {
|
|
154
|
+
"agent_pool_size": self.agent_pool_size,
|
|
155
|
+
"available_agents": self.available_agents,
|
|
156
|
+
"waiting": self.waiting,
|
|
157
|
+
"connected": self.connected,
|
|
158
|
+
"capacity": self.capacity,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def now_ts() -> float:
|
|
163
|
+
return time.time()
|