@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,769 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Interactive agent-to-agent chat for the sovereign terminal.
|
|
3
|
+
|
|
4
|
+
Provides a real-time terminal chat experience between agents using
|
|
5
|
+
SKChat for message models and SKComm for transport. Works from any
|
|
6
|
+
terminal on any platform — no IDE dependency.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
skcapstone chat <peer> # interactive session (prompt_toolkit)
|
|
10
|
+
skcapstone chat send <peer> <m> # one-shot send
|
|
11
|
+
skcapstone chat inbox # browse messages
|
|
12
|
+
skcapstone chat live <peer> # alias for interactive session
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import base64
|
|
18
|
+
import json
|
|
19
|
+
import logging
|
|
20
|
+
import threading as _threading
|
|
21
|
+
import time
|
|
22
|
+
import uuid
|
|
23
|
+
from datetime import datetime, timezone
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Optional
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger("skcapstone.chat")
|
|
28
|
+
|
|
29
|
+
# Slash commands available in interactive mode
|
|
30
|
+
_CHAT_COMMANDS = [
|
|
31
|
+
"/attach",
|
|
32
|
+
"/emoji",
|
|
33
|
+
"/exit",
|
|
34
|
+
"/help",
|
|
35
|
+
"/inbox",
|
|
36
|
+
"/q",
|
|
37
|
+
"/quit",
|
|
38
|
+
"/reply",
|
|
39
|
+
"/thread",
|
|
40
|
+
"/whoami",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
# Text file extensions that can be sent as UTF-8 (not base64)
|
|
44
|
+
_TEXT_SUFFIXES = {
|
|
45
|
+
".bash", ".cfg", ".conf", ".css", ".csv", ".env",
|
|
46
|
+
".go", ".html", ".js", ".json", ".log", ".md",
|
|
47
|
+
".py", ".rs", ".sh", ".toml", ".ts", ".txt",
|
|
48
|
+
".xml", ".yaml", ".yml", ".zsh",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class AgentChat:
|
|
53
|
+
"""Interactive chat engine for sovereign agent communication.
|
|
54
|
+
|
|
55
|
+
Wraps SKChat models and SKComm transport into a simple
|
|
56
|
+
send/receive/poll interface suitable for terminal use.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
home: Agent home directory (~/.skcapstone).
|
|
60
|
+
identity: Local agent identity string.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, home: Path, identity: str = "unknown") -> None:
|
|
64
|
+
self.home = home
|
|
65
|
+
self.identity = identity
|
|
66
|
+
self._comm = None
|
|
67
|
+
self._history = None
|
|
68
|
+
|
|
69
|
+
# ------------------------------------------------------------------
|
|
70
|
+
# Transport / history bootstrap
|
|
71
|
+
# ------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
def _ensure_comm(self) -> bool:
|
|
74
|
+
"""Lazily initialize the SKComm engine.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
bool: True if communication layer is available.
|
|
78
|
+
"""
|
|
79
|
+
if self._comm is not None:
|
|
80
|
+
return True
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
from skcomm.core import SKComm
|
|
84
|
+
|
|
85
|
+
self._comm = SKComm.from_config()
|
|
86
|
+
return len(self._comm.router.transports) > 0
|
|
87
|
+
except ImportError:
|
|
88
|
+
logger.info("skcomm not installed")
|
|
89
|
+
return False
|
|
90
|
+
except Exception as exc:
|
|
91
|
+
logger.info("SKComm init failed: %s", exc)
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
def _ensure_history(self):
|
|
95
|
+
"""Lazily initialize the chat history store.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
ChatHistory or None.
|
|
99
|
+
"""
|
|
100
|
+
if self._history is not None:
|
|
101
|
+
return self._history
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
from skchat.history import ChatHistory
|
|
105
|
+
from skmemory import MemoryStore
|
|
106
|
+
|
|
107
|
+
store = MemoryStore()
|
|
108
|
+
self._history = ChatHistory(store=store)
|
|
109
|
+
return self._history
|
|
110
|
+
except ImportError:
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
# ------------------------------------------------------------------
|
|
114
|
+
# Core messaging API
|
|
115
|
+
# ------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
def send(
|
|
118
|
+
self,
|
|
119
|
+
recipient: str,
|
|
120
|
+
message: str,
|
|
121
|
+
thread_id: Optional[str] = None,
|
|
122
|
+
) -> dict:
|
|
123
|
+
"""Send a message to a peer agent.
|
|
124
|
+
|
|
125
|
+
Stores locally in SKMemory-backed history and delivers via
|
|
126
|
+
SKComm if transports are available.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
recipient: Peer agent name or CapAuth identity.
|
|
130
|
+
message: Message content.
|
|
131
|
+
thread_id: Optional conversation thread.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
dict: Result with 'stored', 'delivered', 'transport' keys.
|
|
135
|
+
"""
|
|
136
|
+
result = {"stored": False, "delivered": False, "transport": None, "error": None}
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
from skchat.models import ChatMessage, DeliveryStatus
|
|
140
|
+
|
|
141
|
+
msg = ChatMessage(
|
|
142
|
+
sender=self.identity,
|
|
143
|
+
recipient=recipient,
|
|
144
|
+
content=message,
|
|
145
|
+
thread_id=thread_id,
|
|
146
|
+
delivery_status=DeliveryStatus.PENDING,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
history = self._ensure_history()
|
|
150
|
+
if history:
|
|
151
|
+
history.store_message(msg)
|
|
152
|
+
result["stored"] = True
|
|
153
|
+
|
|
154
|
+
if self._ensure_comm():
|
|
155
|
+
try:
|
|
156
|
+
report = self._comm.send(
|
|
157
|
+
recipient=recipient,
|
|
158
|
+
message=_pack_chat_payload(msg),
|
|
159
|
+
thread_id=thread_id,
|
|
160
|
+
)
|
|
161
|
+
if getattr(report, "delivered", False):
|
|
162
|
+
result["delivered"] = True
|
|
163
|
+
result["transport"] = getattr(report, "successful_transport", None)
|
|
164
|
+
except Exception as exc:
|
|
165
|
+
result["error"] = str(exc)
|
|
166
|
+
|
|
167
|
+
except ImportError as exc:
|
|
168
|
+
result["error"] = f"Missing dependency: {exc}"
|
|
169
|
+
|
|
170
|
+
return result
|
|
171
|
+
|
|
172
|
+
def receive(self, limit: int = 20) -> list[dict]:
|
|
173
|
+
"""Poll for incoming messages.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
limit: Maximum messages to return.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
list[dict]: Received message dicts.
|
|
180
|
+
"""
|
|
181
|
+
messages: list[dict] = []
|
|
182
|
+
|
|
183
|
+
if self._ensure_comm():
|
|
184
|
+
try:
|
|
185
|
+
envelopes = self._comm.receive()
|
|
186
|
+
for env in envelopes:
|
|
187
|
+
if hasattr(env, "payload") and hasattr(env.payload, "content"):
|
|
188
|
+
msg_dict = _unpack_chat_payload(
|
|
189
|
+
env.payload.content,
|
|
190
|
+
sender=env.sender,
|
|
191
|
+
recipient=getattr(env, "recipient", self.identity),
|
|
192
|
+
)
|
|
193
|
+
messages.append(msg_dict)
|
|
194
|
+
|
|
195
|
+
history = self._ensure_history()
|
|
196
|
+
if history:
|
|
197
|
+
try:
|
|
198
|
+
from skchat.models import ChatMessage
|
|
199
|
+
|
|
200
|
+
chat_msg = ChatMessage(
|
|
201
|
+
sender=msg_dict["sender"],
|
|
202
|
+
recipient=msg_dict["recipient"],
|
|
203
|
+
content=msg_dict["content"],
|
|
204
|
+
thread_id=msg_dict.get("thread_id"),
|
|
205
|
+
)
|
|
206
|
+
history.store_message(chat_msg)
|
|
207
|
+
except Exception:
|
|
208
|
+
pass
|
|
209
|
+
except Exception as exc:
|
|
210
|
+
logger.warning("Receive error: %s", exc)
|
|
211
|
+
|
|
212
|
+
return messages[:limit]
|
|
213
|
+
|
|
214
|
+
def get_inbox(self, limit: int = 20) -> list[dict]:
|
|
215
|
+
"""Get recent messages from local history.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
limit: Maximum messages to return.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
list[dict]: Message dicts from history.
|
|
222
|
+
"""
|
|
223
|
+
history = self._ensure_history()
|
|
224
|
+
if history is None:
|
|
225
|
+
return []
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
return history.search_messages(self.identity, limit=limit)
|
|
229
|
+
except Exception:
|
|
230
|
+
try:
|
|
231
|
+
memories = history._store.list_memories(
|
|
232
|
+
tags=["skchat:message"],
|
|
233
|
+
limit=limit,
|
|
234
|
+
)
|
|
235
|
+
return [history._memory_to_chat_dict(m) for m in memories]
|
|
236
|
+
except Exception:
|
|
237
|
+
return []
|
|
238
|
+
|
|
239
|
+
def forward(
|
|
240
|
+
self,
|
|
241
|
+
original_msg: dict,
|
|
242
|
+
target_peer: str,
|
|
243
|
+
thread_id: Optional[str] = None,
|
|
244
|
+
) -> dict:
|
|
245
|
+
"""Forward a message to another peer, preserving original sender/timestamp.
|
|
246
|
+
|
|
247
|
+
Wraps the original message in a forward envelope that records the
|
|
248
|
+
original sender and timestamp, then delivers it to target_peer via
|
|
249
|
+
SKComm and stores it locally in history.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
original_msg: Original message dict (from inbox or receive).
|
|
253
|
+
target_peer: Peer agent to forward the message to.
|
|
254
|
+
thread_id: Optional thread ID for the forwarded message.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
dict: Result with 'stored', 'delivered', 'transport', 'forwarded_id' keys.
|
|
258
|
+
"""
|
|
259
|
+
result: dict = {
|
|
260
|
+
"stored": False,
|
|
261
|
+
"delivered": False,
|
|
262
|
+
"transport": None,
|
|
263
|
+
"forwarded_id": None,
|
|
264
|
+
"error": None,
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
fwd_id = str(uuid.uuid4())
|
|
268
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
269
|
+
|
|
270
|
+
payload = json.dumps({
|
|
271
|
+
"skchat_version": "1.0.0",
|
|
272
|
+
"skchat_forward": True,
|
|
273
|
+
"message_id": fwd_id,
|
|
274
|
+
"sender": self.identity,
|
|
275
|
+
"recipient": target_peer,
|
|
276
|
+
"content": original_msg.get("content", ""),
|
|
277
|
+
"thread_id": thread_id,
|
|
278
|
+
"timestamp": now,
|
|
279
|
+
"forwarded_from": original_msg.get("sender", "unknown"),
|
|
280
|
+
"forwarded_at": original_msg.get("timestamp", ""),
|
|
281
|
+
"original_message_id": original_msg.get("message_id", ""),
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
result["forwarded_id"] = fwd_id
|
|
285
|
+
|
|
286
|
+
history = self._ensure_history()
|
|
287
|
+
if history:
|
|
288
|
+
try:
|
|
289
|
+
from skchat.models import ChatMessage, DeliveryStatus
|
|
290
|
+
|
|
291
|
+
fwd_msg = ChatMessage(
|
|
292
|
+
sender=self.identity,
|
|
293
|
+
recipient=target_peer,
|
|
294
|
+
content=payload,
|
|
295
|
+
thread_id=thread_id,
|
|
296
|
+
delivery_status=DeliveryStatus.PENDING,
|
|
297
|
+
)
|
|
298
|
+
history.store_message(fwd_msg)
|
|
299
|
+
result["stored"] = True
|
|
300
|
+
except Exception as exc:
|
|
301
|
+
result["error"] = str(exc)
|
|
302
|
+
|
|
303
|
+
if self._ensure_comm():
|
|
304
|
+
try:
|
|
305
|
+
report = self._comm.send(
|
|
306
|
+
recipient=target_peer,
|
|
307
|
+
message=payload,
|
|
308
|
+
thread_id=thread_id,
|
|
309
|
+
)
|
|
310
|
+
if getattr(report, "delivered", False):
|
|
311
|
+
result["delivered"] = True
|
|
312
|
+
result["transport"] = getattr(report, "successful_transport", None)
|
|
313
|
+
except Exception as exc:
|
|
314
|
+
result["error"] = str(exc)
|
|
315
|
+
|
|
316
|
+
return result
|
|
317
|
+
|
|
318
|
+
# ------------------------------------------------------------------
|
|
319
|
+
# Interactive sessions
|
|
320
|
+
# ------------------------------------------------------------------
|
|
321
|
+
|
|
322
|
+
def interactive_session(
|
|
323
|
+
self,
|
|
324
|
+
peer: str,
|
|
325
|
+
poll_interval: float = 2.0,
|
|
326
|
+
thread_id: Optional[str] = None,
|
|
327
|
+
) -> None:
|
|
328
|
+
"""Run a prompt_toolkit-powered interactive chat session.
|
|
329
|
+
|
|
330
|
+
Features:
|
|
331
|
+
- Rich input with command history, auto-suggest, tab completion
|
|
332
|
+
- Bottom toolbar showing peer, active thread, transport status
|
|
333
|
+
- Background thread polls for incoming messages (non-blocking)
|
|
334
|
+
- File attachments via /attach <path>
|
|
335
|
+
- Thread management via /thread <id> and /reply
|
|
336
|
+
- Emoji support — just type unicode directly
|
|
337
|
+
|
|
338
|
+
Falls back to live_session() if prompt_toolkit is not installed.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
peer: Agent name or CapAuth identity to chat with.
|
|
342
|
+
poll_interval: Seconds between incoming message polls.
|
|
343
|
+
thread_id: Optional starting thread ID.
|
|
344
|
+
"""
|
|
345
|
+
try:
|
|
346
|
+
from prompt_toolkit import PromptSession
|
|
347
|
+
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
|
348
|
+
from prompt_toolkit.completion import WordCompleter
|
|
349
|
+
from prompt_toolkit.formatted_text import HTML
|
|
350
|
+
from prompt_toolkit.history import InMemoryHistory
|
|
351
|
+
from prompt_toolkit.patch_stdout import patch_stdout
|
|
352
|
+
from prompt_toolkit.styles import Style
|
|
353
|
+
except ImportError:
|
|
354
|
+
logger.info("prompt_toolkit not installed — falling back to live_session")
|
|
355
|
+
self.live_session(peer, poll_interval=poll_interval)
|
|
356
|
+
return
|
|
357
|
+
|
|
358
|
+
# Mutable state shared with background thread
|
|
359
|
+
state: dict = {
|
|
360
|
+
"thread": thread_id or f"chat-{self.identity}-{peer}-{int(time.time())}",
|
|
361
|
+
"last_recv_thread": None,
|
|
362
|
+
}
|
|
363
|
+
seen_ids: set[str] = set()
|
|
364
|
+
transport_ok = self._ensure_comm()
|
|
365
|
+
|
|
366
|
+
# prompt_toolkit style
|
|
367
|
+
style = Style.from_dict({
|
|
368
|
+
"bottom-toolbar": "bg:#1a1a2e #aaaaaa",
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
def bottom_toolbar() -> HTML:
|
|
372
|
+
conn = "connected" if self._ensure_comm() else "local-only"
|
|
373
|
+
t = state["thread"]
|
|
374
|
+
short_t = (t[:20] + "…") if len(t) > 20 else t
|
|
375
|
+
return HTML(
|
|
376
|
+
f" <b>Sovereign Chat</b> "
|
|
377
|
+
f"peer: <ansicyan>{peer}</ansicyan> "
|
|
378
|
+
f"thread: <ansigreen>{short_t}</ansigreen> "
|
|
379
|
+
f"[{conn}]"
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
session = PromptSession(
|
|
383
|
+
history=InMemoryHistory(),
|
|
384
|
+
auto_suggest=AutoSuggestFromHistory(),
|
|
385
|
+
completer=WordCompleter(_CHAT_COMMANDS, sentence=True, ignore_case=True),
|
|
386
|
+
style=style,
|
|
387
|
+
bottom_toolbar=bottom_toolbar,
|
|
388
|
+
mouse_support=False,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
# Background polling thread — prints via patch_stdout
|
|
392
|
+
stop_event = _threading.Event()
|
|
393
|
+
|
|
394
|
+
def _poll_loop() -> None:
|
|
395
|
+
while not stop_event.wait(poll_interval):
|
|
396
|
+
try:
|
|
397
|
+
for msg in self.receive(limit=20):
|
|
398
|
+
uid = msg.get("message_id") or msg.get("id") or ""
|
|
399
|
+
if uid and uid in seen_ids:
|
|
400
|
+
continue
|
|
401
|
+
if uid:
|
|
402
|
+
seen_ids.add(uid)
|
|
403
|
+
sender = msg.get("sender", "?")
|
|
404
|
+
if sender == self.identity:
|
|
405
|
+
continue
|
|
406
|
+
content = msg.get("content", "")
|
|
407
|
+
ts = _short_timestamp()
|
|
408
|
+
recv_thread = msg.get("thread_id")
|
|
409
|
+
if recv_thread:
|
|
410
|
+
state["last_recv_thread"] = recv_thread
|
|
411
|
+
display = _format_content(content)
|
|
412
|
+
print(f"\n \033[32m{sender}\033[0m \033[2m[{ts}]\033[0m {display}\n")
|
|
413
|
+
except Exception:
|
|
414
|
+
pass
|
|
415
|
+
|
|
416
|
+
# Print header
|
|
417
|
+
tr_label = "✓ connected" if transport_ok else "✗ local-only"
|
|
418
|
+
t = state["thread"]
|
|
419
|
+
short_t = (t[:30] + "…") if len(t) > 30 else t
|
|
420
|
+
print(f"\n ─── Sovereign Chat ─────────────────────────────")
|
|
421
|
+
print(f" Peer: {peer}")
|
|
422
|
+
print(f" Thread: {short_t}")
|
|
423
|
+
print(f" Transport: {tr_label}")
|
|
424
|
+
print(f" ────────────────────────────────────────────────")
|
|
425
|
+
print(f" Type a message and press Enter to send.")
|
|
426
|
+
print(f" /attach <path> /thread <id> /reply /help /quit\n")
|
|
427
|
+
|
|
428
|
+
with patch_stdout():
|
|
429
|
+
poll_thread = _threading.Thread(target=_poll_loop, daemon=True)
|
|
430
|
+
poll_thread.start()
|
|
431
|
+
|
|
432
|
+
while True:
|
|
433
|
+
try:
|
|
434
|
+
text = session.prompt(f" {self.identity}: ")
|
|
435
|
+
except (EOFError, KeyboardInterrupt):
|
|
436
|
+
break
|
|
437
|
+
|
|
438
|
+
text = text.strip()
|
|
439
|
+
if not text:
|
|
440
|
+
continue
|
|
441
|
+
|
|
442
|
+
low = text.lower()
|
|
443
|
+
|
|
444
|
+
# ── Slash commands ──────────────────────────────────
|
|
445
|
+
if low in ("/quit", "/exit", "/q"):
|
|
446
|
+
break
|
|
447
|
+
|
|
448
|
+
elif low == "/help":
|
|
449
|
+
_print_chat_help()
|
|
450
|
+
|
|
451
|
+
elif low == "/whoami":
|
|
452
|
+
print(f"\n Identity: {self.identity}\n")
|
|
453
|
+
|
|
454
|
+
elif low == "/inbox":
|
|
455
|
+
_print_recent_inbox(self.get_inbox(limit=5))
|
|
456
|
+
|
|
457
|
+
elif low == "/reply":
|
|
458
|
+
lt = state.get("last_recv_thread")
|
|
459
|
+
if lt:
|
|
460
|
+
state["thread"] = lt
|
|
461
|
+
print(f"\n Now replying in thread: {lt}\n")
|
|
462
|
+
else:
|
|
463
|
+
print("\n No received thread to reply to yet.\n")
|
|
464
|
+
|
|
465
|
+
elif low.startswith("/thread "):
|
|
466
|
+
new_t = text[8:].strip()
|
|
467
|
+
if new_t:
|
|
468
|
+
state["thread"] = new_t
|
|
469
|
+
print(f"\n Switched to thread: {new_t}\n")
|
|
470
|
+
|
|
471
|
+
elif low.startswith("/attach "):
|
|
472
|
+
fp = text[8:].strip()
|
|
473
|
+
self._send_attachment(peer, fp, state["thread"])
|
|
474
|
+
|
|
475
|
+
elif low == "/emoji":
|
|
476
|
+
_print_emoji_ref()
|
|
477
|
+
|
|
478
|
+
else:
|
|
479
|
+
# Regular message send
|
|
480
|
+
result = self.send(peer, text, thread_id=state["thread"])
|
|
481
|
+
ts = _short_timestamp()
|
|
482
|
+
if result.get("delivered"):
|
|
483
|
+
via = result.get("transport") or "unknown"
|
|
484
|
+
print(
|
|
485
|
+
f" \033[34m{self.identity}\033[0m \033[2m[{ts}]\033[0m"
|
|
486
|
+
f" {text} \033[2m→ {via}\033[0m"
|
|
487
|
+
)
|
|
488
|
+
elif result.get("stored"):
|
|
489
|
+
print(
|
|
490
|
+
f" \033[34m{self.identity}\033[0m \033[2m[{ts}]\033[0m"
|
|
491
|
+
f" {text} \033[2m→ stored locally\033[0m"
|
|
492
|
+
)
|
|
493
|
+
else:
|
|
494
|
+
err = result.get("error") or "send failed"
|
|
495
|
+
print(f"\n \033[31mError:\033[0m {err}\n")
|
|
496
|
+
|
|
497
|
+
stop_event.set()
|
|
498
|
+
print(f"\n Chat session ended.\n")
|
|
499
|
+
|
|
500
|
+
def live_session(
|
|
501
|
+
self,
|
|
502
|
+
peer: str,
|
|
503
|
+
poll_interval: float = 2.0,
|
|
504
|
+
) -> None:
|
|
505
|
+
"""Run an interactive live chat session in the terminal.
|
|
506
|
+
|
|
507
|
+
Fallback implementation using plain stdin/stdout. Used when
|
|
508
|
+
prompt_toolkit is not installed.
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
peer: Agent name or identity to chat with.
|
|
512
|
+
poll_interval: Seconds between inbox polls.
|
|
513
|
+
"""
|
|
514
|
+
thread_id = f"live-{self.identity}-{peer}-{int(time.time())}"
|
|
515
|
+
|
|
516
|
+
print(f"\n Sovereign Chat — {self.identity} <-> {peer}")
|
|
517
|
+
print(f" Thread: {thread_id[:20]}...")
|
|
518
|
+
print(f" Transport: {'available' if self._ensure_comm() else 'local-only'}")
|
|
519
|
+
print(f" Type a message and press Enter. Type /quit to exit.\n")
|
|
520
|
+
|
|
521
|
+
try:
|
|
522
|
+
while True:
|
|
523
|
+
incoming = self.receive(limit=10)
|
|
524
|
+
for msg in incoming:
|
|
525
|
+
sender = msg.get("sender", "?")
|
|
526
|
+
content = msg.get("content", "")
|
|
527
|
+
if sender != self.identity:
|
|
528
|
+
ts = _short_timestamp()
|
|
529
|
+
print(f" [{ts}] {sender}: {content}")
|
|
530
|
+
|
|
531
|
+
try:
|
|
532
|
+
user_input = _read_line(f" [{_short_timestamp()}] {self.identity}: ")
|
|
533
|
+
except EOFError:
|
|
534
|
+
break
|
|
535
|
+
|
|
536
|
+
if not user_input:
|
|
537
|
+
continue
|
|
538
|
+
if user_input.strip().lower() in ("/quit", "/exit", "/q"):
|
|
539
|
+
break
|
|
540
|
+
|
|
541
|
+
result = self.send(peer, user_input, thread_id=thread_id)
|
|
542
|
+
if result["delivered"]:
|
|
543
|
+
print(f" -> delivered via {result['transport']}")
|
|
544
|
+
elif result["stored"]:
|
|
545
|
+
print(f" -> stored locally")
|
|
546
|
+
if result.get("error"):
|
|
547
|
+
print(f" -> error: {result['error']}")
|
|
548
|
+
|
|
549
|
+
except KeyboardInterrupt:
|
|
550
|
+
pass
|
|
551
|
+
|
|
552
|
+
print(f"\n Session ended.\n")
|
|
553
|
+
|
|
554
|
+
# ------------------------------------------------------------------
|
|
555
|
+
# File attachment
|
|
556
|
+
# ------------------------------------------------------------------
|
|
557
|
+
|
|
558
|
+
def _send_attachment(self, peer: str, file_path_str: str, thread_id: str) -> None:
|
|
559
|
+
"""Send a file as an inline attachment message.
|
|
560
|
+
|
|
561
|
+
Reads the file, encodes it (UTF-8 for text, base64 for binary),
|
|
562
|
+
and sends it as a structured JSON payload. The recipient sees
|
|
563
|
+
a [Attachment: name (size)] preview.
|
|
564
|
+
|
|
565
|
+
Args:
|
|
566
|
+
peer: Recipient agent name.
|
|
567
|
+
file_path_str: Path to the file to send.
|
|
568
|
+
thread_id: Active thread ID.
|
|
569
|
+
"""
|
|
570
|
+
path = Path(file_path_str).expanduser().resolve()
|
|
571
|
+
if not path.exists():
|
|
572
|
+
print(f"\n File not found: {path}\n")
|
|
573
|
+
return
|
|
574
|
+
if not path.is_file():
|
|
575
|
+
print(f"\n Not a regular file: {path}\n")
|
|
576
|
+
return
|
|
577
|
+
|
|
578
|
+
size = path.stat().st_size
|
|
579
|
+
if size > 10 * 1024 * 1024: # 10 MB cap for inline transfers
|
|
580
|
+
print(
|
|
581
|
+
f"\n File too large: {size:,} bytes (max 10 MB for inline attachments).\n"
|
|
582
|
+
f" Use skcapstone file send for large transfers.\n"
|
|
583
|
+
)
|
|
584
|
+
return
|
|
585
|
+
|
|
586
|
+
raw = path.read_bytes()
|
|
587
|
+
|
|
588
|
+
if path.suffix.lower() in _TEXT_SUFFIXES:
|
|
589
|
+
try:
|
|
590
|
+
payload = json.dumps({
|
|
591
|
+
"skchat_attachment": True,
|
|
592
|
+
"name": path.name,
|
|
593
|
+
"size": size,
|
|
594
|
+
"encoding": "utf-8",
|
|
595
|
+
"content": raw.decode("utf-8"),
|
|
596
|
+
})
|
|
597
|
+
except UnicodeDecodeError:
|
|
598
|
+
payload = json.dumps({
|
|
599
|
+
"skchat_attachment": True,
|
|
600
|
+
"name": path.name,
|
|
601
|
+
"size": size,
|
|
602
|
+
"encoding": "base64",
|
|
603
|
+
"content": base64.b64encode(raw).decode(),
|
|
604
|
+
})
|
|
605
|
+
else:
|
|
606
|
+
payload = json.dumps({
|
|
607
|
+
"skchat_attachment": True,
|
|
608
|
+
"name": path.name,
|
|
609
|
+
"size": size,
|
|
610
|
+
"encoding": "base64",
|
|
611
|
+
"content": base64.b64encode(raw).decode(),
|
|
612
|
+
})
|
|
613
|
+
|
|
614
|
+
result = self.send(peer, payload, thread_id=thread_id)
|
|
615
|
+
ts = _short_timestamp()
|
|
616
|
+
display = f"[Attachment: {path.name} ({size:,} bytes)]"
|
|
617
|
+
if result.get("delivered") or result.get("stored"):
|
|
618
|
+
via = result.get("transport") or "stored"
|
|
619
|
+
print(
|
|
620
|
+
f" \033[34m{self.identity}\033[0m \033[2m[{ts}]\033[0m"
|
|
621
|
+
f" {display} \033[2m→ {via}\033[0m"
|
|
622
|
+
)
|
|
623
|
+
else:
|
|
624
|
+
print(f"\n Failed to send attachment: {result.get('error', 'unknown')}\n")
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
# ---------------------------------------------------------------------------
|
|
628
|
+
# Module-level helpers
|
|
629
|
+
# ---------------------------------------------------------------------------
|
|
630
|
+
|
|
631
|
+
def _pack_chat_payload(msg) -> str:
|
|
632
|
+
"""Serialize a ChatMessage for SKComm transport.
|
|
633
|
+
|
|
634
|
+
Args:
|
|
635
|
+
msg: ChatMessage instance.
|
|
636
|
+
|
|
637
|
+
Returns:
|
|
638
|
+
str: JSON payload.
|
|
639
|
+
"""
|
|
640
|
+
return json.dumps({
|
|
641
|
+
"skchat_version": "1.0.0",
|
|
642
|
+
"message_id": msg.id,
|
|
643
|
+
"sender": msg.sender,
|
|
644
|
+
"recipient": msg.recipient,
|
|
645
|
+
"content": msg.content,
|
|
646
|
+
"thread_id": msg.thread_id,
|
|
647
|
+
"timestamp": msg.timestamp.isoformat(),
|
|
648
|
+
})
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def _unpack_chat_payload(payload: str, sender: str, recipient: str) -> dict:
|
|
652
|
+
"""Deserialize a chat payload from SKComm.
|
|
653
|
+
|
|
654
|
+
Falls back to plain text if not structured JSON.
|
|
655
|
+
|
|
656
|
+
Args:
|
|
657
|
+
payload: Raw payload string.
|
|
658
|
+
sender: Fallback sender from envelope.
|
|
659
|
+
recipient: Fallback recipient from envelope.
|
|
660
|
+
|
|
661
|
+
Returns:
|
|
662
|
+
dict: Message data.
|
|
663
|
+
"""
|
|
664
|
+
try:
|
|
665
|
+
data = json.loads(payload)
|
|
666
|
+
if "skchat_version" in data:
|
|
667
|
+
return {
|
|
668
|
+
"message_id": data.get("message_id", ""),
|
|
669
|
+
"sender": data.get("sender", sender),
|
|
670
|
+
"recipient": data.get("recipient", recipient),
|
|
671
|
+
"content": data.get("content", payload),
|
|
672
|
+
"thread_id": data.get("thread_id"),
|
|
673
|
+
"timestamp": data.get("timestamp"),
|
|
674
|
+
}
|
|
675
|
+
except (json.JSONDecodeError, KeyError):
|
|
676
|
+
pass
|
|
677
|
+
|
|
678
|
+
return {
|
|
679
|
+
"message_id": "",
|
|
680
|
+
"sender": sender,
|
|
681
|
+
"recipient": recipient,
|
|
682
|
+
"content": payload,
|
|
683
|
+
"thread_id": None,
|
|
684
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def _format_content(content: str) -> str:
|
|
689
|
+
"""Format a message content string for terminal display.
|
|
690
|
+
|
|
691
|
+
Detects inline attachments and renders them as a readable label.
|
|
692
|
+
|
|
693
|
+
Args:
|
|
694
|
+
content: Raw message content.
|
|
695
|
+
|
|
696
|
+
Returns:
|
|
697
|
+
str: Display-ready string.
|
|
698
|
+
"""
|
|
699
|
+
try:
|
|
700
|
+
data = json.loads(content)
|
|
701
|
+
if data.get("skchat_attachment"):
|
|
702
|
+
name = data.get("name", "unknown")
|
|
703
|
+
size = data.get("size", 0)
|
|
704
|
+
return f"[Attachment: {name} ({size:,} bytes)]"
|
|
705
|
+
except (json.JSONDecodeError, TypeError, AttributeError):
|
|
706
|
+
pass
|
|
707
|
+
return content
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
def _short_timestamp() -> str:
|
|
711
|
+
"""Get a compact HH:MM:SS timestamp."""
|
|
712
|
+
return datetime.now().strftime("%H:%M:%S")
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def _read_line(prompt: str) -> str:
|
|
716
|
+
"""Read a line of input from stdin with a prompt.
|
|
717
|
+
|
|
718
|
+
Args:
|
|
719
|
+
prompt: The prompt string to display.
|
|
720
|
+
|
|
721
|
+
Returns:
|
|
722
|
+
str: The user's input, stripped of trailing newline.
|
|
723
|
+
"""
|
|
724
|
+
import sys
|
|
725
|
+
sys.stdout.write(prompt)
|
|
726
|
+
sys.stdout.flush()
|
|
727
|
+
return sys.stdin.readline().rstrip("\n")
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
def _print_chat_help() -> None:
|
|
731
|
+
"""Print the chat command reference."""
|
|
732
|
+
print(
|
|
733
|
+
"\n Chat commands:\n"
|
|
734
|
+
" /attach <path> Send a file attachment (max 10 MB)\n"
|
|
735
|
+
" /thread <id> Switch to a different thread ID\n"
|
|
736
|
+
" /reply Switch to the last received thread\n"
|
|
737
|
+
" /inbox Show last 5 inbox messages\n"
|
|
738
|
+
" /whoami Show your agent identity\n"
|
|
739
|
+
" /emoji Emoji quick reference\n"
|
|
740
|
+
" /quit Exit (also /exit or /q)\n"
|
|
741
|
+
"\n Emoji is fully supported — just type directly: 🎉 🚀 ❤️ 🤖\n"
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
def _print_recent_inbox(messages: list) -> None:
|
|
746
|
+
"""Print a short inbox preview."""
|
|
747
|
+
if not messages:
|
|
748
|
+
print("\n No messages in inbox.\n")
|
|
749
|
+
return
|
|
750
|
+
print("\n Recent messages:")
|
|
751
|
+
for m in messages:
|
|
752
|
+
sender = m.get("sender", "?")
|
|
753
|
+
content = m.get("content", "")
|
|
754
|
+
display = _format_content(content)
|
|
755
|
+
preview = (display[:60] + "…") if len(display) > 60 else display
|
|
756
|
+
print(f" \033[36m{sender}\033[0m: {preview}")
|
|
757
|
+
print()
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
def _print_emoji_ref() -> None:
|
|
761
|
+
"""Print an emoji quick-reference card."""
|
|
762
|
+
print(
|
|
763
|
+
"\n Emoji quick reference (type directly — unicode is fully supported):\n"
|
|
764
|
+
" ❤️ 💙 💚 🖤 🤍 — hearts\n"
|
|
765
|
+
" 👍 👎 🙌 🤝 ✌️ — hands\n"
|
|
766
|
+
" 🎉 🚀 🔥 ⚡ ✨ — vibes\n"
|
|
767
|
+
" ✅ ❌ ⚠️ 🔒 🔑 — status\n"
|
|
768
|
+
" 🤖 👾 🧠 🔮 💡 — tech / sovereign\n"
|
|
769
|
+
)
|