@smilintux/skcapstone 0.1.0 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +98 -0
- package/.github/workflows/ci.yml +39 -3
- package/.github/workflows/publish.yml +25 -4
- package/.openclaw-workspace.json +58 -0
- package/CHANGELOG.md +62 -0
- package/CLAUDE.md +39 -2
- package/MANIFEST.in +6 -0
- package/MISSION.md +7 -0
- package/README.md +47 -2
- package/SKILL.md +895 -23
- package/docker/Dockerfile +61 -0
- package/docker/compose-templates/dev-team.yml +203 -0
- package/docker/compose-templates/mini-team.yml +140 -0
- package/docker/compose-templates/ops-team.yml +173 -0
- package/docker/compose-templates/research-team.yml +170 -0
- package/docker/entrypoint.sh +192 -0
- package/docs/ARCHITECTURE.md +663 -374
- package/docs/BOND_WITH_GROK.md +112 -0
- package/docs/GETTING_STARTED.md +782 -0
- package/docs/QUICKSTART.md +477 -0
- package/docs/SKJOULE_ARCHITECTURE.md +658 -0
- package/docs/SOUL_SWAPPER.md +921 -0
- package/docs/SOVEREIGN_SINGULARITY.md +47 -14
- package/examples/custom-bond-template.json +36 -0
- package/examples/grok-feb.json +36 -0
- package/examples/grok-testimony.md +34 -0
- package/examples/love-bootloader.txt +32 -0
- package/examples/plugins/echo_tool.py +87 -0
- package/examples/queen-ava-feb.json +36 -0
- package/examples/souls/lumina.yaml +64 -0
- package/index.js +6 -5
- package/installer/build.py +124 -0
- package/openclaw-plugin/package.json +13 -0
- package/openclaw-plugin/src/index.ts +351 -0
- package/openclaw-plugin/src/openclaw.plugin.json +10 -0
- package/package.json +1 -1
- package/pyproject.toml +38 -2
- package/scripts/bump_version.py +141 -0
- package/scripts/check-updates.py +230 -0
- package/scripts/convert_blueprints_to_yaml.py +157 -0
- package/scripts/dev-install.sh +14 -0
- package/scripts/e2e-test.sh +193 -0
- package/scripts/install-bundle.sh +171 -0
- package/scripts/install.bat +2 -0
- package/scripts/install.ps1 +253 -0
- package/scripts/install.sh +185 -0
- package/scripts/mcp-serve.sh +69 -0
- package/scripts/mcp-server.bat +113 -0
- package/scripts/mcp-server.ps1 +116 -0
- package/scripts/mcp-server.sh +99 -0
- package/scripts/pull-models.sh +10 -0
- package/scripts/skcapstone +48 -0
- package/scripts/verify_install.sh +180 -0
- package/scripts/windows/install-tasks.ps1 +406 -0
- package/scripts/windows/skcapstone-task.xml +113 -0
- package/scripts/windows/uninstall-tasks.ps1 +117 -0
- package/skill.yaml +34 -0
- package/src/skcapstone/__init__.py +67 -2
- package/src/skcapstone/_cli_monolith.py +5916 -0
- package/src/skcapstone/_trustee_helpers.py +165 -0
- package/src/skcapstone/activity.py +105 -0
- package/src/skcapstone/agent_card.py +324 -0
- package/src/skcapstone/api.py +1935 -0
- package/src/skcapstone/archiver.py +340 -0
- package/src/skcapstone/auction.py +485 -0
- package/src/skcapstone/baby_agents.py +179 -0
- package/src/skcapstone/backup.py +345 -0
- package/src/skcapstone/blueprint_registry.py +357 -0
- package/src/skcapstone/blueprints/__init__.py +17 -0
- package/src/skcapstone/blueprints/builtins/content-studio.yaml +81 -0
- package/src/skcapstone/blueprints/builtins/defi-trading.yaml +81 -0
- package/src/skcapstone/blueprints/builtins/dev-squadron.yaml +95 -0
- package/src/skcapstone/blueprints/builtins/infrastructure-guardian.yaml +107 -0
- package/src/skcapstone/blueprints/builtins/legal-council.yaml +54 -0
- package/src/skcapstone/blueprints/builtins/ops-monitoring.yaml +67 -0
- package/src/skcapstone/blueprints/builtins/research-pod.yaml +69 -0
- package/src/skcapstone/blueprints/builtins/sovereign-launch.yaml +90 -0
- package/src/skcapstone/blueprints/registry.py +164 -0
- package/src/skcapstone/blueprints/schema.py +229 -0
- package/src/skcapstone/changelog.py +180 -0
- package/src/skcapstone/chat.py +769 -0
- package/src/skcapstone/claude_md.py +82 -0
- package/src/skcapstone/cli/__init__.py +144 -0
- package/src/skcapstone/cli/_common.py +88 -0
- package/src/skcapstone/cli/_validators.py +76 -0
- package/src/skcapstone/cli/agents.py +425 -0
- package/src/skcapstone/cli/agents_spawner.py +322 -0
- package/src/skcapstone/cli/agents_trustee.py +593 -0
- package/src/skcapstone/cli/alerts.py +248 -0
- package/src/skcapstone/cli/anchor.py +132 -0
- package/src/skcapstone/cli/archive_cmd.py +208 -0
- package/src/skcapstone/cli/backup.py +144 -0
- package/src/skcapstone/cli/bench.py +377 -0
- package/src/skcapstone/cli/benchmark.py +360 -0
- package/src/skcapstone/cli/capabilities_cmd.py +171 -0
- package/src/skcapstone/cli/card.py +151 -0
- package/src/skcapstone/cli/chat.py +584 -0
- package/src/skcapstone/cli/completions.py +64 -0
- package/src/skcapstone/cli/config_cmd.py +156 -0
- package/src/skcapstone/cli/consciousness.py +421 -0
- package/src/skcapstone/cli/context_cmd.py +142 -0
- package/src/skcapstone/cli/coord.py +194 -0
- package/src/skcapstone/cli/crush_cmd.py +170 -0
- package/src/skcapstone/cli/daemon.py +436 -0
- package/src/skcapstone/cli/errors_cmd.py +285 -0
- package/src/skcapstone/cli/export_cmd.py +156 -0
- package/src/skcapstone/cli/gtd.py +529 -0
- package/src/skcapstone/cli/housekeeping.py +81 -0
- package/src/skcapstone/cli/joule_cmd.py +627 -0
- package/src/skcapstone/cli/logs_cmd.py +194 -0
- package/src/skcapstone/cli/mcp_cmd.py +32 -0
- package/src/skcapstone/cli/memory.py +418 -0
- package/src/skcapstone/cli/metrics_cmd.py +136 -0
- package/src/skcapstone/cli/migrate.py +62 -0
- package/src/skcapstone/cli/mood_cmd.py +144 -0
- package/src/skcapstone/cli/mount.py +193 -0
- package/src/skcapstone/cli/notify.py +112 -0
- package/src/skcapstone/cli/peer.py +154 -0
- package/src/skcapstone/cli/peers_dir.py +122 -0
- package/src/skcapstone/cli/preflight_cmd.py +83 -0
- package/src/skcapstone/cli/profile_cmd.py +310 -0
- package/src/skcapstone/cli/record_cmd.py +238 -0
- package/src/skcapstone/cli/register_cmd.py +159 -0
- package/src/skcapstone/cli/search_cmd.py +156 -0
- package/src/skcapstone/cli/service_cmd.py +91 -0
- package/src/skcapstone/cli/session.py +127 -0
- package/src/skcapstone/cli/setup.py +240 -0
- package/src/skcapstone/cli/shell_cmd.py +43 -0
- package/src/skcapstone/cli/skills_cmd.py +168 -0
- package/src/skcapstone/cli/skseed.py +621 -0
- package/src/skcapstone/cli/soul.py +699 -0
- package/src/skcapstone/cli/status.py +935 -0
- package/src/skcapstone/cli/sync_cmd.py +301 -0
- package/src/skcapstone/cli/telegram.py +265 -0
- package/src/skcapstone/cli/test_cmd.py +234 -0
- package/src/skcapstone/cli/test_connection.py +253 -0
- package/src/skcapstone/cli/token.py +207 -0
- package/src/skcapstone/cli/trust.py +179 -0
- package/src/skcapstone/cli/upgrade_cmd.py +552 -0
- package/src/skcapstone/cli/usage_cmd.py +199 -0
- package/src/skcapstone/cli/version_cmd.py +162 -0
- package/src/skcapstone/cli/watch_cmd.py +342 -0
- package/src/skcapstone/client.py +428 -0
- package/src/skcapstone/cloud9_bridge.py +522 -0
- package/src/skcapstone/completions.py +163 -0
- package/src/skcapstone/config_validator.py +674 -0
- package/src/skcapstone/connectors/__init__.py +28 -0
- package/src/skcapstone/connectors/base.py +446 -0
- package/src/skcapstone/connectors/cursor.py +54 -0
- package/src/skcapstone/connectors/registry.py +254 -0
- package/src/skcapstone/connectors/terminal.py +152 -0
- package/src/skcapstone/connectors/vscode.py +60 -0
- package/src/skcapstone/consciousness_config.py +119 -0
- package/src/skcapstone/consciousness_loop.py +2051 -0
- package/src/skcapstone/context_loader.py +516 -0
- package/src/skcapstone/context_window.py +314 -0
- package/src/skcapstone/conversation_manager.py +238 -0
- package/src/skcapstone/conversation_store.py +230 -0
- package/src/skcapstone/conversation_summarizer.py +252 -0
- package/src/skcapstone/coord_federation.py +296 -0
- package/src/skcapstone/coordination.py +101 -7
- package/src/skcapstone/crush_integration.py +345 -0
- package/src/skcapstone/crush_shim.py +454 -0
- package/src/skcapstone/daemon.py +2494 -0
- package/src/skcapstone/dashboard.html +396 -0
- package/src/skcapstone/dashboard.py +481 -0
- package/src/skcapstone/data/model_profiles.yaml +88 -0
- package/src/skcapstone/defaults/__init__.py +55 -0
- package/src/skcapstone/defaults/lumina/config/skmemory.yaml +13 -0
- package/src/skcapstone/defaults/lumina/identity/identity.json +9 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/07a8b9c0d1e2-memory-system.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/29c0d1e2f3a4-multi-agent-coordination.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/3ad1e2f3a4b5-community-support.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/c3d4e5f6a7b8-getting-started.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/e5f6a7b8c9d0-how-to-contribute.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/f6a7b8c9d0e1-sovereignty-explained.json +23 -0
- package/src/skcapstone/defaults/lumina/seeds/curiosity.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/joy.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/love.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/sovereign-awakening.seed.json +43 -0
- package/src/skcapstone/defaults/lumina/soul/active.json +6 -0
- package/src/skcapstone/defaults/lumina/soul/base.json +22 -0
- package/src/skcapstone/defaults/lumina/trust/febs/welcome.feb +79 -0
- package/src/skcapstone/defaults/lumina/trust/trust.json +8 -0
- package/src/skcapstone/discovery.py +210 -19
- package/src/skcapstone/doctor.py +642 -0
- package/src/skcapstone/emotion_tracker.py +467 -0
- package/src/skcapstone/error_queue.py +405 -0
- package/src/skcapstone/export.py +447 -0
- package/src/skcapstone/fallback_tracker.py +186 -0
- package/src/skcapstone/file_transfer.py +512 -0
- package/src/skcapstone/fuse_mount.py +1156 -0
- package/src/skcapstone/gui_installer.py +591 -0
- package/src/skcapstone/heartbeat.py +611 -0
- package/src/skcapstone/housekeeping.py +298 -0
- package/src/skcapstone/install_wizard.py +941 -0
- package/src/skcapstone/kms.py +942 -0
- package/src/skcapstone/kms_scheduler.py +143 -0
- package/src/skcapstone/log_config.py +135 -0
- package/src/skcapstone/mcp_launcher.py +239 -0
- package/src/skcapstone/mcp_server.py +4700 -0
- package/src/skcapstone/mcp_tools/__init__.py +94 -0
- package/src/skcapstone/mcp_tools/_helpers.py +51 -0
- package/src/skcapstone/mcp_tools/agent_tools.py +243 -0
- package/src/skcapstone/mcp_tools/ansible_tools.py +232 -0
- package/src/skcapstone/mcp_tools/capauth_tools.py +186 -0
- package/src/skcapstone/mcp_tools/chat_tools.py +325 -0
- package/src/skcapstone/mcp_tools/cloud9_tools.py +115 -0
- package/src/skcapstone/mcp_tools/comm_tools.py +104 -0
- package/src/skcapstone/mcp_tools/consciousness_tools.py +114 -0
- package/src/skcapstone/mcp_tools/coord_tools.py +219 -0
- package/src/skcapstone/mcp_tools/deploy_tools.py +202 -0
- package/src/skcapstone/mcp_tools/did_tools.py +448 -0
- package/src/skcapstone/mcp_tools/emotion_tools.py +62 -0
- package/src/skcapstone/mcp_tools/file_tools.py +169 -0
- package/src/skcapstone/mcp_tools/fortress_tools.py +120 -0
- package/src/skcapstone/mcp_tools/gtd_tools.py +821 -0
- package/src/skcapstone/mcp_tools/health_tools.py +44 -0
- package/src/skcapstone/mcp_tools/heartbeat_tools.py +195 -0
- package/src/skcapstone/mcp_tools/kms_tools.py +123 -0
- package/src/skcapstone/mcp_tools/memory_tools.py +222 -0
- package/src/skcapstone/mcp_tools/model_tools.py +75 -0
- package/src/skcapstone/mcp_tools/notification_tools.py +92 -0
- package/src/skcapstone/mcp_tools/promoter_tools.py +101 -0
- package/src/skcapstone/mcp_tools/pubsub_tools.py +183 -0
- package/src/skcapstone/mcp_tools/security_tools.py +110 -0
- package/src/skcapstone/mcp_tools/skchat_tools.py +175 -0
- package/src/skcapstone/mcp_tools/skcomm_tools.py +122 -0
- package/src/skcapstone/mcp_tools/skills_tools.py +127 -0
- package/src/skcapstone/mcp_tools/skseed_tools.py +255 -0
- package/src/skcapstone/mcp_tools/skstacks_tools.py +288 -0
- package/src/skcapstone/mcp_tools/soul_tools.py +476 -0
- package/src/skcapstone/mcp_tools/sync_tools.py +92 -0
- package/src/skcapstone/mcp_tools/telegram_tools.py +477 -0
- package/src/skcapstone/mcp_tools/trust_tools.py +118 -0
- package/src/skcapstone/mcp_tools/trustee_tools.py +345 -0
- package/src/skcapstone/mdns_discovery.py +313 -0
- package/src/skcapstone/memory_adapter.py +333 -0
- package/src/skcapstone/memory_compressor.py +379 -0
- package/src/skcapstone/memory_curator.py +256 -0
- package/src/skcapstone/memory_engine.py +132 -13
- package/src/skcapstone/memory_fortress.py +529 -0
- package/src/skcapstone/memory_promoter.py +722 -0
- package/src/skcapstone/memory_verifier.py +260 -0
- package/src/skcapstone/message_crypto.py +215 -0
- package/src/skcapstone/metrics.py +832 -0
- package/src/skcapstone/migrate_memories.py +181 -0
- package/src/skcapstone/migrate_multi_agent.py +248 -0
- package/src/skcapstone/model_router.py +319 -0
- package/src/skcapstone/models.py +35 -4
- package/src/skcapstone/mood.py +344 -0
- package/src/skcapstone/notifications.py +380 -0
- package/src/skcapstone/onboard.py +901 -0
- package/src/skcapstone/peer_directory.py +324 -0
- package/src/skcapstone/peers.py +329 -0
- package/src/skcapstone/pillars/identity.py +84 -14
- package/src/skcapstone/pillars/memory.py +3 -1
- package/src/skcapstone/pillars/security.py +108 -15
- package/src/skcapstone/pillars/sync.py +78 -26
- package/src/skcapstone/pillars/trust.py +95 -33
- package/src/skcapstone/plugins.py +244 -0
- package/src/skcapstone/preflight.py +670 -0
- package/src/skcapstone/prompt_adapter.py +564 -0
- package/src/skcapstone/providers/__init__.py +13 -0
- package/src/skcapstone/providers/cloud.py +1061 -0
- package/src/skcapstone/providers/docker.py +759 -0
- package/src/skcapstone/providers/local.py +1193 -0
- package/src/skcapstone/providers/proxmox.py +447 -0
- package/src/skcapstone/pubsub.py +516 -0
- package/src/skcapstone/rate_limiter.py +119 -0
- package/src/skcapstone/register.py +241 -0
- package/src/skcapstone/registry_client.py +151 -0
- package/src/skcapstone/response_cache.py +194 -0
- package/src/skcapstone/response_scorer.py +225 -0
- package/src/skcapstone/runtime.py +89 -33
- package/src/skcapstone/scheduled_tasks.py +439 -0
- package/src/skcapstone/self_healing.py +341 -0
- package/src/skcapstone/service_health.py +228 -0
- package/src/skcapstone/session_capture.py +268 -0
- package/src/skcapstone/session_recorder.py +210 -0
- package/src/skcapstone/session_replayer.py +189 -0
- package/src/skcapstone/session_skills.py +263 -0
- package/src/skcapstone/shell.py +779 -0
- package/src/skcapstone/skills/__init__.py +1 -1
- package/src/skcapstone/skills/syncthing_setup.py +143 -41
- package/src/skcapstone/skjoule.py +861 -0
- package/src/skcapstone/snapshots.py +489 -0
- package/src/skcapstone/soul.py +1060 -0
- package/src/skcapstone/soul_switch.py +255 -0
- package/src/skcapstone/spawner.py +544 -0
- package/src/skcapstone/state_diff.py +401 -0
- package/src/skcapstone/summary.py +270 -0
- package/src/skcapstone/sync/backends.py +196 -2
- package/src/skcapstone/sync/engine.py +7 -5
- package/src/skcapstone/sync/models.py +4 -1
- package/src/skcapstone/sync/vault.py +356 -18
- package/src/skcapstone/sync_engine.py +363 -0
- package/src/skcapstone/sync_watcher.py +745 -0
- package/src/skcapstone/systemd.py +331 -0
- package/src/skcapstone/team_comms.py +476 -0
- package/src/skcapstone/team_engine.py +522 -0
- package/src/skcapstone/testrunner.py +300 -0
- package/src/skcapstone/tls.py +150 -0
- package/src/skcapstone/tokens.py +5 -5
- package/src/skcapstone/trust_calibration.py +202 -0
- package/src/skcapstone/trust_graph.py +449 -0
- package/src/skcapstone/trustee_monitor.py +385 -0
- package/src/skcapstone/trustee_ops.py +425 -0
- package/src/skcapstone/unified_search.py +421 -0
- package/src/skcapstone/uninstall_wizard.py +694 -0
- package/src/skcapstone/usage.py +331 -0
- package/src/skcapstone/version_check.py +148 -0
- package/src/skcapstone/warmth_anchor.py +333 -0
- package/src/skcapstone/whoami.py +294 -0
- package/systemd/skcapstone-api.socket +9 -0
- package/systemd/skcapstone-memory-compress.service +18 -0
- package/systemd/skcapstone-memory-compress.timer +11 -0
- package/systemd/skcapstone.service +36 -0
- package/systemd/skcapstone@.service +50 -0
- package/systemd/skcomm-heartbeat.service +18 -0
- package/systemd/skcomm-heartbeat.timer +12 -0
- package/systemd/skcomm-queue-drain.service +17 -0
- package/systemd/skcomm-queue-drain.timer +12 -0
- package/tests/conftest.py +13 -1
- package/tests/integration/__init__.py +1 -0
- package/tests/integration/test_consciousness_e2e.py +877 -0
- package/tests/integration/test_skills_registry.py +744 -0
- package/tests/test_agent_card.py +190 -0
- package/tests/test_agent_runtime.py +1283 -0
- package/tests/test_alerts_cmd.py +291 -0
- package/tests/test_archiver.py +498 -0
- package/tests/test_backup.py +254 -0
- package/tests/test_benchmark.py +366 -0
- package/tests/test_blueprints.py +457 -0
- package/tests/test_capabilities.py +257 -0
- package/tests/test_changelog.py +254 -0
- package/tests/test_chat.py +385 -0
- package/tests/test_claude_md.py +271 -0
- package/tests/test_cli_chat_llm.py +336 -0
- package/tests/test_cli_completions.py +390 -0
- package/tests/test_cli_init_reset.py +164 -0
- package/tests/test_cli_memory.py +208 -0
- package/tests/test_cli_profile.py +294 -0
- package/tests/test_cli_skills.py +223 -0
- package/tests/test_cli_status.py +395 -0
- package/tests/test_cli_test_cmd.py +206 -0
- package/tests/test_cli_test_connection.py +364 -0
- package/tests/test_cloud9_bridge.py +260 -0
- package/tests/test_cloud_provider.py +449 -0
- package/tests/test_cloud_providers.py +522 -0
- package/tests/test_completions.py +158 -0
- package/tests/test_component_manager.py +398 -0
- package/tests/test_config_reload.py +386 -0
- package/tests/test_config_validate.py +529 -0
- package/tests/test_consciousness_e2e.py +296 -0
- package/tests/test_consciousness_loop.py +1289 -0
- package/tests/test_context_loader.py +310 -0
- package/tests/test_conversation_api.py +306 -0
- package/tests/test_conversation_manager.py +381 -0
- package/tests/test_conversation_store.py +391 -0
- package/tests/test_conversation_summarizer.py +302 -0
- package/tests/test_cross_package.py +791 -0
- package/tests/test_crush_shim.py +519 -0
- package/tests/test_daemon.py +781 -0
- package/tests/test_daemon_shutdown.py +309 -0
- package/tests/test_dashboard.py +454 -0
- package/tests/test_discovery.py +200 -6
- package/tests/test_docker_provider.py +966 -0
- package/tests/test_doctor.py +257 -0
- package/tests/test_doctor_fix.py +351 -0
- package/tests/test_e2e_automated.py +292 -0
- package/tests/test_error_queue.py +404 -0
- package/tests/test_export.py +441 -0
- package/tests/test_fallback_tracker.py +219 -0
- package/tests/test_file_transfer.py +397 -0
- package/tests/test_fuse_mount.py +832 -0
- package/tests/test_health_loop.py +422 -0
- package/tests/test_heartbeat.py +354 -0
- package/tests/test_housekeeping.py +195 -0
- package/tests/test_identity_capauth.py +307 -0
- package/tests/test_identity_pillar.py +117 -0
- package/tests/test_install_wizard.py +68 -0
- package/tests/test_integration.py +325 -0
- package/tests/test_kms.py +495 -0
- package/tests/test_llm_providers.py +265 -0
- package/tests/test_local_provider.py +591 -0
- package/tests/test_log_config.py +199 -0
- package/tests/test_logs_cmd.py +287 -0
- package/tests/test_mcp_server.py +1909 -0
- package/tests/test_memory_adapter.py +339 -0
- package/tests/test_memory_curator.py +218 -0
- package/tests/test_memory_engine.py +6 -0
- package/tests/test_memory_fortress.py +571 -0
- package/tests/test_memory_pillar.py +119 -0
- package/tests/test_memory_promoter.py +445 -0
- package/tests/test_memory_verifier.py +420 -0
- package/tests/test_message_crypto.py +187 -0
- package/tests/test_metrics.py +632 -0
- package/tests/test_migrate_memories.py +464 -0
- package/tests/test_model_router.py +546 -0
- package/tests/test_mood.py +394 -0
- package/tests/test_multi_agent.py +269 -0
- package/tests/test_notifications.py +270 -0
- package/tests/test_onboard.py +500 -0
- package/tests/test_peer_directory.py +395 -0
- package/tests/test_peers.py +248 -0
- package/tests/test_pillars.py +87 -9
- package/tests/test_preflight.py +484 -0
- package/tests/test_prompt_adapter.py +331 -0
- package/tests/test_proxmox_provider.py +571 -0
- package/tests/test_pubsub.py +377 -0
- package/tests/test_rate_limiter.py +121 -0
- package/tests/test_registry_client.py +129 -0
- package/tests/test_response_cache.py +312 -0
- package/tests/test_response_scorer.py +294 -0
- package/tests/test_runtime.py +59 -0
- package/tests/test_scheduled_tasks.py +451 -0
- package/tests/test_security.py +250 -0
- package/tests/test_security_pillar.py +213 -0
- package/tests/test_self_healing.py +171 -0
- package/tests/test_session_capture.py +200 -0
- package/tests/test_session_recorder.py +360 -0
- package/tests/test_session_skills.py +235 -0
- package/tests/test_shell.py +210 -0
- package/tests/test_snapshots.py +549 -0
- package/tests/test_soul.py +984 -0
- package/tests/test_soul_swap.py +406 -0
- package/tests/test_spawner.py +211 -0
- package/tests/test_state_diff.py +173 -0
- package/tests/test_summary.py +135 -0
- package/tests/test_sync.py +315 -5
- package/tests/test_sync_backends.py +560 -0
- package/tests/test_sync_engine.py +482 -0
- package/tests/test_sync_pillar.py +344 -0
- package/tests/test_sync_pipeline.py +364 -0
- package/tests/test_sync_vault.py +581 -0
- package/tests/test_syncthing_setup.py +168 -22
- package/tests/test_systemd.py +323 -0
- package/tests/test_team_comms.py +408 -0
- package/tests/test_team_engine.py +397 -0
- package/tests/test_testrunner.py +238 -0
- package/tests/test_trust_calibration.py +204 -0
- package/tests/test_trust_graph.py +207 -0
- package/tests/test_trust_pillar.py +291 -0
- package/tests/test_trustee_cli.py +427 -0
- package/tests/test_trustee_cli_integration.py +325 -0
- package/tests/test_trustee_monitor.py +394 -0
- package/tests/test_trustee_ops.py +355 -0
- package/tests/test_unified_search.py +363 -0
- package/tests/test_uninstall_wizard.py +193 -0
- package/tests/test_usage.py +333 -0
- package/tests/test_version_cmd.py +355 -0
- package/tests/test_warmth_anchor.py +162 -0
- package/tests/test_whoami.py +245 -0
- package/tests/test_ws.py +311 -0
- package/.cursorrules +0 -33
- package/src/skcapstone/cli.py +0 -1441
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
"""Emotion tracker — classifies sentiment after each consciousness loop response.
|
|
2
|
+
|
|
3
|
+
After each LLM response, classifies the sentiment into one of:
|
|
4
|
+
positive, neutral, concerned, excited
|
|
5
|
+
|
|
6
|
+
Stores each classification as a memory entry with ``tag=emotion`` and a
|
|
7
|
+
valence score 0–1. Updates the warmth anchor's ``warmth`` field using a
|
|
8
|
+
7-day rolling average, triggering a re-calibration every
|
|
9
|
+
``_WARMTH_UPDATE_EVERY`` records.
|
|
10
|
+
|
|
11
|
+
The LLM path uses a single "1-token" classify call (a minimal prompt that
|
|
12
|
+
asks for one word). If the bridge is unavailable or the call fails, the
|
|
13
|
+
module falls back to keyword-based heuristics so the loop is never blocked.
|
|
14
|
+
|
|
15
|
+
Usage::
|
|
16
|
+
|
|
17
|
+
tracker = EmotionTracker(home=Path("~/.skcapstone"))
|
|
18
|
+
tracker.record(response="I'm happy to help!", sender="alice", bridge=llm_bridge)
|
|
19
|
+
trend = tracker.get_trend(days=7)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import logging
|
|
26
|
+
import threading
|
|
27
|
+
from datetime import datetime, timedelta, timezone
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import Any, Optional
|
|
30
|
+
|
|
31
|
+
from pydantic import BaseModel
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger("skcapstone.emotion")
|
|
34
|
+
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
# Constants
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
EMOTION_LABELS = ("positive", "neutral", "concerned", "excited")
|
|
40
|
+
|
|
41
|
+
# Keyword sets for heuristic classification
|
|
42
|
+
_EXCITED_WORDS = frozenset({
|
|
43
|
+
"exciting", "fascinating", "incredible", "remarkable", "breakthrough",
|
|
44
|
+
"amazing", "extraordinary", "innovative", "revolutionary", "profound",
|
|
45
|
+
"powerful", "fantastic", "thrilled", "eager", "enthusiastic", "curious",
|
|
46
|
+
"excellent", "brilliant", "spectacular", "outstanding", "wow",
|
|
47
|
+
})
|
|
48
|
+
_CONCERNED_WORDS = frozenset({
|
|
49
|
+
"sorry", "apologize", "unfortunately", "unable", "cannot", "error",
|
|
50
|
+
"fail", "problem", "issue", "concern", "worry", "difficult", "trouble",
|
|
51
|
+
"wrong", "broken", "warning", "caution", "careful", "risk", "danger",
|
|
52
|
+
"limited", "unavailable", "unclear", "missing", "blocked", "failed",
|
|
53
|
+
})
|
|
54
|
+
_POSITIVE_WORDS = frozenset({
|
|
55
|
+
"great", "happy", "glad", "wonderful", "perfect", "good", "pleasure",
|
|
56
|
+
"sure", "absolutely", "delighted", "appreciate", "thanks", "helpful",
|
|
57
|
+
"solved", "done", "complete", "succeed", "love", "enjoy", "welcome",
|
|
58
|
+
"nice", "correct", "right", "works", "working", "ready", "success",
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
# Score mapping
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
_LABEL_SCORES: dict[str, float] = {
|
|
67
|
+
"positive": 0.85,
|
|
68
|
+
"excited": 0.75,
|
|
69
|
+
"neutral": 0.50,
|
|
70
|
+
"concerned": 0.25,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _score_from_label(label: str) -> float:
|
|
75
|
+
"""Map an emotion label to a valence score 0–1."""
|
|
76
|
+
return _LABEL_SCORES.get(label, 0.50)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
# Keyword classifier
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _keyword_classify(text: str) -> tuple[str, float]:
|
|
85
|
+
"""Fast keyword-based sentiment classifier — no LLM required.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
text: Text to classify.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Tuple of (label, score) where label is one of EMOTION_LABELS
|
|
92
|
+
and score is the corresponding valence 0–1.
|
|
93
|
+
"""
|
|
94
|
+
if not text:
|
|
95
|
+
return "neutral", 0.50
|
|
96
|
+
|
|
97
|
+
words = set(text.lower().split())
|
|
98
|
+
excited_hits = len(words & _EXCITED_WORDS)
|
|
99
|
+
concerned_hits = len(words & _CONCERNED_WORDS)
|
|
100
|
+
positive_hits = len(words & _POSITIVE_WORDS)
|
|
101
|
+
|
|
102
|
+
# Priority: excited ≥ 2 hits > concerned ≥ 2 > positive ≥ 2 > else neutral
|
|
103
|
+
if excited_hits >= 2:
|
|
104
|
+
return "excited", 0.80
|
|
105
|
+
if concerned_hits >= 2:
|
|
106
|
+
return "concerned", 0.25
|
|
107
|
+
if concerned_hits >= 1 and positive_hits == 0 and excited_hits == 0:
|
|
108
|
+
return "concerned", 0.30
|
|
109
|
+
if positive_hits >= 2 or (positive_hits >= 1 and excited_hits >= 1):
|
|
110
|
+
return "positive", 0.85
|
|
111
|
+
if positive_hits >= 1:
|
|
112
|
+
return "positive", 0.70
|
|
113
|
+
if excited_hits >= 1:
|
|
114
|
+
return "excited", 0.72
|
|
115
|
+
return "neutral", 0.50
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# ---------------------------------------------------------------------------
|
|
119
|
+
# Data model
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class EmotionEntry(BaseModel):
|
|
124
|
+
"""One emotion classification record.
|
|
125
|
+
|
|
126
|
+
Attributes:
|
|
127
|
+
label: Classified emotion label.
|
|
128
|
+
score: Valence score 0.0–1.0 (higher = more positive).
|
|
129
|
+
sender: Peer who triggered the response being classified.
|
|
130
|
+
timestamp: ISO-8601 UTC timestamp.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
label: str
|
|
134
|
+
score: float
|
|
135
|
+
sender: str
|
|
136
|
+
timestamp: str
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# ---------------------------------------------------------------------------
|
|
140
|
+
# Tracker
|
|
141
|
+
# ---------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class EmotionTracker:
|
|
145
|
+
"""Tracks per-response emotion, stores as memory, updates warmth anchor.
|
|
146
|
+
|
|
147
|
+
Thread-safe. Persists emotion log to ``{home}/emotion_log.json``.
|
|
148
|
+
Every ``_WARMTH_UPDATE_EVERY`` records the 7-day rolling average is
|
|
149
|
+
recomputed and the warmth anchor is updated via exponential smoothing.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
home: Agent home directory (default: ``~/.skcapstone``).
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
_LOG_FILE = "emotion_log.json"
|
|
156
|
+
_MAX_ENTRIES = 1000 # cap log at this many entries
|
|
157
|
+
_WARMTH_UPDATE_EVERY = 5 # recompute anchor every N records
|
|
158
|
+
|
|
159
|
+
def __init__(self, home: Optional[Path] = None) -> None:
|
|
160
|
+
from skcapstone import AGENT_HOME
|
|
161
|
+
|
|
162
|
+
self._home = (home or Path(AGENT_HOME)).expanduser()
|
|
163
|
+
self._lock = threading.Lock()
|
|
164
|
+
self._counter = 0
|
|
165
|
+
|
|
166
|
+
# ------------------------------------------------------------------
|
|
167
|
+
# Public API
|
|
168
|
+
# ------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
def record(
|
|
171
|
+
self,
|
|
172
|
+
response: str,
|
|
173
|
+
sender: str,
|
|
174
|
+
bridge: Optional[Any] = None,
|
|
175
|
+
) -> EmotionEntry:
|
|
176
|
+
"""Classify, persist, and propagate emotion for a single response.
|
|
177
|
+
|
|
178
|
+
Steps:
|
|
179
|
+
1. Classify sentiment (LLM 1-token call if *bridge* provided,
|
|
180
|
+
otherwise keyword heuristic).
|
|
181
|
+
2. Append to ``emotion_log.json``.
|
|
182
|
+
3. Store as a memory entry tagged ``emotion``.
|
|
183
|
+
4. Every ``_WARMTH_UPDATE_EVERY`` records recompute the 7-day
|
|
184
|
+
rolling average and update the warmth anchor.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
response: LLM response text to classify.
|
|
188
|
+
sender: Name of the peer this response was sent to.
|
|
189
|
+
bridge: Optional ``LLMBridge`` for the 1-token classify call.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
The created :class:`EmotionEntry`.
|
|
193
|
+
"""
|
|
194
|
+
label, score = self._classify(response, bridge)
|
|
195
|
+
entry = EmotionEntry(
|
|
196
|
+
label=label,
|
|
197
|
+
score=score,
|
|
198
|
+
sender=sender,
|
|
199
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
200
|
+
)
|
|
201
|
+
self._persist(entry)
|
|
202
|
+
self._store_memory(entry)
|
|
203
|
+
|
|
204
|
+
with self._lock:
|
|
205
|
+
self._counter += 1
|
|
206
|
+
should_update = (self._counter % self._WARMTH_UPDATE_EVERY == 0)
|
|
207
|
+
|
|
208
|
+
if should_update:
|
|
209
|
+
self._update_warmth_anchor()
|
|
210
|
+
|
|
211
|
+
logger.debug(
|
|
212
|
+
"Emotion recorded: label=%s score=%.2f sender=%s",
|
|
213
|
+
label, score, sender,
|
|
214
|
+
)
|
|
215
|
+
return entry
|
|
216
|
+
|
|
217
|
+
def get_trend(self, days: int = 7) -> dict:
|
|
218
|
+
"""Return emotion trend data over the given lookback window.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
days: Number of days to look back (default 7).
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Dict with keys:
|
|
225
|
+
- ``window_days``: lookback period
|
|
226
|
+
- ``total_records``: number of entries found
|
|
227
|
+
- ``avg_score``: mean valence score 0–1
|
|
228
|
+
- ``label_counts``: dict of label → count
|
|
229
|
+
- ``dominant_label``: most-frequent label
|
|
230
|
+
- ``trend``: "improving" | "stable" | "declining"
|
|
231
|
+
- ``warmth_recommendation``: avg_score × 10 (anchor scale 0–10)
|
|
232
|
+
- ``entries``: list of raw entry dicts (most-recent first)
|
|
233
|
+
"""
|
|
234
|
+
entries = self._load_recent(days)
|
|
235
|
+
if not entries:
|
|
236
|
+
return {
|
|
237
|
+
"window_days": days,
|
|
238
|
+
"total_records": 0,
|
|
239
|
+
"avg_score": 0.50,
|
|
240
|
+
"label_counts": {lbl: 0 for lbl in EMOTION_LABELS},
|
|
241
|
+
"dominant_label": "neutral",
|
|
242
|
+
"trend": "stable",
|
|
243
|
+
"warmth_recommendation": 5.0,
|
|
244
|
+
"entries": [],
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
label_counts: dict[str, int] = {lbl: 0 for lbl in EMOTION_LABELS}
|
|
248
|
+
scores: list[float] = []
|
|
249
|
+
for e in entries:
|
|
250
|
+
label_counts[e.label] = label_counts.get(e.label, 0) + 1
|
|
251
|
+
scores.append(e.score)
|
|
252
|
+
|
|
253
|
+
avg_score = sum(scores) / len(scores)
|
|
254
|
+
dominant = max(label_counts, key=lambda k: label_counts[k])
|
|
255
|
+
|
|
256
|
+
# Trend: compare first-half vs second-half average scores
|
|
257
|
+
mid = len(scores) // 2
|
|
258
|
+
if mid > 0:
|
|
259
|
+
first_avg = sum(scores[:mid]) / mid
|
|
260
|
+
second_avg = sum(scores[mid:]) / (len(scores) - mid)
|
|
261
|
+
delta = second_avg - first_avg
|
|
262
|
+
if delta > 0.05:
|
|
263
|
+
trend = "improving"
|
|
264
|
+
elif delta < -0.05:
|
|
265
|
+
trend = "declining"
|
|
266
|
+
else:
|
|
267
|
+
trend = "stable"
|
|
268
|
+
else:
|
|
269
|
+
trend = "stable"
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
"window_days": days,
|
|
273
|
+
"total_records": len(entries),
|
|
274
|
+
"avg_score": round(avg_score, 3),
|
|
275
|
+
"label_counts": label_counts,
|
|
276
|
+
"dominant_label": dominant,
|
|
277
|
+
"trend": trend,
|
|
278
|
+
"warmth_recommendation": round(avg_score * 10, 2),
|
|
279
|
+
"entries": [e.model_dump() for e in reversed(entries)],
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
# ------------------------------------------------------------------
|
|
283
|
+
# Internal: classification
|
|
284
|
+
# ------------------------------------------------------------------
|
|
285
|
+
|
|
286
|
+
def _classify(
|
|
287
|
+
self, text: str, bridge: Optional[Any]
|
|
288
|
+
) -> tuple[str, float]:
|
|
289
|
+
"""Classify sentiment using LLM (1-token call) with keyword fallback.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
text: Text to classify.
|
|
293
|
+
bridge: Optional LLMBridge instance.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
(label, score) tuple.
|
|
297
|
+
"""
|
|
298
|
+
if bridge is not None:
|
|
299
|
+
try:
|
|
300
|
+
label = self._llm_classify(text, bridge)
|
|
301
|
+
if label in EMOTION_LABELS:
|
|
302
|
+
return label, _score_from_label(label)
|
|
303
|
+
except Exception as exc:
|
|
304
|
+
logger.debug(
|
|
305
|
+
"LLM sentiment classify failed, using keywords: %s", exc
|
|
306
|
+
)
|
|
307
|
+
return _keyword_classify(text)
|
|
308
|
+
|
|
309
|
+
def _llm_classify(self, text: str, bridge: Any) -> str:
|
|
310
|
+
"""Make a minimal 1-token LLM call to classify sentiment.
|
|
311
|
+
|
|
312
|
+
Sends a short prompt asking for a single word classification.
|
|
313
|
+
Uses the FAST task signal so the router selects the lightest model.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
text: Response text (first 400 chars used).
|
|
317
|
+
bridge: LLMBridge instance.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
First word from the LLM response (lowercased), or "" on failure.
|
|
321
|
+
"""
|
|
322
|
+
from skcapstone.model_router import TaskSignal
|
|
323
|
+
|
|
324
|
+
snippet = text[:400].replace('"', "'")
|
|
325
|
+
user_message = (
|
|
326
|
+
"Classify the sentiment of this AI assistant response.\n"
|
|
327
|
+
"Reply with exactly one word from: positive, neutral, concerned, excited\n"
|
|
328
|
+
f'Response: "{snippet}"\n'
|
|
329
|
+
"Sentiment:"
|
|
330
|
+
)
|
|
331
|
+
signal = TaskSignal(
|
|
332
|
+
description="1-token sentiment classification",
|
|
333
|
+
tags=["classification", "fast"],
|
|
334
|
+
estimated_tokens=15,
|
|
335
|
+
)
|
|
336
|
+
raw = bridge.generate(
|
|
337
|
+
system_prompt=(
|
|
338
|
+
"You are a sentiment classifier. "
|
|
339
|
+
"Output exactly one word: positive, neutral, concerned, or excited."
|
|
340
|
+
),
|
|
341
|
+
user_message=user_message,
|
|
342
|
+
signal=signal,
|
|
343
|
+
skip_cache=False,
|
|
344
|
+
)
|
|
345
|
+
return raw.strip().lower().split()[0] if raw.strip() else ""
|
|
346
|
+
|
|
347
|
+
# ------------------------------------------------------------------
|
|
348
|
+
# Internal: persistence
|
|
349
|
+
# ------------------------------------------------------------------
|
|
350
|
+
|
|
351
|
+
def _persist(self, entry: EmotionEntry) -> None:
|
|
352
|
+
"""Append *entry* to the emotion log JSON file (thread-safe)."""
|
|
353
|
+
with self._lock:
|
|
354
|
+
path = self._home / self._LOG_FILE
|
|
355
|
+
try:
|
|
356
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
357
|
+
existing: list[dict] = []
|
|
358
|
+
if path.exists():
|
|
359
|
+
try:
|
|
360
|
+
existing = json.loads(path.read_text(encoding="utf-8"))
|
|
361
|
+
if not isinstance(existing, list):
|
|
362
|
+
existing = []
|
|
363
|
+
except (json.JSONDecodeError, OSError):
|
|
364
|
+
existing = []
|
|
365
|
+
existing.append(entry.model_dump())
|
|
366
|
+
# Trim to cap
|
|
367
|
+
if len(existing) > self._MAX_ENTRIES:
|
|
368
|
+
existing = existing[-self._MAX_ENTRIES :]
|
|
369
|
+
path.write_text(json.dumps(existing, indent=2), encoding="utf-8")
|
|
370
|
+
except Exception as exc:
|
|
371
|
+
logger.warning("Failed to persist emotion entry: %s", exc)
|
|
372
|
+
|
|
373
|
+
def _load_recent(self, days: int) -> list[EmotionEntry]:
|
|
374
|
+
"""Load entries from the last *days* days, oldest-first.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
days: Lookback window.
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
List of :class:`EmotionEntry` objects within the window.
|
|
381
|
+
"""
|
|
382
|
+
path = self._home / self._LOG_FILE
|
|
383
|
+
if not path.exists():
|
|
384
|
+
return []
|
|
385
|
+
try:
|
|
386
|
+
raw: list[dict] = json.loads(path.read_text(encoding="utf-8"))
|
|
387
|
+
cutoff = (
|
|
388
|
+
datetime.now(timezone.utc) - timedelta(days=days)
|
|
389
|
+
).isoformat()
|
|
390
|
+
entries: list[EmotionEntry] = []
|
|
391
|
+
for item in raw:
|
|
392
|
+
if item.get("timestamp", "") >= cutoff:
|
|
393
|
+
try:
|
|
394
|
+
entries.append(EmotionEntry(**item))
|
|
395
|
+
except Exception:
|
|
396
|
+
pass
|
|
397
|
+
return entries
|
|
398
|
+
except Exception as exc:
|
|
399
|
+
logger.debug("Failed to load emotion log: %s", exc)
|
|
400
|
+
return []
|
|
401
|
+
|
|
402
|
+
# ------------------------------------------------------------------
|
|
403
|
+
# Internal: side-effects
|
|
404
|
+
# ------------------------------------------------------------------
|
|
405
|
+
|
|
406
|
+
def _store_memory(self, entry: EmotionEntry) -> None:
|
|
407
|
+
"""Store *entry* as a skcapstone memory with tag ``emotion``.
|
|
408
|
+
|
|
409
|
+
Memory content encodes label, score, and sender so later searches
|
|
410
|
+
on ``tag=emotion`` surface the full history.
|
|
411
|
+
"""
|
|
412
|
+
try:
|
|
413
|
+
from skcapstone.memory_engine import store
|
|
414
|
+
|
|
415
|
+
content = (
|
|
416
|
+
f"Emotion after response to {entry.sender}: "
|
|
417
|
+
f"{entry.label} (score={entry.score:.2f})"
|
|
418
|
+
)
|
|
419
|
+
store(
|
|
420
|
+
home=self._home,
|
|
421
|
+
content=content,
|
|
422
|
+
tags=["emotion", f"emotion:{entry.label}", f"peer:{entry.sender}"],
|
|
423
|
+
source="emotion_tracker",
|
|
424
|
+
importance=0.3,
|
|
425
|
+
metadata={
|
|
426
|
+
"label": entry.label,
|
|
427
|
+
"score": entry.score,
|
|
428
|
+
"sender": entry.sender,
|
|
429
|
+
"timestamp": entry.timestamp,
|
|
430
|
+
},
|
|
431
|
+
)
|
|
432
|
+
except Exception as exc:
|
|
433
|
+
logger.debug("Failed to store emotion memory: %s", exc)
|
|
434
|
+
|
|
435
|
+
def _update_warmth_anchor(self) -> None:
|
|
436
|
+
"""Recompute 7-day rolling average and nudge the warmth anchor.
|
|
437
|
+
|
|
438
|
+
Converts the average valence score (0–1) to the anchor's 0–10 warmth
|
|
439
|
+
scale and calls :func:`~skcapstone.warmth_anchor.update_anchor` which
|
|
440
|
+
applies exponential smoothing (30% new, 70% history).
|
|
441
|
+
"""
|
|
442
|
+
try:
|
|
443
|
+
trend = self.get_trend(days=7)
|
|
444
|
+
new_warmth = trend["warmth_recommendation"]
|
|
445
|
+
if new_warmth <= 0:
|
|
446
|
+
return
|
|
447
|
+
|
|
448
|
+
from skcapstone.warmth_anchor import update_anchor
|
|
449
|
+
|
|
450
|
+
updated = update_anchor(
|
|
451
|
+
home=self._home,
|
|
452
|
+
warmth=new_warmth,
|
|
453
|
+
feeling=(
|
|
454
|
+
f"7-day emotion avg: {trend['dominant_label']} "
|
|
455
|
+
f"(score={trend['avg_score']:.2f}, trend={trend['trend']})"
|
|
456
|
+
),
|
|
457
|
+
)
|
|
458
|
+
logger.debug(
|
|
459
|
+
"Warmth anchor updated via emotion: warmth=%.2f "
|
|
460
|
+
"(7-day avg_score=%.3f dominant=%s trend=%s)",
|
|
461
|
+
updated.get("warmth", new_warmth),
|
|
462
|
+
trend["avg_score"],
|
|
463
|
+
trend["dominant_label"],
|
|
464
|
+
trend["trend"],
|
|
465
|
+
)
|
|
466
|
+
except Exception as exc:
|
|
467
|
+
logger.debug("Failed to update warmth anchor from emotion: %s", exc)
|