@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,165 @@
|
|
|
1
|
+
# session-summary capability self-describing manifest
|
|
2
|
+
# Type: capability (optional install)
|
|
3
|
+
|
|
4
|
+
name: "session-summary"
|
|
5
|
+
version: "1.0.0"
|
|
6
|
+
type: "capability"
|
|
7
|
+
description: "Automatic session summarization + structured summary output (turn recording / key fact extraction / LLM secondary summarization)"
|
|
8
|
+
|
|
9
|
+
dependencies:
|
|
10
|
+
- name: "conversation-core"
|
|
11
|
+
version: ">=1.0.0,<2.0.0"
|
|
12
|
+
|
|
13
|
+
# Optional integration (soft dependency, not hard dependency):
|
|
14
|
+
# When human-handoff is also installed, ticket creation will auto-trigger this capability
|
|
15
|
+
# to generate a summary for that session and attach it to the ticket. Agents can see the
|
|
16
|
+
# customer issue context directly in the dashboard ticket details (no need to manually click "generate summary").
|
|
17
|
+
# The integration is implemented as a best-effort call from human-handoff (capabilities/human-handoff/src/summary_link.py);
|
|
18
|
+
# when this capability is not installed, the call silently skips without affecting the handoff main flow.
|
|
19
|
+
optional_integrations:
|
|
20
|
+
- capability: "human-handoff"
|
|
21
|
+
trigger: "ticket.create"
|
|
22
|
+
behavior: "auto-finalize session summary (heuristic) and embed into ticket payload"
|
|
23
|
+
|
|
24
|
+
extensions:
|
|
25
|
+
# Archive as a user turn before text injection
|
|
26
|
+
- inject_at: "agent.before_push_text"
|
|
27
|
+
inline_code: |
|
|
28
|
+
# [session-summary] record user turn
|
|
29
|
+
from ._capability_loader import try_load_capability
|
|
30
|
+
_ss_recorder = try_load_capability("session-summary", "src/recorder.py")
|
|
31
|
+
if _ss_recorder is not None:
|
|
32
|
+
_ss_recorder.record_user_turn(session_id, text)
|
|
33
|
+
- inject_at: "agent.after_start"
|
|
34
|
+
inline_code: |
|
|
35
|
+
# [session-summary] open session
|
|
36
|
+
from ._capability_loader import try_load_capability
|
|
37
|
+
_ss_recorder = try_load_capability("session-summary", "src/recorder.py")
|
|
38
|
+
if _ss_recorder is not None:
|
|
39
|
+
_ss_recorder.open_session(session_id)
|
|
40
|
+
- inject_at: "server.router_extension"
|
|
41
|
+
inline_code: |
|
|
42
|
+
# [session-summary] mount sub-router
|
|
43
|
+
from ._capability_loader import try_load_capability as _try_load_capability
|
|
44
|
+
_ss_router_mod = _try_load_capability("session-summary", "src/router.py")
|
|
45
|
+
if _ss_router_mod is not None and hasattr(_ss_router_mod, "router"):
|
|
46
|
+
app.include_router(
|
|
47
|
+
_ss_router_mod.router, prefix="/api/v1/summary", tags=["session-summary"]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
config:
|
|
51
|
+
adapter:
|
|
52
|
+
description: "Summary write-back target: mock (default, no external deps) | local_json | default_rest"
|
|
53
|
+
default: "mock"
|
|
54
|
+
env: "SS_ADAPTER"
|
|
55
|
+
options:
|
|
56
|
+
mock: "Log-only + returns mock record_id, suitable for local / Path A demos"
|
|
57
|
+
local_json: "Append to local JSONL (data/_writeback.jsonl)"
|
|
58
|
+
default_rest: "POST to real CRM / ticketing system (requires SS_REST_BASE_URL[/SS_REST_TOKEN])"
|
|
59
|
+
storage_dir:
|
|
60
|
+
description: "Summary file landing directory (one JSON per session)"
|
|
61
|
+
default: "capabilities/session-summary/data/"
|
|
62
|
+
llm_summary:
|
|
63
|
+
description: "Whether to enable LLM secondary summarization (consumes LLM_API_KEY; set false for offline scenarios)"
|
|
64
|
+
default: true
|
|
65
|
+
retention_days:
|
|
66
|
+
description: "Summary retention days; auto-cleanup after expiry"
|
|
67
|
+
default: 30
|
|
68
|
+
redact_patterns:
|
|
69
|
+
description: "Fields to auto-redact before writing summaries (same standard as skeleton log_redaction)"
|
|
70
|
+
default:
|
|
71
|
+
- "secret_id"
|
|
72
|
+
- "secret_key"
|
|
73
|
+
- "api_key"
|
|
74
|
+
- "token"
|
|
75
|
+
|
|
76
|
+
endpoints:
|
|
77
|
+
- method: GET
|
|
78
|
+
path: /api/v1/summary/{session_id}
|
|
79
|
+
description: Get single session summary (includes turn list and structured summary)
|
|
80
|
+
- method: POST
|
|
81
|
+
path: /api/v1/summary/{session_id}/finalize
|
|
82
|
+
description: Close session and trigger LLM secondary summarization
|
|
83
|
+
- method: GET
|
|
84
|
+
path: /api/v1/summary/_list
|
|
85
|
+
description: List most recent N summaries (_offset / _limit query)
|
|
86
|
+
|
|
87
|
+
integration:
|
|
88
|
+
mode: "auto"
|
|
89
|
+
auto_adapters:
|
|
90
|
+
- tech_stack: ["express", "koa", "fastify"]
|
|
91
|
+
adapter: "node-backend"
|
|
92
|
+
- tech_stack: ["flask", "fastapi", "django"]
|
|
93
|
+
adapter: "python-backend"
|
|
94
|
+
- tech_stack: ["react", "vue", "angular"]
|
|
95
|
+
adapter: "frontend-spa"
|
|
96
|
+
description: "Append summary viewer panel to SPA"
|
|
97
|
+
fallback:
|
|
98
|
+
guided_templates:
|
|
99
|
+
- "../../auto_adapters/integration_templates/generic-backend.md"
|
|
100
|
+
manual_api:
|
|
101
|
+
rest_endpoint: "/api/v1/summary"
|
|
102
|
+
|
|
103
|
+
security:
|
|
104
|
+
log_redaction:
|
|
105
|
+
enabled: true
|
|
106
|
+
patterns: ["secret_id", "secret_key", "api_key", "token", "credential"]
|
|
107
|
+
storage:
|
|
108
|
+
file_permission: "0600"
|
|
109
|
+
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
# Business contract (Phase 3 new; follows references/business-contract-spec.md v1.0)
|
|
112
|
+
# Note: Source code for this capability is not refactored in this phase (compromise strategy);
|
|
113
|
+
# only declares the external contract for contract-adapt.py consumption.
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
business_contract:
|
|
116
|
+
port_class: "src.adapters.base.SummarySink"
|
|
117
|
+
default_adapter: "src.adapters.default_rest.DefaultRestSink"
|
|
118
|
+
mock_adapter: "src.adapters.mock.MockSink"
|
|
119
|
+
customization_sop: "INTERFACE_ADAPT.md"
|
|
120
|
+
external_apis:
|
|
121
|
+
# CRM / Ticketing: write session summary back
|
|
122
|
+
- name: summary.write_to_crm
|
|
123
|
+
direction: outbound
|
|
124
|
+
method: POST
|
|
125
|
+
path: /sessions/{session_id}/summary
|
|
126
|
+
description: "After session finalize, write structured summary to user's CRM / ticketing / data platform"
|
|
127
|
+
request_schema:
|
|
128
|
+
session_id: string
|
|
129
|
+
user_id: string
|
|
130
|
+
topic: string
|
|
131
|
+
outcome: enum[resolved, transferred, abandoned, follow_up]
|
|
132
|
+
next_actions: string[]
|
|
133
|
+
full_transcript: string[]
|
|
134
|
+
structured_facts: object
|
|
135
|
+
finalized_at: int
|
|
136
|
+
response_schema:
|
|
137
|
+
record_id: string
|
|
138
|
+
accepted: bool
|
|
139
|
+
adapter_slots:
|
|
140
|
+
- request.topic
|
|
141
|
+
- request.outcome
|
|
142
|
+
- request.next_actions
|
|
143
|
+
- request.structured_facts
|
|
144
|
+
- response.record_id
|
|
145
|
+
auth:
|
|
146
|
+
type: bearer
|
|
147
|
+
location: header
|
|
148
|
+
name: Authorization
|
|
149
|
+
timeout_ms: 5000
|
|
150
|
+
|
|
151
|
+
# Secondary summarization call (reuses conversation-core LLM interface; declared here only as reference)
|
|
152
|
+
- name: summary.llm_summarize
|
|
153
|
+
direction: outbound
|
|
154
|
+
description: "Reuses conversation-core llm.chat_completions interface;
|
|
155
|
+
uses same LLM_API_URL / LLM_API_KEY config; no separate adaptation needed."
|
|
156
|
+
method: POST
|
|
157
|
+
path: "(see conversation-core: llm.chat_completions)"
|
|
158
|
+
request_schema: {}
|
|
159
|
+
response_schema: {}
|
|
160
|
+
adapter_slots: []
|
|
161
|
+
|
|
162
|
+
acceptance:
|
|
163
|
+
- "Each user/assistant turn persisted as JSONL"
|
|
164
|
+
- "After calling finalize, produce structured summary (includes topics / next_actions)"
|
|
165
|
+
- "File permissions set to 0600, sensitive fields auto-redacted"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""session-summary write-back adapter layer.
|
|
2
|
+
|
|
3
|
+
Consistent "three-tier adapter + factory" paradigm matching knowledge-base / human-handoff:
|
|
4
|
+
mock — Default; summary only logged + returns mock record_id, no external system needed,
|
|
5
|
+
allows local / Path A demos to immediately see "write-back success".
|
|
6
|
+
local_json — Append to local JSONL (data/_writeback.jsonl), can be checked offline.
|
|
7
|
+
default_rest — POST to user's real CRM / ticketing system (SS_REST_BASE_URL).
|
|
8
|
+
|
|
9
|
+
External contract see manifest.business_contract.external_apis[summary.write_to_crm].
|
|
10
|
+
When interface fields don't align, refer to INTERFACE_ADAPT.md for request/response mapping.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import abc
|
|
15
|
+
from typing import Any, Dict
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SummarySink(abc.ABC):
|
|
19
|
+
"""Unified abstraction for session summary write-back targets."""
|
|
20
|
+
|
|
21
|
+
name: str = "base"
|
|
22
|
+
|
|
23
|
+
@abc.abstractmethod
|
|
24
|
+
def write(self, summary_record: Dict[str, Any]) -> Dict[str, Any]:
|
|
25
|
+
"""Write a finalized summary record to the target system.
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
dict: {"record_id": str, "accepted": bool, "sink": str}
|
|
30
|
+
"""
|
|
31
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""default_rest write-back sink — POST to user's real CRM / ticketing system.
|
|
2
|
+
|
|
3
|
+
Environment variables:
|
|
4
|
+
SS_REST_BASE_URL Required, e.g. https://crm.example.com/api
|
|
5
|
+
SS_REST_TOKEN Optional, Bearer Token
|
|
6
|
+
SS_REST_TIMEOUT_MS Optional, default 5000
|
|
7
|
+
|
|
8
|
+
Aligned with manifest.business_contract.external_apis[summary.write_to_crm]:
|
|
9
|
+
POST {base}/sessions/{session_id}/summary
|
|
10
|
+
Adjust _build_payload in this file or refer to INTERFACE_ADAPT.md when fields don't align.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
import os
|
|
16
|
+
from typing import Any, Dict
|
|
17
|
+
|
|
18
|
+
from .base import SummarySink
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DefaultRestSink(SummarySink):
|
|
24
|
+
name = "default_rest"
|
|
25
|
+
|
|
26
|
+
def __init__(self) -> None:
|
|
27
|
+
self._base = (os.getenv("SS_REST_BASE_URL") or "").rstrip("/")
|
|
28
|
+
self._token = os.getenv("SS_REST_TOKEN") or ""
|
|
29
|
+
self._timeout = max(int(os.getenv("SS_REST_TIMEOUT_MS", "5000")), 100) / 1000.0
|
|
30
|
+
if not self._base:
|
|
31
|
+
raise RuntimeError("SS_REST_BASE_URL not configured for default_rest sink")
|
|
32
|
+
# Security: forbid plaintext http (except localhost debugging), prevent credentials / summaries over unencrypted channels
|
|
33
|
+
if not self._base.startswith("https://") and "localhost" not in self._base:
|
|
34
|
+
raise RuntimeError(f"SS_REST_BASE_URL must use HTTPS: {self._base}")
|
|
35
|
+
|
|
36
|
+
def _build_payload(self, rec: Dict[str, Any]) -> Dict[str, Any]:
|
|
37
|
+
summary = rec.get("summary") or {}
|
|
38
|
+
return {
|
|
39
|
+
"session_id": rec.get("session_id"),
|
|
40
|
+
"topic": (summary.get("topics") or [None])[0],
|
|
41
|
+
"outcome": summary.get("outcome", "follow_up"),
|
|
42
|
+
"next_actions": summary.get("next_actions") or [],
|
|
43
|
+
"full_transcript": [t.get("text", "") for t in rec.get("turns") or []],
|
|
44
|
+
"structured_facts": summary,
|
|
45
|
+
"finalized_at": rec.get("closed_at"),
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
def write(self, summary_record: Dict[str, Any]) -> Dict[str, Any]:
|
|
49
|
+
import requests
|
|
50
|
+
|
|
51
|
+
sid = summary_record.get("session_id", "")
|
|
52
|
+
url = f"{self._base}/sessions/{sid}/summary"
|
|
53
|
+
headers = {"Content-Type": "application/json"}
|
|
54
|
+
if self._token:
|
|
55
|
+
headers["Authorization"] = f"Bearer {self._token}"
|
|
56
|
+
resp = requests.post(url, json=self._build_payload(summary_record),
|
|
57
|
+
headers=headers, timeout=self._timeout)
|
|
58
|
+
resp.raise_for_status()
|
|
59
|
+
try:
|
|
60
|
+
data = resp.json()
|
|
61
|
+
except ValueError:
|
|
62
|
+
data = {}
|
|
63
|
+
return {
|
|
64
|
+
"record_id": data.get("record_id", ""),
|
|
65
|
+
"accepted": bool(data.get("accepted", True)),
|
|
66
|
+
"sink": self.name,
|
|
67
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Write-back sink factory — selects implementation by env SS_ADAPTER (consistent with KB/handoff factory paradigm).
|
|
2
|
+
|
|
3
|
+
SS_ADAPTER=mock Default; no external dependencies
|
|
4
|
+
SS_ADAPTER=local_json Write local JSONL
|
|
5
|
+
SS_ADAPTER=default_rest POST to real CRM (requires SS_REST_BASE_URL)
|
|
6
|
+
|
|
7
|
+
When any implementation initialization fails (e.g. default_rest missing base_url), safely degrades to mock,
|
|
8
|
+
ensuring the finalize flow is never interrupted due to unavailable write-back targets.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
import threading
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
from .base import SummarySink
|
|
18
|
+
from .mock import MockSink
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
_lock = threading.RLock()
|
|
23
|
+
_instance: Optional[SummarySink] = None
|
|
24
|
+
_instance_key: Optional[str] = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _build(name: str) -> SummarySink:
|
|
28
|
+
name = (name or "mock").strip().lower()
|
|
29
|
+
if name == "local_json":
|
|
30
|
+
from .local_json import LocalJsonSink
|
|
31
|
+
return LocalJsonSink()
|
|
32
|
+
if name == "default_rest":
|
|
33
|
+
from .default_rest import DefaultRestSink
|
|
34
|
+
return DefaultRestSink()
|
|
35
|
+
return MockSink()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_sink() -> SummarySink:
|
|
39
|
+
"""Return the currently configured write-back sink (cached by SS_ADAPTER; rebuilds on env change)."""
|
|
40
|
+
global _instance, _instance_key
|
|
41
|
+
key = (os.getenv("SS_ADAPTER", "mock") or "mock").strip().lower()
|
|
42
|
+
with _lock:
|
|
43
|
+
if _instance is not None and _instance_key == key:
|
|
44
|
+
return _instance
|
|
45
|
+
try:
|
|
46
|
+
_instance = _build(key)
|
|
47
|
+
except Exception as exc: # noqa: BLE001
|
|
48
|
+
logger.warning("session-summary sink '%s' init failed, fallback to mock: %s", key, exc)
|
|
49
|
+
_instance = MockSink()
|
|
50
|
+
_instance_key = key
|
|
51
|
+
return _instance
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""local_json write-back sink — append to local JSONL for offline verification."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict
|
|
11
|
+
|
|
12
|
+
from .base import SummarySink
|
|
13
|
+
|
|
14
|
+
_LOCK = threading.RLock()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LocalJsonSink(SummarySink):
|
|
18
|
+
name = "local_json"
|
|
19
|
+
|
|
20
|
+
def __init__(self) -> None:
|
|
21
|
+
base = os.getenv(
|
|
22
|
+
"SS_STORAGE_DIR",
|
|
23
|
+
str(Path(__file__).resolve().parents[2] / "data"),
|
|
24
|
+
)
|
|
25
|
+
self._file = Path(base) / "_writeback.jsonl"
|
|
26
|
+
self._file.parent.mkdir(parents=True, exist_ok=True)
|
|
27
|
+
|
|
28
|
+
def write(self, summary_record: Dict[str, Any]) -> Dict[str, Any]:
|
|
29
|
+
sid = str(summary_record.get("session_id", ""))
|
|
30
|
+
record_id = "CRM-LOCAL-" + hashlib.md5(sid.encode("utf-8")).hexdigest()[:10].upper()
|
|
31
|
+
line = json.dumps(
|
|
32
|
+
{"record_id": record_id, "written_at": int(time.time()), "record": summary_record},
|
|
33
|
+
ensure_ascii=False,
|
|
34
|
+
)
|
|
35
|
+
with _LOCK:
|
|
36
|
+
with self._file.open("a", encoding="utf-8") as f:
|
|
37
|
+
f.write(line + "\n")
|
|
38
|
+
try:
|
|
39
|
+
os.chmod(self._file, 0o600)
|
|
40
|
+
except OSError:
|
|
41
|
+
pass
|
|
42
|
+
return {"record_id": record_id, "accepted": True, "sink": self.name}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""mock write-back sink — default implementation, no external dependencies, suitable for demos."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import hashlib
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, Dict
|
|
7
|
+
|
|
8
|
+
from .base import SummarySink
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MockSink(SummarySink):
|
|
14
|
+
name = "mock"
|
|
15
|
+
|
|
16
|
+
def write(self, summary_record: Dict[str, Any]) -> Dict[str, Any]:
|
|
17
|
+
sid = str(summary_record.get("session_id", ""))
|
|
18
|
+
record_id = "CRM-MOCK-" + hashlib.md5(sid.encode("utf-8")).hexdigest()[:10].upper()
|
|
19
|
+
topics = (summary_record.get("summary") or {}).get("topics") or []
|
|
20
|
+
logger.info("[session-summary][mock] write-back session=%s topics=%s -> %s",
|
|
21
|
+
sid, topics[:3], record_id)
|
|
22
|
+
return {"record_id": record_id, "accepted": True, "sink": self.name, "_mock": True}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""Session turn recorder.
|
|
2
|
+
|
|
3
|
+
Persistence strategy:
|
|
4
|
+
- Each session generates ``{session_id}.json`` under storage_dir, permissions 0600.
|
|
5
|
+
- Sensitive fields redacted before writing (same standard as skeleton log_filter).
|
|
6
|
+
- File structure:
|
|
7
|
+
{
|
|
8
|
+
"session_id": "...",
|
|
9
|
+
"opened_at": 1717830000.0,
|
|
10
|
+
"closed_at": null,
|
|
11
|
+
"turns": [
|
|
12
|
+
{"role": "user", "ts": 1717830001.0, "text": "..."},
|
|
13
|
+
{"role": "assistant", "ts": 1717830002.0, "text": "..."}
|
|
14
|
+
],
|
|
15
|
+
"summary": null # Populated after finalize
|
|
16
|
+
}
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
import threading
|
|
24
|
+
import time
|
|
25
|
+
from dataclasses import dataclass, field
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any, Dict, List, Optional
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
_DEFAULT_STORAGE_DIR = Path(
|
|
31
|
+
os.getenv(
|
|
32
|
+
"SS_STORAGE_DIR",
|
|
33
|
+
str(Path(__file__).resolve().parent.parent / "data"),
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
_RETENTION_DAYS = int(os.getenv("SS_RETENTION_DAYS", "30"))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Default redaction mode (aligned with skeleton log_filter)
|
|
40
|
+
_REDACT_PATTERNS = [
|
|
41
|
+
re.compile(r"(?i)(secret_id|secret_key|api_key|token|credential|authorization)\s*[:=]\s*([^\s,'\"\\]+)"),
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _redact(text: str) -> str:
|
|
46
|
+
if not text:
|
|
47
|
+
return text
|
|
48
|
+
out = text
|
|
49
|
+
for pat in _REDACT_PATTERNS:
|
|
50
|
+
out = pat.sub(lambda m: f"{m.group(1)}=***", out)
|
|
51
|
+
return out
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class Turn:
|
|
56
|
+
role: str
|
|
57
|
+
text: str
|
|
58
|
+
ts: float = field(default_factory=time.time)
|
|
59
|
+
|
|
60
|
+
def to_dict(self) -> dict:
|
|
61
|
+
return {"role": self.role, "text": self.text, "ts": self.ts}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class SessionRecord:
|
|
66
|
+
session_id: str
|
|
67
|
+
opened_at: float = field(default_factory=time.time)
|
|
68
|
+
closed_at: Optional[float] = None
|
|
69
|
+
turns: List[Turn] = field(default_factory=list)
|
|
70
|
+
summary: Optional[Dict[str, Any]] = None
|
|
71
|
+
|
|
72
|
+
def to_dict(self) -> dict:
|
|
73
|
+
return {
|
|
74
|
+
"session_id": self.session_id,
|
|
75
|
+
"opened_at": self.opened_at,
|
|
76
|
+
"closed_at": self.closed_at,
|
|
77
|
+
"turns": [t.to_dict() for t in self.turns],
|
|
78
|
+
"summary": self.summary,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Recorder:
|
|
83
|
+
def __init__(self, storage_dir: Optional[Path] = None) -> None:
|
|
84
|
+
self._lock = threading.RLock()
|
|
85
|
+
self._dir = Path(storage_dir or _DEFAULT_STORAGE_DIR)
|
|
86
|
+
self._dir.mkdir(parents=True, exist_ok=True)
|
|
87
|
+
self._cache: Dict[str, SessionRecord] = {}
|
|
88
|
+
self._cleanup_old_files()
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def storage_dir(self) -> Path:
|
|
92
|
+
return self._dir
|
|
93
|
+
|
|
94
|
+
def open(self, session_id: str) -> SessionRecord:
|
|
95
|
+
with self._lock:
|
|
96
|
+
rec = self._cache.get(session_id)
|
|
97
|
+
if rec is not None:
|
|
98
|
+
return rec
|
|
99
|
+
path = self._path(session_id)
|
|
100
|
+
if path.exists():
|
|
101
|
+
rec = self._load(path)
|
|
102
|
+
else:
|
|
103
|
+
rec = SessionRecord(session_id=session_id)
|
|
104
|
+
self._cache[session_id] = rec
|
|
105
|
+
self._persist(rec)
|
|
106
|
+
return rec
|
|
107
|
+
|
|
108
|
+
def add_turn(self, session_id: str, role: str, text: str) -> None:
|
|
109
|
+
if not text or not text.strip():
|
|
110
|
+
return
|
|
111
|
+
if role not in ("user", "assistant", "system", "tool"):
|
|
112
|
+
return
|
|
113
|
+
with self._lock:
|
|
114
|
+
rec = self._cache.get(session_id) or self.open(session_id)
|
|
115
|
+
rec.turns.append(Turn(role=role, text=_redact(text)[:4096]))
|
|
116
|
+
self._persist(rec)
|
|
117
|
+
|
|
118
|
+
def get(self, session_id: str) -> Optional[SessionRecord]:
|
|
119
|
+
with self._lock:
|
|
120
|
+
rec = self._cache.get(session_id)
|
|
121
|
+
if rec is not None:
|
|
122
|
+
return rec
|
|
123
|
+
path = self._path(session_id)
|
|
124
|
+
if path.exists():
|
|
125
|
+
rec = self._load(path)
|
|
126
|
+
self._cache[session_id] = rec
|
|
127
|
+
return rec
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
def list_recent(self, offset: int = 0, limit: int = 20) -> List[Dict[str, Any]]:
|
|
131
|
+
files = sorted(
|
|
132
|
+
self._dir.glob("*.json"),
|
|
133
|
+
key=lambda p: p.stat().st_mtime,
|
|
134
|
+
reverse=True,
|
|
135
|
+
)[offset : offset + limit]
|
|
136
|
+
out: List[Dict[str, Any]] = []
|
|
137
|
+
for p in files:
|
|
138
|
+
try:
|
|
139
|
+
out.append(json.loads(p.read_text(encoding="utf-8")))
|
|
140
|
+
except (OSError, json.JSONDecodeError):
|
|
141
|
+
continue
|
|
142
|
+
return out
|
|
143
|
+
|
|
144
|
+
def finalize(self, session_id: str, summary: Dict[str, Any]) -> SessionRecord:
|
|
145
|
+
with self._lock:
|
|
146
|
+
rec = self.get(session_id)
|
|
147
|
+
if rec is None:
|
|
148
|
+
raise ValueError(f"session not found: {session_id}")
|
|
149
|
+
rec.closed_at = time.time()
|
|
150
|
+
rec.summary = summary
|
|
151
|
+
self._persist(rec)
|
|
152
|
+
return rec
|
|
153
|
+
|
|
154
|
+
# ------------------------------------------------------------------
|
|
155
|
+
def _path(self, session_id: str) -> Path:
|
|
156
|
+
safe = re.sub(r"[^A-Za-z0-9_\-]", "_", session_id)[:64]
|
|
157
|
+
return self._dir / f"{safe}.json"
|
|
158
|
+
|
|
159
|
+
def _load(self, path: Path) -> SessionRecord:
|
|
160
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
161
|
+
rec = SessionRecord(
|
|
162
|
+
session_id=data.get("session_id", path.stem),
|
|
163
|
+
opened_at=float(data.get("opened_at", time.time())),
|
|
164
|
+
closed_at=data.get("closed_at"),
|
|
165
|
+
summary=data.get("summary"),
|
|
166
|
+
)
|
|
167
|
+
for t in data.get("turns") or []:
|
|
168
|
+
rec.turns.append(Turn(role=t.get("role", "user"), text=t.get("text", ""), ts=float(t.get("ts", 0))))
|
|
169
|
+
return rec
|
|
170
|
+
|
|
171
|
+
def _persist(self, rec: SessionRecord) -> None:
|
|
172
|
+
path = self._path(rec.session_id)
|
|
173
|
+
tmp = path.with_suffix(".json.tmp")
|
|
174
|
+
tmp.write_text(json.dumps(rec.to_dict(), ensure_ascii=False, indent=2), encoding="utf-8")
|
|
175
|
+
os.replace(tmp, path)
|
|
176
|
+
try:
|
|
177
|
+
os.chmod(path, 0o600)
|
|
178
|
+
except OSError:
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
def _cleanup_old_files(self) -> None:
|
|
182
|
+
cutoff = time.time() - _RETENTION_DAYS * 86400
|
|
183
|
+
for p in self._dir.glob("*.json"):
|
|
184
|
+
try:
|
|
185
|
+
if p.stat().st_mtime < cutoff:
|
|
186
|
+
p.unlink()
|
|
187
|
+
except OSError:
|
|
188
|
+
continue
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# ---------------------------------------------------------------------------
|
|
192
|
+
# Global singleton + manifest.extensions injection functions
|
|
193
|
+
# ---------------------------------------------------------------------------
|
|
194
|
+
_global_recorder = Recorder()
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def get_recorder() -> Recorder:
|
|
198
|
+
return _global_recorder
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def open_session(session_id: str) -> None:
|
|
202
|
+
_global_recorder.open(session_id)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def record_user_turn(session_id: str, text: str) -> None:
|
|
206
|
+
_global_recorder.add_turn(session_id, "user", text)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def record_assistant_turn(session_id: str, text: str) -> None:
|
|
210
|
+
_global_recorder.add_turn(session_id, "assistant", text)
|