ciris-agent 1.7.7__py3-none-any.whl
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.
- ciris_adapters/README.md +113 -0
- ciris_adapters/__init__.py +30 -0
- ciris_adapters/ciris_covenant_metrics/README.md +144 -0
- ciris_adapters/ciris_covenant_metrics/__init__.py +36 -0
- ciris_adapters/ciris_covenant_metrics/adapter.py +249 -0
- ciris_adapters/ciris_covenant_metrics/manifest.json +152 -0
- ciris_adapters/ciris_covenant_metrics/services.py +403 -0
- ciris_adapters/ciris_hosted_tools/__init__.py +24 -0
- ciris_adapters/ciris_hosted_tools/adapter.py +169 -0
- ciris_adapters/ciris_hosted_tools/manifest.json +94 -0
- ciris_adapters/ciris_hosted_tools/services.py +744 -0
- ciris_adapters/external_data_sql/README.md +559 -0
- ciris_adapters/external_data_sql/__init__.py +43 -0
- ciris_adapters/external_data_sql/adapter.py +144 -0
- ciris_adapters/external_data_sql/configurable.py +315 -0
- ciris_adapters/external_data_sql/dialects/__init__.py +37 -0
- ciris_adapters/external_data_sql/dialects/base.py +133 -0
- ciris_adapters/external_data_sql/dialects/mysql.py +63 -0
- ciris_adapters/external_data_sql/dialects/postgresql.py +59 -0
- ciris_adapters/external_data_sql/dialects/sqlite.py +62 -0
- ciris_adapters/external_data_sql/example_config.json +88 -0
- ciris_adapters/external_data_sql/example_privacy_schema.yaml +127 -0
- ciris_adapters/external_data_sql/manifest.json +195 -0
- ciris_adapters/external_data_sql/privacy_schema_loader.py +189 -0
- ciris_adapters/external_data_sql/protocol.py +101 -0
- ciris_adapters/external_data_sql/schemas.py +146 -0
- ciris_adapters/external_data_sql/service.py +1547 -0
- ciris_adapters/external_data_sql/service_old.py +492 -0
- ciris_adapters/home_assistant/__init__.py +63 -0
- ciris_adapters/home_assistant/adapter.py +201 -0
- ciris_adapters/home_assistant/communication_service.py +347 -0
- ciris_adapters/home_assistant/configurable.py +667 -0
- ciris_adapters/home_assistant/manifest.json +203 -0
- ciris_adapters/home_assistant/schemas.py +129 -0
- ciris_adapters/home_assistant/service.py +751 -0
- ciris_adapters/home_assistant/tool_service.py +441 -0
- ciris_adapters/mcp_client/__init__.py +82 -0
- ciris_adapters/mcp_client/adapter.py +847 -0
- ciris_adapters/mcp_client/config.py +280 -0
- ciris_adapters/mcp_client/configurable.py +422 -0
- ciris_adapters/mcp_client/manifest.json +185 -0
- ciris_adapters/mcp_client/mcp_communication_service.py +393 -0
- ciris_adapters/mcp_client/mcp_tool_service.py +463 -0
- ciris_adapters/mcp_client/mcp_wise_service.py +394 -0
- ciris_adapters/mcp_client/schemas.py +149 -0
- ciris_adapters/mcp_client/security.py +592 -0
- ciris_adapters/mcp_common/__init__.py +44 -0
- ciris_adapters/mcp_common/manifest.json +25 -0
- ciris_adapters/mcp_common/protocol.py +315 -0
- ciris_adapters/mcp_common/schemas.py +225 -0
- ciris_adapters/mcp_server/__init__.py +47 -0
- ciris_adapters/mcp_server/adapter.py +581 -0
- ciris_adapters/mcp_server/config.py +260 -0
- ciris_adapters/mcp_server/configurable.py +393 -0
- ciris_adapters/mcp_server/handlers.py +663 -0
- ciris_adapters/mcp_server/manifest.json +211 -0
- ciris_adapters/mcp_server/security.py +500 -0
- ciris_adapters/mock_llm/README.md +117 -0
- ciris_adapters/mock_llm/__init__.py +21 -0
- ciris_adapters/mock_llm/adapter.py +131 -0
- ciris_adapters/mock_llm/configurable.py +237 -0
- ciris_adapters/mock_llm/manifest.json +106 -0
- ciris_adapters/mock_llm/protocol.py +37 -0
- ciris_adapters/mock_llm/responses.py +520 -0
- ciris_adapters/mock_llm/responses_action_selection.py +1041 -0
- ciris_adapters/mock_llm/responses_epistemic.py +17 -0
- ciris_adapters/mock_llm/responses_feedback.py +27 -0
- ciris_adapters/mock_llm/schemas.py +35 -0
- ciris_adapters/mock_llm/service.py +294 -0
- ciris_adapters/navigation/__init__.py +21 -0
- ciris_adapters/navigation/adapter.py +129 -0
- ciris_adapters/navigation/configurable.py +239 -0
- ciris_adapters/navigation/manifest.json +104 -0
- ciris_adapters/navigation/service.py +487 -0
- ciris_adapters/reddit/README.md +132 -0
- ciris_adapters/reddit/REDDIT_ADAPTER_ANALYSIS.md +715 -0
- ciris_adapters/reddit/REDDIT_ADAPTER_SUMMARY.txt +278 -0
- ciris_adapters/reddit/REDDIT_ANALYSIS_INDEX.md +307 -0
- ciris_adapters/reddit/REDDIT_PRODUCTION_READINESS_PLAN.md +518 -0
- ciris_adapters/reddit/__init__.py +15 -0
- ciris_adapters/reddit/adapter.py +189 -0
- ciris_adapters/reddit/configurable.py +274 -0
- ciris_adapters/reddit/error_handler.py +307 -0
- ciris_adapters/reddit/manifest.json +218 -0
- ciris_adapters/reddit/observer.py +532 -0
- ciris_adapters/reddit/protocol.py +34 -0
- ciris_adapters/reddit/schemas.py +433 -0
- ciris_adapters/reddit/service.py +1471 -0
- ciris_adapters/sample_adapter/README.md +474 -0
- ciris_adapters/sample_adapter/__init__.py +45 -0
- ciris_adapters/sample_adapter/adapter.py +208 -0
- ciris_adapters/sample_adapter/configurable.py +469 -0
- ciris_adapters/sample_adapter/manifest.json +247 -0
- ciris_adapters/sample_adapter/services.py +486 -0
- ciris_adapters/weather/__init__.py +16 -0
- ciris_adapters/weather/adapter.py +130 -0
- ciris_adapters/weather/configurable.py +240 -0
- ciris_adapters/weather/manifest.json +156 -0
- ciris_adapters/weather/service.py +600 -0
- ciris_agent-1.7.7.dist-info/METADATA +284 -0
- ciris_agent-1.7.7.dist-info/RECORD +986 -0
- ciris_agent-1.7.7.dist-info/WHEEL +5 -0
- ciris_agent-1.7.7.dist-info/entry_points.txt +15 -0
- ciris_agent-1.7.7.dist-info/licenses/LICENSE +205 -0
- ciris_agent-1.7.7.dist-info/licenses/NOTICE +82 -0
- ciris_agent-1.7.7.dist-info/top_level.txt +4 -0
- ciris_engine/__init__.py +15 -0
- ciris_engine/ciris_templates/ally.yaml +632 -0
- ciris_engine/ciris_templates/default.yaml +411 -0
- ciris_engine/ciris_templates/echo-core.yaml +629 -0
- ciris_engine/ciris_templates/echo-speculative.yaml +764 -0
- ciris_engine/ciris_templates/echo.yaml +647 -0
- ciris_engine/ciris_templates/sage.yaml +332 -0
- ciris_engine/ciris_templates/scout.yaml +338 -0
- ciris_engine/ciris_templates/test.yaml +168 -0
- ciris_engine/cli.py +42 -0
- ciris_engine/config/CIRIS_SERVICES.json +19 -0
- ciris_engine/config/MODEL_CAPABILITIES.json +419 -0
- ciris_engine/config/PRICING_DATA.json +179 -0
- ciris_engine/config/__init__.py +50 -0
- ciris_engine/config/ciris_services.py +113 -0
- ciris_engine/config/model_capabilities.py +388 -0
- ciris_engine/config/pricing_models.py +276 -0
- ciris_engine/constants.py +35 -0
- ciris_engine/data/__init__.py +1 -0
- ciris_engine/data/covenant_1.0b.txt +978 -0
- ciris_engine/gui_static/11steps.svg +107 -0
- ciris_engine/gui_static/2x-schematics.png +0 -0
- ciris_engine/gui_static/404/index.html +1 -0
- ciris_engine/gui_static/404.html +1 -0
- ciris_engine/gui_static/_next/static/0edhkwDxd5UccTsCmtaBi/_buildManifest.js +1 -0
- ciris_engine/gui_static/_next/static/0edhkwDxd5UccTsCmtaBi/_ssgManifest.js +1 -0
- ciris_engine/gui_static/_next/static/U-3xTQao7hc2wnAi-Uekm/_buildManifest.js +1 -0
- ciris_engine/gui_static/_next/static/U-3xTQao7hc2wnAi-Uekm/_ssgManifest.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/3297-60e86ba0f8a7b040.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/3835-2aad4b7f5f8e4643.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/4499-99a0bc47de0b8975.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/4534-af88cd4ba6e99bff.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/4541-84b455f9e0dc4cfe.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/4789-61412711484754bb.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/6539-c6398bc9d7018430.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/704-8e827b26cc8c2d32.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/704-fb45d630f3192c6f.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/8072-de4952a2e6d2b33f.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/8315-b91d03a3949db0af.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/8386-f93a83ccbd789bd9.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/87c73c54-781a7f35148d5433.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/8903-fefea3339a02d41b.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/9090-e66485adf8d9d990.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/_not-found/page-a67d9808462c23b1.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/api-keys/page-2d7ee1583bbbd02e.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/api-keys/page-6a3c2bae6fe92b7b.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/consent/page-2ed3a035136bc4e8.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/consent/page-b2f5c91844a32422.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/page-25b90f89af3ea58c.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/page-b65d16c94ecaf69c.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/privacy/page-675b6d05c8f9184f.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/privacy/page-cbee2e1c8ab52145.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/settings/page-0f44da06697cf9f0.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/account/settings/page-563420253577edbf.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/adapters/page-1854631018bc32be.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/agents/page-8353752c176a7c70.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/agents/page-f61a529f110a6040.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/api-demo/page-7f19b9d20d39be28.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/api-demo/page-d1063938f249b8bd.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/audit/page-321b6728b8fff0bb.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/audit/page-ebac35ca961a1277.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/billing/page-6f3dc3bd02924f8e.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/billing/page-fa4a469f814c821a.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/comms/page-0d4f734269addd8f.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/comms/page-79227d426050089c.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/config/page-018d21d683b6e5bc.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/config/page-2aa5a5363ca2a371.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/consent/page-198373205fd316e2.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/consent/page-f2ca39e7713b13f8.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/dashboard/page-1dd5a196f643c60d.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/dashboard/page-530a04d3abbb8cda.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/docs/page-3193b06d094ab654.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/docs/page-330e996dedb87aba.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/layout-0a70f5fc460298b1.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/layout-21f2f99dd5b336e9.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/login/page-33240e6c6034a49d.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/login/page-68ffab6d54a7fdcd.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/logs/page-8a6167aecc4a475c.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/memory/page-9ca8c5d0056de3ff.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/memory/page-e961226941c18f81.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/page-6fdb065a787a4974.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/page-89f87d431be6064a.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/runtime/page-2e728b9c43aa164d.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/runtime/page-c7dd033dc40a72f0.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/services/page-ae9f0bdf11d01a95.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/services/page-b10feb79ca5d75e5.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/sessions/page-13ebe7ef1c16ae11.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/sessions/page-e6c82b16d617f785.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/setup/page-0beb5f5b5a5c20fc.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/setup/page-2595e729eae30c0e.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/status-dashboard/page-1037c987aecc3653.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/status-dashboard/page-2ffd147f6d3162ff.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/system/page-2c5798d58cafcd91.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/system/page-505b1ba4eceb01c3.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/test-auth/page-b0cad31d5cb1b2fa.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/test-auth/page-f3ecd7a8012df230.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/test-login/page-f35117fdc4105801.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/test-login/page-fb583a7924114906.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/test-sdk/page-50f116fd76935563.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/test-sdk/page-c37d8aa5ba623a44.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/tools/page-429aec7a707777ef.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/tools/page-5f705aad60e0c04e.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/users/page-13476b8b0f3808cc.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/users/page-7e500d154ed5bba4.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/wa/page-cc4a9d8a5cb44d08.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/app/wa/page-ec3e429efbc79230.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/framework-9d29490f5ba089ba.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/main-1f554952e47a82c4.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/main-app-26fa8aed029082e5.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/main-app-97b0486ef6bcef25.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/pages/_app-6ce685456e616eb2.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/pages/_error-d4bce98d93fe21e7.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- ciris_engine/gui_static/_next/static/chunks/webpack-fcebd240b7f8477d.js +1 -0
- ciris_engine/gui_static/_next/static/css/16b94b1fe0cc6e37.css +3 -0
- ciris_engine/gui_static/_next/static/css/77a24ceaae86deff.css +3 -0
- ciris_engine/gui_static/_next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
- ciris_engine/gui_static/_next/static/media/747892c23ea88013-s.woff2 +0 -0
- ciris_engine/gui_static/_next/static/media/8d697b304b401681-s.woff2 +0 -0
- ciris_engine/gui_static/_next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
- ciris_engine/gui_static/_next/static/media/9610d9e46709d722-s.woff2 +0 -0
- ciris_engine/gui_static/_next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
- ciris_engine/gui_static/_next/static/media/d8298875641ec7d4-s.p.woff2 +0 -0
- ciris_engine/gui_static/account/api-keys/index.html +1 -0
- ciris_engine/gui_static/account/api-keys/index.txt +27 -0
- ciris_engine/gui_static/account/consent/index.html +1 -0
- ciris_engine/gui_static/account/consent/index.txt +27 -0
- ciris_engine/gui_static/account/index.html +1 -0
- ciris_engine/gui_static/account/index.txt +27 -0
- ciris_engine/gui_static/account/privacy/index.html +1 -0
- ciris_engine/gui_static/account/privacy/index.txt +27 -0
- ciris_engine/gui_static/account/settings/index.html +1 -0
- ciris_engine/gui_static/account/settings/index.txt +27 -0
- ciris_engine/gui_static/adapters/index.html +1 -0
- ciris_engine/gui_static/adapters/index.txt +27 -0
- ciris_engine/gui_static/agents/index.html +1 -0
- ciris_engine/gui_static/agents/index.txt +27 -0
- ciris_engine/gui_static/andrew-roberts-euBRXcx57T4-unsplash.jpg +0 -0
- ciris_engine/gui_static/api-demo/index.html +1 -0
- ciris_engine/gui_static/api-demo/index.txt +27 -0
- ciris_engine/gui_static/audit/index.html +1 -0
- ciris_engine/gui_static/audit/index.txt +27 -0
- ciris_engine/gui_static/billing/index.html +1 -0
- ciris_engine/gui_static/billing/index.txt +27 -0
- ciris_engine/gui_static/blurryinfo.png +0 -0
- ciris_engine/gui_static/chip-vincent-PkQDwfl9Flc-unsplash.jpg +0 -0
- ciris_engine/gui_static/ciris-architecture.svg +338 -0
- ciris_engine/gui_static/comms/index.html +1 -0
- ciris_engine/gui_static/comms/index.txt +27 -0
- ciris_engine/gui_static/config/index.html +1 -0
- ciris_engine/gui_static/config/index.txt +27 -0
- ciris_engine/gui_static/consent/index.html +1 -0
- ciris_engine/gui_static/consent/index.txt +27 -0
- ciris_engine/gui_static/dashboard/index.html +1 -0
- ciris_engine/gui_static/dashboard/index.txt +27 -0
- ciris_engine/gui_static/docs/index.html +1 -0
- ciris_engine/gui_static/docs/index.txt +27 -0
- ciris_engine/gui_static/eric.png +0 -0
- ciris_engine/gui_static/file.svg +1 -0
- ciris_engine/gui_static/globe.svg +1 -0
- ciris_engine/gui_static/index.html +1 -0
- ciris_engine/gui_static/index.txt +27 -0
- ciris_engine/gui_static/infogfx-1@2x.png +0 -0
- ciris_engine/gui_static/infogfx-2.png +0 -0
- ciris_engine/gui_static/infogfx-dark-1.png +0 -0
- ciris_engine/gui_static/kelly-vohs-soSTXmIxTDU-unsplash.jpg +0 -0
- ciris_engine/gui_static/login/index.html +1 -0
- ciris_engine/gui_static/login/index.txt +27 -0
- ciris_engine/gui_static/logs/index.html +1 -0
- ciris_engine/gui_static/logs/index.txt +27 -0
- ciris_engine/gui_static/memory/index.html +1 -0
- ciris_engine/gui_static/memory/index.txt +27 -0
- ciris_engine/gui_static/nathan-farrish-ArcTfEoBgzs-unsplash.jpg +0 -0
- ciris_engine/gui_static/next.svg +1 -0
- ciris_engine/gui_static/overview.svg +512 -0
- ciris_engine/gui_static/overview1.svg +407 -0
- ciris_engine/gui_static/overview2.svg +370 -0
- ciris_engine/gui_static/pipeline-visualization.svg +278 -0
- ciris_engine/gui_static/privacy-policy.html +160 -0
- ciris_engine/gui_static/runtime/index.html +8 -0
- ciris_engine/gui_static/runtime/index.txt +27 -0
- ciris_engine/gui_static/services/index.html +1 -0
- ciris_engine/gui_static/services/index.txt +27 -0
- ciris_engine/gui_static/sessions/index.html +1 -0
- ciris_engine/gui_static/sessions/index.txt +27 -0
- ciris_engine/gui_static/setup/index.html +1 -0
- ciris_engine/gui_static/setup/index.txt +27 -0
- ciris_engine/gui_static/status-dashboard/index.html +1 -0
- ciris_engine/gui_static/status-dashboard/index.txt +27 -0
- ciris_engine/gui_static/system/index.html +1 -0
- ciris_engine/gui_static/system/index.txt +27 -0
- ciris_engine/gui_static/terms-of-service.html +174 -0
- ciris_engine/gui_static/test-auth/index.html +1 -0
- ciris_engine/gui_static/test-auth/index.txt +27 -0
- ciris_engine/gui_static/test-login/index.html +1 -0
- ciris_engine/gui_static/test-login/index.txt +27 -0
- ciris_engine/gui_static/test-sdk/index.html +1 -0
- ciris_engine/gui_static/test-sdk/index.txt +27 -0
- ciris_engine/gui_static/tools/index.html +1 -0
- ciris_engine/gui_static/tools/index.txt +27 -0
- ciris_engine/gui_static/users/index.html +1 -0
- ciris_engine/gui_static/users/index.txt +27 -0
- ciris_engine/gui_static/vercel.svg +1 -0
- ciris_engine/gui_static/videos/video1.mp4 +0 -0
- ciris_engine/gui_static/videos/video3.mp4 +0 -0
- ciris_engine/gui_static/wa/index.html +1 -0
- ciris_engine/gui_static/wa/index.txt +27 -0
- ciris_engine/gui_static/window.svg +1 -0
- ciris_engine/logic/__init__.py +8 -0
- ciris_engine/logic/adapters/__init__.py +74 -0
- ciris_engine/logic/adapters/api/__init__.py +5 -0
- ciris_engine/logic/adapters/api/adapter.py +1037 -0
- ciris_engine/logic/adapters/api/api_communication.py +370 -0
- ciris_engine/logic/adapters/api/api_document.py +330 -0
- ciris_engine/logic/adapters/api/api_observer.py +24 -0
- ciris_engine/logic/adapters/api/api_runtime_control.py +388 -0
- ciris_engine/logic/adapters/api/api_tools.py +299 -0
- ciris_engine/logic/adapters/api/api_vision.py +215 -0
- ciris_engine/logic/adapters/api/app.py +272 -0
- ciris_engine/logic/adapters/api/auth.py +159 -0
- ciris_engine/logic/adapters/api/config.py +101 -0
- ciris_engine/logic/adapters/api/constants.py +55 -0
- ciris_engine/logic/adapters/api/dependencies/__init__.py +1 -0
- ciris_engine/logic/adapters/api/dependencies/auth.py +260 -0
- ciris_engine/logic/adapters/api/endpoints/__init__.py +1 -0
- ciris_engine/logic/adapters/api/endpoints/emergency.py +86 -0
- ciris_engine/logic/adapters/api/middleware/__init__.py +1 -0
- ciris_engine/logic/adapters/api/middleware/rate_limiter.py +302 -0
- ciris_engine/logic/adapters/api/models.py +29 -0
- ciris_engine/logic/adapters/api/routes/__init__.py +52 -0
- ciris_engine/logic/adapters/api/routes/agent.py +1762 -0
- ciris_engine/logic/adapters/api/routes/audit.py +707 -0
- ciris_engine/logic/adapters/api/routes/auth.py +1745 -0
- ciris_engine/logic/adapters/api/routes/billing.py +895 -0
- ciris_engine/logic/adapters/api/routes/config.py +329 -0
- ciris_engine/logic/adapters/api/routes/connectors.py +534 -0
- ciris_engine/logic/adapters/api/routes/consent.py +637 -0
- ciris_engine/logic/adapters/api/routes/dsar.py +637 -0
- ciris_engine/logic/adapters/api/routes/dsar_multi_source.py +484 -0
- ciris_engine/logic/adapters/api/routes/emergency.py +302 -0
- ciris_engine/logic/adapters/api/routes/memory.py +733 -0
- ciris_engine/logic/adapters/api/routes/memory_filters.py +230 -0
- ciris_engine/logic/adapters/api/routes/memory_models.py +112 -0
- ciris_engine/logic/adapters/api/routes/memory_queries.py +236 -0
- ciris_engine/logic/adapters/api/routes/memory_query_helpers.py +394 -0
- ciris_engine/logic/adapters/api/routes/memory_visualization.py +359 -0
- ciris_engine/logic/adapters/api/routes/memory_visualization_helpers.py +110 -0
- ciris_engine/logic/adapters/api/routes/partnership.py +541 -0
- ciris_engine/logic/adapters/api/routes/setup.py +1374 -0
- ciris_engine/logic/adapters/api/routes/system.py +3049 -0
- ciris_engine/logic/adapters/api/routes/system_extensions.py +952 -0
- ciris_engine/logic/adapters/api/routes/telemetry.py +1987 -0
- ciris_engine/logic/adapters/api/routes/telemetry_converters.py +141 -0
- ciris_engine/logic/adapters/api/routes/telemetry_helpers.py +111 -0
- ciris_engine/logic/adapters/api/routes/telemetry_logs_reader.py +280 -0
- ciris_engine/logic/adapters/api/routes/telemetry_metrics.py +131 -0
- ciris_engine/logic/adapters/api/routes/telemetry_models.py +190 -0
- ciris_engine/logic/adapters/api/routes/telemetry_otlp.py +878 -0
- ciris_engine/logic/adapters/api/routes/telemetry_resource_helpers.py +191 -0
- ciris_engine/logic/adapters/api/routes/tickets.py +541 -0
- ciris_engine/logic/adapters/api/routes/tools.py +556 -0
- ciris_engine/logic/adapters/api/routes/transparency.py +281 -0
- ciris_engine/logic/adapters/api/routes/users.py +981 -0
- ciris_engine/logic/adapters/api/routes/verification.py +373 -0
- ciris_engine/logic/adapters/api/routes/wa.py +369 -0
- ciris_engine/logic/adapters/api/service_configuration.py +177 -0
- ciris_engine/logic/adapters/api/services/__init__.py +1 -0
- ciris_engine/logic/adapters/api/services/auth_service.py +1417 -0
- ciris_engine/logic/adapters/api/services/oauth_security.py +68 -0
- ciris_engine/logic/adapters/base.py +141 -0
- ciris_engine/logic/adapters/base_adapter.py +73 -0
- ciris_engine/logic/adapters/base_observer.py +1141 -0
- ciris_engine/logic/adapters/base_vision.py +312 -0
- ciris_engine/logic/adapters/cirisnode_client.py +307 -0
- ciris_engine/logic/adapters/cli/__init__.py +3 -0
- ciris_engine/logic/adapters/cli/adapter.py +207 -0
- ciris_engine/logic/adapters/cli/cli_adapter.py +902 -0
- ciris_engine/logic/adapters/cli/cli_observer.py +268 -0
- ciris_engine/logic/adapters/cli/cli_tools.py +427 -0
- ciris_engine/logic/adapters/cli/cli_wa_service.py +134 -0
- ciris_engine/logic/adapters/cli/config.py +73 -0
- ciris_engine/logic/adapters/discord/__init__.py +3 -0
- ciris_engine/logic/adapters/discord/adapter.py +783 -0
- ciris_engine/logic/adapters/discord/ciris_discord_client.py +159 -0
- ciris_engine/logic/adapters/discord/config.py +177 -0
- ciris_engine/logic/adapters/discord/constants.py +185 -0
- ciris_engine/logic/adapters/discord/discord-stubs.pyi +50 -0
- ciris_engine/logic/adapters/discord/discord_adapter.py +1584 -0
- ciris_engine/logic/adapters/discord/discord_audit.py +150 -0
- ciris_engine/logic/adapters/discord/discord_channel_manager.py +351 -0
- ciris_engine/logic/adapters/discord/discord_connection_manager.py +313 -0
- ciris_engine/logic/adapters/discord/discord_embed_formatter.py +369 -0
- ciris_engine/logic/adapters/discord/discord_error_classifier.py +302 -0
- ciris_engine/logic/adapters/discord/discord_error_handler.py +316 -0
- ciris_engine/logic/adapters/discord/discord_guidance_handler.py +460 -0
- ciris_engine/logic/adapters/discord/discord_message_handler.py +207 -0
- ciris_engine/logic/adapters/discord/discord_observer.py +670 -0
- ciris_engine/logic/adapters/discord/discord_rate_limiter.py +249 -0
- ciris_engine/logic/adapters/discord/discord_reaction_handler.py +278 -0
- ciris_engine/logic/adapters/discord/discord_tool_handler.py +465 -0
- ciris_engine/logic/adapters/discord/discord_tool_service.py +790 -0
- ciris_engine/logic/adapters/discord/discord_tools.py +90 -0
- ciris_engine/logic/adapters/discord/discord_vision_helper.py +148 -0
- ciris_engine/logic/adapters/discord/py.typed +0 -0
- ciris_engine/logic/adapters/document_parser.py +320 -0
- ciris_engine/logic/audit/__init__.py +10 -0
- ciris_engine/logic/audit/hash_chain.py +313 -0
- ciris_engine/logic/audit/signature_manager.py +352 -0
- ciris_engine/logic/audit/verifier.py +408 -0
- ciris_engine/logic/buses/__init__.py +21 -0
- ciris_engine/logic/buses/base_bus.py +178 -0
- ciris_engine/logic/buses/bus_manager.py +121 -0
- ciris_engine/logic/buses/communication_bus.py +387 -0
- ciris_engine/logic/buses/llm_bus.py +722 -0
- ciris_engine/logic/buses/memory_bus.py +577 -0
- ciris_engine/logic/buses/prohibitions.py +502 -0
- ciris_engine/logic/buses/runtime_control_bus.py +539 -0
- ciris_engine/logic/buses/tool_bus.py +482 -0
- ciris_engine/logic/buses/wise_bus.py +684 -0
- ciris_engine/logic/config/__init__.py +25 -0
- ciris_engine/logic/config/bootstrap.py +255 -0
- ciris_engine/logic/config/config_accessor.py +202 -0
- ciris_engine/logic/config/db_paths.py +194 -0
- ciris_engine/logic/config/env_utils.py +39 -0
- ciris_engine/logic/conscience/__init__.py +16 -0
- ciris_engine/logic/conscience/build_deferral_package.py +0 -0
- ciris_engine/logic/conscience/core.py +688 -0
- ciris_engine/logic/conscience/interface.py +33 -0
- ciris_engine/logic/conscience/registry.py +76 -0
- ciris_engine/logic/conscience/thought_depth_guardrail.py +231 -0
- ciris_engine/logic/conscience/updated_status_conscience.py +156 -0
- ciris_engine/logic/context/__init__.py +10 -0
- ciris_engine/logic/context/batch_context.py +550 -0
- ciris_engine/logic/context/builder.py +149 -0
- ciris_engine/logic/context/channel_resolution.py +136 -0
- ciris_engine/logic/context/secrets_snapshot.py +52 -0
- ciris_engine/logic/context/system_snapshot.py +116 -0
- ciris_engine/logic/context/system_snapshot_helpers.py +1651 -0
- ciris_engine/logic/covenant/__init__.py +33 -0
- ciris_engine/logic/covenant/executor.py +303 -0
- ciris_engine/logic/covenant/extractor.py +382 -0
- ciris_engine/logic/covenant/handler.py +241 -0
- ciris_engine/logic/covenant/verifier.py +383 -0
- ciris_engine/logic/dma/__init__.py +15 -0
- ciris_engine/logic/dma/action_selection/__init__.py +11 -0
- ciris_engine/logic/dma/action_selection/action_instruction_generator.py +444 -0
- ciris_engine/logic/dma/action_selection/context_builder.py +508 -0
- ciris_engine/logic/dma/action_selection/faculty_integration.py +193 -0
- ciris_engine/logic/dma/action_selection/special_cases.py +132 -0
- ciris_engine/logic/dma/action_selection_pdma.py +365 -0
- ciris_engine/logic/dma/base_dma.py +335 -0
- ciris_engine/logic/dma/csdma.py +239 -0
- ciris_engine/logic/dma/dma_executor.py +575 -0
- ciris_engine/logic/dma/dsdma_base.py +410 -0
- ciris_engine/logic/dma/exceptions.py +4 -0
- ciris_engine/logic/dma/factory.py +150 -0
- ciris_engine/logic/dma/pdma.py +120 -0
- ciris_engine/logic/dma/prompt_loader.py +189 -0
- ciris_engine/logic/dma/prompts/action_selection_pdma.yml +58 -0
- ciris_engine/logic/dma/prompts/csdma_common_sense.yml +28 -0
- ciris_engine/logic/dma/prompts/dsdma_base.yml +17 -0
- ciris_engine/logic/dma/prompts/pdma_ethical.yml +42 -0
- ciris_engine/logic/formatters/__init__.py +26 -0
- ciris_engine/logic/formatters/crisis_resources.py +80 -0
- ciris_engine/logic/formatters/escalation.py +21 -0
- ciris_engine/logic/formatters/identity.py +224 -0
- ciris_engine/logic/formatters/prompt_blocks.py +64 -0
- ciris_engine/logic/formatters/system_snapshot.py +193 -0
- ciris_engine/logic/formatters/user_profiles.py +108 -0
- ciris_engine/logic/handlers/__init__.py +1 -0
- ciris_engine/logic/handlers/control/__init__.py +1 -0
- ciris_engine/logic/handlers/control/defer_handler.py +195 -0
- ciris_engine/logic/handlers/control/ponder_handler.py +154 -0
- ciris_engine/logic/handlers/control/reject_handler.py +81 -0
- ciris_engine/logic/handlers/external/__init__.py +1 -0
- ciris_engine/logic/handlers/external/observe_handler.py +154 -0
- ciris_engine/logic/handlers/external/speak_handler.py +250 -0
- ciris_engine/logic/handlers/external/tool_handler.py +148 -0
- ciris_engine/logic/handlers/memory/__init__.py +1 -0
- ciris_engine/logic/handlers/memory/forget_handler.py +107 -0
- ciris_engine/logic/handlers/memory/memorize_handler.py +391 -0
- ciris_engine/logic/handlers/memory/recall_handler.py +213 -0
- ciris_engine/logic/handlers/terminal/__init__.py +1 -0
- ciris_engine/logic/handlers/terminal/task_complete_handler.py +299 -0
- ciris_engine/logic/infrastructure/__init__.py +1 -0
- ciris_engine/logic/infrastructure/handlers/__init__.py +8 -0
- ciris_engine/logic/infrastructure/handlers/action_dispatcher.py +382 -0
- ciris_engine/logic/infrastructure/handlers/base_handler.py +450 -0
- ciris_engine/logic/infrastructure/handlers/exceptions.py +2 -0
- ciris_engine/logic/infrastructure/handlers/handler_registry.py +59 -0
- ciris_engine/logic/infrastructure/handlers/helpers.py +55 -0
- ciris_engine/logic/infrastructure/step_streaming.py +149 -0
- ciris_engine/logic/infrastructure/sub_services/__init__.py +1 -0
- ciris_engine/logic/infrastructure/sub_services/identity_variance_monitor.py +1035 -0
- ciris_engine/logic/infrastructure/sub_services/pattern_analysis_loop.py +758 -0
- ciris_engine/logic/infrastructure/sub_services/wa_cli_bootstrap.py +229 -0
- ciris_engine/logic/infrastructure/sub_services/wa_cli_display.py +176 -0
- ciris_engine/logic/infrastructure/sub_services/wa_cli_oauth.py +404 -0
- ciris_engine/logic/infrastructure/sub_services/wa_cli_wizard.py +181 -0
- ciris_engine/logic/persistence/__init__.py +130 -0
- ciris_engine/logic/persistence/analytics.py +97 -0
- ciris_engine/logic/persistence/db/__init__.py +28 -0
- ciris_engine/logic/persistence/db/core.py +520 -0
- ciris_engine/logic/persistence/db/dialect.py +380 -0
- ciris_engine/logic/persistence/db/execution_helpers.py +216 -0
- ciris_engine/logic/persistence/db/migration_runner.py +191 -0
- ciris_engine/logic/persistence/db/operations.py +313 -0
- ciris_engine/logic/persistence/db/query_builder.py +232 -0
- ciris_engine/logic/persistence/db/retry.py +154 -0
- ciris_engine/logic/persistence/db/setup.py +18 -0
- ciris_engine/logic/persistence/migrations/postgres/001_initial_schema.sql +4 -0
- ciris_engine/logic/persistence/migrations/postgres/002_add_retry_status.sql +3 -0
- ciris_engine/logic/persistence/migrations/postgres/003_add_task_update_tracking.sql +8 -0
- ciris_engine/logic/persistence/migrations/postgres/004_add_occurrence_id.sql +54 -0
- ciris_engine/logic/persistence/migrations/postgres/005_add_consolidation_locks.sql +22 -0
- ciris_engine/logic/persistence/migrations/postgres/006_add_correlation_id_unique_index.sql +16 -0
- ciris_engine/logic/persistence/migrations/postgres/007_add_dsar_tickets.sql +39 -0
- ciris_engine/logic/persistence/migrations/postgres/008_rename_to_tickets_add_sop.sql +123 -0
- ciris_engine/logic/persistence/migrations/postgres/009_add_ticket_status_columns.sql +39 -0
- ciris_engine/logic/persistence/migrations/postgres/010_add_images_to_tasks.sql +5 -0
- ciris_engine/logic/persistence/migrations/sqlite/001_initial_schema.sql +357 -0
- ciris_engine/logic/persistence/migrations/sqlite/002_add_retry_status.sql +3 -0
- ciris_engine/logic/persistence/migrations/sqlite/003_add_task_update_tracking.sql +8 -0
- ciris_engine/logic/persistence/migrations/sqlite/004_add_occurrence_id.sql +45 -0
- ciris_engine/logic/persistence/migrations/sqlite/005_add_consolidation_locks.sql +22 -0
- ciris_engine/logic/persistence/migrations/sqlite/006_add_correlation_id_unique_index.sql +16 -0
- ciris_engine/logic/persistence/migrations/sqlite/007_add_dsar_tickets.sql +39 -0
- ciris_engine/logic/persistence/migrations/sqlite/008_rename_to_tickets_add_sop.sql +120 -0
- ciris_engine/logic/persistence/migrations/sqlite/009_add_ticket_status_columns.sql +129 -0
- ciris_engine/logic/persistence/migrations/sqlite/010_add_images_to_tasks.sql +17 -0
- ciris_engine/logic/persistence/models/__init__.py +141 -0
- ciris_engine/logic/persistence/models/correlations.py +881 -0
- ciris_engine/logic/persistence/models/deferral.py +68 -0
- ciris_engine/logic/persistence/models/dsar.py +286 -0
- ciris_engine/logic/persistence/models/graph.py +362 -0
- ciris_engine/logic/persistence/models/identity.py +264 -0
- ciris_engine/logic/persistence/models/queue_status.py +139 -0
- ciris_engine/logic/persistence/models/tasks.py +1043 -0
- ciris_engine/logic/persistence/models/thoughts.py +400 -0
- ciris_engine/logic/persistence/models/tickets.py +518 -0
- ciris_engine/logic/persistence/stores/__init__.py +13 -0
- ciris_engine/logic/persistence/stores/auth_helpers.py +117 -0
- ciris_engine/logic/persistence/stores/authentication_store.py +414 -0
- ciris_engine/logic/persistence/utils.py +212 -0
- ciris_engine/logic/processors/__init__.py +30 -0
- ciris_engine/logic/processors/core/__init__.py +1 -0
- ciris_engine/logic/processors/core/base_processor.py +280 -0
- ciris_engine/logic/processors/core/main_processor.py +1777 -0
- ciris_engine/logic/processors/core/step_decorators.py +1583 -0
- ciris_engine/logic/processors/core/thought_processor/__init__.py +20 -0
- ciris_engine/logic/processors/core/thought_processor/action_execution.py +49 -0
- ciris_engine/logic/processors/core/thought_processor/conscience_execution.py +382 -0
- ciris_engine/logic/processors/core/thought_processor/finalize_action.py +66 -0
- ciris_engine/logic/processors/core/thought_processor/gather_context.py +120 -0
- ciris_engine/logic/processors/core/thought_processor/main.py +920 -0
- ciris_engine/logic/processors/core/thought_processor/perform_aspdma.py +86 -0
- ciris_engine/logic/processors/core/thought_processor/perform_dmas.py +106 -0
- ciris_engine/logic/processors/core/thought_processor/recursive_processing.py +237 -0
- ciris_engine/logic/processors/core/thought_processor/round_complete.py +52 -0
- ciris_engine/logic/processors/core/thought_processor/start_round.py +64 -0
- ciris_engine/logic/processors/exceptions.py +59 -0
- ciris_engine/logic/processors/states/__init__.py +1 -0
- ciris_engine/logic/processors/states/dream_processor.py +1381 -0
- ciris_engine/logic/processors/states/play_processor.py +141 -0
- ciris_engine/logic/processors/states/shutdown_processor.py +623 -0
- ciris_engine/logic/processors/states/solitude_processor.py +305 -0
- ciris_engine/logic/processors/states/wakeup_processor.py +802 -0
- ciris_engine/logic/processors/states/work_processor.py +742 -0
- ciris_engine/logic/processors/support/__init__.py +1 -0
- ciris_engine/logic/processors/support/dma_orchestrator.py +336 -0
- ciris_engine/logic/processors/support/processing_queue.py +133 -0
- ciris_engine/logic/processors/support/shutdown_condition_evaluator.py +294 -0
- ciris_engine/logic/processors/support/state_manager.py +358 -0
- ciris_engine/logic/processors/support/task_manager.py +303 -0
- ciris_engine/logic/processors/support/thought_escalation.py +116 -0
- ciris_engine/logic/processors/support/thought_manager.py +328 -0
- ciris_engine/logic/processors/support/thought_manager_enhanced.py +105 -0
- ciris_engine/logic/registries/__init__.py +34 -0
- ciris_engine/logic/registries/base.py +653 -0
- ciris_engine/logic/registries/circuit_breaker.py +275 -0
- ciris_engine/logic/registries/typed_registries.py +184 -0
- ciris_engine/logic/runtime/__init__.py +7 -0
- ciris_engine/logic/runtime/adapter_loader.py +261 -0
- ciris_engine/logic/runtime/adapter_manager.py +1053 -0
- ciris_engine/logic/runtime/ciris_runtime.py +2342 -0
- ciris_engine/logic/runtime/ciris_runtime_helpers.py +923 -0
- ciris_engine/logic/runtime/component_builder.py +361 -0
- ciris_engine/logic/runtime/identity_manager.py +219 -0
- ciris_engine/logic/runtime/module_loader.py +207 -0
- ciris_engine/logic/runtime/prevent_sideeffects.py +30 -0
- ciris_engine/logic/runtime/runtime_interface.py +23 -0
- ciris_engine/logic/runtime/service_initializer.py +1623 -0
- ciris_engine/logic/secrets/__init__.py +30 -0
- ciris_engine/logic/secrets/encryption.py +175 -0
- ciris_engine/logic/secrets/filter.py +295 -0
- ciris_engine/logic/secrets/service.py +652 -0
- ciris_engine/logic/secrets/store.py +669 -0
- ciris_engine/logic/services/__init__.py +1 -0
- ciris_engine/logic/services/adaptation/__init__.py +3 -0
- ciris_engine/logic/services/base_graph_service.py +142 -0
- ciris_engine/logic/services/base_infrastructure_service.py +69 -0
- ciris_engine/logic/services/base_scheduled_service.py +136 -0
- ciris_engine/logic/services/base_service.py +247 -0
- ciris_engine/logic/services/governance/__init__.py +3 -0
- ciris_engine/logic/services/governance/adaptive_filter/__init__.py +14 -0
- ciris_engine/logic/services/governance/adaptive_filter/service.py +818 -0
- ciris_engine/logic/services/governance/consent/__init__.py +53 -0
- ciris_engine/logic/services/governance/consent/air.py +403 -0
- ciris_engine/logic/services/governance/consent/decay.py +324 -0
- ciris_engine/logic/services/governance/consent/dsar_automation.py +589 -0
- ciris_engine/logic/services/governance/consent/exceptions.py +106 -0
- ciris_engine/logic/services/governance/consent/metrics.py +270 -0
- ciris_engine/logic/services/governance/consent/partnership.py +533 -0
- ciris_engine/logic/services/governance/consent/service.py +1256 -0
- ciris_engine/logic/services/governance/dsar/__init__.py +29 -0
- ciris_engine/logic/services/governance/dsar/orchestrator.py +977 -0
- ciris_engine/logic/services/governance/dsar/schemas.py +141 -0
- ciris_engine/logic/services/governance/dsar/signature_service.py +283 -0
- ciris_engine/logic/services/governance/self_observation/__init__.py +20 -0
- ciris_engine/logic/services/governance/self_observation/service.py +1153 -0
- ciris_engine/logic/services/governance/visibility/__init__.py +17 -0
- ciris_engine/logic/services/governance/visibility/service.py +512 -0
- ciris_engine/logic/services/governance/wise_authority/__init__.py +15 -0
- ciris_engine/logic/services/governance/wise_authority/service.py +827 -0
- ciris_engine/logic/services/graph/__init__.py +5 -0
- ciris_engine/logic/services/graph/audit_service/__init__.py +5 -0
- ciris_engine/logic/services/graph/audit_service/service.py +1675 -0
- ciris_engine/logic/services/graph/base.py +208 -0
- ciris_engine/logic/services/graph/config_service/__init__.py +5 -0
- ciris_engine/logic/services/graph/config_service/service.py +372 -0
- ciris_engine/logic/services/graph/incident_service/__init__.py +5 -0
- ciris_engine/logic/services/graph/incident_service/service.py +803 -0
- ciris_engine/logic/services/graph/memory_service.py +1120 -0
- ciris_engine/logic/services/graph/telemetry_service/__init__.py +5 -0
- ciris_engine/logic/services/graph/telemetry_service/exceptions.py +104 -0
- ciris_engine/logic/services/graph/telemetry_service/helpers.py +1337 -0
- ciris_engine/logic/services/graph/telemetry_service/service.py +2429 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/__init__.py +17 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/aggregation_helpers.py +355 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/cleanup_helpers.py +438 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/compressor.py +260 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/__init__.py +27 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/audit.py +326 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/conversation.py +291 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/memory.py +197 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/metrics.py +251 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/task.py +257 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/consolidators/trace.py +363 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/data_converter.py +545 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/date_calculation_helpers.py +193 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/db_query_helpers.py +296 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/edge_helpers.py +92 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/edge_manager.py +896 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/extensive_helpers.py +322 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/period_manager.py +152 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/profound_helpers.py +277 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/query_manager.py +812 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/service.py +1692 -0
- ciris_engine/logic/services/graph/tsdb_consolidation/sql_builders.py +363 -0
- ciris_engine/logic/services/infrastructure/__init__.py +1 -0
- ciris_engine/logic/services/infrastructure/authentication/__init__.py +5 -0
- ciris_engine/logic/services/infrastructure/authentication/service.py +1634 -0
- ciris_engine/logic/services/infrastructure/database_maintenance/__init__.py +15 -0
- ciris_engine/logic/services/infrastructure/database_maintenance/service.py +764 -0
- ciris_engine/logic/services/infrastructure/resource_monitor/__init__.py +7 -0
- ciris_engine/logic/services/infrastructure/resource_monitor/ciris_billing_provider.py +755 -0
- ciris_engine/logic/services/infrastructure/resource_monitor/service.py +409 -0
- ciris_engine/logic/services/infrastructure/resource_monitor/simple_credit_provider.py +129 -0
- ciris_engine/logic/services/lifecycle/__init__.py +3 -0
- ciris_engine/logic/services/lifecycle/initialization/__init__.py +10 -0
- ciris_engine/logic/services/lifecycle/initialization/service.py +312 -0
- ciris_engine/logic/services/lifecycle/scheduler/__init__.py +5 -0
- ciris_engine/logic/services/lifecycle/scheduler/service.py +607 -0
- ciris_engine/logic/services/lifecycle/shutdown/__init__.py +9 -0
- ciris_engine/logic/services/lifecycle/shutdown/service.py +378 -0
- ciris_engine/logic/services/lifecycle/time/__init__.py +15 -0
- ciris_engine/logic/services/lifecycle/time/service.py +259 -0
- ciris_engine/logic/services/memory_service/__init__.py +8 -0
- ciris_engine/logic/services/mixins/__init__.py +13 -0
- ciris_engine/logic/services/mixins/example_usage.py +200 -0
- ciris_engine/logic/services/mixins/request_metrics.py +179 -0
- ciris_engine/logic/services/runtime/__init__.py +3 -0
- ciris_engine/logic/services/runtime/adapter_configuration/__init__.py +16 -0
- ciris_engine/logic/services/runtime/adapter_configuration/service.py +674 -0
- ciris_engine/logic/services/runtime/adapter_configuration/session.py +67 -0
- ciris_engine/logic/services/runtime/control_service/__init__.py +5 -0
- ciris_engine/logic/services/runtime/control_service/service.py +2269 -0
- ciris_engine/logic/services/runtime/llm_service/__init__.py +14 -0
- ciris_engine/logic/services/runtime/llm_service/pricing_calculator.py +279 -0
- ciris_engine/logic/services/runtime/llm_service/service.py +930 -0
- ciris_engine/logic/services/tools/__init__.py +5 -0
- ciris_engine/logic/services/tools/core_tool_service/__init__.py +8 -0
- ciris_engine/logic/services/tools/core_tool_service/service.py +852 -0
- ciris_engine/logic/setup/__init__.py +1 -0
- ciris_engine/logic/setup/first_run.py +250 -0
- ciris_engine/logic/setup/wizard.py +327 -0
- ciris_engine/logic/telemetry/__init__.py +46 -0
- ciris_engine/logic/telemetry/core.py +239 -0
- ciris_engine/logic/telemetry/hot_cold_config.py +133 -0
- ciris_engine/logic/telemetry/log_collector.py +190 -0
- ciris_engine/logic/telemetry/resource_monitor.py +7 -0
- ciris_engine/logic/telemetry/security.py +79 -0
- ciris_engine/logic/utils/__init__.py +18 -0
- ciris_engine/logic/utils/channel_utils.py +75 -0
- ciris_engine/logic/utils/consent/__init__.py +1 -0
- ciris_engine/logic/utils/consent/partnership_utils.py +172 -0
- ciris_engine/logic/utils/constants.py +92 -0
- ciris_engine/logic/utils/context_utils.py +145 -0
- ciris_engine/logic/utils/directory_setup.py +533 -0
- ciris_engine/logic/utils/graphql_context_provider.py +152 -0
- ciris_engine/logic/utils/identity_resolution.py +843 -0
- ciris_engine/logic/utils/incident_capture_handler.py +303 -0
- ciris_engine/logic/utils/initialization_manager.py +74 -0
- ciris_engine/logic/utils/jsondict_helpers.py +290 -0
- ciris_engine/logic/utils/log_sanitizer.py +97 -0
- ciris_engine/logic/utils/logging_config.py +151 -0
- ciris_engine/logic/utils/observability_decorators.py +544 -0
- ciris_engine/logic/utils/occurrence_utils.py +155 -0
- ciris_engine/logic/utils/path_resolution.py +281 -0
- ciris_engine/logic/utils/platform_detection.py +286 -0
- ciris_engine/logic/utils/privacy.py +266 -0
- ciris_engine/logic/utils/profile_loader.py +124 -0
- ciris_engine/logic/utils/profile_manager.py +16 -0
- ciris_engine/logic/utils/runtime_utils.py +69 -0
- ciris_engine/logic/utils/shutdown_manager.py +107 -0
- ciris_engine/logic/utils/task_formatters.py +60 -0
- ciris_engine/logic/utils/task_thought_factory.py +404 -0
- ciris_engine/logic/utils/thought_utils.py +54 -0
- ciris_engine/logic/utils/user_utils.py +70 -0
- ciris_engine/protocols/__init__.py +0 -0
- ciris_engine/protocols/adapters/__init__.py +35 -0
- ciris_engine/protocols/adapters/base.py +149 -0
- ciris_engine/protocols/adapters/configurable.py +265 -0
- ciris_engine/protocols/adapters/message.py +90 -0
- ciris_engine/protocols/audit/__init__.py +1 -0
- ciris_engine/protocols/buses/__init__.py +1 -0
- ciris_engine/protocols/config/__init__.py +1 -0
- ciris_engine/protocols/conscience/__init__.py +1 -0
- ciris_engine/protocols/consent.py +88 -0
- ciris_engine/protocols/context/__init__.py +1 -0
- ciris_engine/protocols/data/__init__.py +1 -0
- ciris_engine/protocols/dma/__init__.py +1 -0
- ciris_engine/protocols/dma/base.py +107 -0
- ciris_engine/protocols/faculties.py +34 -0
- ciris_engine/protocols/formatters/__init__.py +1 -0
- ciris_engine/protocols/handlers/__init__.py +1 -0
- ciris_engine/protocols/infrastructure/__init__.py +25 -0
- ciris_engine/protocols/infrastructure/base.py +377 -0
- ciris_engine/protocols/persistence/__init__.py +1 -0
- ciris_engine/protocols/pipeline_control.py +609 -0
- ciris_engine/protocols/processors/__init__.py +19 -0
- ciris_engine/protocols/processors/agent.py +299 -0
- ciris_engine/protocols/processors/base.py +130 -0
- ciris_engine/protocols/processors/orchestration.py +62 -0
- ciris_engine/protocols/registries/__init__.py +1 -0
- ciris_engine/protocols/runtime/__init__.py +1 -0
- ciris_engine/protocols/runtime/base.py +163 -0
- ciris_engine/protocols/secrets/__init__.py +1 -0
- ciris_engine/protocols/services/__init__.py +80 -0
- ciris_engine/protocols/services/adaptation/__init__.py +7 -0
- ciris_engine/protocols/services/adaptation/self_observation.py +265 -0
- ciris_engine/protocols/services/governance/__init__.py +20 -0
- ciris_engine/protocols/services/governance/communication.py +58 -0
- ciris_engine/protocols/services/governance/filter.py +56 -0
- ciris_engine/protocols/services/governance/visibility.py +32 -0
- ciris_engine/protocols/services/governance/wa_auth.py +192 -0
- ciris_engine/protocols/services/governance/wise_authority.py +75 -0
- ciris_engine/protocols/services/graph/__init__.py +19 -0
- ciris_engine/protocols/services/graph/audit.py +92 -0
- ciris_engine/protocols/services/graph/config.py +54 -0
- ciris_engine/protocols/services/graph/incident_management.py +103 -0
- ciris_engine/protocols/services/graph/memory.py +110 -0
- ciris_engine/protocols/services/graph/telemetry.py +51 -0
- ciris_engine/protocols/services/graph/tsdb_consolidation.py +87 -0
- ciris_engine/protocols/services/infrastructure/__init__.py +11 -0
- ciris_engine/protocols/services/infrastructure/authentication.py +159 -0
- ciris_engine/protocols/services/infrastructure/credit_gate.py +46 -0
- ciris_engine/protocols/services/infrastructure/database_maintenance.py +25 -0
- ciris_engine/protocols/services/infrastructure/resource_monitor.py +83 -0
- ciris_engine/protocols/services/lifecycle/__init__.py +13 -0
- ciris_engine/protocols/services/lifecycle/initialization.py +41 -0
- ciris_engine/protocols/services/lifecycle/scheduler.py +42 -0
- ciris_engine/protocols/services/lifecycle/shutdown.py +50 -0
- ciris_engine/protocols/services/lifecycle/time.py +31 -0
- ciris_engine/protocols/services/runtime/__init__.py +13 -0
- ciris_engine/protocols/services/runtime/llm.py +50 -0
- ciris_engine/protocols/services/runtime/runtime_control.py +193 -0
- ciris_engine/protocols/services/runtime/secrets.py +100 -0
- ciris_engine/protocols/services/runtime/tool.py +123 -0
- ciris_engine/protocols/telemetry/__init__.py +1 -0
- ciris_engine/protocols/utils/__init__.py +1 -0
- ciris_engine/schemas/__init__.py +112 -0
- ciris_engine/schemas/actions/__init__.py +37 -0
- ciris_engine/schemas/actions/parameters.py +137 -0
- ciris_engine/schemas/adapters/__init__.py +13 -0
- ciris_engine/schemas/adapters/cirisnode.py +135 -0
- ciris_engine/schemas/adapters/cli.py +97 -0
- ciris_engine/schemas/adapters/cli_tools.py +98 -0
- ciris_engine/schemas/adapters/discord.py +125 -0
- ciris_engine/schemas/adapters/graphql_core.py +144 -0
- ciris_engine/schemas/adapters/registration.py +47 -0
- ciris_engine/schemas/adapters/runtime_context.py +48 -0
- ciris_engine/schemas/adapters/tool_execution.py +45 -0
- ciris_engine/schemas/adapters/tools.py +96 -0
- ciris_engine/schemas/api/__init__.py +1 -0
- ciris_engine/schemas/api/agent.py +50 -0
- ciris_engine/schemas/api/audit.py +38 -0
- ciris_engine/schemas/api/auth.py +351 -0
- ciris_engine/schemas/api/config_security.py +242 -0
- ciris_engine/schemas/api/emergency.py +111 -0
- ciris_engine/schemas/api/responses.py +72 -0
- ciris_engine/schemas/api/runtime.py +26 -0
- ciris_engine/schemas/api/telemetry.py +109 -0
- ciris_engine/schemas/api/wa.py +90 -0
- ciris_engine/schemas/audit/__init__.py +13 -0
- ciris_engine/schemas/audit/core.py +139 -0
- ciris_engine/schemas/audit/hash_chain.py +58 -0
- ciris_engine/schemas/audit/verification.py +131 -0
- ciris_engine/schemas/buses/__init__.py +1 -0
- ciris_engine/schemas/config/__init__.py +41 -0
- ciris_engine/schemas/config/agent.py +279 -0
- ciris_engine/schemas/config/cognitive_state_behaviors.py +194 -0
- ciris_engine/schemas/config/default_dsar_sops.py +178 -0
- ciris_engine/schemas/config/essential.py +195 -0
- ciris_engine/schemas/config/tickets.py +86 -0
- ciris_engine/schemas/conscience/__init__.py +25 -0
- ciris_engine/schemas/conscience/context.py +34 -0
- ciris_engine/schemas/conscience/core.py +145 -0
- ciris_engine/schemas/conscience/results.py +24 -0
- ciris_engine/schemas/consent/__init__.py +5 -0
- ciris_engine/schemas/consent/core.py +404 -0
- ciris_engine/schemas/context/__init__.py +1 -0
- ciris_engine/schemas/covenant.py +382 -0
- ciris_engine/schemas/data/__init__.py +1 -0
- ciris_engine/schemas/dma/__init__.py +16 -0
- ciris_engine/schemas/dma/core.py +199 -0
- ciris_engine/schemas/dma/faculty.py +192 -0
- ciris_engine/schemas/dma/prompts.py +172 -0
- ciris_engine/schemas/dma/results.py +103 -0
- ciris_engine/schemas/formatters/__init__.py +1 -0
- ciris_engine/schemas/handlers/__init__.py +10 -0
- ciris_engine/schemas/handlers/context.py +119 -0
- ciris_engine/schemas/handlers/contexts.py +100 -0
- ciris_engine/schemas/handlers/core.py +167 -0
- ciris_engine/schemas/handlers/memory_schemas.py +67 -0
- ciris_engine/schemas/handlers/schemas.py +95 -0
- ciris_engine/schemas/identity.py +149 -0
- ciris_engine/schemas/infrastructure/__init__.py +1 -0
- ciris_engine/schemas/infrastructure/base.py +256 -0
- ciris_engine/schemas/infrastructure/behavioral_patterns.py +129 -0
- ciris_engine/schemas/infrastructure/feedback_loop.py +57 -0
- ciris_engine/schemas/infrastructure/identity_variance.py +141 -0
- ciris_engine/schemas/infrastructure/oauth.py +175 -0
- ciris_engine/schemas/infrastructure/wa_cli_wizard.py +54 -0
- ciris_engine/schemas/persistence/__init__.py +34 -0
- ciris_engine/schemas/persistence/core.py +140 -0
- ciris_engine/schemas/persistence/correlations.py +73 -0
- ciris_engine/schemas/persistence/postgres/__init__.py +1 -0
- ciris_engine/schemas/persistence/postgres/tables.py +280 -0
- ciris_engine/schemas/persistence/sqlite/__init__.py +1 -0
- ciris_engine/schemas/persistence/sqlite/tables.py +281 -0
- ciris_engine/schemas/platform.py +149 -0
- ciris_engine/schemas/processors/__init__.py +26 -0
- ciris_engine/schemas/processors/base.py +130 -0
- ciris_engine/schemas/processors/cognitive.py +77 -0
- ciris_engine/schemas/processors/context.py +35 -0
- ciris_engine/schemas/processors/core.py +152 -0
- ciris_engine/schemas/processors/dma.py +105 -0
- ciris_engine/schemas/processors/error.py +122 -0
- ciris_engine/schemas/processors/main.py +109 -0
- ciris_engine/schemas/processors/phase_results.py +21 -0
- ciris_engine/schemas/processors/results.py +99 -0
- ciris_engine/schemas/processors/solitude.py +79 -0
- ciris_engine/schemas/processors/state.py +202 -0
- ciris_engine/schemas/processors/state_example.py +177 -0
- ciris_engine/schemas/processors/states.py +21 -0
- ciris_engine/schemas/processors/status.py +34 -0
- ciris_engine/schemas/registries/__init__.py +1 -0
- ciris_engine/schemas/registries/base.py +66 -0
- ciris_engine/schemas/resources/__init__.py +15 -0
- ciris_engine/schemas/resources/crisis.py +315 -0
- ciris_engine/schemas/runtime/__init__.py +42 -0
- ciris_engine/schemas/runtime/adapter_management.py +186 -0
- ciris_engine/schemas/runtime/api.py +58 -0
- ciris_engine/schemas/runtime/audit.py +50 -0
- ciris_engine/schemas/runtime/bootstrap.py +33 -0
- ciris_engine/schemas/runtime/contexts.py +61 -0
- ciris_engine/schemas/runtime/core.py +161 -0
- ciris_engine/schemas/runtime/enums.py +167 -0
- ciris_engine/schemas/runtime/extended.py +232 -0
- ciris_engine/schemas/runtime/manifest.py +311 -0
- ciris_engine/schemas/runtime/memory.py +60 -0
- ciris_engine/schemas/runtime/messages.py +108 -0
- ciris_engine/schemas/runtime/models.py +156 -0
- ciris_engine/schemas/runtime/processing_context.py +43 -0
- ciris_engine/schemas/runtime/protocols_core.py +96 -0
- ciris_engine/schemas/runtime/resources.py +33 -0
- ciris_engine/schemas/runtime/system_context.py +417 -0
- ciris_engine/schemas/secrets/__init__.py +1 -0
- ciris_engine/schemas/secrets/core.py +267 -0
- ciris_engine/schemas/secrets/service.py +95 -0
- ciris_engine/schemas/services/__init__.py +33 -0
- ciris_engine/schemas/services/audit_summary_node.py +172 -0
- ciris_engine/schemas/services/authority/__init__.py +39 -0
- ciris_engine/schemas/services/authority/jwt.py +158 -0
- ciris_engine/schemas/services/authority/wa_updates.py +138 -0
- ciris_engine/schemas/services/authority/wise_authority.py +163 -0
- ciris_engine/schemas/services/authority_core.py +370 -0
- ciris_engine/schemas/services/capabilities.py +72 -0
- ciris_engine/schemas/services/community_core.py +95 -0
- ciris_engine/schemas/services/context.py +111 -0
- ciris_engine/schemas/services/conversation_summary_node.py +189 -0
- ciris_engine/schemas/services/core/__init__.py +153 -0
- ciris_engine/schemas/services/core/runtime.py +262 -0
- ciris_engine/schemas/services/core/runtime_config.py +117 -0
- ciris_engine/schemas/services/core/secrets.py +65 -0
- ciris_engine/schemas/services/correlation_node.py +179 -0
- ciris_engine/schemas/services/credit_gate.py +92 -0
- ciris_engine/schemas/services/discord_nodes.py +299 -0
- ciris_engine/schemas/services/feedback_core.py +131 -0
- ciris_engine/schemas/services/filters_core.py +270 -0
- ciris_engine/schemas/services/governance.py +26 -0
- ciris_engine/schemas/services/graph/__init__.py +26 -0
- ciris_engine/schemas/services/graph/attributes.py +254 -0
- ciris_engine/schemas/services/graph/audit.py +98 -0
- ciris_engine/schemas/services/graph/consolidation.py +338 -0
- ciris_engine/schemas/services/graph/edge_types.py +43 -0
- ciris_engine/schemas/services/graph/edges.py +88 -0
- ciris_engine/schemas/services/graph/incident.py +312 -0
- ciris_engine/schemas/services/graph/memory.py +84 -0
- ciris_engine/schemas/services/graph/node_data.py +174 -0
- ciris_engine/schemas/services/graph/query_results.py +82 -0
- ciris_engine/schemas/services/graph/telemetry.py +250 -0
- ciris_engine/schemas/services/graph/tsdb_consolidation.py +27 -0
- ciris_engine/schemas/services/graph/tsdb_models.py +107 -0
- ciris_engine/schemas/services/graph_core.py +196 -0
- ciris_engine/schemas/services/graph_typed_nodes.py +194 -0
- ciris_engine/schemas/services/infrastructure/__init__.py +1 -0
- ciris_engine/schemas/services/infrastructure/resource_monitor.py +20 -0
- ciris_engine/schemas/services/lifecycle/__init__.py +9 -0
- ciris_engine/schemas/services/lifecycle/initialization.py +33 -0
- ciris_engine/schemas/services/lifecycle/time.py +50 -0
- ciris_engine/schemas/services/llm.py +187 -0
- ciris_engine/schemas/services/metadata.py +43 -0
- ciris_engine/schemas/services/nodes.py +704 -0
- ciris_engine/schemas/services/operations.py +126 -0
- ciris_engine/schemas/services/requests.py +128 -0
- ciris_engine/schemas/services/resources_core.py +182 -0
- ciris_engine/schemas/services/runtime_control.py +1010 -0
- ciris_engine/schemas/services/shutdown.py +88 -0
- ciris_engine/schemas/services/special/__init__.py +0 -0
- ciris_engine/schemas/services/special/self_observation.py +396 -0
- ciris_engine/schemas/services/trace_summary_node.py +199 -0
- ciris_engine/schemas/services/visibility.py +98 -0
- ciris_engine/schemas/streaming/__init__.py +10 -0
- ciris_engine/schemas/streaming/reasoning_stream.py +95 -0
- ciris_engine/schemas/telemetry/__init__.py +0 -0
- ciris_engine/schemas/telemetry/collector.py +67 -0
- ciris_engine/schemas/telemetry/core.py +252 -0
- ciris_engine/schemas/telemetry/unified.py +59 -0
- ciris_engine/schemas/tools.py +72 -0
- ciris_engine/schemas/types.py +47 -0
- ciris_engine/schemas/utils/__init__.py +1 -0
- ciris_engine/schemas/utils/config_validator.py +54 -0
- ciris_engine/utils/__init__.py +1 -0
- ciris_engine/utils/serialization.py +35 -0
- ciris_sdk/__init__.py +124 -0
- ciris_sdk/auth_store.py +261 -0
- ciris_sdk/client.py +261 -0
- ciris_sdk/exceptions.py +73 -0
- ciris_sdk/model_types.py +258 -0
- ciris_sdk/models.py +354 -0
- ciris_sdk/pagination.py +214 -0
- ciris_sdk/rate_limiter.py +188 -0
- ciris_sdk/setup.py +17 -0
- ciris_sdk/telemetry_models.py +257 -0
- ciris_sdk/telemetry_responses.py +199 -0
- ciris_sdk/transport.py +177 -0
- ciris_sdk/websocket.py +400 -0
- main.py +766 -0
|
@@ -0,0 +1,1651 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helper functions for system_snapshot.py to reduce complexity.
|
|
3
|
+
|
|
4
|
+
Organized by logical function groups:
|
|
5
|
+
1. Thought Processing
|
|
6
|
+
2. Channel Resolution
|
|
7
|
+
3. Identity Management
|
|
8
|
+
4. Task Processing
|
|
9
|
+
5. System Context
|
|
10
|
+
6. Service Health
|
|
11
|
+
7. System Data
|
|
12
|
+
8. User Management
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
import re
|
|
18
|
+
from datetime import datetime, timezone
|
|
19
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
20
|
+
|
|
21
|
+
from pydantic import BaseModel
|
|
22
|
+
|
|
23
|
+
from ciris_engine.logic import persistence
|
|
24
|
+
from ciris_engine.logic.secrets.service import SecretsService
|
|
25
|
+
from ciris_engine.logic.services.memory_service import LocalGraphMemoryService
|
|
26
|
+
from ciris_engine.logic.utils.jsondict_helpers import get_list
|
|
27
|
+
from ciris_engine.protocols.services.lifecycle.time import TimeServiceProtocol
|
|
28
|
+
from ciris_engine.schemas.adapters.tools import ToolInfo
|
|
29
|
+
from ciris_engine.schemas.infrastructure.identity_variance import IdentityData, IdentitySummary
|
|
30
|
+
from ciris_engine.schemas.runtime.models import Task
|
|
31
|
+
from ciris_engine.schemas.runtime.system_context import ChannelContext, TaskSummary, ThoughtSummary, UserProfile
|
|
32
|
+
from ciris_engine.schemas.services.graph_core import ConnectedNodeInfo, GraphNode, GraphScope, NodeType, SecretsData
|
|
33
|
+
from ciris_engine.schemas.services.lifecycle.time import LocalizedTimeData
|
|
34
|
+
from ciris_engine.schemas.services.operations import MemoryQuery
|
|
35
|
+
from ciris_engine.schemas.types import JSONDict
|
|
36
|
+
|
|
37
|
+
from .secrets_snapshot import ERROR_KEY, build_secrets_snapshot
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# =============================================================================
|
|
43
|
+
# 1. THOUGHT PROCESSING
|
|
44
|
+
# =============================================================================
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _extract_thought_summary(thought: Any) -> Optional[ThoughtSummary]:
|
|
48
|
+
"""Extract thought summary from thought object."""
|
|
49
|
+
if not thought:
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
status_val = getattr(thought, "status", None)
|
|
53
|
+
if status_val is not None and hasattr(status_val, "value"):
|
|
54
|
+
status_val = status_val.value
|
|
55
|
+
elif status_val is not None:
|
|
56
|
+
status_val = str(status_val)
|
|
57
|
+
|
|
58
|
+
thought_type_val = getattr(thought, "thought_type", None)
|
|
59
|
+
thought_id_val = getattr(thought, "thought_id", None)
|
|
60
|
+
if thought_id_val is None:
|
|
61
|
+
thought_id_val = "unknown" # Provide a default value for required field
|
|
62
|
+
|
|
63
|
+
return ThoughtSummary(
|
|
64
|
+
thought_id=thought_id_val,
|
|
65
|
+
content=getattr(thought, "content", None),
|
|
66
|
+
status=status_val,
|
|
67
|
+
source_task_id=getattr(thought, "source_task_id", None),
|
|
68
|
+
thought_type=thought_type_val,
|
|
69
|
+
thought_depth=getattr(thought, "thought_depth", None),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# =============================================================================
|
|
74
|
+
# 1. STANDARDIZED NODE ATTRIBUTE EXTRACTION
|
|
75
|
+
# =============================================================================
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def extract_node_attributes(node: Any) -> JSONDict:
|
|
79
|
+
"""Extract attributes dictionary from any GraphNode - standardized and reusable.
|
|
80
|
+
|
|
81
|
+
This function handles all the different ways GraphNode attributes can be stored
|
|
82
|
+
and provides a consistent interface for accessing them.
|
|
83
|
+
|
|
84
|
+
Always returns a JSON-compatible dict (never None), returning empty dict for invalid nodes.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
JSONDict: Graph node attributes as JSON-serializable dictionary
|
|
88
|
+
"""
|
|
89
|
+
if not node or not hasattr(node, "attributes"):
|
|
90
|
+
return {}
|
|
91
|
+
|
|
92
|
+
if node.attributes is None:
|
|
93
|
+
return {}
|
|
94
|
+
elif isinstance(node.attributes, dict):
|
|
95
|
+
return node.attributes
|
|
96
|
+
elif hasattr(node.attributes, "model_dump"):
|
|
97
|
+
# Cast to dict to satisfy type checker
|
|
98
|
+
return dict(node.attributes.model_dump())
|
|
99
|
+
else:
|
|
100
|
+
logger.warning(f"Unexpected node attributes type: {type(node.attributes)}")
|
|
101
|
+
return {}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def collect_memorized_attributes(attrs: JSONDict, known_fields: Set[str]) -> Dict[str, str]:
|
|
105
|
+
"""Collect arbitrary attributes as memorized_attributes - standardized and reusable.
|
|
106
|
+
|
|
107
|
+
This function extracts all attributes that aren't in the known_fields set
|
|
108
|
+
and converts them to string values for type safety.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
attrs: Node attributes from graph (JSON-compatible dict)
|
|
112
|
+
known_fields: Set of known field names to exclude
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Dict mapping attribute names to string values
|
|
116
|
+
"""
|
|
117
|
+
import json
|
|
118
|
+
|
|
119
|
+
memorized_attributes = {}
|
|
120
|
+
for key, value in attrs.items():
|
|
121
|
+
if key not in known_fields:
|
|
122
|
+
# Convert value to string for type safety
|
|
123
|
+
if value is None:
|
|
124
|
+
memorized_attributes[key] = ""
|
|
125
|
+
elif isinstance(value, (dict, list)):
|
|
126
|
+
# Use JSON serialization with datetime handler for complex objects
|
|
127
|
+
memorized_attributes[key] = json.dumps(value, default=_json_serial_for_users)
|
|
128
|
+
else:
|
|
129
|
+
# Use string conversion for simple types (handles datetime via str())
|
|
130
|
+
memorized_attributes[key] = str(value)
|
|
131
|
+
return memorized_attributes
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_channel_id_from_node(node: Any, attrs: JSONDict) -> str:
|
|
135
|
+
"""Extract channel_id from node, with fallback to node.id.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
node: GraphNode object
|
|
139
|
+
attrs: Node attributes (JSON-compatible dict)
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Channel ID string
|
|
143
|
+
"""
|
|
144
|
+
return str(attrs.get("channel_id", node.id.split("/")[-1] if "/" in node.id else node.id))
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# =============================================================================
|
|
148
|
+
# 2. CHANNEL RESOLUTION
|
|
149
|
+
# =============================================================================
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _extract_from_system_snapshot_channel_context(
|
|
153
|
+
context: Any, source_name: str
|
|
154
|
+
) -> Tuple[Optional[str], Optional[Any]]:
|
|
155
|
+
"""Extract channel info from system_snapshot.channel_context."""
|
|
156
|
+
if hasattr(context, "system_snapshot") and hasattr(context.system_snapshot, "channel_context"):
|
|
157
|
+
extracted_context = context.system_snapshot.channel_context
|
|
158
|
+
if extracted_context and hasattr(extracted_context, "channel_id"):
|
|
159
|
+
extracted_id = str(extracted_context.channel_id)
|
|
160
|
+
logger.debug(f"Found channel_context in {source_name}.system_snapshot.channel_context")
|
|
161
|
+
return extracted_id, extracted_context
|
|
162
|
+
return None, None
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _extract_from_system_snapshot_channel_id(context: Any, source_name: str) -> Tuple[Optional[str], Optional[Any]]:
|
|
166
|
+
"""Extract channel info from system_snapshot.channel_id."""
|
|
167
|
+
if hasattr(context, "system_snapshot") and hasattr(context.system_snapshot, "channel_id"):
|
|
168
|
+
cid = context.system_snapshot.channel_id
|
|
169
|
+
if cid is not None:
|
|
170
|
+
logger.debug(f"Found channel_id '{cid}' in {source_name}.system_snapshot.channel_id")
|
|
171
|
+
return str(cid), None
|
|
172
|
+
return None, None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _extract_from_direct_channel_id(context: Any) -> Tuple[Optional[str], Optional[Any]]:
|
|
176
|
+
"""Extract channel info from direct channel_id attribute."""
|
|
177
|
+
if isinstance(context, dict):
|
|
178
|
+
cid = context.get("channel_id")
|
|
179
|
+
return str(cid) if cid is not None else None, None
|
|
180
|
+
elif hasattr(context, "channel_id"):
|
|
181
|
+
cid = getattr(context, "channel_id", None)
|
|
182
|
+
return str(cid) if cid is not None else None, None
|
|
183
|
+
return None, None
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _safe_extract_channel_info(context: Any, source_name: str) -> Tuple[Optional[str], Optional[Any]]:
|
|
187
|
+
"""Extract both channel_id and channel_context from context."""
|
|
188
|
+
if not context:
|
|
189
|
+
return None, None
|
|
190
|
+
try:
|
|
191
|
+
# First check if context has system_snapshot.channel_context
|
|
192
|
+
channel_id, channel_context = _extract_from_system_snapshot_channel_context(context, source_name)
|
|
193
|
+
if channel_id:
|
|
194
|
+
return channel_id, channel_context
|
|
195
|
+
|
|
196
|
+
# Then check if context has system_snapshot.channel_id
|
|
197
|
+
channel_id, channel_context = _extract_from_system_snapshot_channel_id(context, source_name)
|
|
198
|
+
if channel_id:
|
|
199
|
+
return channel_id, channel_context
|
|
200
|
+
|
|
201
|
+
# Then check direct channel_id attribute
|
|
202
|
+
return _extract_from_direct_channel_id(context)
|
|
203
|
+
|
|
204
|
+
except Exception as e: # pragma: no cover - defensive
|
|
205
|
+
logger.error(f"Error extracting channel info from {source_name}: {e}")
|
|
206
|
+
raise # FAIL FAST AND LOUD - configuration/programming error
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _get_initial_channel_info(task: Optional[Task], thought: Any) -> Tuple[Optional[str], Optional[Any]]:
|
|
210
|
+
"""Extract initial channel ID and context from task/thought."""
|
|
211
|
+
channel_id = None
|
|
212
|
+
channel_context = None
|
|
213
|
+
|
|
214
|
+
if task and task.context:
|
|
215
|
+
channel_id, channel_context = _safe_extract_channel_info(task.context, "task.context")
|
|
216
|
+
if not channel_id and thought and thought.context:
|
|
217
|
+
channel_id, channel_context = _safe_extract_channel_info(thought.context, "thought.context")
|
|
218
|
+
|
|
219
|
+
return channel_id, channel_context
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
async def _perform_direct_channel_lookup(memory_service: Any, channel_id: str) -> List[Any]:
|
|
223
|
+
"""Perform direct memory lookup for channel by node_id."""
|
|
224
|
+
query = MemoryQuery(
|
|
225
|
+
node_id=f"channel/{channel_id}",
|
|
226
|
+
scope=GraphScope.LOCAL,
|
|
227
|
+
type=NodeType.CHANNEL,
|
|
228
|
+
include_edges=False,
|
|
229
|
+
depth=1,
|
|
230
|
+
)
|
|
231
|
+
logger.debug(f"[DEBUG DB TIMING] About to query memory service for channel/{channel_id}")
|
|
232
|
+
channel_nodes = await memory_service.recall(query)
|
|
233
|
+
logger.debug(f"[DEBUG DB TIMING] Completed memory service query for channel/{channel_id}")
|
|
234
|
+
return list(channel_nodes) if channel_nodes else []
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
async def _perform_channel_search(memory_service: Any, channel_id: str) -> List[Any]:
|
|
238
|
+
"""Perform search-based channel lookup."""
|
|
239
|
+
from ciris_engine.schemas.services.graph.memory import MemorySearchFilter
|
|
240
|
+
|
|
241
|
+
search_filter = MemorySearchFilter(node_type=NodeType.CHANNEL.value, scope=GraphScope.LOCAL.value, limit=10)
|
|
242
|
+
logger.debug(f"[DEBUG DB TIMING] About to search memory service for channel {channel_id}")
|
|
243
|
+
search_results = await memory_service.search(query=channel_id, filters=search_filter)
|
|
244
|
+
logger.debug(f"[DEBUG DB TIMING] Completed memory service search for channel {channel_id}")
|
|
245
|
+
return list(search_results) if search_results else []
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _extract_channel_from_search_results(search_results: List[Any], channel_id: str) -> Optional[Any]:
|
|
249
|
+
"""Extract matching channel from search results."""
|
|
250
|
+
for node in search_results:
|
|
251
|
+
if node.attributes:
|
|
252
|
+
attrs = node.attributes if isinstance(node.attributes, dict) else node.attributes.model_dump()
|
|
253
|
+
if attrs.get("channel_id") == channel_id or node.id == f"channel/{channel_id}":
|
|
254
|
+
return node
|
|
255
|
+
return None
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# Legacy function - now uses standardized extract_node_attributes
|
|
259
|
+
def _extract_channel_node_attributes(
|
|
260
|
+
node: Any,
|
|
261
|
+
) -> Optional[JSONDict]:
|
|
262
|
+
"""Extract attributes dictionary from channel GraphNode."""
|
|
263
|
+
return extract_node_attributes(node)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _get_known_channel_fields() -> Set[str]:
|
|
267
|
+
"""Get set of known ChannelContext fields."""
|
|
268
|
+
return {
|
|
269
|
+
"channel_id",
|
|
270
|
+
"channel_type",
|
|
271
|
+
"created_at",
|
|
272
|
+
"channel_name",
|
|
273
|
+
"is_private",
|
|
274
|
+
"participants",
|
|
275
|
+
"is_active",
|
|
276
|
+
"last_activity",
|
|
277
|
+
"message_count",
|
|
278
|
+
"allowed_actions",
|
|
279
|
+
"moderation_level",
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _build_required_channel_fields(attrs: JSONDict, node: Any) -> JSONDict:
|
|
284
|
+
"""Build required ChannelContext fields with defaults."""
|
|
285
|
+
# Get created_at, or generate new timestamp if missing
|
|
286
|
+
created_at_val = attrs.get("created_at")
|
|
287
|
+
if created_at_val is None:
|
|
288
|
+
created_at_val = datetime.now(timezone.utc).isoformat()
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
"channel_id": get_channel_id_from_node(node, attrs),
|
|
292
|
+
"channel_type": attrs.get("channel_type", "unknown"),
|
|
293
|
+
"created_at": created_at_val,
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _build_optional_channel_fields(
|
|
298
|
+
attrs: JSONDict,
|
|
299
|
+
) -> JSONDict:
|
|
300
|
+
"""Build optional ChannelContext fields with defaults."""
|
|
301
|
+
return {
|
|
302
|
+
"channel_name": attrs.get("channel_name", None),
|
|
303
|
+
"is_private": attrs.get("is_private", False),
|
|
304
|
+
"participants": attrs.get("participants", []),
|
|
305
|
+
"is_active": attrs.get("is_active", True),
|
|
306
|
+
"last_activity": attrs.get("last_activity", None),
|
|
307
|
+
"message_count": attrs.get("message_count", 0),
|
|
308
|
+
"allowed_actions": attrs.get("allowed_actions", []),
|
|
309
|
+
"moderation_level": attrs.get("moderation_level", "standard"),
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
# Legacy function - now uses standardized collect_memorized_attributes
|
|
314
|
+
def _collect_memorized_attributes(attrs: JSONDict, known_fields: Set[str]) -> Dict[str, str]:
|
|
315
|
+
"""Collect arbitrary attributes the agent memorized about this channel."""
|
|
316
|
+
return collect_memorized_attributes(attrs, known_fields)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _convert_graph_node_to_channel_context(node: Any) -> Optional[ChannelContext]:
|
|
320
|
+
"""Convert a GraphNode containing channel data to a ChannelContext object."""
|
|
321
|
+
if not node or not node.attributes:
|
|
322
|
+
return None
|
|
323
|
+
|
|
324
|
+
try:
|
|
325
|
+
# Extract attributes from GraphNode
|
|
326
|
+
attrs = _extract_channel_node_attributes(node)
|
|
327
|
+
if attrs is None:
|
|
328
|
+
return None
|
|
329
|
+
|
|
330
|
+
# Get known field definitions
|
|
331
|
+
known_fields = _get_known_channel_fields()
|
|
332
|
+
|
|
333
|
+
# Build context data
|
|
334
|
+
context_data = _build_required_channel_fields(attrs, node)
|
|
335
|
+
context_data.update(_build_optional_channel_fields(attrs))
|
|
336
|
+
context_data["memorized_attributes"] = _collect_memorized_attributes(attrs, known_fields)
|
|
337
|
+
|
|
338
|
+
return ChannelContext(**context_data)
|
|
339
|
+
|
|
340
|
+
except Exception as e:
|
|
341
|
+
logger.warning(f"Failed to convert GraphNode to ChannelContext: {e}")
|
|
342
|
+
return None
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
async def _resolve_channel_context(
|
|
346
|
+
task: Optional[Task], thought: Any, memory_service: Optional[LocalGraphMemoryService]
|
|
347
|
+
) -> Tuple[Optional[str], Optional[ChannelContext]]:
|
|
348
|
+
"""Resolve channel ID and context from task/thought with memory lookup."""
|
|
349
|
+
# Get initial channel info from task/thought
|
|
350
|
+
channel_id, initial_context = _get_initial_channel_info(task, thought)
|
|
351
|
+
|
|
352
|
+
# If we already have a ChannelContext, use it
|
|
353
|
+
if isinstance(initial_context, ChannelContext):
|
|
354
|
+
return channel_id, initial_context
|
|
355
|
+
|
|
356
|
+
# Start with the initial context (may be None or some other object)
|
|
357
|
+
channel_context = initial_context
|
|
358
|
+
|
|
359
|
+
# Attempt memory lookup if we have both channel_id and memory_service
|
|
360
|
+
if channel_id and memory_service:
|
|
361
|
+
try:
|
|
362
|
+
# First try direct lookup for performance
|
|
363
|
+
channel_nodes = await _perform_direct_channel_lookup(memory_service, channel_id)
|
|
364
|
+
|
|
365
|
+
if channel_nodes:
|
|
366
|
+
# Convert the first found channel node to ChannelContext
|
|
367
|
+
channel_context = _convert_graph_node_to_channel_context(channel_nodes[0])
|
|
368
|
+
else:
|
|
369
|
+
# If not found, try search
|
|
370
|
+
search_results = await _perform_channel_search(memory_service, channel_id)
|
|
371
|
+
found_channel = _extract_channel_from_search_results(search_results, channel_id)
|
|
372
|
+
if found_channel:
|
|
373
|
+
# Convert the found channel node to ChannelContext
|
|
374
|
+
channel_context = _convert_graph_node_to_channel_context(found_channel)
|
|
375
|
+
|
|
376
|
+
except Exception as e:
|
|
377
|
+
logger.debug(f"Failed to retrieve channel context for {channel_id}: {e}")
|
|
378
|
+
|
|
379
|
+
return channel_id, channel_context
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
# =============================================================================
|
|
383
|
+
# 3. IDENTITY MANAGEMENT
|
|
384
|
+
# =============================================================================
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
async def _extract_agent_identity(
|
|
388
|
+
memory_service: Optional[LocalGraphMemoryService],
|
|
389
|
+
) -> Tuple[IdentityData, IdentitySummary]:
|
|
390
|
+
"""Extract agent identity data from graph memory."""
|
|
391
|
+
# Default values for when no memory service or identity is available
|
|
392
|
+
default_identity_data = IdentityData(
|
|
393
|
+
agent_id="unknown",
|
|
394
|
+
description="No identity data available",
|
|
395
|
+
role="Unknown",
|
|
396
|
+
trust_level=0.5,
|
|
397
|
+
)
|
|
398
|
+
default_identity_summary = IdentitySummary()
|
|
399
|
+
|
|
400
|
+
if not memory_service:
|
|
401
|
+
return default_identity_data, default_identity_summary
|
|
402
|
+
|
|
403
|
+
try:
|
|
404
|
+
# Query for the agent's identity node from the graph
|
|
405
|
+
identity_query = MemoryQuery(
|
|
406
|
+
node_id="agent/identity", scope=GraphScope.IDENTITY, type=NodeType.AGENT, include_edges=False, depth=1
|
|
407
|
+
)
|
|
408
|
+
logger.debug("[DEBUG DB TIMING] About to query memory service for agent/identity")
|
|
409
|
+
identity_nodes = await memory_service.recall(identity_query)
|
|
410
|
+
logger.debug("[DEBUG DB TIMING] Completed memory service query for agent/identity")
|
|
411
|
+
identity_result = identity_nodes[0] if identity_nodes else None
|
|
412
|
+
|
|
413
|
+
if not identity_result or not identity_result.attributes:
|
|
414
|
+
return default_identity_data, default_identity_summary
|
|
415
|
+
|
|
416
|
+
# Extract attributes using the standardized helper
|
|
417
|
+
attrs_dict = extract_node_attributes(identity_result)
|
|
418
|
+
if not attrs_dict:
|
|
419
|
+
return default_identity_data, default_identity_summary
|
|
420
|
+
|
|
421
|
+
# Create typed identity data
|
|
422
|
+
identity_data = IdentityData(
|
|
423
|
+
agent_id=attrs_dict.get("agent_id", "unknown"),
|
|
424
|
+
description=attrs_dict.get("description", "No description available"),
|
|
425
|
+
role=attrs_dict.get("role_description", "Unknown"),
|
|
426
|
+
trust_level=attrs_dict.get("trust_level", 0.5),
|
|
427
|
+
stewardship=attrs_dict.get("stewardship"),
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
# Create typed identity summary
|
|
431
|
+
identity_summary = IdentitySummary(
|
|
432
|
+
identity_purpose=attrs_dict.get("role_description"),
|
|
433
|
+
identity_capabilities=attrs_dict.get("permitted_actions", []),
|
|
434
|
+
identity_restrictions=attrs_dict.get("restricted_capabilities", []),
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
return identity_data, identity_summary
|
|
438
|
+
|
|
439
|
+
except Exception as e:
|
|
440
|
+
logger.warning(f"Failed to retrieve agent identity from graph: {e}")
|
|
441
|
+
return default_identity_data, default_identity_summary
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
# =============================================================================
|
|
445
|
+
# 4. TASK PROCESSING
|
|
446
|
+
# =============================================================================
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def _get_recent_tasks(limit: int = 10) -> List[TaskSummary]:
|
|
450
|
+
"""Get recent completed tasks as TaskSummary objects."""
|
|
451
|
+
recent_tasks_list: List[TaskSummary] = []
|
|
452
|
+
logger.debug("[DEBUG DB TIMING] About to get recent completed tasks")
|
|
453
|
+
db_recent_tasks = persistence.get_recent_completed_tasks("default", limit)
|
|
454
|
+
logger.debug(f"[DEBUG DB TIMING] Completed get recent completed tasks: {len(db_recent_tasks)} tasks")
|
|
455
|
+
|
|
456
|
+
for t_obj in db_recent_tasks:
|
|
457
|
+
# db_recent_tasks returns List[Task], convert to TaskSummary
|
|
458
|
+
if isinstance(t_obj, BaseModel):
|
|
459
|
+
recent_tasks_list.append(
|
|
460
|
+
TaskSummary(
|
|
461
|
+
task_id=t_obj.task_id,
|
|
462
|
+
channel_id=getattr(t_obj, "channel_id", "system"),
|
|
463
|
+
created_at=t_obj.created_at,
|
|
464
|
+
status=t_obj.status.value if hasattr(t_obj.status, "value") else str(t_obj.status),
|
|
465
|
+
priority=getattr(t_obj, "priority", 0),
|
|
466
|
+
retry_count=getattr(t_obj, "retry_count", 0),
|
|
467
|
+
parent_task_id=getattr(t_obj, "parent_task_id", None),
|
|
468
|
+
)
|
|
469
|
+
)
|
|
470
|
+
return recent_tasks_list
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def _get_top_tasks(limit: int = 10) -> List[TaskSummary]:
|
|
474
|
+
"""Get top pending tasks as TaskSummary objects."""
|
|
475
|
+
top_tasks_list: List[TaskSummary] = []
|
|
476
|
+
logger.debug("[DEBUG DB TIMING] About to get top tasks")
|
|
477
|
+
db_top_tasks = persistence.get_top_tasks("default", limit)
|
|
478
|
+
logger.debug(f"[DEBUG DB TIMING] Completed get top tasks: {len(db_top_tasks)} tasks")
|
|
479
|
+
|
|
480
|
+
for t_obj in db_top_tasks:
|
|
481
|
+
# db_top_tasks returns List[Task], convert to TaskSummary
|
|
482
|
+
if isinstance(t_obj, BaseModel):
|
|
483
|
+
top_tasks_list.append(
|
|
484
|
+
TaskSummary(
|
|
485
|
+
task_id=t_obj.task_id,
|
|
486
|
+
channel_id=getattr(t_obj, "channel_id", "system"),
|
|
487
|
+
created_at=t_obj.created_at,
|
|
488
|
+
status=t_obj.status.value if hasattr(t_obj.status, "value") else str(t_obj.status),
|
|
489
|
+
priority=getattr(t_obj, "priority", 0),
|
|
490
|
+
retry_count=getattr(t_obj, "retry_count", 0),
|
|
491
|
+
parent_task_id=getattr(t_obj, "parent_task_id", None),
|
|
492
|
+
)
|
|
493
|
+
)
|
|
494
|
+
return top_tasks_list
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def _build_current_task_summary(task: Optional[Task]) -> Optional[TaskSummary]:
|
|
498
|
+
"""Convert Task to TaskSummary."""
|
|
499
|
+
if not task:
|
|
500
|
+
return None
|
|
501
|
+
|
|
502
|
+
# Convert Task to TaskSummary
|
|
503
|
+
if isinstance(task, BaseModel):
|
|
504
|
+
return TaskSummary(
|
|
505
|
+
task_id=task.task_id,
|
|
506
|
+
channel_id=getattr(task, "channel_id", "system"),
|
|
507
|
+
created_at=task.created_at,
|
|
508
|
+
status=task.status.value if hasattr(task.status, "value") else str(task.status),
|
|
509
|
+
priority=getattr(task, "priority", 0),
|
|
510
|
+
retry_count=getattr(task, "retry_count", 0),
|
|
511
|
+
parent_task_id=getattr(task, "parent_task_id", None),
|
|
512
|
+
)
|
|
513
|
+
return None
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
# =============================================================================
|
|
517
|
+
# 5. SYSTEM CONTEXT
|
|
518
|
+
# =============================================================================
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
async def _get_secrets_data(secrets_service: Optional[SecretsService]) -> SecretsData:
|
|
522
|
+
"""Get secrets snapshot data."""
|
|
523
|
+
if secrets_service:
|
|
524
|
+
# Get the raw snapshot data
|
|
525
|
+
snapshot_data = await build_secrets_snapshot(secrets_service)
|
|
526
|
+
|
|
527
|
+
# Check if there was an error
|
|
528
|
+
error_message = snapshot_data.get(ERROR_KEY)
|
|
529
|
+
filter_status = "active" if not error_message else "error"
|
|
530
|
+
|
|
531
|
+
if error_message:
|
|
532
|
+
logger.warning(
|
|
533
|
+
"Secrets snapshot reported an internal error in secrets service. See monitoring for details."
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
# Convert to typed schema
|
|
537
|
+
return SecretsData(
|
|
538
|
+
secrets_count=snapshot_data.get("total_secrets_stored", 0),
|
|
539
|
+
filter_status=filter_status,
|
|
540
|
+
last_updated=None, # Not provided by build_secrets_snapshot
|
|
541
|
+
detected_secrets=snapshot_data.get("detected_secrets", []),
|
|
542
|
+
secrets_filter_version=snapshot_data.get("secrets_filter_version", 0),
|
|
543
|
+
additional_data=snapshot_data, # Store full data for backwards compatibility
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
return SecretsData()
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def _get_shutdown_context(runtime: Optional[Any]) -> Optional[Any]:
|
|
550
|
+
"""Extract shutdown context from runtime."""
|
|
551
|
+
if runtime and hasattr(runtime, "current_shutdown_context"):
|
|
552
|
+
return runtime.current_shutdown_context
|
|
553
|
+
return None
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def _format_critical_alert(alert: str) -> str:
|
|
557
|
+
"""Format a critical resource alert."""
|
|
558
|
+
return f"🚨 CRITICAL! RESOURCE LIMIT BREACHED! {alert} - REJECT OR DEFER ALL TASKS!"
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def _get_system_unhealthy_alert() -> str:
|
|
562
|
+
"""Get system unhealthy alert message."""
|
|
563
|
+
return "🚨 CRITICAL! SYSTEM UNHEALTHY! RESOURCE LIMITS EXCEEDED - IMMEDIATE ACTION REQUIRED!"
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def _get_resource_check_failed_alert(error: str) -> str:
|
|
567
|
+
"""Get resource check failed alert message."""
|
|
568
|
+
return f"🚨 CRITICAL! FAILED TO CHECK RESOURCES: {error}"
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def _process_critical_alerts(snapshot: Any, resource_alerts: List[str]) -> None:
|
|
572
|
+
"""Process critical resource alerts from snapshot."""
|
|
573
|
+
if snapshot.critical:
|
|
574
|
+
for alert in snapshot.critical:
|
|
575
|
+
resource_alerts.append(_format_critical_alert(alert))
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def _check_system_health(snapshot: Any, resource_alerts: List[str]) -> None:
|
|
579
|
+
"""Check system health and add alerts if unhealthy."""
|
|
580
|
+
if not snapshot.healthy:
|
|
581
|
+
resource_alerts.append(_get_system_unhealthy_alert())
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def _collect_resource_alerts(resource_monitor: Any) -> List[str]:
|
|
585
|
+
"""Collect critical resource alerts."""
|
|
586
|
+
resource_alerts: List[str] = []
|
|
587
|
+
try:
|
|
588
|
+
if resource_monitor is not None:
|
|
589
|
+
snapshot = resource_monitor.snapshot
|
|
590
|
+
_process_critical_alerts(snapshot, resource_alerts)
|
|
591
|
+
_check_system_health(snapshot, resource_alerts)
|
|
592
|
+
else:
|
|
593
|
+
logger.warning("Resource monitor not available - cannot check resource constraints")
|
|
594
|
+
except Exception as e:
|
|
595
|
+
logger.error(f"Failed to get resource alerts: {e}")
|
|
596
|
+
resource_alerts.append(_get_resource_check_failed_alert(str(e)))
|
|
597
|
+
return resource_alerts
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
# =============================================================================
|
|
601
|
+
# 6. SERVICE HEALTH
|
|
602
|
+
# =============================================================================
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
async def _safe_get_health_status(service: Any) -> tuple[bool, bool]:
|
|
606
|
+
"""Safely get health status from a service.
|
|
607
|
+
|
|
608
|
+
Returns:
|
|
609
|
+
(has_health_method, is_healthy): Tuple indicating if service has health methods and its health status
|
|
610
|
+
"""
|
|
611
|
+
try:
|
|
612
|
+
# Check for ServiceProtocol standard method first
|
|
613
|
+
if hasattr(service, "is_healthy"):
|
|
614
|
+
health_status = await service.is_healthy()
|
|
615
|
+
return True, bool(health_status)
|
|
616
|
+
# Fallback to legacy method
|
|
617
|
+
elif hasattr(service, "get_health_status"):
|
|
618
|
+
health_status = await service.get_health_status()
|
|
619
|
+
return True, getattr(health_status, "is_healthy", False)
|
|
620
|
+
except Exception as e:
|
|
621
|
+
logger.warning(f"Failed to get health status from service: {e}")
|
|
622
|
+
return True, False # Has method but failed
|
|
623
|
+
return False, False # No health method
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
async def _safe_get_circuit_breaker_status(service: Any) -> tuple[bool, str]:
|
|
627
|
+
"""Safely get circuit breaker status from a service.
|
|
628
|
+
|
|
629
|
+
Returns:
|
|
630
|
+
(has_circuit_breaker, status): Tuple indicating if service has circuit breaker and its status
|
|
631
|
+
"""
|
|
632
|
+
try:
|
|
633
|
+
if hasattr(service, "get_circuit_breaker_status"):
|
|
634
|
+
cb_status = await service.get_circuit_breaker_status()
|
|
635
|
+
return True, str(cb_status) if cb_status else "UNKNOWN"
|
|
636
|
+
except Exception as e:
|
|
637
|
+
logger.warning(f"Failed to get circuit breaker status from service: {e}")
|
|
638
|
+
return True, "UNKNOWN" # Has method but failed
|
|
639
|
+
return False, "UNKNOWN" # No circuit breaker method
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
async def _process_single_service(
|
|
643
|
+
service: Any, service_name: str, service_health: Dict[str, bool], circuit_breaker_status: Dict[str, str]
|
|
644
|
+
) -> None:
|
|
645
|
+
"""Process health and circuit breaker status for a single service."""
|
|
646
|
+
# Get health status - only include if service has health methods
|
|
647
|
+
has_health_method, health_status = await _safe_get_health_status(service)
|
|
648
|
+
if has_health_method:
|
|
649
|
+
service_health[service_name] = health_status
|
|
650
|
+
|
|
651
|
+
# Get circuit breaker status - only include if service has circuit breaker methods
|
|
652
|
+
has_circuit_breaker, cb_status = await _safe_get_circuit_breaker_status(service)
|
|
653
|
+
if has_circuit_breaker:
|
|
654
|
+
circuit_breaker_status[service_name] = cb_status
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
async def _process_services_group(
|
|
658
|
+
services_group: JSONDict,
|
|
659
|
+
prefix: str,
|
|
660
|
+
service_health: Dict[str, bool],
|
|
661
|
+
circuit_breaker_status: Dict[str, str],
|
|
662
|
+
) -> None:
|
|
663
|
+
"""Process a group of services (handlers or global services)."""
|
|
664
|
+
for service_type, services_raw in services_group.items():
|
|
665
|
+
# Type narrow services to list before iteration
|
|
666
|
+
services_list = get_list(services_group, service_type, [])
|
|
667
|
+
for service in services_list:
|
|
668
|
+
service_name = f"{prefix}.{service_type}"
|
|
669
|
+
await _process_single_service(service, service_name, service_health, circuit_breaker_status)
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
async def _collect_service_health(
|
|
673
|
+
service_registry: Optional[Any], runtime: Optional[Any] = None
|
|
674
|
+
) -> Tuple[Dict[str, bool], Dict[str, str]]:
|
|
675
|
+
"""Collect service health and circuit breaker status."""
|
|
676
|
+
service_health: Dict[str, bool] = {}
|
|
677
|
+
circuit_breaker_status: Dict[str, str] = {}
|
|
678
|
+
|
|
679
|
+
# First, collect core services from runtime (21 core services)
|
|
680
|
+
if runtime:
|
|
681
|
+
core_services = [
|
|
682
|
+
# Graph Services (6)
|
|
683
|
+
"memory_service",
|
|
684
|
+
"config_service",
|
|
685
|
+
"telemetry_service",
|
|
686
|
+
"audit_service",
|
|
687
|
+
"incident_management_service",
|
|
688
|
+
"tsdb_consolidation_service",
|
|
689
|
+
# Infrastructure Services (7)
|
|
690
|
+
"time_service",
|
|
691
|
+
"shutdown_service",
|
|
692
|
+
"initialization_service",
|
|
693
|
+
"authentication_service",
|
|
694
|
+
"resource_monitor",
|
|
695
|
+
"maintenance_service", # database maintenance
|
|
696
|
+
"secrets_service",
|
|
697
|
+
# Governance Services (4)
|
|
698
|
+
"wa_auth_system", # wise authority
|
|
699
|
+
"adaptive_filter_service",
|
|
700
|
+
"visibility_service",
|
|
701
|
+
"self_observation_service",
|
|
702
|
+
# Runtime Services (3)
|
|
703
|
+
"llm_service",
|
|
704
|
+
"runtime_control_service",
|
|
705
|
+
"task_scheduler",
|
|
706
|
+
# Tool Services (1)
|
|
707
|
+
"secrets_tool_service", # secrets tool
|
|
708
|
+
]
|
|
709
|
+
|
|
710
|
+
for service_name in core_services:
|
|
711
|
+
service = getattr(runtime, service_name, None)
|
|
712
|
+
if service:
|
|
713
|
+
await _process_single_service(service, service_name, service_health, circuit_breaker_status)
|
|
714
|
+
|
|
715
|
+
# Then, collect handler-specific services from service_registry
|
|
716
|
+
if service_registry:
|
|
717
|
+
try:
|
|
718
|
+
registry_info = service_registry.get_provider_info()
|
|
719
|
+
|
|
720
|
+
# Process handler-specific services
|
|
721
|
+
for handler, service_types in registry_info.get("handlers", {}).items():
|
|
722
|
+
await _process_services_group(service_types, handler, service_health, circuit_breaker_status)
|
|
723
|
+
|
|
724
|
+
# Process global services
|
|
725
|
+
global_services = registry_info.get("global_services", {})
|
|
726
|
+
await _process_services_group(global_services, "global", service_health, circuit_breaker_status)
|
|
727
|
+
|
|
728
|
+
except Exception as e:
|
|
729
|
+
logger.warning(f"Failed to collect service health status: {e}")
|
|
730
|
+
|
|
731
|
+
return service_health, circuit_breaker_status
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
# =============================================================================
|
|
735
|
+
# 7. SYSTEM DATA
|
|
736
|
+
# =============================================================================
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
async def _get_telemetry_summary(telemetry_service: Optional[Any]) -> Optional[Any]:
|
|
740
|
+
"""Get telemetry summary for resource usage."""
|
|
741
|
+
if telemetry_service:
|
|
742
|
+
try:
|
|
743
|
+
telemetry_summary = await telemetry_service.get_telemetry_summary()
|
|
744
|
+
logger.debug("Successfully retrieved telemetry summary")
|
|
745
|
+
return telemetry_summary
|
|
746
|
+
except Exception as e:
|
|
747
|
+
logger.warning(f"Failed to get telemetry summary: {e}")
|
|
748
|
+
return None
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
async def _get_continuity_summary(telemetry_service: Optional[Any]) -> Optional[Any]:
|
|
752
|
+
"""Get continuity awareness summary from lifecycle events."""
|
|
753
|
+
if telemetry_service and hasattr(telemetry_service, "get_continuity_summary"):
|
|
754
|
+
try:
|
|
755
|
+
continuity_summary = await telemetry_service.get_continuity_summary()
|
|
756
|
+
logger.debug("Successfully retrieved continuity summary")
|
|
757
|
+
return continuity_summary
|
|
758
|
+
except Exception as e:
|
|
759
|
+
logger.warning(f"Failed to get continuity summary: {e}")
|
|
760
|
+
return None
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
def _validate_channel_list(channels: List[Any], adapter_name: str) -> None:
|
|
764
|
+
"""Validate that channel list contains ChannelContext objects."""
|
|
765
|
+
if channels and not isinstance(channels[0], ChannelContext):
|
|
766
|
+
raise TypeError(
|
|
767
|
+
f"Adapter {adapter_name} returned invalid channel list type: {type(channels[0])}, expected ChannelContext"
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
def _process_adapter_channels(
|
|
772
|
+
adapter_name: str, adapter: Any, adapter_channels: Dict[str, List[ChannelContext]]
|
|
773
|
+
) -> None:
|
|
774
|
+
"""Process channels from a single adapter."""
|
|
775
|
+
if hasattr(adapter, "get_channel_list"):
|
|
776
|
+
channels = adapter.get_channel_list()
|
|
777
|
+
if channels:
|
|
778
|
+
_validate_channel_list(channels, adapter_name)
|
|
779
|
+
# Use channel_type from first channel
|
|
780
|
+
adapter_type = channels[0].channel_type
|
|
781
|
+
adapter_channels[adapter_type] = channels
|
|
782
|
+
logger.debug(f"Found {len(channels)} channels for {adapter_type} adapter")
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def _has_valid_adapter_manager(runtime: Optional[Any]) -> bool:
|
|
786
|
+
"""Check if runtime has valid adapter manager."""
|
|
787
|
+
return runtime is not None and hasattr(runtime, "adapter_manager") and runtime.adapter_manager is not None
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
async def _collect_adapter_channels(runtime: Optional[Any]) -> Dict[str, List[ChannelContext]]:
|
|
791
|
+
"""Collect available channels from all adapters."""
|
|
792
|
+
adapter_channels: Dict[str, List[ChannelContext]] = {}
|
|
793
|
+
|
|
794
|
+
if _has_valid_adapter_manager(runtime):
|
|
795
|
+
try:
|
|
796
|
+
# Assert runtime is not None since we checked it
|
|
797
|
+
assert runtime is not None
|
|
798
|
+
adapter_manager = runtime.adapter_manager
|
|
799
|
+
# Get all active adapters
|
|
800
|
+
for adapter_name, adapter in adapter_manager._adapters.items():
|
|
801
|
+
_process_adapter_channels(adapter_name, adapter, adapter_channels)
|
|
802
|
+
except Exception as e:
|
|
803
|
+
logger.error(f"Failed to get adapter channels: {e}")
|
|
804
|
+
raise # FAIL FAST AND LOUD
|
|
805
|
+
|
|
806
|
+
return adapter_channels
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
def _validate_runtime_capabilities(runtime: Optional[Any]) -> bool:
|
|
810
|
+
"""Check if runtime has required attributes for tool collection."""
|
|
811
|
+
if runtime is None:
|
|
812
|
+
return False
|
|
813
|
+
if not hasattr(runtime, "bus_manager"):
|
|
814
|
+
return False
|
|
815
|
+
if not hasattr(runtime, "service_registry"):
|
|
816
|
+
return False
|
|
817
|
+
return True
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
def _get_tool_services(service_registry: Any) -> List[Any]:
|
|
821
|
+
"""Get and validate tool services from registry."""
|
|
822
|
+
tool_services = service_registry.get_services_by_type("tool")
|
|
823
|
+
|
|
824
|
+
# Validate tool_services is iterable but not a string
|
|
825
|
+
try:
|
|
826
|
+
# Check if it's truly iterable and not a mock
|
|
827
|
+
if not hasattr(tool_services, "__iter__") or isinstance(tool_services, str):
|
|
828
|
+
logger.error(f"get_services_by_type('tool') returned non-iterable: {type(tool_services)}")
|
|
829
|
+
return []
|
|
830
|
+
|
|
831
|
+
# Try to convert to list to ensure it's really iterable
|
|
832
|
+
return list(tool_services)
|
|
833
|
+
except (TypeError, AttributeError):
|
|
834
|
+
logger.error(f"get_services_by_type('tool') returned non-iterable: {type(tool_services)}")
|
|
835
|
+
return []
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
async def _call_async_or_sync_method(obj: Any, method_name: str, *args: Any) -> Any:
|
|
839
|
+
"""Call a method that might be async or sync."""
|
|
840
|
+
import inspect
|
|
841
|
+
|
|
842
|
+
if not hasattr(obj, method_name):
|
|
843
|
+
return None
|
|
844
|
+
|
|
845
|
+
method = getattr(obj, method_name)
|
|
846
|
+
|
|
847
|
+
# Handle Mock objects that don't have real methods
|
|
848
|
+
if hasattr(method, "_mock_name"):
|
|
849
|
+
# This is a mock, call it and check if result is a coroutine
|
|
850
|
+
result = method(*args)
|
|
851
|
+
if inspect.iscoroutine(result):
|
|
852
|
+
return await result
|
|
853
|
+
return result
|
|
854
|
+
|
|
855
|
+
if inspect.iscoroutinefunction(method):
|
|
856
|
+
return await method(*args)
|
|
857
|
+
else:
|
|
858
|
+
return method(*args)
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
async def _get_tool_info_safely(tool_service: Any, tool_name: str, adapter_id: str) -> Optional[ToolInfo]:
|
|
862
|
+
"""Get tool info with error handling and type validation."""
|
|
863
|
+
if not hasattr(tool_service, "get_tool_info"):
|
|
864
|
+
return None
|
|
865
|
+
|
|
866
|
+
try:
|
|
867
|
+
tool_info = await _call_async_or_sync_method(tool_service, "get_tool_info", tool_name)
|
|
868
|
+
|
|
869
|
+
if tool_info:
|
|
870
|
+
if not isinstance(tool_info, ToolInfo):
|
|
871
|
+
raise TypeError(
|
|
872
|
+
f"Tool service {adapter_id} returned invalid type for {tool_name}: {type(tool_info)}, expected ToolInfo"
|
|
873
|
+
)
|
|
874
|
+
return tool_info
|
|
875
|
+
except Exception as e:
|
|
876
|
+
logger.error(f"Failed to get info for tool {tool_name}: {e}")
|
|
877
|
+
raise
|
|
878
|
+
|
|
879
|
+
return None
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
def _extract_adapter_type(adapter_id: str) -> str:
|
|
883
|
+
"""Extract adapter type from adapter_id."""
|
|
884
|
+
return adapter_id.split("_")[0] if "_" in adapter_id else adapter_id
|
|
885
|
+
|
|
886
|
+
|
|
887
|
+
def _validate_tool_infos(tool_infos: List[ToolInfo]) -> None:
|
|
888
|
+
"""Validate all tools are ToolInfo instances - FAIL FAST."""
|
|
889
|
+
for ti in tool_infos:
|
|
890
|
+
if not isinstance(ti, ToolInfo):
|
|
891
|
+
raise TypeError(f"Non-ToolInfo object in tool_infos: {type(ti)}, this violates type safety!")
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
async def _collect_available_tools(runtime: Optional[Any]) -> Dict[str, List[ToolInfo]]:
|
|
895
|
+
"""Collect available tools from all adapters via tool bus."""
|
|
896
|
+
available_tools: Dict[str, List[ToolInfo]] = {}
|
|
897
|
+
|
|
898
|
+
if not _validate_runtime_capabilities(runtime):
|
|
899
|
+
return available_tools
|
|
900
|
+
|
|
901
|
+
try:
|
|
902
|
+
# Assert runtime is not None since we validated it
|
|
903
|
+
assert runtime is not None
|
|
904
|
+
service_registry = runtime.service_registry
|
|
905
|
+
tool_services = _get_tool_services(service_registry)
|
|
906
|
+
|
|
907
|
+
for tool_service in tool_services:
|
|
908
|
+
adapter_id = getattr(tool_service, "adapter_id", "unknown")
|
|
909
|
+
|
|
910
|
+
# Get available tools from this service
|
|
911
|
+
tool_names = await _call_async_or_sync_method(tool_service, "get_available_tools")
|
|
912
|
+
if not tool_names:
|
|
913
|
+
continue
|
|
914
|
+
|
|
915
|
+
# Get detailed info for each tool
|
|
916
|
+
tool_infos: List[ToolInfo] = []
|
|
917
|
+
for tool_name in tool_names:
|
|
918
|
+
tool_info = await _get_tool_info_safely(tool_service, tool_name, adapter_id)
|
|
919
|
+
if tool_info:
|
|
920
|
+
tool_infos.append(tool_info)
|
|
921
|
+
|
|
922
|
+
if tool_infos:
|
|
923
|
+
_validate_tool_infos(tool_infos)
|
|
924
|
+
|
|
925
|
+
# Group by adapter type
|
|
926
|
+
adapter_type = _extract_adapter_type(adapter_id)
|
|
927
|
+
if adapter_type not in available_tools:
|
|
928
|
+
available_tools[adapter_type] = []
|
|
929
|
+
available_tools[adapter_type].extend(tool_infos)
|
|
930
|
+
logger.debug(f"Found {len(tool_infos)} tools for {adapter_type} adapter")
|
|
931
|
+
|
|
932
|
+
except Exception as e:
|
|
933
|
+
logger.error(f"Failed to get available tools: {e}")
|
|
934
|
+
raise # FAIL FAST AND LOUD
|
|
935
|
+
|
|
936
|
+
return available_tools
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
def _collect_enrichment_tools(available_tools: Dict[str, List[ToolInfo]]) -> List[tuple[str, ToolInfo]]:
|
|
940
|
+
"""Collect all tools marked for context enrichment."""
|
|
941
|
+
enrichment_tools: List[tuple[str, ToolInfo]] = []
|
|
942
|
+
for adapter_type, tools in available_tools.items():
|
|
943
|
+
for tool in tools:
|
|
944
|
+
if tool.context_enrichment:
|
|
945
|
+
enrichment_tools.append((adapter_type, tool))
|
|
946
|
+
logger.info(f"[CONTEXT_ENRICHMENT] Found enrichment tool: {adapter_type}:{tool.name}")
|
|
947
|
+
return enrichment_tools
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
def _log_no_enrichment_tools(available_tools: Dict[str, List[ToolInfo]]) -> None:
|
|
951
|
+
"""Log debug info when no enrichment tools are found."""
|
|
952
|
+
logger.info("[CONTEXT_ENRICHMENT] No context enrichment tools found in available_tools")
|
|
953
|
+
logger.info(f"[CONTEXT_ENRICHMENT] available_tools keys: {list(available_tools.keys())}")
|
|
954
|
+
for adapter_type, tools in available_tools.items():
|
|
955
|
+
logger.info(f"[CONTEXT_ENRICHMENT] {adapter_type} has {len(tools)} tools: {[t.name for t in tools]}")
|
|
956
|
+
|
|
957
|
+
|
|
958
|
+
async def _find_tool_service(tool_services: List[Any], adapter_type: str, tool_name: str) -> Optional[Any]:
|
|
959
|
+
"""Find the tool service that provides the specified tool."""
|
|
960
|
+
for ts in tool_services:
|
|
961
|
+
adapter_id = getattr(ts, "adapter_id", "")
|
|
962
|
+
ts_adapter_type = _extract_adapter_type(adapter_id)
|
|
963
|
+
if ts_adapter_type == adapter_type:
|
|
964
|
+
available = await _call_async_or_sync_method(ts, "get_available_tools")
|
|
965
|
+
if available and tool_name in available:
|
|
966
|
+
return ts
|
|
967
|
+
return None
|
|
968
|
+
|
|
969
|
+
|
|
970
|
+
def _process_tool_result(result: Any, tool_key: str) -> Any:
|
|
971
|
+
"""Process and return the appropriate result from a tool execution."""
|
|
972
|
+
if hasattr(result, "data") and result.data is not None:
|
|
973
|
+
logger.info(f"[CONTEXT_ENRICHMENT] {tool_key} returned data with {len(result.data)} keys")
|
|
974
|
+
return result.data
|
|
975
|
+
elif hasattr(result, "success") and result.success:
|
|
976
|
+
return {"success": True, "message": "Tool executed successfully"}
|
|
977
|
+
else:
|
|
978
|
+
logger.info(f"[CONTEXT_ENRICHMENT] {tool_key} returned raw result")
|
|
979
|
+
return result
|
|
980
|
+
|
|
981
|
+
|
|
982
|
+
async def _execute_enrichment_tool(tool_services: List[Any], adapter_type: str, tool: ToolInfo) -> tuple[str, Any]:
|
|
983
|
+
"""Execute a single enrichment tool and return (tool_key, result)."""
|
|
984
|
+
tool_key = f"{adapter_type}:{tool.name}"
|
|
985
|
+
|
|
986
|
+
tool_service = await _find_tool_service(tool_services, adapter_type, tool.name)
|
|
987
|
+
if not tool_service:
|
|
988
|
+
logger.warning(f"[CONTEXT_ENRICHMENT] No tool service found for {tool_key}")
|
|
989
|
+
return tool_key, None
|
|
990
|
+
|
|
991
|
+
params = tool.context_enrichment_params or {}
|
|
992
|
+
logger.info(f"[CONTEXT_ENRICHMENT] Executing {tool_key} with params: {params}")
|
|
993
|
+
|
|
994
|
+
result = await _call_async_or_sync_method(tool_service, "execute_tool", tool.name, params)
|
|
995
|
+
if result:
|
|
996
|
+
return tool_key, _process_tool_result(result, tool_key)
|
|
997
|
+
return tool_key, None
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
async def _run_context_enrichment_tools(
|
|
1001
|
+
runtime: Optional[Any], available_tools: Dict[str, List[ToolInfo]]
|
|
1002
|
+
) -> Dict[str, Any]:
|
|
1003
|
+
"""Run context enrichment tools and return their results.
|
|
1004
|
+
|
|
1005
|
+
Context enrichment tools are marked with context_enrichment=True in their ToolInfo.
|
|
1006
|
+
They are automatically executed during context gathering to provide additional
|
|
1007
|
+
information for action selection (e.g., listing available Home Assistant entities).
|
|
1008
|
+
|
|
1009
|
+
Args:
|
|
1010
|
+
runtime: The runtime object with service_registry and bus_manager
|
|
1011
|
+
available_tools: Already collected available tools by adapter type
|
|
1012
|
+
|
|
1013
|
+
Returns:
|
|
1014
|
+
Dict mapping "adapter_type:tool_name" to tool execution results
|
|
1015
|
+
"""
|
|
1016
|
+
enrichment_results: Dict[str, Any] = {}
|
|
1017
|
+
|
|
1018
|
+
if not _validate_runtime_capabilities(runtime):
|
|
1019
|
+
return enrichment_results
|
|
1020
|
+
|
|
1021
|
+
assert runtime is not None
|
|
1022
|
+
|
|
1023
|
+
enrichment_tools = _collect_enrichment_tools(available_tools)
|
|
1024
|
+
if not enrichment_tools:
|
|
1025
|
+
_log_no_enrichment_tools(available_tools)
|
|
1026
|
+
return enrichment_results
|
|
1027
|
+
|
|
1028
|
+
tool_services = _get_tool_services(runtime.service_registry)
|
|
1029
|
+
|
|
1030
|
+
for adapter_type, tool in enrichment_tools:
|
|
1031
|
+
try:
|
|
1032
|
+
tool_key, result = await _execute_enrichment_tool(tool_services, adapter_type, tool)
|
|
1033
|
+
if result is not None:
|
|
1034
|
+
enrichment_results[tool_key] = result
|
|
1035
|
+
except Exception as e:
|
|
1036
|
+
tool_key = f"{adapter_type}:{tool.name}"
|
|
1037
|
+
logger.error(f"[CONTEXT_ENRICHMENT] Failed to execute {tool_key}: {e}")
|
|
1038
|
+
enrichment_results[tool_key] = {"error": str(e)}
|
|
1039
|
+
|
|
1040
|
+
logger.info(f"[CONTEXT_ENRICHMENT] Collected {len(enrichment_results)} enrichment results")
|
|
1041
|
+
return enrichment_results
|
|
1042
|
+
|
|
1043
|
+
|
|
1044
|
+
# =============================================================================
|
|
1045
|
+
# 8. USER MANAGEMENT
|
|
1046
|
+
# =============================================================================
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
def _extract_user_from_task_context(task: Optional[Task], user_ids: Set[str]) -> None:
|
|
1050
|
+
"""Extract user ID from task context."""
|
|
1051
|
+
if task and task.context:
|
|
1052
|
+
logger.debug(f"[USER EXTRACTION] Task context exists, user_id value: {repr(task.context.user_id)}")
|
|
1053
|
+
if task.context.user_id:
|
|
1054
|
+
user_ids.add(str(task.context.user_id))
|
|
1055
|
+
logger.debug(f"[USER EXTRACTION] Found user {task.context.user_id} from task context")
|
|
1056
|
+
else:
|
|
1057
|
+
logger.debug("[USER EXTRACTION] Task context.user_id is None or empty")
|
|
1058
|
+
else:
|
|
1059
|
+
logger.debug(f"[USER EXTRACTION] Task or task.context is None (task={task is not None})")
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
def _extract_users_from_thought_content(thought: Any, user_ids: Set[str]) -> None:
|
|
1063
|
+
"""Extract user IDs from thought content patterns."""
|
|
1064
|
+
if not thought:
|
|
1065
|
+
return
|
|
1066
|
+
|
|
1067
|
+
thought_content = getattr(thought, "content", "") or ""
|
|
1068
|
+
|
|
1069
|
+
# Discord user ID pattern
|
|
1070
|
+
discord_mentions = re.findall(r"<@(\d+)>", thought_content)
|
|
1071
|
+
if discord_mentions:
|
|
1072
|
+
user_ids.update(discord_mentions)
|
|
1073
|
+
logger.debug(f"[USER EXTRACTION] Found {len(discord_mentions)} users from Discord mentions: {discord_mentions}")
|
|
1074
|
+
|
|
1075
|
+
# Also look for "ID: <number>" pattern
|
|
1076
|
+
id_mentions = re.findall(r"ID:\s*(\d+)", thought_content)
|
|
1077
|
+
if id_mentions:
|
|
1078
|
+
user_ids.update(id_mentions)
|
|
1079
|
+
logger.debug(f"[USER EXTRACTION] Found {len(id_mentions)} users from ID patterns: {id_mentions}")
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
def _extract_user_from_thought_context(thought: Any, user_ids: Set[str]) -> None:
|
|
1083
|
+
"""Extract user ID from thought context."""
|
|
1084
|
+
if hasattr(thought, "context") and thought.context:
|
|
1085
|
+
if hasattr(thought.context, "user_id") and thought.context.user_id:
|
|
1086
|
+
user_ids.add(str(thought.context.user_id))
|
|
1087
|
+
logger.debug(f"[USER EXTRACTION] Found user {thought.context.user_id} from thought context")
|
|
1088
|
+
|
|
1089
|
+
|
|
1090
|
+
def _extract_users_from_correlation_history(task: Optional[Task], user_ids: Set[str]) -> None:
|
|
1091
|
+
"""Extract user IDs from correlation history database."""
|
|
1092
|
+
if not (task and task.context and task.context.correlation_id):
|
|
1093
|
+
return
|
|
1094
|
+
|
|
1095
|
+
try:
|
|
1096
|
+
from ciris_engine.logic.persistence.db.dialect import get_adapter
|
|
1097
|
+
|
|
1098
|
+
adapter = get_adapter()
|
|
1099
|
+
|
|
1100
|
+
with persistence.get_db_connection() as conn:
|
|
1101
|
+
cursor = conn.cursor()
|
|
1102
|
+
|
|
1103
|
+
# Build dialect-specific SQL
|
|
1104
|
+
if adapter.is_postgresql():
|
|
1105
|
+
sql = """
|
|
1106
|
+
SELECT DISTINCT tags->>'user_id' as user_id
|
|
1107
|
+
FROM service_correlations
|
|
1108
|
+
WHERE correlation_id = %s
|
|
1109
|
+
AND tags->>'user_id' IS NOT NULL
|
|
1110
|
+
"""
|
|
1111
|
+
else:
|
|
1112
|
+
sql = """
|
|
1113
|
+
SELECT DISTINCT json_extract(tags, '$.user_id') as user_id
|
|
1114
|
+
FROM service_correlations
|
|
1115
|
+
WHERE correlation_id = ?
|
|
1116
|
+
AND json_extract(tags, '$.user_id') IS NOT NULL
|
|
1117
|
+
"""
|
|
1118
|
+
|
|
1119
|
+
cursor.execute(sql, (task.context.correlation_id,))
|
|
1120
|
+
correlation_users = cursor.fetchall()
|
|
1121
|
+
for row in correlation_users:
|
|
1122
|
+
if row["user_id"]:
|
|
1123
|
+
user_ids.add(str(row["user_id"]))
|
|
1124
|
+
logger.debug(f"[USER EXTRACTION] Found user {row['user_id']} from correlation history")
|
|
1125
|
+
except Exception as e:
|
|
1126
|
+
logger.warning(f"[USER EXTRACTION] Failed to extract users from correlation history: {e}")
|
|
1127
|
+
|
|
1128
|
+
|
|
1129
|
+
def _extract_user_ids_from_context(task: Optional[Task], thought: Any) -> Set[str]:
|
|
1130
|
+
"""Extract user IDs from task, thought, and correlation history."""
|
|
1131
|
+
user_ids_to_enrich: Set[str] = set()
|
|
1132
|
+
|
|
1133
|
+
# Extract from all sources using helper functions
|
|
1134
|
+
_extract_user_from_task_context(task, user_ids_to_enrich)
|
|
1135
|
+
_extract_users_from_thought_content(thought, user_ids_to_enrich)
|
|
1136
|
+
_extract_user_from_thought_context(thought, user_ids_to_enrich)
|
|
1137
|
+
_extract_users_from_correlation_history(task, user_ids_to_enrich)
|
|
1138
|
+
|
|
1139
|
+
logger.info(f"[USER EXTRACTION] Total users to enrich: {len(user_ids_to_enrich)} users: {user_ids_to_enrich}")
|
|
1140
|
+
return user_ids_to_enrich
|
|
1141
|
+
|
|
1142
|
+
|
|
1143
|
+
def _json_serial_for_users(obj: Any) -> Any:
|
|
1144
|
+
"""JSON serializer for user profile data."""
|
|
1145
|
+
if hasattr(obj, "isoformat"):
|
|
1146
|
+
return obj.isoformat()
|
|
1147
|
+
if hasattr(obj, "model_dump"):
|
|
1148
|
+
return obj.model_dump()
|
|
1149
|
+
return str(obj)
|
|
1150
|
+
|
|
1151
|
+
|
|
1152
|
+
def _should_skip_user_enrichment(user_id: str, existing_user_ids: Set[str]) -> bool:
|
|
1153
|
+
"""Check if user should be skipped during enrichment."""
|
|
1154
|
+
if user_id in existing_user_ids:
|
|
1155
|
+
logger.debug(f"[USER EXTRACTION] User {user_id} already exists, skipping")
|
|
1156
|
+
return True
|
|
1157
|
+
return False
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
def _create_user_memory_query(user_id: str) -> MemoryQuery:
|
|
1161
|
+
"""Create memory query for user enrichment."""
|
|
1162
|
+
return MemoryQuery(
|
|
1163
|
+
node_id=f"user/{user_id}",
|
|
1164
|
+
scope=GraphScope.LOCAL,
|
|
1165
|
+
type=NodeType.USER,
|
|
1166
|
+
include_edges=True,
|
|
1167
|
+
depth=2,
|
|
1168
|
+
)
|
|
1169
|
+
|
|
1170
|
+
|
|
1171
|
+
def _determine_if_admin_user(user_id: str) -> bool:
|
|
1172
|
+
"""Determine if a user_id represents an admin or system user."""
|
|
1173
|
+
# Check for admin patterns
|
|
1174
|
+
user_id_lower = user_id.lower()
|
|
1175
|
+
return user_id_lower.startswith("wa-") or user_id_lower == "admin" or user_id_lower.startswith("admin_")
|
|
1176
|
+
|
|
1177
|
+
|
|
1178
|
+
async def _create_default_user_node(
|
|
1179
|
+
user_id: str, memory_service: LocalGraphMemoryService, channel_id: Optional[str]
|
|
1180
|
+
) -> Optional[GraphNode]:
|
|
1181
|
+
"""Create a new user node with appropriate defaults.
|
|
1182
|
+
|
|
1183
|
+
Creates minimal user profile for new users:
|
|
1184
|
+
- Admin/WA users: minimal profile with system defaults
|
|
1185
|
+
- Regular users: basic profile ready for user settings
|
|
1186
|
+
"""
|
|
1187
|
+
try:
|
|
1188
|
+
# Determine user type
|
|
1189
|
+
is_admin = _determine_if_admin_user(user_id)
|
|
1190
|
+
|
|
1191
|
+
# Create timestamp for first_seen
|
|
1192
|
+
current_time = datetime.now(timezone.utc)
|
|
1193
|
+
|
|
1194
|
+
# Build base attributes as JSON-compatible dict
|
|
1195
|
+
base_attributes: JSONDict = {
|
|
1196
|
+
"user_id": user_id,
|
|
1197
|
+
"display_name": "Admin" if is_admin else f"User_{user_id}",
|
|
1198
|
+
"first_seen": current_time.isoformat(),
|
|
1199
|
+
"created_by": "UserEnrichment",
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
# Add channel if provided
|
|
1203
|
+
if channel_id:
|
|
1204
|
+
base_attributes["channels"] = [channel_id]
|
|
1205
|
+
|
|
1206
|
+
# Add role-specific defaults
|
|
1207
|
+
if is_admin:
|
|
1208
|
+
# Admin users get minimal profile
|
|
1209
|
+
base_attributes["trust_level"] = 1.0
|
|
1210
|
+
base_attributes["is_wa"] = user_id.lower().startswith("wa-")
|
|
1211
|
+
else:
|
|
1212
|
+
# Regular users get basic profile
|
|
1213
|
+
base_attributes["trust_level"] = 0.5
|
|
1214
|
+
base_attributes["communication_style"] = "formal"
|
|
1215
|
+
base_attributes["preferred_language"] = "en"
|
|
1216
|
+
base_attributes["timezone"] = "UTC"
|
|
1217
|
+
|
|
1218
|
+
# Create GraphNode
|
|
1219
|
+
new_node = GraphNode(
|
|
1220
|
+
id=f"user/{user_id}",
|
|
1221
|
+
type=NodeType.USER,
|
|
1222
|
+
scope=GraphScope.LOCAL,
|
|
1223
|
+
attributes=base_attributes,
|
|
1224
|
+
)
|
|
1225
|
+
|
|
1226
|
+
# Save to memory graph
|
|
1227
|
+
logger.info(
|
|
1228
|
+
f"[USER EXTRACTION] Creating new user node for {user_id} (admin={is_admin}) with attributes: {list(base_attributes.keys())}"
|
|
1229
|
+
)
|
|
1230
|
+
await memory_service.memorize(new_node)
|
|
1231
|
+
|
|
1232
|
+
return new_node
|
|
1233
|
+
|
|
1234
|
+
except Exception as e:
|
|
1235
|
+
logger.error(f"Failed to create default user node for {user_id}: {e}")
|
|
1236
|
+
return None
|
|
1237
|
+
|
|
1238
|
+
|
|
1239
|
+
async def _process_user_node_for_profile(
|
|
1240
|
+
user_node: Any, user_id: str, memory_service: LocalGraphMemoryService, channel_id: Optional[str]
|
|
1241
|
+
) -> UserProfile:
|
|
1242
|
+
"""Process user node to create enriched user profile."""
|
|
1243
|
+
# Extract node attributes using helper
|
|
1244
|
+
attrs = _extract_node_attributes(user_node)
|
|
1245
|
+
|
|
1246
|
+
# Collect connected nodes using helper
|
|
1247
|
+
connected_nodes_info = await _collect_connected_nodes(memory_service, user_id)
|
|
1248
|
+
|
|
1249
|
+
# Parse datetime fields safely
|
|
1250
|
+
last_interaction = _parse_datetime_safely(attrs.get("last_seen"), "last_seen", user_id)
|
|
1251
|
+
created_at = _parse_datetime_safely(attrs.get("first_seen") or attrs.get("created_at"), "created_at", user_id)
|
|
1252
|
+
|
|
1253
|
+
# Create user profile using helper
|
|
1254
|
+
user_profile = _create_user_profile_from_node(user_id, attrs, connected_nodes_info, last_interaction, created_at)
|
|
1255
|
+
|
|
1256
|
+
# Collect cross-channel messages and add to profile notes
|
|
1257
|
+
if channel_id:
|
|
1258
|
+
recent_messages = await _collect_cross_channel_messages(user_id, channel_id)
|
|
1259
|
+
if recent_messages:
|
|
1260
|
+
# Ensure notes is not None before concatenating
|
|
1261
|
+
current_notes = user_profile.notes or ""
|
|
1262
|
+
user_profile.notes = current_notes + (
|
|
1263
|
+
f"\nRecent messages from other channels: {json.dumps(recent_messages, default=_json_serial_for_users)}"
|
|
1264
|
+
)
|
|
1265
|
+
|
|
1266
|
+
logger.info(
|
|
1267
|
+
f"Added user profile for {user_id} with attributes: {list(attrs.keys())} and {len(connected_nodes_info)} connected nodes"
|
|
1268
|
+
)
|
|
1269
|
+
return user_profile
|
|
1270
|
+
|
|
1271
|
+
|
|
1272
|
+
async def _enrich_single_user_profile(
|
|
1273
|
+
user_id: str, memory_service: LocalGraphMemoryService, channel_id: Optional[str]
|
|
1274
|
+
) -> Optional[UserProfile]:
|
|
1275
|
+
"""Enrich a single user profile from memory graph.
|
|
1276
|
+
|
|
1277
|
+
If no user node exists, creates one automatically with appropriate defaults.
|
|
1278
|
+
This ensures user_profiles is never empty in system snapshots.
|
|
1279
|
+
"""
|
|
1280
|
+
try:
|
|
1281
|
+
# Query user node with ALL attributes
|
|
1282
|
+
user_query = _create_user_memory_query(user_id)
|
|
1283
|
+
logger.debug(f"[DEBUG] Querying memory for user/{user_id}")
|
|
1284
|
+
user_results = await memory_service.recall(user_query)
|
|
1285
|
+
logger.debug(
|
|
1286
|
+
f"[USER EXTRACTION] Query returned {len(user_results) if user_results else 0} results for user {user_id}"
|
|
1287
|
+
)
|
|
1288
|
+
|
|
1289
|
+
if user_results:
|
|
1290
|
+
user_node = user_results[0]
|
|
1291
|
+
return await _process_user_node_for_profile(user_node, user_id, memory_service, channel_id)
|
|
1292
|
+
|
|
1293
|
+
# No user node exists - create one automatically
|
|
1294
|
+
logger.info(f"[USER EXTRACTION] No node found for user/{user_id}, creating new user node with defaults")
|
|
1295
|
+
new_user_node = await _create_default_user_node(user_id, memory_service, channel_id)
|
|
1296
|
+
|
|
1297
|
+
if new_user_node:
|
|
1298
|
+
return await _process_user_node_for_profile(new_user_node, user_id, memory_service, channel_id)
|
|
1299
|
+
|
|
1300
|
+
except Exception as e:
|
|
1301
|
+
logger.warning(f"Failed to enrich user {user_id}: {e}")
|
|
1302
|
+
|
|
1303
|
+
return None
|
|
1304
|
+
|
|
1305
|
+
|
|
1306
|
+
async def _enrich_user_profiles(
|
|
1307
|
+
memory_service: LocalGraphMemoryService,
|
|
1308
|
+
user_ids: Set[str],
|
|
1309
|
+
channel_id: Optional[str],
|
|
1310
|
+
existing_profiles: List[UserProfile],
|
|
1311
|
+
) -> List[UserProfile]:
|
|
1312
|
+
"""Enrich user profiles from memory graph with comprehensive data."""
|
|
1313
|
+
existing_user_ids = {p.user_id for p in existing_profiles}
|
|
1314
|
+
|
|
1315
|
+
for user_id in user_ids:
|
|
1316
|
+
logger.debug(f"[USER EXTRACTION] Processing user {user_id}")
|
|
1317
|
+
|
|
1318
|
+
if _should_skip_user_enrichment(user_id, existing_user_ids):
|
|
1319
|
+
continue
|
|
1320
|
+
|
|
1321
|
+
user_profile = await _enrich_single_user_profile(user_id, memory_service, channel_id)
|
|
1322
|
+
if user_profile:
|
|
1323
|
+
existing_profiles.append(user_profile)
|
|
1324
|
+
|
|
1325
|
+
return existing_profiles
|
|
1326
|
+
|
|
1327
|
+
|
|
1328
|
+
# =============================================================================
|
|
1329
|
+
# TIME LOCALIZATION HELPERS
|
|
1330
|
+
# =============================================================================
|
|
1331
|
+
|
|
1332
|
+
|
|
1333
|
+
def _get_localized_times(time_service: TimeServiceProtocol) -> LocalizedTimeData:
|
|
1334
|
+
"""Get current time localized to LONDON, CHICAGO, and TOKYO timezones.
|
|
1335
|
+
|
|
1336
|
+
FAILS FAST AND LOUD if time_service is None.
|
|
1337
|
+
|
|
1338
|
+
Cross-platform compatible: Falls back to UTC offsets on Windows where
|
|
1339
|
+
IANA timezone database may not be available.
|
|
1340
|
+
"""
|
|
1341
|
+
if time_service is None:
|
|
1342
|
+
raise RuntimeError(
|
|
1343
|
+
"CRITICAL: time_service is None! Cannot get localized times. "
|
|
1344
|
+
"The system must be properly initialized with a time service."
|
|
1345
|
+
)
|
|
1346
|
+
|
|
1347
|
+
from datetime import datetime, timedelta
|
|
1348
|
+
from datetime import timezone as dt_timezone
|
|
1349
|
+
from datetime import tzinfo
|
|
1350
|
+
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|
1351
|
+
|
|
1352
|
+
# Get current UTC time from time service
|
|
1353
|
+
utc_time = time_service.now()
|
|
1354
|
+
if not isinstance(utc_time, datetime):
|
|
1355
|
+
raise RuntimeError(
|
|
1356
|
+
f"CRITICAL: time_service.now() returned {type(utc_time)}, expected datetime. "
|
|
1357
|
+
f"Time service is not properly configured."
|
|
1358
|
+
)
|
|
1359
|
+
|
|
1360
|
+
# Define timezone objects using zoneinfo with Windows fallback
|
|
1361
|
+
# Windows may not have IANA timezone database, use fixed UTC offsets as fallback
|
|
1362
|
+
london_tz: tzinfo
|
|
1363
|
+
try:
|
|
1364
|
+
london_tz = ZoneInfo("Europe/London")
|
|
1365
|
+
except ZoneInfoNotFoundError:
|
|
1366
|
+
# GMT/BST: UTC+0/+1 - use UTC+0 as conservative fallback
|
|
1367
|
+
london_tz = dt_timezone(timedelta(hours=0))
|
|
1368
|
+
logger.warning("Europe/London timezone not found, using UTC+0 fallback")
|
|
1369
|
+
|
|
1370
|
+
chicago_tz: tzinfo
|
|
1371
|
+
try:
|
|
1372
|
+
chicago_tz = ZoneInfo("America/Chicago")
|
|
1373
|
+
except ZoneInfoNotFoundError:
|
|
1374
|
+
# CST/CDT: UTC-6/-5 - use UTC-6 as conservative fallback
|
|
1375
|
+
chicago_tz = dt_timezone(timedelta(hours=-6))
|
|
1376
|
+
logger.warning("America/Chicago timezone not found, using UTC-6 fallback")
|
|
1377
|
+
|
|
1378
|
+
tokyo_tz: tzinfo
|
|
1379
|
+
try:
|
|
1380
|
+
tokyo_tz = ZoneInfo("Asia/Tokyo")
|
|
1381
|
+
except ZoneInfoNotFoundError:
|
|
1382
|
+
# JST: UTC+9 (no DST)
|
|
1383
|
+
tokyo_tz = dt_timezone(timedelta(hours=9))
|
|
1384
|
+
logger.warning("Asia/Tokyo timezone not found, using UTC+9 fallback")
|
|
1385
|
+
|
|
1386
|
+
# Convert to localized times
|
|
1387
|
+
utc_iso = utc_time.isoformat()
|
|
1388
|
+
london_time = utc_time.astimezone(london_tz).isoformat()
|
|
1389
|
+
chicago_time = utc_time.astimezone(chicago_tz).isoformat()
|
|
1390
|
+
tokyo_time = utc_time.astimezone(tokyo_tz).isoformat()
|
|
1391
|
+
|
|
1392
|
+
return LocalizedTimeData(
|
|
1393
|
+
utc=utc_iso,
|
|
1394
|
+
london=london_time,
|
|
1395
|
+
chicago=chicago_time,
|
|
1396
|
+
tokyo=tokyo_time,
|
|
1397
|
+
)
|
|
1398
|
+
|
|
1399
|
+
|
|
1400
|
+
# =============================================================================
|
|
1401
|
+
# USER PROFILE ENRICHMENT HELPERS (Reusable for other adapters)
|
|
1402
|
+
# =============================================================================
|
|
1403
|
+
|
|
1404
|
+
|
|
1405
|
+
def get_known_user_fields() -> Set[str]:
|
|
1406
|
+
"""Get set of known UserProfile fields."""
|
|
1407
|
+
return {
|
|
1408
|
+
"user_id",
|
|
1409
|
+
"display_name",
|
|
1410
|
+
"username", # Alternative for display_name
|
|
1411
|
+
"created_at",
|
|
1412
|
+
"first_seen", # Alternative for created_at
|
|
1413
|
+
"preferred_language",
|
|
1414
|
+
"language", # Alternative for preferred_language
|
|
1415
|
+
"timezone",
|
|
1416
|
+
"communication_style",
|
|
1417
|
+
# User-configurable preferences (protected from agent, visible in snapshot)
|
|
1418
|
+
"user_preferred_name",
|
|
1419
|
+
"location",
|
|
1420
|
+
"interaction_preferences",
|
|
1421
|
+
"oauth_name",
|
|
1422
|
+
# User interaction tracking
|
|
1423
|
+
"total_interactions",
|
|
1424
|
+
"last_interaction",
|
|
1425
|
+
"last_seen", # Alternative for last_interaction
|
|
1426
|
+
"trust_level",
|
|
1427
|
+
"is_wa",
|
|
1428
|
+
"permissions",
|
|
1429
|
+
"restrictions",
|
|
1430
|
+
"consent_stream",
|
|
1431
|
+
"consent_expires_at",
|
|
1432
|
+
"partnership_requested_at",
|
|
1433
|
+
"partnership_approved",
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
|
|
1437
|
+
def build_user_profile_from_node(
|
|
1438
|
+
user_id: str,
|
|
1439
|
+
attrs: JSONDict,
|
|
1440
|
+
connected_nodes_info: List[ConnectedNodeInfo],
|
|
1441
|
+
last_interaction: Optional[datetime],
|
|
1442
|
+
created_at: Optional[datetime],
|
|
1443
|
+
) -> UserProfile:
|
|
1444
|
+
"""Create a UserProfile from node attributes using standardized approach."""
|
|
1445
|
+
# Get known field definitions
|
|
1446
|
+
known_fields = get_known_user_fields()
|
|
1447
|
+
|
|
1448
|
+
# Collect arbitrary attributes in memorized_attributes
|
|
1449
|
+
memorized_attributes = collect_memorized_attributes(attrs, known_fields)
|
|
1450
|
+
|
|
1451
|
+
# Create connected nodes summary for notes
|
|
1452
|
+
notes_content = ""
|
|
1453
|
+
if connected_nodes_info:
|
|
1454
|
+
# Convert typed objects to dict for JSON serialization
|
|
1455
|
+
connected_data = [node.model_dump() for node in connected_nodes_info]
|
|
1456
|
+
notes_content = f"Connected nodes: {json.dumps(connected_data, default=_json_serial_for_users)}"
|
|
1457
|
+
|
|
1458
|
+
# Parse consent information from node attributes
|
|
1459
|
+
consent_expires_at = _parse_datetime_safely(attrs.get("consent_expires_at"), "consent_expires_at", user_id)
|
|
1460
|
+
partnership_requested_at = _parse_datetime_safely(
|
|
1461
|
+
attrs.get("partnership_requested_at"), "partnership_requested_at", user_id
|
|
1462
|
+
)
|
|
1463
|
+
|
|
1464
|
+
return UserProfile(
|
|
1465
|
+
user_id=user_id,
|
|
1466
|
+
display_name=attrs.get("username", attrs.get("display_name", f"User_{user_id}")),
|
|
1467
|
+
created_at=created_at or datetime.now(),
|
|
1468
|
+
preferred_language=attrs.get("language", attrs.get("preferred_language", "en")),
|
|
1469
|
+
timezone=attrs.get("timezone", "UTC"),
|
|
1470
|
+
communication_style=attrs.get("communication_style", "formal"),
|
|
1471
|
+
# User-configurable preferences (protected from agent modification in MANAGED_USER_ATTRIBUTES)
|
|
1472
|
+
user_preferred_name=attrs.get("user_preferred_name"),
|
|
1473
|
+
location=attrs.get("location"),
|
|
1474
|
+
interaction_preferences=attrs.get("interaction_preferences"),
|
|
1475
|
+
oauth_name=attrs.get("oauth_name"),
|
|
1476
|
+
total_interactions=attrs.get("total_interactions", 0),
|
|
1477
|
+
last_interaction=last_interaction,
|
|
1478
|
+
trust_level=attrs.get("trust_level", 0.5),
|
|
1479
|
+
is_wa=attrs.get("is_wa", False),
|
|
1480
|
+
permissions=attrs.get("permissions", []),
|
|
1481
|
+
restrictions=attrs.get("restrictions", []),
|
|
1482
|
+
# Consent relationship state
|
|
1483
|
+
consent_stream=attrs.get("consent_stream", "TEMPORARY"),
|
|
1484
|
+
consent_expires_at=consent_expires_at,
|
|
1485
|
+
partnership_requested_at=partnership_requested_at,
|
|
1486
|
+
partnership_approved=attrs.get("partnership_approved", False),
|
|
1487
|
+
# Store arbitrary attributes the agent memorized
|
|
1488
|
+
memorized_attributes=memorized_attributes,
|
|
1489
|
+
# Store connected nodes info in notes
|
|
1490
|
+
notes=notes_content,
|
|
1491
|
+
)
|
|
1492
|
+
|
|
1493
|
+
|
|
1494
|
+
# Legacy function - now uses standardized extract_node_attributes
|
|
1495
|
+
def _extract_node_attributes(
|
|
1496
|
+
node: GraphNode,
|
|
1497
|
+
) -> JSONDict:
|
|
1498
|
+
"""Extract attributes from a graph node, handling both dict and Pydantic models."""
|
|
1499
|
+
return extract_node_attributes(node)
|
|
1500
|
+
|
|
1501
|
+
|
|
1502
|
+
def _parse_datetime_safely(raw_value: Any, field_name: str, user_id: str) -> Optional[datetime]:
|
|
1503
|
+
"""Parse datetime value safely, returning None for any invalid data."""
|
|
1504
|
+
if raw_value is None:
|
|
1505
|
+
return None
|
|
1506
|
+
|
|
1507
|
+
if isinstance(raw_value, datetime):
|
|
1508
|
+
return raw_value
|
|
1509
|
+
elif isinstance(raw_value, str):
|
|
1510
|
+
try:
|
|
1511
|
+
# Try to parse as ISO datetime
|
|
1512
|
+
return datetime.fromisoformat(raw_value.replace("Z", "+00:00"))
|
|
1513
|
+
except (ValueError, AttributeError):
|
|
1514
|
+
logger.warning(
|
|
1515
|
+
f"FIELD_FAILED_VALIDATION: User {user_id} has invalid {field_name}: '{raw_value}', using None"
|
|
1516
|
+
)
|
|
1517
|
+
return None
|
|
1518
|
+
else:
|
|
1519
|
+
logger.warning(
|
|
1520
|
+
f"FIELD_FAILED_VALIDATION: User {user_id} has non-datetime {field_name}: {type(raw_value)}, using None"
|
|
1521
|
+
)
|
|
1522
|
+
return None
|
|
1523
|
+
|
|
1524
|
+
|
|
1525
|
+
async def _collect_connected_nodes(memory_service: LocalGraphMemoryService, user_id: str) -> List[ConnectedNodeInfo]:
|
|
1526
|
+
"""Collect connected nodes for a user node from the memory graph."""
|
|
1527
|
+
connected_nodes_info = []
|
|
1528
|
+
try:
|
|
1529
|
+
# Get edges for this user node
|
|
1530
|
+
from ciris_engine.logic.persistence.models.graph import get_edges_for_node
|
|
1531
|
+
|
|
1532
|
+
edges = get_edges_for_node(f"user/{user_id}", GraphScope.LOCAL)
|
|
1533
|
+
|
|
1534
|
+
for edge in edges:
|
|
1535
|
+
# Get the connected node
|
|
1536
|
+
connected_node_id = edge.target if edge.source == f"user/{user_id}" else edge.source
|
|
1537
|
+
connected_query = MemoryQuery(
|
|
1538
|
+
node_id=connected_node_id, scope=GraphScope.LOCAL, include_edges=False, depth=1
|
|
1539
|
+
)
|
|
1540
|
+
connected_results = await memory_service.recall(connected_query)
|
|
1541
|
+
if connected_results:
|
|
1542
|
+
connected_node = connected_results[0]
|
|
1543
|
+
# Extract attributes using our standardized helper
|
|
1544
|
+
connected_attrs = extract_node_attributes(connected_node)
|
|
1545
|
+
|
|
1546
|
+
# Create typed connected node info
|
|
1547
|
+
connected_info = ConnectedNodeInfo(
|
|
1548
|
+
node_id=connected_node.id,
|
|
1549
|
+
node_type=connected_node.type,
|
|
1550
|
+
relationship=edge.relationship,
|
|
1551
|
+
attributes=connected_attrs or {},
|
|
1552
|
+
)
|
|
1553
|
+
connected_nodes_info.append(connected_info)
|
|
1554
|
+
except Exception as e:
|
|
1555
|
+
logger.warning(f"Failed to get connected nodes for user {user_id}: {e}")
|
|
1556
|
+
|
|
1557
|
+
return connected_nodes_info
|
|
1558
|
+
|
|
1559
|
+
|
|
1560
|
+
# Legacy function - now uses standardized build_user_profile_from_node
|
|
1561
|
+
def _create_user_profile_from_node(
|
|
1562
|
+
user_id: str,
|
|
1563
|
+
attrs: JSONDict,
|
|
1564
|
+
connected_nodes_info: List[ConnectedNodeInfo],
|
|
1565
|
+
last_interaction: Optional[datetime],
|
|
1566
|
+
created_at: Optional[datetime],
|
|
1567
|
+
) -> UserProfile:
|
|
1568
|
+
"""Create a UserProfile from node attributes and connected nodes."""
|
|
1569
|
+
return build_user_profile_from_node(user_id, attrs, connected_nodes_info, last_interaction, created_at)
|
|
1570
|
+
|
|
1571
|
+
|
|
1572
|
+
async def _collect_cross_channel_messages(user_id: str, channel_id: str) -> List[JSONDict]:
|
|
1573
|
+
"""Collect recent messages from this user in other channels."""
|
|
1574
|
+
recent_messages = []
|
|
1575
|
+
try:
|
|
1576
|
+
from ciris_engine.logic.persistence.db.dialect import get_adapter
|
|
1577
|
+
|
|
1578
|
+
adapter = get_adapter()
|
|
1579
|
+
|
|
1580
|
+
# Query service correlations for user's recent messages
|
|
1581
|
+
with persistence.get_db_connection() as conn:
|
|
1582
|
+
cursor = conn.cursor()
|
|
1583
|
+
|
|
1584
|
+
# Build dialect-specific SQL
|
|
1585
|
+
if adapter.is_postgresql():
|
|
1586
|
+
# PostgreSQL: Cast JSONB to text for LIKE operator
|
|
1587
|
+
sql = """
|
|
1588
|
+
SELECT
|
|
1589
|
+
c.correlation_id,
|
|
1590
|
+
c.handler_name,
|
|
1591
|
+
c.request_data,
|
|
1592
|
+
c.created_at,
|
|
1593
|
+
c.tags
|
|
1594
|
+
FROM service_correlations c
|
|
1595
|
+
WHERE
|
|
1596
|
+
c.tags::text LIKE %s
|
|
1597
|
+
AND c.tags::text NOT LIKE %s
|
|
1598
|
+
AND c.handler_name IN ('ObserveHandler', 'SpeakHandler')
|
|
1599
|
+
ORDER BY c.created_at DESC
|
|
1600
|
+
LIMIT 3
|
|
1601
|
+
"""
|
|
1602
|
+
else:
|
|
1603
|
+
# SQLite: tags is TEXT, LIKE works directly
|
|
1604
|
+
sql = """
|
|
1605
|
+
SELECT
|
|
1606
|
+
c.correlation_id,
|
|
1607
|
+
c.handler_name,
|
|
1608
|
+
c.request_data,
|
|
1609
|
+
c.created_at,
|
|
1610
|
+
c.tags
|
|
1611
|
+
FROM service_correlations c
|
|
1612
|
+
WHERE
|
|
1613
|
+
c.tags LIKE ?
|
|
1614
|
+
AND c.tags NOT LIKE ?
|
|
1615
|
+
AND c.handler_name IN ('ObserveHandler', 'SpeakHandler')
|
|
1616
|
+
ORDER BY c.created_at DESC
|
|
1617
|
+
LIMIT 3
|
|
1618
|
+
"""
|
|
1619
|
+
|
|
1620
|
+
cursor.execute(sql, (f'%"user_id":"{user_id}"%', f'%"channel_id":"{channel_id}"%'))
|
|
1621
|
+
|
|
1622
|
+
for row in cursor.fetchall():
|
|
1623
|
+
try:
|
|
1624
|
+
tags = json.loads(row["tags"]) if row["tags"] else {}
|
|
1625
|
+
msg_channel = tags.get("channel_id", "unknown")
|
|
1626
|
+
msg_content = "Message in " + msg_channel
|
|
1627
|
+
|
|
1628
|
+
# Try to extract content from request_data
|
|
1629
|
+
if row["request_data"]:
|
|
1630
|
+
req_data = json.loads(row["request_data"])
|
|
1631
|
+
if isinstance(req_data, dict):
|
|
1632
|
+
msg_content = req_data.get("content", req_data.get("message", msg_content))
|
|
1633
|
+
|
|
1634
|
+
recent_messages.append(
|
|
1635
|
+
{
|
|
1636
|
+
"channel": msg_channel,
|
|
1637
|
+
"content": msg_content,
|
|
1638
|
+
"timestamp": (
|
|
1639
|
+
row["created_at"].isoformat()
|
|
1640
|
+
if hasattr(row["created_at"], "isoformat")
|
|
1641
|
+
else str(row["created_at"])
|
|
1642
|
+
),
|
|
1643
|
+
}
|
|
1644
|
+
)
|
|
1645
|
+
except (json.JSONDecodeError, TypeError, AttributeError, KeyError):
|
|
1646
|
+
# Skip malformed entries
|
|
1647
|
+
pass
|
|
1648
|
+
except Exception as e:
|
|
1649
|
+
logger.warning(f"Failed to collect cross-channel messages for user {user_id}: {e}")
|
|
1650
|
+
|
|
1651
|
+
return recent_messages
|