@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,192 @@
|
|
|
1
|
+
"""HandoffService — Application service chaining IntentDetector with HandoffClient.
|
|
2
|
+
|
|
3
|
+
Only depends on ports (HandoffClient interface), does not know the specific backend implementation.
|
|
4
|
+
Switching adapter only requires re-injecting client (adapters.factory.set_client).
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import List, Optional
|
|
9
|
+
|
|
10
|
+
from ..ports.handoff_client import HandoffClient
|
|
11
|
+
from ..summary_link import attach_summary_to_ticket
|
|
12
|
+
from .intent_detector import IntentDetector, get_default_detector
|
|
13
|
+
from .models import OverallStatus, Ticket, TicketStatus, TicketStatusEnum
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class HandoffService:
|
|
17
|
+
"""Handoff business service."""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
*,
|
|
22
|
+
client: HandoffClient,
|
|
23
|
+
detector: Optional[IntentDetector] = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
self._client = client
|
|
26
|
+
self._detector = detector or get_default_detector()
|
|
27
|
+
|
|
28
|
+
# ------------------------------------------------------------------
|
|
29
|
+
# Intent detection + trigger (for injection into conversation-core.before_push_text)
|
|
30
|
+
# ------------------------------------------------------------------
|
|
31
|
+
def maybe_handoff(self, session_id: str, text: str) -> Optional[str]:
|
|
32
|
+
"""Recognize handoff intent; if matched, request ticket and return assembled script; otherwise return None."""
|
|
33
|
+
if not session_id or not text:
|
|
34
|
+
return None
|
|
35
|
+
if not self._detector.is_handoff_intent(text):
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
# Reuse existing ticket (don't duplicate for same user in progress)
|
|
39
|
+
existing = self._client.get_or_attach(session_id)
|
|
40
|
+
if existing is not None and existing.status in (
|
|
41
|
+
TicketStatusEnum.PENDING.value,
|
|
42
|
+
TicketStatusEnum.PROCESSING.value,
|
|
43
|
+
):
|
|
44
|
+
return self._render_handoff_message(existing)
|
|
45
|
+
|
|
46
|
+
ticket = self._client.create_ticket(
|
|
47
|
+
user_id=session_id,
|
|
48
|
+
subject=text[:64],
|
|
49
|
+
description=text[:512],
|
|
50
|
+
priority="normal",
|
|
51
|
+
)
|
|
52
|
+
# Attach session summary at ticket creation so agents can immediately see the issue on the dashboard (no-op if session-summary is not installed)
|
|
53
|
+
attach_summary_to_ticket(ticket)
|
|
54
|
+
return self._render_handoff_message(ticket)
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def _render_handoff_message(t: Ticket) -> Optional[str]:
|
|
58
|
+
if t.status == TicketStatusEnum.PROCESSING.value:
|
|
59
|
+
return (
|
|
60
|
+
f"[handoff state=connected agent={t.agent_id}]\n"
|
|
61
|
+
"You are now connected to a human agent. Please wait a moment."
|
|
62
|
+
)
|
|
63
|
+
if t.status == TicketStatusEnum.PENDING.value:
|
|
64
|
+
return (
|
|
65
|
+
f"[handoff state=waiting position={t.queue_position} "
|
|
66
|
+
f"eta={t.eta_seconds}s]\n"
|
|
67
|
+
f"You are number {t.queue_position} in the agent queue, "
|
|
68
|
+
f"estimated wait {t.eta_seconds} seconds."
|
|
69
|
+
)
|
|
70
|
+
if t.status == TicketStatusEnum.TIMEOUT.value:
|
|
71
|
+
return "[handoff state=timeout]\nNo agents are currently available. Please try again later."
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
# ------------------------------------------------------------------
|
|
75
|
+
# Explicit operations (for router calls)
|
|
76
|
+
# ------------------------------------------------------------------
|
|
77
|
+
def request(
|
|
78
|
+
self,
|
|
79
|
+
session_id: str,
|
|
80
|
+
*,
|
|
81
|
+
reason: str = "",
|
|
82
|
+
subject: Optional[str] = None,
|
|
83
|
+
description: Optional[str] = None,
|
|
84
|
+
) -> Ticket:
|
|
85
|
+
existing = self._client.get_or_attach(session_id)
|
|
86
|
+
if existing is not None and existing.status in (
|
|
87
|
+
TicketStatusEnum.PENDING.value,
|
|
88
|
+
TicketStatusEnum.PROCESSING.value,
|
|
89
|
+
):
|
|
90
|
+
return existing
|
|
91
|
+
ticket = self._client.create_ticket(
|
|
92
|
+
user_id=session_id,
|
|
93
|
+
subject=(subject or reason or "human handoff")[:64],
|
|
94
|
+
description=(description or reason or "")[:512],
|
|
95
|
+
priority="normal",
|
|
96
|
+
)
|
|
97
|
+
# Attach session summary at ticket creation (no-op if session-summary not installed; does not affect main flow)
|
|
98
|
+
attach_summary_to_ticket(ticket)
|
|
99
|
+
return ticket
|
|
100
|
+
|
|
101
|
+
def connect(self, session_id: str, agent_id: str) -> Ticket:
|
|
102
|
+
"""For /api/v1/handoff/connect: force-connect a session to a specified agent."""
|
|
103
|
+
# Look up active ticket by user_id first
|
|
104
|
+
ticket = self._client.get_or_attach(session_id)
|
|
105
|
+
if ticket is None:
|
|
106
|
+
raise ValueError(f"session {session_id} not waiting")
|
|
107
|
+
if ticket.status == TicketStatusEnum.PROCESSING.value:
|
|
108
|
+
return ticket
|
|
109
|
+
updated = self._client.update_status(
|
|
110
|
+
ticket.ticket_id,
|
|
111
|
+
TicketStatusEnum.PROCESSING.value,
|
|
112
|
+
agent_id=agent_id,
|
|
113
|
+
)
|
|
114
|
+
if updated is None:
|
|
115
|
+
raise ValueError(f"ticket {ticket.ticket_id} not found")
|
|
116
|
+
return updated
|
|
117
|
+
|
|
118
|
+
def cancel(self, session_id: str, *, reason: str = "") -> Ticket:
|
|
119
|
+
ticket = self._client.get_or_attach(session_id)
|
|
120
|
+
if ticket is None:
|
|
121
|
+
raise ValueError(f"session not found: {session_id}")
|
|
122
|
+
result = self._client.cancel_ticket(ticket.ticket_id, reason=reason)
|
|
123
|
+
if result is None:
|
|
124
|
+
raise ValueError(f"ticket {ticket.ticket_id} not found")
|
|
125
|
+
return result
|
|
126
|
+
|
|
127
|
+
def get_by_session(self, session_id: str) -> Optional[Ticket]:
|
|
128
|
+
return self._client.get_or_attach(session_id)
|
|
129
|
+
|
|
130
|
+
def overall_status(self) -> OverallStatus:
|
|
131
|
+
return self._client.overall_status()
|
|
132
|
+
|
|
133
|
+
# ------------------------------------------------------------------
|
|
134
|
+
# Dashboard helpers
|
|
135
|
+
# ------------------------------------------------------------------
|
|
136
|
+
def list_tickets(
|
|
137
|
+
self,
|
|
138
|
+
*,
|
|
139
|
+
limit: int = 50,
|
|
140
|
+
status: Optional[str] = None,
|
|
141
|
+
) -> List[Ticket]:
|
|
142
|
+
return self._client.list_tickets(limit=limit, status=status)
|
|
143
|
+
|
|
144
|
+
def update_ticket_status(
|
|
145
|
+
self,
|
|
146
|
+
ticket_id: str,
|
|
147
|
+
status: str,
|
|
148
|
+
*,
|
|
149
|
+
agent_id: Optional[str] = None,
|
|
150
|
+
) -> Optional[Ticket]:
|
|
151
|
+
return self._client.update_status(ticket_id, status, agent_id=agent_id)
|
|
152
|
+
|
|
153
|
+
def query_ticket(self, ticket_id: str) -> Optional[TicketStatus]:
|
|
154
|
+
return self._client.query_status(ticket_id)
|
|
155
|
+
|
|
156
|
+
# ------------------------------------------------------------------
|
|
157
|
+
# Customer satisfaction feedback
|
|
158
|
+
# ------------------------------------------------------------------
|
|
159
|
+
def submit_feedback(self, session_id: str, rating: int, comment: str = "") -> dict:
|
|
160
|
+
"""Persist a satisfaction rating and, if a ticket exists for the session,
|
|
161
|
+
attach it to that ticket so agents can see the score on the dashboard."""
|
|
162
|
+
from ..feedback_store import make_feedback, save_feedback
|
|
163
|
+
|
|
164
|
+
fb = make_feedback(rating, comment)
|
|
165
|
+
save_feedback(session_id, fb)
|
|
166
|
+
try:
|
|
167
|
+
ticket = self._client.get_or_attach(session_id)
|
|
168
|
+
if ticket is not None:
|
|
169
|
+
ticket.extra["feedback"] = fb
|
|
170
|
+
except Exception: # noqa: BLE001 - feedback must never break on ticket lookup
|
|
171
|
+
pass
|
|
172
|
+
return {"session_id": session_id, "feedback": fb}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# ---------------------------------------------------------------------------
|
|
176
|
+
# Default service singleton
|
|
177
|
+
# ---------------------------------------------------------------------------
|
|
178
|
+
_default_service: Optional[HandoffService] = None
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def get_default_service() -> HandoffService:
|
|
182
|
+
"""Build service singleton from current environment (client from adapters.factory)."""
|
|
183
|
+
global _default_service
|
|
184
|
+
if _default_service is None:
|
|
185
|
+
from ..adapters.factory import get_client
|
|
186
|
+
_default_service = HandoffService(client=get_client())
|
|
187
|
+
return _default_service
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def reset_default_service() -> None:
|
|
191
|
+
global _default_service
|
|
192
|
+
_default_service = None
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""In-process satisfaction feedback store (CSAT).
|
|
2
|
+
|
|
3
|
+
Lightweight memory store keyed by session_id. Used by HandoffService.submit_feedback
|
|
4
|
+
so the dashboard can look up a session's rating without requiring a ticket to exist.
|
|
5
|
+
|
|
6
|
+
Why memory-only?
|
|
7
|
+
- The default adapter (HH_ADAPTER=local_queue) is itself in-process, so an in-memory
|
|
8
|
+
feedback store keeps the demo self-contained with zero external dependencies.
|
|
9
|
+
- Production deployments that persist tickets out-of-band should swap this for a
|
|
10
|
+
persistent sink; the public surface (make_feedback / save_feedback / get_feedback)
|
|
11
|
+
stays stable.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import threading
|
|
16
|
+
import time
|
|
17
|
+
import uuid
|
|
18
|
+
from typing import Any, Dict, Optional
|
|
19
|
+
|
|
20
|
+
_lock = threading.RLock()
|
|
21
|
+
_store: Dict[str, Dict[str, Any]] = {}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def make_feedback(rating: int, comment: str = "") -> Dict[str, Any]:
|
|
25
|
+
"""Build a normalized feedback record (does not persist)."""
|
|
26
|
+
rating = int(rating)
|
|
27
|
+
if rating < 1:
|
|
28
|
+
rating = 1
|
|
29
|
+
elif rating > 5:
|
|
30
|
+
rating = 5
|
|
31
|
+
return {
|
|
32
|
+
"feedback_id": f"fb_{uuid.uuid4().hex[:12]}",
|
|
33
|
+
"rating": rating,
|
|
34
|
+
"comment": (comment or "").strip()[:1000],
|
|
35
|
+
"created_at": time.time(),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def save_feedback(session_id: str, feedback: Dict[str, Any]) -> Dict[str, Any]:
|
|
40
|
+
"""Persist a feedback record keyed by session_id (overwrites prior entry)."""
|
|
41
|
+
if not session_id:
|
|
42
|
+
raise ValueError("session_id is required")
|
|
43
|
+
with _lock:
|
|
44
|
+
_store[session_id] = dict(feedback)
|
|
45
|
+
return feedback
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_feedback(session_id: str) -> Optional[Dict[str, Any]]:
|
|
49
|
+
"""Return the stored feedback for a session, or None if not rated yet."""
|
|
50
|
+
if not session_id:
|
|
51
|
+
return None
|
|
52
|
+
with _lock:
|
|
53
|
+
fb = _store.get(session_id)
|
|
54
|
+
return dict(fb) if fb is not None else None
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""human-handoff abstract port (Port).
|
|
2
|
+
|
|
3
|
+
One-to-one correspondence with manifest.yaml.business_contract fields:
|
|
4
|
+
- create_ticket -> ticket.create
|
|
5
|
+
- query_status -> ticket.status_query
|
|
6
|
+
- cancel_ticket -> ticket.cancel
|
|
7
|
+
- overall_status -> internal status (not in external contract; for local dashboard use only)
|
|
8
|
+
|
|
9
|
+
All concrete implementations (local_queue / default_rest / mock / user_custom) must inherit this ABC.
|
|
10
|
+
The core layer only depends on this interface, unaware of any specific backend type; switching backends only requires changing the adapter.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from abc import ABC, abstractmethod
|
|
15
|
+
from typing import List, Optional
|
|
16
|
+
|
|
17
|
+
from ..core.models import OverallStatus, Ticket, TicketStatus
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class HandoffClient(ABC):
|
|
21
|
+
"""Unified interface contract for handoff / ticketing backends."""
|
|
22
|
+
|
|
23
|
+
# --- Methods aligned with business_contract --------------------------
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def create_ticket(
|
|
27
|
+
self,
|
|
28
|
+
*,
|
|
29
|
+
user_id: str,
|
|
30
|
+
subject: str = "",
|
|
31
|
+
description: str = "",
|
|
32
|
+
priority: str = "normal",
|
|
33
|
+
transcript: Optional[List[str]] = None,
|
|
34
|
+
) -> Ticket:
|
|
35
|
+
"""Create ticket. Corresponds to business_contract.ticket.create."""
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def query_status(self, ticket_id: str) -> Optional[TicketStatus]:
|
|
39
|
+
"""Query single ticket status. Corresponds to ticket.status_query.
|
|
40
|
+
|
|
41
|
+
Returns None if ticket does not exist.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def cancel_ticket(self, ticket_id: str, reason: str = "") -> Optional[Ticket]:
|
|
46
|
+
"""Cancel ticket. Corresponds to ticket.cancel. Returns None if ticket does not exist."""
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
def overall_status(self) -> OverallStatus:
|
|
50
|
+
"""Overall queue status (dashboard use, not external contract)."""
|
|
51
|
+
|
|
52
|
+
# --- Dashboard helper methods (default implementation; remote backends may not override) ---------
|
|
53
|
+
|
|
54
|
+
def list_tickets(
|
|
55
|
+
self,
|
|
56
|
+
*,
|
|
57
|
+
limit: int = 50,
|
|
58
|
+
status: Optional[str] = None,
|
|
59
|
+
) -> List[Ticket]:
|
|
60
|
+
"""List tickets (default returns empty; remote backends override as needed)."""
|
|
61
|
+
return []
|
|
62
|
+
|
|
63
|
+
def update_status(
|
|
64
|
+
self,
|
|
65
|
+
ticket_id: str,
|
|
66
|
+
status: str,
|
|
67
|
+
*,
|
|
68
|
+
agent_id: Optional[str] = None,
|
|
69
|
+
) -> Optional[Ticket]:
|
|
70
|
+
"""Agent manually updates ticket status (not supported by default; can be overridden by mock / local_queue)."""
|
|
71
|
+
raise NotImplementedError(
|
|
72
|
+
f"{type(self).__name__} does not support manual status update"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# --- Bridge interface compatible with old trigger.maybe_handoff ---------
|
|
76
|
+
|
|
77
|
+
def get_or_attach(self, user_id: str) -> Optional[Ticket]:
|
|
78
|
+
"""Find existing ticket by user_id (old session_id); returns None if not found.
|
|
79
|
+
|
|
80
|
+
This method allows the facade layer to query existing ticket status without breaking the old API.
|
|
81
|
+
Default implementation iterates list_tickets.
|
|
82
|
+
"""
|
|
83
|
+
for t in self.list_tickets(limit=200):
|
|
84
|
+
if t.user_id == user_id:
|
|
85
|
+
return t
|
|
86
|
+
return None
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""queue.py — compatibility facade.
|
|
2
|
+
|
|
3
|
+
Keeps the `attach_session` public symbol for manifest.extensions (agent.after_start)
|
|
4
|
+
to continue calling as `_hh_queue.attach_session(session_id, info=...)`.
|
|
5
|
+
|
|
6
|
+
Old version's `get_queue()` / `HandoffQueue` / `HandoffRecord` / `HandoffState`
|
|
7
|
+
are no longer used under the new architecture; kept as deprecated shims for gradual migration of third-party code.
|
|
8
|
+
|
|
9
|
+
New code should use directly:
|
|
10
|
+
- adapters.factory.get_client() Get HandoffClient instance
|
|
11
|
+
- core.service.get_default_service() Get HandoffService instance
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import warnings
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from .adapters.factory import get_client
|
|
19
|
+
from .core.models import ( # noqa: F401 (backward compatible export)
|
|
20
|
+
OverallStatus,
|
|
21
|
+
Ticket,
|
|
22
|
+
TicketStatus,
|
|
23
|
+
TicketStatusEnum,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def attach_session(session_id: str, info: Any = None) -> None:
|
|
28
|
+
"""For conversation-core.after_start injection point use.
|
|
29
|
+
|
|
30
|
+
Under the refactored implementation, "session registration" is done by client on create_ticket as needed;
|
|
31
|
+
kept here as a no-op entry point to avoid breaking old calls from manifest.extensions.
|
|
32
|
+
info parameter reserved for compatibility with old signature.
|
|
33
|
+
"""
|
|
34
|
+
# Trigger client singleton init to surface config errors early during startup
|
|
35
|
+
_ = get_client()
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# --------------------------------------------------------------------
|
|
40
|
+
# Deprecated shim (for old tests / old external code gradual migration only; new code should not depend)
|
|
41
|
+
# --------------------------------------------------------------------
|
|
42
|
+
def get_queue():
|
|
43
|
+
warnings.warn(
|
|
44
|
+
"human_handoff.queue.get_queue() is deprecated; "
|
|
45
|
+
"use adapters.factory.get_client() or core.service.get_default_service() instead.",
|
|
46
|
+
DeprecationWarning,
|
|
47
|
+
stacklevel=2,
|
|
48
|
+
)
|
|
49
|
+
return get_client()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# Old symbol aliases (some integrators may directly import)
|
|
53
|
+
HandoffState = TicketStatusEnum
|
|
54
|
+
HandoffRecord = Ticket
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
__all__ = [
|
|
58
|
+
"HandoffRecord",
|
|
59
|
+
"HandoffState",
|
|
60
|
+
"attach_session",
|
|
61
|
+
"get_queue",
|
|
62
|
+
]
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""human-handoff FastAPI sub-router.
|
|
2
|
+
|
|
3
|
+
Mounted on skeleton: app.include_router(router, prefix="/api/v1/handoff")
|
|
4
|
+
|
|
5
|
+
Refactoring notes:
|
|
6
|
+
- All business logic delegated to core.service.HandoffService
|
|
7
|
+
- Response fields remain fully consistent with Phase 2 (to_legacy_dict), not breaking Web Demo
|
|
8
|
+
- New /admin/* sub-routes for Phase 3 Path A ticket agent dashboard
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import List, Optional
|
|
13
|
+
|
|
14
|
+
from fastapi import APIRouter, HTTPException, Query
|
|
15
|
+
from pydantic import BaseModel, Field
|
|
16
|
+
|
|
17
|
+
from .core.models import TicketStatusEnum
|
|
18
|
+
from .core.service import get_default_service
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
router = APIRouter()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# Request body
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
class RequestBody(BaseModel):
|
|
28
|
+
session_id: str = Field(..., max_length=64)
|
|
29
|
+
reason: Optional[str] = Field(default="", max_length=512)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ConnectBody(BaseModel):
|
|
33
|
+
session_id: str = Field(..., max_length=64)
|
|
34
|
+
agent_id: str = Field(..., max_length=64)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class CancelBody(BaseModel):
|
|
38
|
+
session_id: str = Field(..., max_length=64)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DetectBody(BaseModel):
|
|
42
|
+
"""Body for handoff intent detection (reuses intent_detector pure function)."""
|
|
43
|
+
|
|
44
|
+
text: str = Field(..., max_length=4096)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class FeedbackBody(BaseModel):
|
|
48
|
+
"""Body for submitting a post-call satisfaction rating."""
|
|
49
|
+
|
|
50
|
+
session_id: str = Field(..., max_length=64)
|
|
51
|
+
rating: int = Field(..., ge=1, le=5)
|
|
52
|
+
comment: Optional[str] = Field(default="", max_length=1000)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class AdminUpdateBody(BaseModel):
|
|
56
|
+
status: str = Field(..., max_length=32)
|
|
57
|
+
agent_id: Optional[str] = Field(default=None, max_length=64)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
# Existing endpoints (fully compatible with Phase 2)
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
@router.get("/status")
|
|
64
|
+
def overall() -> dict:
|
|
65
|
+
return {"code": 0, "data": get_default_service().overall_status().to_dict()}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@router.get("/{session_id}")
|
|
69
|
+
def session_status(session_id: str) -> dict:
|
|
70
|
+
ticket = get_default_service().get_by_session(session_id)
|
|
71
|
+
if ticket is None:
|
|
72
|
+
raise HTTPException(
|
|
73
|
+
status_code=404, detail=f"session not tracked: {session_id}"
|
|
74
|
+
)
|
|
75
|
+
return {"code": 0, "data": ticket.to_legacy_dict()}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@router.post("/request")
|
|
79
|
+
def request_handoff(body: RequestBody) -> dict:
|
|
80
|
+
ticket = get_default_service().request(
|
|
81
|
+
body.session_id, reason=body.reason or ""
|
|
82
|
+
)
|
|
83
|
+
return {"code": 0, "data": ticket.to_legacy_dict()}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@router.post("/connect")
|
|
87
|
+
def connect(body: ConnectBody) -> dict:
|
|
88
|
+
try:
|
|
89
|
+
ticket = get_default_service().connect(body.session_id, body.agent_id)
|
|
90
|
+
except ValueError as exc:
|
|
91
|
+
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
|
92
|
+
except RuntimeError as exc:
|
|
93
|
+
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
|
94
|
+
return {"code": 0, "data": ticket.to_legacy_dict()}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@router.post("/cancel")
|
|
98
|
+
def cancel(body: CancelBody) -> dict:
|
|
99
|
+
try:
|
|
100
|
+
ticket = get_default_service().cancel(body.session_id)
|
|
101
|
+
except ValueError as exc:
|
|
102
|
+
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
|
103
|
+
return {"code": 0, "data": ticket.to_legacy_dict()}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# ---------------------------------------------------------------------------
|
|
107
|
+
# Intent detection + satisfaction feedback (issue 6 / issue 7)
|
|
108
|
+
# ---------------------------------------------------------------------------
|
|
109
|
+
@router.post("/detect")
|
|
110
|
+
def detect_handoff(body: DetectBody) -> dict:
|
|
111
|
+
"""Pure intent detection: returns whether the text implies a handoff request.
|
|
112
|
+
|
|
113
|
+
Reuses core.intent_detector (same logic the backend uses internally), so the
|
|
114
|
+
frontend fast-path and the backend stay in sync.
|
|
115
|
+
"""
|
|
116
|
+
from .core.intent_detector import is_handoff_intent
|
|
117
|
+
|
|
118
|
+
matched = bool(is_handoff_intent(body.text or ""))
|
|
119
|
+
return {"code": 0, "data": {"matched": matched, "text": body.text or ""}}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@router.post("/feedback")
|
|
123
|
+
def submit_feedback(body: FeedbackBody) -> dict:
|
|
124
|
+
"""Persist a post-call CSAT rating and attach it to the session's ticket (if any)."""
|
|
125
|
+
result = get_default_service().submit_feedback(
|
|
126
|
+
body.session_id, body.rating, body.comment or ""
|
|
127
|
+
)
|
|
128
|
+
return {"code": 0, "data": result}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@router.get("/feedback/{session_id}")
|
|
132
|
+
def get_feedback(session_id: str) -> dict:
|
|
133
|
+
"""Return the stored feedback for a session (404 if not yet rated)."""
|
|
134
|
+
from .feedback_store import get_feedback as _get
|
|
135
|
+
|
|
136
|
+
fb = _get(session_id)
|
|
137
|
+
if fb is None:
|
|
138
|
+
raise HTTPException(
|
|
139
|
+
status_code=404, detail=f"no feedback for session: {session_id}"
|
|
140
|
+
)
|
|
141
|
+
return {"code": 0, "data": {"session_id": session_id, "feedback": fb}}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# ---------------------------------------------------------------------------
|
|
145
|
+
# New: Ticket agent dashboard endpoints (Phase 3 Path A)
|
|
146
|
+
# Path: /admin/tickets
|
|
147
|
+
# These endpoints output "new version" fields (including ticket_id / subject / priority / transcript),
|
|
148
|
+
# coexisting with the legacy field format of existing /handoff/{session_id}.
|
|
149
|
+
# ---------------------------------------------------------------------------
|
|
150
|
+
@router.get("/admin/tickets")
|
|
151
|
+
def admin_list_tickets(
|
|
152
|
+
limit: int = Query(default=50, ge=1, le=200),
|
|
153
|
+
status: Optional[str] = Query(default=None, max_length=32),
|
|
154
|
+
) -> dict:
|
|
155
|
+
items = get_default_service().list_tickets(limit=limit, status=status)
|
|
156
|
+
return {
|
|
157
|
+
"code": 0,
|
|
158
|
+
"data": {
|
|
159
|
+
"items": [t.to_dict() for t in items],
|
|
160
|
+
"count": len(items),
|
|
161
|
+
},
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@router.get("/admin/tickets/{ticket_id}")
|
|
166
|
+
def admin_get_ticket(ticket_id: str) -> dict:
|
|
167
|
+
status = get_default_service().query_ticket(ticket_id)
|
|
168
|
+
if status is None:
|
|
169
|
+
raise HTTPException(status_code=404, detail=f"ticket not found: {ticket_id}")
|
|
170
|
+
items = [
|
|
171
|
+
t for t in get_default_service().list_tickets(limit=200)
|
|
172
|
+
if t.ticket_id == ticket_id
|
|
173
|
+
]
|
|
174
|
+
if not items:
|
|
175
|
+
raise HTTPException(status_code=404, detail=f"ticket not found: {ticket_id}")
|
|
176
|
+
return {"code": 0, "data": items[0].to_dict()}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@router.post("/admin/tickets/{ticket_id}/status")
|
|
180
|
+
def admin_update_status(ticket_id: str, body: AdminUpdateBody) -> dict:
|
|
181
|
+
# Validate status value
|
|
182
|
+
try:
|
|
183
|
+
TicketStatusEnum(body.status)
|
|
184
|
+
except ValueError as exc:
|
|
185
|
+
raise HTTPException(
|
|
186
|
+
status_code=400,
|
|
187
|
+
detail=f"invalid status: {body.status}",
|
|
188
|
+
) from exc
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
ticket = get_default_service().update_ticket_status(
|
|
192
|
+
ticket_id, body.status, agent_id=body.agent_id
|
|
193
|
+
)
|
|
194
|
+
except NotImplementedError as exc:
|
|
195
|
+
raise HTTPException(status_code=405, detail=str(exc)) from exc
|
|
196
|
+
except ValueError as exc:
|
|
197
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
198
|
+
|
|
199
|
+
if ticket is None:
|
|
200
|
+
raise HTTPException(status_code=404, detail=f"ticket not found: {ticket_id}")
|
|
201
|
+
return {"code": 0, "data": ticket.to_dict()}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Best-effort linkage with session-summary (optional capability).
|
|
2
|
+
|
|
3
|
+
When a handoff ticket is created, auto-generate a session summary and attach it to the ticket,
|
|
4
|
+
so agents can see customer issue context directly in the ticket details — no need to manually click "generate summary".
|
|
5
|
+
|
|
6
|
+
Design principles:
|
|
7
|
+
- Soft dependency: silently no-ops when session-summary is not installed; does not affect the handoff main flow.
|
|
8
|
+
- Non-blocking: defaults to heuristic summary (local, zero latency); does not call LLM in the ticket creation chain.
|
|
9
|
+
- Decoupled: dynamically loaded via conversation-core's _capability_loader;
|
|
10
|
+
human-handoff has no static import dependency on session-summary.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import importlib.util
|
|
15
|
+
import logging
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any, Optional
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
_loader: Optional[Any] = None
|
|
22
|
+
_loader_resolved = False
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _get_loader() -> Optional[Any]:
|
|
26
|
+
"""Dynamically load conversation-core's _capability_loader (no relative imports, can load independently)."""
|
|
27
|
+
global _loader, _loader_resolved
|
|
28
|
+
if _loader_resolved:
|
|
29
|
+
return _loader
|
|
30
|
+
_loader_resolved = True
|
|
31
|
+
try:
|
|
32
|
+
# <root>/capabilities/human-handoff/src/summary_link.py → parents[3] = <root>
|
|
33
|
+
repo_root = Path(__file__).resolve().parents[3]
|
|
34
|
+
loader_path = (
|
|
35
|
+
repo_root / "capabilities" / "conversation-core" / "src" / "_capability_loader.py"
|
|
36
|
+
)
|
|
37
|
+
if not loader_path.is_file():
|
|
38
|
+
return None
|
|
39
|
+
spec = importlib.util.spec_from_file_location("_hh_capability_loader", loader_path)
|
|
40
|
+
if spec is None or spec.loader is None:
|
|
41
|
+
return None
|
|
42
|
+
mod = importlib.util.module_from_spec(spec)
|
|
43
|
+
spec.loader.exec_module(mod)
|
|
44
|
+
_loader = mod
|
|
45
|
+
except Exception as exc: # noqa: BLE001
|
|
46
|
+
logger.info("session-summary link unavailable: %s", exc)
|
|
47
|
+
_loader = None
|
|
48
|
+
return _loader
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def attach_summary_to_ticket(ticket: Any) -> None:
|
|
52
|
+
"""Generate an LLM narrative summary of the session chat and write it into the ticket's
|
|
53
|
+
Description field (from AI connect → handoff trigger).
|
|
54
|
+
|
|
55
|
+
The ticket Description becomes an LLM summary of the conversation, so agents see the
|
|
56
|
+
context directly without a separate "Session Summary" block. session-summary not
|
|
57
|
+
installed / any exception → silently skip (does not affect ticket creation main flow).
|
|
58
|
+
"""
|
|
59
|
+
loader = _get_loader()
|
|
60
|
+
if loader is None:
|
|
61
|
+
return
|
|
62
|
+
try:
|
|
63
|
+
recorder_mod = loader.try_load_capability("session-summary", "src/recorder.py")
|
|
64
|
+
summarizer_mod = loader.try_load_capability("session-summary", "src/summarizer.py")
|
|
65
|
+
if recorder_mod is None or summarizer_mod is None:
|
|
66
|
+
return
|
|
67
|
+
session_id = ticket.user_id
|
|
68
|
+
recorder = recorder_mod.get_recorder()
|
|
69
|
+
rec = recorder.get(session_id)
|
|
70
|
+
if rec is None:
|
|
71
|
+
return # No transcript recorded for this session (e.g. manually inserted test ticket), skip
|
|
72
|
+
# LLM-generated one-paragraph summary of the chat → ticket Description.
|
|
73
|
+
paragraph = summarizer_mod.summarize_paragraph(rec)
|
|
74
|
+
if paragraph:
|
|
75
|
+
ticket.description = paragraph
|
|
76
|
+
except Exception as exc: # noqa: BLE001
|
|
77
|
+
logger.info("attach description summary skipped: %s", exc)
|