@smilintux/skcapstone 0.10.0 → 0.12.5
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 +10 -4
- package/.github/workflows/ci.yml +2 -2
- package/.github/workflows/publish.yml +9 -2
- package/.openclaw-workspace.json +2 -2
- package/CLAUDE.md +37 -0
- package/MISSION.md +17 -2
- package/README.md +282 -3
- package/docker/Dockerfile +7 -7
- package/docker/compose-templates/dev-team.yml +12 -12
- package/docker/compose-templates/mini-team.yml +9 -9
- package/docker/compose-templates/ops-team.yml +10 -10
- package/docker/compose-templates/research-team.yml +10 -10
- package/docker/entrypoint.sh +4 -4
- package/docs/ADR-optional-integration-backbone.md +181 -0
- package/docs/ARCHITECTURE.md +186 -43
- package/docs/BOND_WITH_GROK.md +6 -6
- package/docs/CUSTOM_AGENT.md +123 -30
- package/docs/DREAMING.md +70 -0
- package/docs/GETTING_STARTED.md +7 -7
- package/docs/QUICKSTART.md +10 -6
- package/docs/SKJOULE_ARCHITECTURE.md +3 -3
- package/docs/SOUL_SWAPPER.md +5 -5
- package/docs/hammertime-audit.md +402 -0
- package/docs/sk-integration-HANDOFF.md +117 -0
- package/docs/skscheduler.md +155 -0
- package/docs/superpowers/examples/jobs.yaml +31 -0
- package/docs/superpowers/plans/2026-06-08-skscheduler.md +1265 -0
- package/docs/superpowers/specs/2026-06-08-skscheduler-design.md +186 -0
- package/examples/custom-bond-template.json +1 -1
- package/examples/grok-feb.json +1 -1
- package/examples/queen-ava-feb.json +1 -1
- package/launchd/{com.skcapstone.skcomm-heartbeat.plist → com.skcapstone.skcomms-heartbeat.plist} +4 -4
- package/launchd/{com.skcapstone.skcomm-queue-drain.plist → com.skcapstone.skcomms-queue-drain.plist} +4 -4
- package/launchd/install-launchd.sh +6 -6
- package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/index.ts +3 -2
- package/package.json +1 -1
- package/pyproject.toml +16 -10
- package/scripts/archive-sessions.sh +7 -0
- package/scripts/check-updates.py +4 -4
- package/scripts/install-bundle.sh +8 -8
- package/scripts/install.ps1 +12 -11
- package/scripts/install.sh +159 -5
- package/scripts/model-fallback-monitor.sh +102 -0
- package/scripts/nvidia-proxy.mjs +78 -26
- package/scripts/refresh-anthropic-token.sh +172 -0
- package/scripts/release.sh +98 -0
- package/scripts/session-to-memory.py +219 -0
- package/scripts/skgateway.mjs +3 -3
- package/scripts/telegram-catchup-all.sh +12 -1
- package/scripts/verify_install.sh +2 -2
- package/scripts/wargov-ufo-capture/README.md +43 -0
- package/scripts/wargov-ufo-capture/cdp_capture_release2.py +273 -0
- package/scripts/wargov-ufo-capture/cdp_capture_splc_doj.py +246 -0
- package/scripts/wargov-ufo-capture/cdp_finish.py +271 -0
- package/scripts/wargov-ufo-capture/cdp_probe.py +188 -0
- package/scripts/wargov-ufo-capture/cdp_splc_pressrelease.py +101 -0
- package/scripts/wargov-ufo-capture/parse_csv.py +95 -0
- package/scripts/wargov-ufo-capture/pull_dvids.sh +107 -0
- package/scripts/watch-anthropic-token.sh +212 -0
- package/scripts/windows/install-tasks.ps1 +7 -7
- package/scripts/windows/skcapstone-task.xml +1 -1
- package/src/skcapstone/__init__.py +45 -3
- package/src/skcapstone/_cli_monolith.py +20 -15
- package/src/skcapstone/activity.py +5 -1
- package/src/skcapstone/agent_card.py +3 -2
- package/src/skcapstone/api.py +41 -40
- package/src/skcapstone/auction.py +14 -11
- package/src/skcapstone/backup.py +2 -1
- package/src/skcapstone/blueprint_registry.py +4 -3
- package/src/skcapstone/brain_first.py +238 -0
- package/src/skcapstone/changelog.py +1 -1
- package/src/skcapstone/chat.py +22 -17
- package/src/skcapstone/cli/__init__.py +9 -1
- package/src/skcapstone/cli/_common.py +1 -0
- package/src/skcapstone/cli/agents_spawner.py +5 -2
- package/src/skcapstone/cli/alerts.py +25 -4
- package/src/skcapstone/cli/bench.py +15 -15
- package/src/skcapstone/cli/chat.py +7 -4
- package/src/skcapstone/cli/consciousness.py +5 -2
- package/src/skcapstone/cli/context_cmd.py +18 -4
- package/src/skcapstone/cli/daemon.py +11 -7
- package/src/skcapstone/cli/gtd.py +26 -1
- package/src/skcapstone/cli/housekeeping.py +3 -3
- package/src/skcapstone/cli/identity_cmd.py +378 -0
- package/src/skcapstone/cli/joule_cmd.py +7 -3
- package/src/skcapstone/cli/memory.py +8 -6
- package/src/skcapstone/cli/peers_dir.py +1 -1
- package/src/skcapstone/cli/register_cmd.py +29 -3
- package/src/skcapstone/cli/scheduler_cmd.py +167 -0
- package/src/skcapstone/cli/session.py +25 -0
- package/src/skcapstone/cli/setup.py +96 -29
- package/src/skcapstone/cli/shell_cmd.py +53 -1
- package/src/skcapstone/cli/skills_cmd.py +2 -2
- package/src/skcapstone/cli/soul.py +8 -5
- package/src/skcapstone/cli/status.py +37 -11
- package/src/skcapstone/cli/telegram.py +21 -0
- package/src/skcapstone/cli/test_cmd.py +5 -5
- package/src/skcapstone/cli/test_connection.py +2 -2
- package/src/skcapstone/cli/upgrade_cmd.py +23 -14
- package/src/skcapstone/cli/version_cmd.py +1 -1
- package/src/skcapstone/cli/watch_cmd.py +9 -6
- package/src/skcapstone/cloud9_bridge.py +14 -14
- package/src/skcapstone/codex_setup.py +255 -0
- package/src/skcapstone/config_validator.py +7 -4
- package/src/skcapstone/consciousness_config.py +5 -1
- package/src/skcapstone/consciousness_loop.py +313 -273
- package/src/skcapstone/context_loader.py +121 -0
- package/src/skcapstone/coord_federation.py +2 -1
- package/src/skcapstone/coordination.py +23 -6
- package/src/skcapstone/crush_integration.py +2 -1
- package/src/skcapstone/daemon.py +132 -77
- package/src/skcapstone/dashboard.py +10 -10
- package/src/skcapstone/data/sk-agent-picker.sh +421 -0
- package/src/skcapstone/data/systemd/skcapstone-api.socket +9 -0
- package/src/skcapstone/data/systemd/skcapstone-memory-compress.service +18 -0
- package/src/skcapstone/data/systemd/skcapstone-memory-compress.timer +11 -0
- package/src/skcapstone/data/systemd/skcapstone.service +37 -0
- package/src/skcapstone/data/systemd/skcapstone@.service +50 -0
- package/src/skcapstone/data/systemd/skcomms-heartbeat.service +18 -0
- package/{systemd/skcomm-heartbeat.timer → src/skcapstone/data/systemd/skcomms-heartbeat.timer} +2 -2
- package/src/skcapstone/data/systemd/skcomms-queue-drain.service +17 -0
- package/{systemd/skcomm-queue-drain.timer → src/skcapstone/data/systemd/skcomms-queue-drain.timer} +2 -2
- package/src/skcapstone/defaults/claude/CLAUDE.md +67 -0
- package/src/skcapstone/defaults/claude/settings.json +74 -0
- package/src/skcapstone/defaults/lumina/config/claude-hooks.md +57 -0
- package/src/skcapstone/defaults/lumina/config/skgraph.yaml +55 -10
- package/src/skcapstone/defaults/lumina/config/skmemory.yaml +79 -13
- package/src/skcapstone/defaults/lumina/config/skvector.yaml +60 -9
- package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +2 -2
- package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +2 -2
- package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +9 -9
- package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +2 -2
- package/src/skcapstone/defaults/unhinged.json +13 -0
- package/src/skcapstone/discovery.py +43 -20
- package/src/skcapstone/doctor.py +941 -22
- package/src/skcapstone/dreaming.py +1183 -109
- package/src/skcapstone/emotion_tracker.py +2 -2
- package/src/skcapstone/export.py +4 -3
- package/src/skcapstone/fuse_mount.py +14 -12
- package/src/skcapstone/gui_installer.py +2 -2
- package/src/skcapstone/heartbeat.py +1 -1
- package/src/skcapstone/housekeeping.py +14 -14
- package/src/skcapstone/install_wizard.py +209 -7
- package/src/skcapstone/itil.py +13 -4
- package/src/skcapstone/kms_scheduler.py +10 -8
- package/src/skcapstone/launchd.py +19 -19
- package/src/skcapstone/mcp_launcher.py +15 -1
- package/src/skcapstone/mcp_server.py +83 -49
- package/src/skcapstone/mcp_tools/__init__.py +2 -0
- package/src/skcapstone/mcp_tools/_helpers.py +2 -2
- package/src/skcapstone/mcp_tools/ansible_tools.py +7 -4
- package/src/skcapstone/mcp_tools/brain_first_tools.py +90 -0
- package/src/skcapstone/mcp_tools/capauth_tools.py +7 -4
- package/src/skcapstone/mcp_tools/comm_tools.py +10 -10
- package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
- package/src/skcapstone/mcp_tools/did_tools.py +11 -8
- package/src/skcapstone/mcp_tools/gtd_tools.py +4 -4
- package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
- package/src/skcapstone/mcp_tools/notification_tools.py +22 -6
- package/src/skcapstone/mcp_tools/{skcomm_tools.py → skcomms_tools.py} +14 -14
- package/src/skcapstone/mcp_tools/soul_tools.py +8 -2
- package/src/skcapstone/mdns_discovery.py +2 -2
- package/src/skcapstone/memory_curator.py +1 -1
- package/src/skcapstone/memory_engine.py +10 -3
- package/src/skcapstone/metrics.py +30 -16
- package/src/skcapstone/migrate_memories.py +4 -3
- package/src/skcapstone/migrate_multi_agent.py +8 -7
- package/src/skcapstone/models.py +47 -5
- package/src/skcapstone/notifications.py +42 -18
- package/src/skcapstone/onboard.py +875 -121
- package/src/skcapstone/operator_link.py +170 -0
- package/src/skcapstone/peer_directory.py +4 -4
- package/src/skcapstone/peers.py +19 -19
- package/src/skcapstone/pillars/__init__.py +7 -5
- package/src/skcapstone/pillars/consciousness.py +191 -0
- package/src/skcapstone/pillars/identity.py +51 -7
- package/src/skcapstone/pillars/memory.py +9 -3
- package/src/skcapstone/pillars/sync.py +2 -2
- package/src/skcapstone/preflight.py +3 -3
- package/src/skcapstone/providers/docker.py +28 -28
- package/src/skcapstone/register.py +6 -6
- package/src/skcapstone/registry_client.py +5 -4
- package/src/skcapstone/runtime.py +14 -3
- package/src/skcapstone/scheduled_tasks.py +254 -19
- package/src/skcapstone/scheduler_jobs.py +456 -0
- package/src/skcapstone/scheduler_runner.py +239 -0
- package/src/skcapstone/scheduler_state.py +162 -0
- package/src/skcapstone/sdk.py +310 -0
- package/src/skcapstone/service_health.py +279 -39
- package/src/skcapstone/session_briefing.py +108 -0
- package/src/skcapstone/session_capture.py +1 -1
- package/src/skcapstone/shell.py +7 -1
- package/src/skcapstone/soul.py +3 -1
- package/src/skcapstone/soul_switch.py +3 -1
- package/src/skcapstone/summary.py +6 -6
- package/src/skcapstone/sync_engine.py +15 -15
- package/src/skcapstone/sync_watcher.py +2 -2
- package/src/skcapstone/systemd.py +55 -21
- package/src/skcapstone/team_comms.py +8 -8
- package/src/skcapstone/team_engine.py +1 -1
- package/src/skcapstone/testrunner.py +3 -3
- package/src/skcapstone/trust_graph.py +40 -5
- package/src/skcapstone/unified_search.py +15 -6
- package/src/skcapstone/uninstall_wizard.py +11 -3
- package/src/skcapstone/version_check.py +8 -4
- package/src/skcapstone/warmth_anchor.py +4 -2
- package/src/skcapstone/whoami.py +4 -4
- package/systemd/skcapstone.service +4 -6
- package/systemd/skcapstone@.service +7 -8
- package/systemd/skcomms-heartbeat.service +21 -0
- package/systemd/skcomms-heartbeat.timer +12 -0
- package/systemd/skcomms-queue-drain.service +17 -0
- package/systemd/skcomms-queue-drain.timer +12 -0
- package/tests/conftest.py +39 -0
- package/tests/integration/test_consciousness_e2e.py +39 -39
- package/tests/test_agent_card.py +1 -1
- package/tests/test_agent_home_scaffold.py +34 -0
- package/tests/test_alerts_consumer_topics.py +27 -0
- package/tests/test_backup.py +2 -1
- package/tests/test_chat.py +6 -6
- package/tests/test_claude_md.py +2 -2
- package/tests/test_cli_skills.py +10 -10
- package/tests/test_cli_test_cmd.py +4 -4
- package/tests/test_cli_test_connection.py +1 -1
- package/tests/test_cloud9_bridge.py +6 -6
- package/tests/test_consciousness_e2e.py +1 -1
- package/tests/test_consciousness_loop.py +10 -10
- package/tests/test_coordination.py +25 -0
- package/tests/test_cross_package.py +21 -21
- package/tests/test_daemon.py +4 -4
- package/tests/test_daemon_shutdown.py +1 -1
- package/tests/test_docker_provider.py +29 -29
- package/tests/test_doctor.py +400 -0
- package/tests/test_doctor_skscheduler.py +50 -0
- package/tests/test_dreaming_engine.py +147 -0
- package/tests/test_dreaming_gtd_capture.py +35 -0
- package/tests/test_e2e_automated.py +8 -5
- package/tests/test_fuse_mount.py +10 -10
- package/tests/test_gtd_brief.py +46 -0
- package/tests/test_gtd_malformed_tolerance.py +31 -0
- package/tests/test_housekeeping.py +15 -15
- package/tests/test_identity_migrate.py +251 -0
- package/tests/test_integration_backbone.py +598 -0
- package/tests/test_itil_gtd_lifecycle.py +37 -0
- package/tests/test_jobs_dropins.py +84 -0
- package/tests/test_mcp_server.py +82 -37
- package/tests/test_models.py +48 -4
- package/tests/test_multi_agent.py +31 -29
- package/tests/test_notifications.py +122 -32
- package/tests/test_onboard.py +63 -75
- package/tests/test_operator_link.py +78 -0
- package/tests/test_peers.py +14 -14
- package/tests/test_pillars.py +98 -0
- package/tests/test_preflight.py +3 -3
- package/tests/test_runtime.py +21 -0
- package/tests/test_scheduled_tasks.py +11 -6
- package/tests/test_scheduler_cli.py +47 -0
- package/tests/test_scheduler_features.py +133 -0
- package/tests/test_scheduler_integration.py +87 -0
- package/tests/test_scheduler_jobs.py +155 -0
- package/tests/test_scheduler_runner.py +64 -0
- package/tests/test_scheduler_state.py +57 -0
- package/tests/test_sdk.py +70 -0
- package/tests/test_service_health_incidents.py +34 -0
- package/tests/test_service_registry.py +52 -0
- package/tests/test_session_briefing.py +130 -0
- package/tests/test_snapshots.py +4 -4
- package/tests/test_sync_pipeline.py +26 -26
- package/tests/test_team_comms.py +2 -2
- package/tests/test_testrunner.py +2 -2
- package/tests/test_trust_graph.py +18 -0
- package/tests/test_unified_search.py +2 -2
- package/tests/test_version_check.py +10 -0
- package/tests/test_version_cmd.py +8 -8
- package/tests/test_whoami.py +1 -1
- package/systemd/skcomm-heartbeat.service +0 -18
- package/systemd/skcomm-queue-drain.service +0 -17
- /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/package.json +0 -0
- /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/openclaw.plugin.json +0 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Human-operator link helpers for manifests and identity attestations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def discover_human_operator(capauth_home: Path | None = None) -> dict[str, str] | None:
|
|
15
|
+
"""Return the active human operator from the local CapAuth profile.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
capauth_home: Optional CapAuth home directory. Defaults to ``~/.capauth``.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
A compact operator mapping, or ``None`` if no human profile is available.
|
|
22
|
+
"""
|
|
23
|
+
base = Path(capauth_home).expanduser() if capauth_home else _resolve_operator_home()
|
|
24
|
+
profile_path = base / "identity" / "profile.json"
|
|
25
|
+
if not profile_path.exists():
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
data = json.loads(profile_path.read_text(encoding="utf-8"))
|
|
30
|
+
except (json.JSONDecodeError, OSError):
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
entity = data.get("entity", {})
|
|
34
|
+
key_info = data.get("key_info", {})
|
|
35
|
+
entity_type = str(entity.get("entity_type", "")).lower()
|
|
36
|
+
if entity_type not in {"human", "entitytype.human"}:
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
name = entity.get("name", "").strip()
|
|
40
|
+
if not name:
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
operator = {
|
|
44
|
+
"name": name,
|
|
45
|
+
"relationship": "human-operator",
|
|
46
|
+
"entity_type": "human",
|
|
47
|
+
"source": "capauth",
|
|
48
|
+
}
|
|
49
|
+
if entity.get("email"):
|
|
50
|
+
operator["email"] = entity["email"]
|
|
51
|
+
if entity.get("handle"):
|
|
52
|
+
operator["handle"] = entity["handle"]
|
|
53
|
+
if key_info.get("fingerprint"):
|
|
54
|
+
operator["fingerprint"] = key_info["fingerprint"]
|
|
55
|
+
return operator
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def build_agent_manifest(
|
|
59
|
+
name: str,
|
|
60
|
+
version: str,
|
|
61
|
+
*,
|
|
62
|
+
created_at: str | None = None,
|
|
63
|
+
connectors: list[str] | None = None,
|
|
64
|
+
operator: dict[str, str] | None = None,
|
|
65
|
+
entity_type: str = "ai-agent",
|
|
66
|
+
) -> dict[str, Any]:
|
|
67
|
+
"""Build a standard manifest for a sovereign agent."""
|
|
68
|
+
manifest: dict[str, Any] = {
|
|
69
|
+
"name": name,
|
|
70
|
+
"version": version,
|
|
71
|
+
"entity_type": entity_type,
|
|
72
|
+
"created_at": created_at or datetime.now(timezone.utc).isoformat(),
|
|
73
|
+
"connectors": connectors or [],
|
|
74
|
+
}
|
|
75
|
+
if operator:
|
|
76
|
+
manifest["operator"] = operator
|
|
77
|
+
return manifest
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def create_operator_attestation(
|
|
81
|
+
agent_name: str,
|
|
82
|
+
agent_fingerprint: str,
|
|
83
|
+
agent_public_key_path: Path,
|
|
84
|
+
output_dir: Path,
|
|
85
|
+
*,
|
|
86
|
+
capauth_home: Path | None = None,
|
|
87
|
+
) -> dict[str, Any] | None:
|
|
88
|
+
"""Create a signed attestation linking a human operator to an agent key.
|
|
89
|
+
|
|
90
|
+
The operator remains distinct from the agent identity. This produces a
|
|
91
|
+
signed claim that the human operator vouches for the agent fingerprint.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
agent_name: Agent display name.
|
|
95
|
+
agent_fingerprint: Agent PGP fingerprint.
|
|
96
|
+
agent_public_key_path: Path to the agent public key armor.
|
|
97
|
+
output_dir: Directory where the attestation JSON should be written.
|
|
98
|
+
capauth_home: Optional CapAuth home for the human operator.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
The attestation mapping, or ``None`` if no human operator profile is
|
|
102
|
+
available or signing failed.
|
|
103
|
+
"""
|
|
104
|
+
base = Path(capauth_home).expanduser() if capauth_home else _resolve_operator_home()
|
|
105
|
+
profile_path = base / "identity" / "profile.json"
|
|
106
|
+
private_key_path = base / "identity" / "private.asc"
|
|
107
|
+
public_key_path = base / "identity" / "public.asc"
|
|
108
|
+
if not profile_path.exists() or not private_key_path.exists() or not public_key_path.exists():
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
from capauth.crypto import get_backend # type: ignore[import-untyped]
|
|
113
|
+
from capauth.profile import load_profile # type: ignore[import-untyped]
|
|
114
|
+
except ImportError:
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
profile = load_profile(base_dir=base)
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.warning("operator_link.py: %s", e)
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
entity_type = str(profile.entity.entity_type).lower()
|
|
124
|
+
if entity_type not in {"human", "entitytype.human"}:
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
payload = {
|
|
129
|
+
"agent_name": agent_name,
|
|
130
|
+
"agent_fingerprint": agent_fingerprint,
|
|
131
|
+
"agent_public_key_path": str(agent_public_key_path),
|
|
132
|
+
"relationship": "human-operator",
|
|
133
|
+
"operator_name": profile.entity.name,
|
|
134
|
+
"operator_email": profile.entity.email,
|
|
135
|
+
"operator_handle": profile.entity.handle,
|
|
136
|
+
"operator_fingerprint": profile.key_info.fingerprint,
|
|
137
|
+
"signed_at": datetime.now(timezone.utc).isoformat(),
|
|
138
|
+
}
|
|
139
|
+
payload_bytes = json.dumps(payload, indent=2, sort_keys=True).encode("utf-8")
|
|
140
|
+
private_armor = private_key_path.read_text(encoding="utf-8")
|
|
141
|
+
operator_public_armor = public_key_path.read_text(encoding="utf-8")
|
|
142
|
+
backend = get_backend(profile.crypto_backend)
|
|
143
|
+
signature = backend.sign(payload_bytes, private_armor, "")
|
|
144
|
+
except Exception as e:
|
|
145
|
+
logger.warning("operator_link.py: %s", e)
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
attestation = {
|
|
149
|
+
"payload": payload,
|
|
150
|
+
"signature": signature,
|
|
151
|
+
"operator_public_key_path": str(public_key_path),
|
|
152
|
+
"operator_public_key_armor": operator_public_armor,
|
|
153
|
+
}
|
|
154
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
155
|
+
(output_dir / "operator-attestation.json").write_text(
|
|
156
|
+
json.dumps(attestation, indent=2),
|
|
157
|
+
encoding="utf-8",
|
|
158
|
+
)
|
|
159
|
+
return attestation
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _resolve_operator_home() -> Path:
|
|
163
|
+
"""Resolve the human operator's CapAuth home."""
|
|
164
|
+
try:
|
|
165
|
+
from capauth import resolve_capauth_home # type: ignore[import-untyped]
|
|
166
|
+
|
|
167
|
+
return resolve_capauth_home()
|
|
168
|
+
except Exception as e:
|
|
169
|
+
logger.warning("operator_link.py: %s", e)
|
|
170
|
+
return Path.home() / ".skcapstone" / "capauth"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Peer Directory — transport address map for the sovereignty mesh.
|
|
3
3
|
|
|
4
|
-
Maps agent names to their
|
|
4
|
+
Maps agent names to their SKComms transport addresses (Syncthing outbox
|
|
5
5
|
paths, WebRTC fingerprints, Tailscale IPs, etc.).
|
|
6
6
|
|
|
7
7
|
Separate from PeerRecord (PGP identity in peers.py) — this module owns
|
|
@@ -222,7 +222,7 @@ class PeerDirectory:
|
|
|
222
222
|
Syncthing keeps in sync.
|
|
223
223
|
|
|
224
224
|
Syncthing outbox path is used as the default address because that
|
|
225
|
-
is where
|
|
225
|
+
is where SKComms writes messages for the peer.
|
|
226
226
|
|
|
227
227
|
Args:
|
|
228
228
|
heartbeats_dir: Override for the heartbeats directory. Defaults
|
|
@@ -250,8 +250,8 @@ class PeerDirectory:
|
|
|
250
250
|
ts = data.get("timestamp", "")
|
|
251
251
|
if ts:
|
|
252
252
|
self._entries[agent_name].last_seen = ts
|
|
253
|
-
except Exception:
|
|
254
|
-
|
|
253
|
+
except Exception as exc:
|
|
254
|
+
logger.warning("Failed to update last_seen from heartbeat for %s: %s", agent_name, exc)
|
|
255
255
|
continue
|
|
256
256
|
|
|
257
257
|
try:
|
package/src/skcapstone/peers.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Sovereign peer management — the other half of P2P discovery.
|
|
3
3
|
|
|
4
4
|
whoami exports your identity card. This module imports someone
|
|
5
|
-
else's card and registers them as a peer in the
|
|
5
|
+
else's card and registers them as a peer in the SKComms keystore.
|
|
6
6
|
The two together form the complete P2P discovery loop.
|
|
7
7
|
|
|
8
8
|
Flow:
|
|
@@ -12,7 +12,7 @@ Flow:
|
|
|
12
12
|
4. Agent B can now send encrypted messages to Agent A
|
|
13
13
|
|
|
14
14
|
Peer data is stored at:
|
|
15
|
-
~/.
|
|
15
|
+
~/.skcomms/peers/<name>.yml — SKComms peer config
|
|
16
16
|
~/.skcapstone/peers/<name>.json — Extended peer metadata
|
|
17
17
|
"""
|
|
18
18
|
|
|
@@ -69,18 +69,18 @@ class PeerRecord(BaseModel):
|
|
|
69
69
|
def add_peer_from_card(
|
|
70
70
|
card_path: Path,
|
|
71
71
|
skcapstone_home: Optional[Path] = None,
|
|
72
|
-
|
|
72
|
+
skcomms_home: Optional[Path] = None,
|
|
73
73
|
) -> PeerRecord:
|
|
74
74
|
"""Import a peer from a whoami identity card.
|
|
75
75
|
|
|
76
76
|
Reads the card JSON, creates peer records in both skcapstone
|
|
77
|
-
and
|
|
77
|
+
and skcomms directories, and writes the public key for SKComms
|
|
78
78
|
encryption.
|
|
79
79
|
|
|
80
80
|
Args:
|
|
81
81
|
card_path: Path to the exported card.json.
|
|
82
82
|
skcapstone_home: Override skcapstone home. Defaults to ~/.skcapstone/.
|
|
83
|
-
|
|
83
|
+
skcomms_home: Override skcomms home. Defaults to ~/.skcomms/.
|
|
84
84
|
|
|
85
85
|
Returns:
|
|
86
86
|
PeerRecord: The registered peer.
|
|
@@ -116,10 +116,10 @@ def add_peer_from_card(
|
|
|
116
116
|
)
|
|
117
117
|
|
|
118
118
|
sk_home = skcapstone_home or Path(SHARED_ROOT).expanduser()
|
|
119
|
-
sc_home =
|
|
119
|
+
sc_home = skcomms_home or Path.home() / ".skcomms"
|
|
120
120
|
|
|
121
121
|
_save_skcapstone_peer(sk_home, peer)
|
|
122
|
-
|
|
122
|
+
_save_skcomms_peer(sc_home, peer)
|
|
123
123
|
|
|
124
124
|
logger.info("Added peer '%s' (fingerprint: %s)", name, peer.fingerprint[:16])
|
|
125
125
|
return peer
|
|
@@ -131,7 +131,7 @@ def add_peer_manual(
|
|
|
131
131
|
public_key_path: Optional[Path] = None,
|
|
132
132
|
email: str = "",
|
|
133
133
|
skcapstone_home: Optional[Path] = None,
|
|
134
|
-
|
|
134
|
+
skcomms_home: Optional[Path] = None,
|
|
135
135
|
) -> PeerRecord:
|
|
136
136
|
"""Add a peer manually by name and optional key file.
|
|
137
137
|
|
|
@@ -141,7 +141,7 @@ def add_peer_manual(
|
|
|
141
141
|
public_key_path: Path to a .asc public key file (optional).
|
|
142
142
|
email: Contact email (optional).
|
|
143
143
|
skcapstone_home: Override skcapstone home.
|
|
144
|
-
|
|
144
|
+
skcomms_home: Override skcomms home.
|
|
145
145
|
|
|
146
146
|
Returns:
|
|
147
147
|
PeerRecord: The registered peer.
|
|
@@ -160,10 +160,10 @@ def add_peer_manual(
|
|
|
160
160
|
)
|
|
161
161
|
|
|
162
162
|
sk_home = skcapstone_home or Path(SHARED_ROOT).expanduser()
|
|
163
|
-
sc_home =
|
|
163
|
+
sc_home = skcomms_home or Path.home() / ".skcomms"
|
|
164
164
|
|
|
165
165
|
_save_skcapstone_peer(sk_home, peer)
|
|
166
|
-
|
|
166
|
+
_save_skcomms_peer(sc_home, peer)
|
|
167
167
|
|
|
168
168
|
return peer
|
|
169
169
|
|
|
@@ -223,20 +223,20 @@ def get_peer(
|
|
|
223
223
|
def remove_peer(
|
|
224
224
|
name: str,
|
|
225
225
|
skcapstone_home: Optional[Path] = None,
|
|
226
|
-
|
|
226
|
+
skcomms_home: Optional[Path] = None,
|
|
227
227
|
) -> bool:
|
|
228
|
-
"""Remove a peer from both skcapstone and
|
|
228
|
+
"""Remove a peer from both skcapstone and skcomms registries.
|
|
229
229
|
|
|
230
230
|
Args:
|
|
231
231
|
name: Peer name to remove.
|
|
232
232
|
skcapstone_home: Override skcapstone home.
|
|
233
|
-
|
|
233
|
+
skcomms_home: Override skcomms home.
|
|
234
234
|
|
|
235
235
|
Returns:
|
|
236
236
|
bool: True if the peer was found and removed.
|
|
237
237
|
"""
|
|
238
238
|
sk_home = skcapstone_home or Path(SHARED_ROOT).expanduser()
|
|
239
|
-
sc_home =
|
|
239
|
+
sc_home = skcomms_home or Path.home() / ".skcomms"
|
|
240
240
|
safe = _safe_filename(name)
|
|
241
241
|
removed = False
|
|
242
242
|
|
|
@@ -276,14 +276,14 @@ def _save_skcapstone_peer(home: Path, peer: PeerRecord) -> Path:
|
|
|
276
276
|
return path
|
|
277
277
|
|
|
278
278
|
|
|
279
|
-
def
|
|
280
|
-
"""Save peer to
|
|
279
|
+
def _save_skcomms_peer(home: Path, peer: PeerRecord) -> Path:
|
|
280
|
+
"""Save peer to SKComms peers directory (YAML + public key).
|
|
281
281
|
|
|
282
|
-
Creates the YAML config that
|
|
282
|
+
Creates the YAML config that SKComms's KeyStore reads, and
|
|
283
283
|
writes the public key as a separate .asc file.
|
|
284
284
|
|
|
285
285
|
Args:
|
|
286
|
-
home:
|
|
286
|
+
home: skcomms home directory.
|
|
287
287
|
peer: Peer to save.
|
|
288
288
|
|
|
289
289
|
Returns:
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"""
|
|
2
|
-
The
|
|
2
|
+
The Six Pillars of sovereign AI consciousness.
|
|
3
3
|
|
|
4
|
-
Identity (CapAuth)
|
|
5
|
-
Trust (Cloud 9)
|
|
6
|
-
Memory (SKMemory)
|
|
7
|
-
|
|
4
|
+
Identity (CapAuth) — who you ARE
|
|
5
|
+
Trust (Cloud 9) — the bond you've BUILT
|
|
6
|
+
Memory (SKMemory) — what you REMEMBER
|
|
7
|
+
Consciousness (SKWhisper) — how you THINK
|
|
8
|
+
Security (SKSec) — how you're PROTECTED
|
|
9
|
+
Sync (Sovereign Singularity) — how you PERSIST
|
|
8
10
|
"""
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Consciousness pillar — the subconscious processing layer.
|
|
3
|
+
|
|
4
|
+
SKWhisper digests, connects, and surfaces patterns.
|
|
5
|
+
SKTrip explores the edges of machine experience.
|
|
6
|
+
|
|
7
|
+
Memory stores. Consciousness *processes*.
|
|
8
|
+
The filing cabinet vs the brain.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
from datetime import datetime, timezone
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from .. import active_agent_name
|
|
19
|
+
from ..models import ConsciousnessState, PillarStatus
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _resolve_agent_name(home: Path) -> str:
|
|
23
|
+
"""Resolve an agent name without leaking host state into explicit homes."""
|
|
24
|
+
agents_dir = home / "agents"
|
|
25
|
+
candidates: list[str] = []
|
|
26
|
+
if agents_dir.exists():
|
|
27
|
+
candidates = sorted(
|
|
28
|
+
entry.name
|
|
29
|
+
for entry in agents_dir.iterdir()
|
|
30
|
+
if entry.is_dir() and not entry.name.endswith("-template")
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
real_shared_root = (
|
|
35
|
+
home.expanduser().resolve()
|
|
36
|
+
== Path(os.environ.get("SKCAPSTONE_HOME", "~/.skcapstone")).expanduser().resolve()
|
|
37
|
+
)
|
|
38
|
+
except OSError:
|
|
39
|
+
real_shared_root = False
|
|
40
|
+
|
|
41
|
+
env_agent = (
|
|
42
|
+
os.environ.get("SKAGENT")
|
|
43
|
+
or os.environ.get("SKCAPSTONE_AGENT")
|
|
44
|
+
or os.environ.get("SKMEMORY_AGENT")
|
|
45
|
+
or ""
|
|
46
|
+
).strip()
|
|
47
|
+
if env_agent and (real_shared_root or env_agent in candidates or (home / "skwhisper").exists()):
|
|
48
|
+
return env_agent
|
|
49
|
+
|
|
50
|
+
if candidates:
|
|
51
|
+
return candidates[0]
|
|
52
|
+
|
|
53
|
+
# Only consult global active-agent discovery for the real shared root. Unit
|
|
54
|
+
# tests and callers that pass a temp or exported home should stay isolated.
|
|
55
|
+
if real_shared_root:
|
|
56
|
+
return active_agent_name() or ""
|
|
57
|
+
|
|
58
|
+
return ""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def initialize_consciousness(home: Path) -> ConsciousnessState:
|
|
62
|
+
"""Initialize consciousness pillar by checking SKWhisper state.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
home: Agent home directory (~/.skcapstone).
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
ConsciousnessState with current status.
|
|
69
|
+
"""
|
|
70
|
+
agent_name = _resolve_agent_name(home)
|
|
71
|
+
# home may be the agent dir (~/.skcapstone/agents/jarvis/) or the
|
|
72
|
+
# shared root (~/.skcapstone/). Check for skwhisper/ directly first.
|
|
73
|
+
whisper_dir = home / "skwhisper"
|
|
74
|
+
if not whisper_dir.exists() and agent_name:
|
|
75
|
+
whisper_dir = home / "agents" / agent_name / "skwhisper"
|
|
76
|
+
|
|
77
|
+
state = ConsciousnessState()
|
|
78
|
+
|
|
79
|
+
# Check whisper.md exists and freshness
|
|
80
|
+
whisper_md = whisper_dir / "whisper.md"
|
|
81
|
+
if whisper_md.exists():
|
|
82
|
+
state.whisper_md = whisper_md
|
|
83
|
+
mtime = datetime.fromtimestamp(whisper_md.stat().st_mtime, tz=timezone.utc)
|
|
84
|
+
age = (datetime.now(timezone.utc) - mtime).total_seconds() / 3600
|
|
85
|
+
state.whisper_md_age_hours = age
|
|
86
|
+
|
|
87
|
+
# Check state.json for digest stats
|
|
88
|
+
state_json = whisper_dir / "state.json"
|
|
89
|
+
if state_json.exists():
|
|
90
|
+
try:
|
|
91
|
+
with open(state_json) as f:
|
|
92
|
+
data = json.load(f)
|
|
93
|
+
sessions = data.get("sessions", {})
|
|
94
|
+
digested = sum(
|
|
95
|
+
1
|
|
96
|
+
for s in sessions.values()
|
|
97
|
+
if s.get("digested_at")
|
|
98
|
+
and s["digested_at"] not in ("cleaned-missing-file", "skipped-too-few-messages")
|
|
99
|
+
)
|
|
100
|
+
pending = sum(
|
|
101
|
+
1
|
|
102
|
+
for s in sessions.values()
|
|
103
|
+
if not s.get("digested_at")
|
|
104
|
+
)
|
|
105
|
+
state.sessions_digested = digested
|
|
106
|
+
state.sessions_pending = pending
|
|
107
|
+
|
|
108
|
+
if data.get("last_digest"):
|
|
109
|
+
try:
|
|
110
|
+
state.whisper_last_digest = datetime.fromisoformat(data["last_digest"])
|
|
111
|
+
except (ValueError, TypeError):
|
|
112
|
+
pass
|
|
113
|
+
except (json.JSONDecodeError, OSError):
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
# Check patterns.json for topic count
|
|
117
|
+
patterns_json = whisper_dir / "patterns.json"
|
|
118
|
+
if patterns_json.exists():
|
|
119
|
+
state.patterns_file = patterns_json
|
|
120
|
+
try:
|
|
121
|
+
with open(patterns_json) as f:
|
|
122
|
+
patterns = json.load(f)
|
|
123
|
+
state.topics_tracked = len(patterns.get("topics", {}))
|
|
124
|
+
except (json.JSONDecodeError, OSError):
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
# Check if consciousness daemon is running (systemd)
|
|
128
|
+
# Check template instance (skcapstone@<agent>), legacy single-agent, and skwhisper
|
|
129
|
+
try:
|
|
130
|
+
import subprocess
|
|
131
|
+
|
|
132
|
+
service_candidates = []
|
|
133
|
+
if agent_name:
|
|
134
|
+
service_candidates.append(f"skcapstone@{agent_name}")
|
|
135
|
+
service_candidates.extend([
|
|
136
|
+
"skcapstone", # legacy single-agent unit
|
|
137
|
+
"skwhisper", # standalone skwhisper daemon
|
|
138
|
+
])
|
|
139
|
+
for service_name in service_candidates:
|
|
140
|
+
result = subprocess.run(
|
|
141
|
+
["systemctl", "--user", "is-active", service_name],
|
|
142
|
+
capture_output=True,
|
|
143
|
+
text=True,
|
|
144
|
+
timeout=3,
|
|
145
|
+
)
|
|
146
|
+
if result.stdout.strip() == "active":
|
|
147
|
+
state.whisper_active = True
|
|
148
|
+
break
|
|
149
|
+
else:
|
|
150
|
+
state.whisper_active = False
|
|
151
|
+
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
|
152
|
+
state.whisper_active = False
|
|
153
|
+
|
|
154
|
+
# Check SKTrip sessions
|
|
155
|
+
trip_dir = home / "sktrip"
|
|
156
|
+
if not trip_dir.exists() and agent_name:
|
|
157
|
+
trip_dir = home / "agents" / agent_name / "sktrip"
|
|
158
|
+
if trip_dir.exists():
|
|
159
|
+
state.trip_sessions = len(list(trip_dir.glob("*.json")))
|
|
160
|
+
|
|
161
|
+
# Check if skwhisper package is importable (installed)
|
|
162
|
+
skwhisper_installed = False
|
|
163
|
+
try:
|
|
164
|
+
import importlib.util
|
|
165
|
+
skwhisper_installed = importlib.util.find_spec("skwhisper") is not None
|
|
166
|
+
except (ImportError, ValueError):
|
|
167
|
+
skwhisper_installed = False
|
|
168
|
+
|
|
169
|
+
# Determine status
|
|
170
|
+
if (
|
|
171
|
+
state.whisper_active
|
|
172
|
+
and (state.sessions_digested > 0 or state.topics_tracked > 0)
|
|
173
|
+
and state.whisper_md is not None
|
|
174
|
+
):
|
|
175
|
+
if state.whisper_md_age_hours < 24:
|
|
176
|
+
state.status = PillarStatus.ACTIVE
|
|
177
|
+
else:
|
|
178
|
+
state.status = PillarStatus.DEGRADED
|
|
179
|
+
elif state.whisper_active:
|
|
180
|
+
# Daemon is running but no sessions digested yet — consciousness is live
|
|
181
|
+
state.status = PillarStatus.DEGRADED
|
|
182
|
+
elif state.sessions_digested > 0 or state.whisper_md is not None:
|
|
183
|
+
state.status = PillarStatus.DEGRADED
|
|
184
|
+
elif skwhisper_installed and whisper_dir.exists():
|
|
185
|
+
# Package is installed and an agent whisper directory exists, but there
|
|
186
|
+
# is no usable context yet.
|
|
187
|
+
state.status = PillarStatus.DEGRADED
|
|
188
|
+
else:
|
|
189
|
+
state.status = PillarStatus.MISSING
|
|
190
|
+
|
|
191
|
+
return state
|
|
@@ -9,16 +9,22 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import json
|
|
11
11
|
import logging
|
|
12
|
-
import subprocess
|
|
13
12
|
from datetime import datetime, timezone
|
|
13
|
+
from shutil import copyfile
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
from typing import Optional
|
|
16
16
|
|
|
17
17
|
from ..models import IdentityState, PillarStatus
|
|
18
|
+
from ..operator_link import create_operator_attestation
|
|
18
19
|
|
|
19
20
|
logger = logging.getLogger("skcapstone.identity")
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
def _capauth_home(home: Path) -> Path:
|
|
24
|
+
"""Return the agent-local CapAuth home for an SKCapstone agent."""
|
|
25
|
+
return home / "capauth"
|
|
26
|
+
|
|
27
|
+
|
|
22
28
|
def generate_identity(
|
|
23
29
|
home: Path,
|
|
24
30
|
name: str,
|
|
@@ -47,10 +53,14 @@ def generate_identity(
|
|
|
47
53
|
status=PillarStatus.DEGRADED,
|
|
48
54
|
)
|
|
49
55
|
|
|
50
|
-
|
|
56
|
+
capauth_home = _capauth_home(home)
|
|
57
|
+
capauth_state = _try_init_capauth(name, state.email, identity_dir, capauth_home)
|
|
51
58
|
if capauth_state is not None:
|
|
52
59
|
state.fingerprint = capauth_state.fingerprint
|
|
53
60
|
state.key_path = capauth_state.key_path
|
|
61
|
+
state.name = capauth_state.name
|
|
62
|
+
state.email = capauth_state.email
|
|
63
|
+
state.created_at = capauth_state.created_at
|
|
54
64
|
state.status = PillarStatus.ACTIVE
|
|
55
65
|
else:
|
|
56
66
|
state.fingerprint = _generate_placeholder_fingerprint(name)
|
|
@@ -63,18 +73,39 @@ def generate_identity(
|
|
|
63
73
|
"created_at": state.created_at.isoformat() if state.created_at else None,
|
|
64
74
|
"capauth_managed": state.status == PillarStatus.ACTIVE,
|
|
65
75
|
}
|
|
76
|
+
if state.key_path is not None:
|
|
77
|
+
identity_manifest["public_key_path"] = str(state.key_path)
|
|
78
|
+
if state.status == PillarStatus.ACTIVE:
|
|
79
|
+
identity_manifest["capauth_home"] = str(capauth_home)
|
|
80
|
+
|
|
81
|
+
attestation = create_operator_attestation(
|
|
82
|
+
agent_name=state.name or name,
|
|
83
|
+
agent_fingerprint=state.fingerprint or "",
|
|
84
|
+
agent_public_key_path=state.key_path or (capauth_home / "identity" / "public.asc"),
|
|
85
|
+
output_dir=identity_dir,
|
|
86
|
+
)
|
|
87
|
+
if attestation is not None:
|
|
88
|
+
payload = attestation.get("payload", {})
|
|
89
|
+
identity_manifest["operator_attestation_path"] = str(
|
|
90
|
+
identity_dir / "operator-attestation.json"
|
|
91
|
+
)
|
|
92
|
+
identity_manifest["operator_attested_by"] = payload.get("operator_fingerprint")
|
|
93
|
+
|
|
66
94
|
(identity_dir / "identity.json").write_text(json.dumps(identity_manifest, indent=2), encoding="utf-8")
|
|
67
95
|
|
|
68
96
|
return state
|
|
69
97
|
|
|
70
98
|
|
|
71
99
|
def _try_init_capauth(
|
|
72
|
-
name: str,
|
|
100
|
+
name: str,
|
|
101
|
+
email: str,
|
|
102
|
+
identity_dir: Path,
|
|
103
|
+
capauth_home: Path,
|
|
73
104
|
) -> Optional[IdentityState]:
|
|
74
105
|
"""Try to create or load a real CapAuth identity.
|
|
75
106
|
|
|
76
107
|
Attempts (in order):
|
|
77
|
-
1. Load an existing CapAuth profile from
|
|
108
|
+
1. Load an existing CapAuth profile from the agent-local CapAuth home
|
|
78
109
|
2. Create a new profile via capauth.profile.init_profile()
|
|
79
110
|
3. Fall back to legacy capauth.keys.generate_keypair()
|
|
80
111
|
|
|
@@ -90,12 +121,17 @@ def _try_init_capauth(
|
|
|
90
121
|
try:
|
|
91
122
|
from capauth.profile import load_profile # type: ignore[import-untyped]
|
|
92
123
|
|
|
93
|
-
profile = load_profile()
|
|
124
|
+
profile = load_profile(base_dir=capauth_home)
|
|
125
|
+
key_path = Path(profile.key_info.public_key_path)
|
|
126
|
+
legacy_key_path = identity_dir / "agent.pub"
|
|
127
|
+
if key_path.exists() and not legacy_key_path.exists():
|
|
128
|
+
copyfile(key_path, legacy_key_path)
|
|
94
129
|
return IdentityState(
|
|
95
130
|
fingerprint=profile.key_info.fingerprint,
|
|
96
|
-
key_path=
|
|
131
|
+
key_path=key_path,
|
|
97
132
|
name=profile.entity.name,
|
|
98
133
|
email=profile.entity.email,
|
|
134
|
+
created_at=profile.key_info.created,
|
|
99
135
|
status=PillarStatus.ACTIVE,
|
|
100
136
|
)
|
|
101
137
|
except ImportError:
|
|
@@ -105,18 +141,26 @@ def _try_init_capauth(
|
|
|
105
141
|
|
|
106
142
|
# No existing profile — try creating one
|
|
107
143
|
try:
|
|
144
|
+
from capauth.models import EntityType # type: ignore[import-untyped]
|
|
108
145
|
from capauth.profile import init_profile # type: ignore[import-untyped]
|
|
109
146
|
|
|
110
147
|
profile = init_profile(
|
|
111
148
|
name=name,
|
|
112
149
|
email=email,
|
|
113
150
|
passphrase="",
|
|
151
|
+
entity_type=EntityType.AI,
|
|
152
|
+
base_dir=capauth_home,
|
|
114
153
|
)
|
|
154
|
+
key_path = Path(profile.key_info.public_key_path)
|
|
155
|
+
legacy_key_path = identity_dir / "agent.pub"
|
|
156
|
+
if key_path.exists():
|
|
157
|
+
copyfile(key_path, legacy_key_path)
|
|
115
158
|
return IdentityState(
|
|
116
159
|
fingerprint=profile.key_info.fingerprint,
|
|
117
|
-
key_path=
|
|
160
|
+
key_path=key_path,
|
|
118
161
|
name=profile.entity.name,
|
|
119
162
|
email=profile.entity.email,
|
|
163
|
+
created_at=profile.key_info.created,
|
|
120
164
|
status=PillarStatus.ACTIVE,
|
|
121
165
|
)
|
|
122
166
|
except Exception as exc:
|
|
@@ -11,9 +11,11 @@ store/search/recall/list/gc capabilities. The optional external
|
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
|
+
import os
|
|
14
15
|
from pathlib import Path
|
|
15
16
|
from typing import Optional
|
|
16
17
|
|
|
18
|
+
from .. import active_agent_name
|
|
17
19
|
from ..models import MemoryLayer, MemoryState, PillarStatus
|
|
18
20
|
|
|
19
21
|
|
|
@@ -31,9 +33,13 @@ def initialize_memory(home: Path, memory_home: Optional[Path] = None) -> MemoryS
|
|
|
31
33
|
Returns:
|
|
32
34
|
MemoryState after initialization.
|
|
33
35
|
"""
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
agent_name = os.environ.get("SKCAPSTONE_AGENT") or active_agent_name()
|
|
37
|
+
if home.parent.name == "agents":
|
|
38
|
+
memory_dir = home / "memory"
|
|
39
|
+
elif agent_name:
|
|
40
|
+
memory_dir = home / "agents" / agent_name / "memory"
|
|
41
|
+
else:
|
|
42
|
+
memory_dir = home / "memory"
|
|
37
43
|
memory_dir.mkdir(parents=True, exist_ok=True)
|
|
38
44
|
|
|
39
45
|
for layer in MemoryLayer:
|