@smilintux/skcapstone 0.6.2 → 0.6.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.
Files changed (70) hide show
  1. package/.github/workflows/publish.yml +1 -1
  2. package/CLAUDE.md +17 -0
  3. package/docs/CUSTOM_AGENT.md +40 -28
  4. package/docs/SOUL_SWAPPER.md +5 -5
  5. package/docs/hammertime-audit.md +402 -0
  6. package/openclaw-plugin/src/index.ts +2 -1
  7. package/package.json +1 -1
  8. package/pyproject.toml +1 -1
  9. package/scripts/install.sh +126 -1
  10. package/scripts/model-fallback-monitor.sh +4 -2
  11. package/scripts/refresh-anthropic-token.sh +9 -3
  12. package/scripts/release.sh +98 -0
  13. package/scripts/session-to-memory.py +1 -1
  14. package/scripts/sk-agent-picker.sh +237 -0
  15. package/scripts/telegram-catchup-all.sh +2 -1
  16. package/scripts/watch-anthropic-token.sh +12 -17
  17. package/src/skcapstone/__init__.py +34 -2
  18. package/src/skcapstone/cli/__init__.py +3 -1
  19. package/src/skcapstone/cli/_common.py +1 -0
  20. package/src/skcapstone/cli/context_cmd.py +16 -4
  21. package/src/skcapstone/cli/daemon.py +2 -1
  22. package/src/skcapstone/cli/joule_cmd.py +7 -3
  23. package/src/skcapstone/cli/memory.py +4 -2
  24. package/src/skcapstone/cli/register_cmd.py +19 -3
  25. package/src/skcapstone/cli/session.py +25 -0
  26. package/src/skcapstone/cli/setup.py +96 -30
  27. package/src/skcapstone/cli/soul.py +3 -3
  28. package/src/skcapstone/context_loader.py +9 -0
  29. package/src/skcapstone/coordination.py +9 -2
  30. package/src/skcapstone/daemon.py +22 -12
  31. package/src/skcapstone/defaults/claude/CLAUDE.md +67 -0
  32. package/src/skcapstone/defaults/claude/settings.json +74 -0
  33. package/src/skcapstone/defaults/lumina/config/skgraph.yaml +55 -10
  34. package/src/skcapstone/defaults/lumina/config/skmemory.yaml +79 -13
  35. package/src/skcapstone/defaults/lumina/config/skvector.yaml +60 -9
  36. package/src/skcapstone/defaults/unhinged.json +13 -0
  37. package/src/skcapstone/discovery.py +5 -5
  38. package/src/skcapstone/doctor.py +4 -2
  39. package/src/skcapstone/dreaming.py +3 -1
  40. package/src/skcapstone/fuse_mount.py +3 -1
  41. package/src/skcapstone/housekeeping.py +3 -3
  42. package/src/skcapstone/install_wizard.py +131 -0
  43. package/src/skcapstone/mcp_launcher.py +14 -1
  44. package/src/skcapstone/mcp_server.py +6 -21
  45. package/src/skcapstone/mcp_tools/notification_tools.py +3 -1
  46. package/src/skcapstone/memory_engine.py +10 -3
  47. package/src/skcapstone/migrate_multi_agent.py +7 -6
  48. package/src/skcapstone/notifications.py +6 -2
  49. package/src/skcapstone/onboard.py +19 -8
  50. package/src/skcapstone/operator_link.py +164 -0
  51. package/src/skcapstone/pillars/consciousness.py +2 -1
  52. package/src/skcapstone/pillars/identity.py +51 -7
  53. package/src/skcapstone/pillars/memory.py +9 -3
  54. package/src/skcapstone/runtime.py +13 -3
  55. package/src/skcapstone/service_health.py +23 -10
  56. package/src/skcapstone/session_briefing.py +108 -0
  57. package/src/skcapstone/trust_graph.py +40 -5
  58. package/src/skcapstone/unified_search.py +11 -2
  59. package/systemd/skcapstone.service +4 -6
  60. package/systemd/skcapstone@.service +7 -8
  61. package/systemd/skcomm-heartbeat.service +5 -2
  62. package/tests/conftest.py +21 -0
  63. package/tests/test_agent_home_scaffold.py +34 -0
  64. package/tests/test_backup.py +2 -1
  65. package/tests/test_mcp_server.py +78 -33
  66. package/tests/test_multi_agent.py +31 -29
  67. package/tests/test_operator_link.py +78 -0
  68. package/tests/test_runtime.py +21 -0
  69. package/tests/test_session_briefing.py +130 -0
  70. package/tests/test_trust_graph.py +18 -0
@@ -0,0 +1,78 @@
1
+ """Tests for human-operator manifest linking."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ from skcapstone.operator_link import build_agent_manifest, discover_human_operator
9
+
10
+
11
+ def test_discover_human_operator_reads_capauth_profile(tmp_path: Path) -> None:
12
+ """A CapAuth human profile is converted into operator metadata."""
13
+ capauth_home = tmp_path / ".capauth"
14
+ profile = capauth_home / "identity" / "profile.json"
15
+ profile.parent.mkdir(parents=True)
16
+ profile.write_text(
17
+ json.dumps(
18
+ {
19
+ "entity": {
20
+ "name": "Casey",
21
+ "entity_type": "human",
22
+ "email": "casey@example.com",
23
+ "handle": "casey@example.com",
24
+ },
25
+ "key_info": {
26
+ "fingerprint": "ABCDEF1234567890",
27
+ },
28
+ }
29
+ ),
30
+ encoding="utf-8",
31
+ )
32
+
33
+ operator = discover_human_operator(capauth_home)
34
+
35
+ assert operator == {
36
+ "name": "Casey",
37
+ "relationship": "human-operator",
38
+ "entity_type": "human",
39
+ "source": "capauth",
40
+ "email": "casey@example.com",
41
+ "handle": "casey@example.com",
42
+ "fingerprint": "ABCDEF1234567890",
43
+ }
44
+
45
+
46
+ def test_discover_human_operator_ignores_non_human_profile(tmp_path: Path) -> None:
47
+ """AI CapAuth profiles are not treated as human operators."""
48
+ capauth_home = tmp_path / ".capauth"
49
+ profile = capauth_home / "identity" / "profile.json"
50
+ profile.parent.mkdir(parents=True)
51
+ profile.write_text(
52
+ json.dumps(
53
+ {
54
+ "entity": {
55
+ "name": "Jarvis",
56
+ "entity_type": "ai",
57
+ }
58
+ }
59
+ ),
60
+ encoding="utf-8",
61
+ )
62
+
63
+ assert discover_human_operator(capauth_home) is None
64
+
65
+
66
+ def test_build_agent_manifest_includes_operator_when_available() -> None:
67
+ """Operator metadata is persisted directly in the manifest."""
68
+ manifest = build_agent_manifest(
69
+ "jarvis",
70
+ "0.6.0",
71
+ created_at="2026-01-01T00:00:00+00:00",
72
+ operator={"name": "Casey", "fingerprint": "FP123", "relationship": "human-operator"},
73
+ )
74
+
75
+ assert manifest["name"] == "jarvis"
76
+ assert manifest["entity_type"] == "ai-agent"
77
+ assert manifest["operator"]["name"] == "Casey"
78
+ assert manifest["operator"]["fingerprint"] == "FP123"
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import json
5
6
  from pathlib import Path
6
7
 
7
8
  from skcapstone.runtime import AgentRuntime
@@ -28,6 +29,26 @@ class TestAgentRuntime:
28
29
  assert manifest.name == "test-agent"
29
30
  assert manifest.last_awakened is not None
30
31
 
32
+ def test_awaken_prefers_identity_name_over_shared_config(self, tmp_path: Path):
33
+ """Agent-local identity should beat a shared-root fallback config name."""
34
+ shared_root = tmp_path / ".skcapstone"
35
+ agent_home = shared_root / "agents" / "aster"
36
+ (agent_home / "identity").mkdir(parents=True)
37
+ (agent_home / "config").mkdir(parents=True)
38
+
39
+ (shared_root / "config").mkdir(parents=True)
40
+ (shared_root / "config" / "config.yaml").write_text("agent_name: Jarvis\n")
41
+ (agent_home / "manifest.json").write_text(json.dumps({"name": "aster"}))
42
+ (agent_home / "identity" / "identity.json").write_text(json.dumps({
43
+ "name": "Aster",
44
+ "fingerprint": "A" * 40,
45
+ "capauth_managed": True,
46
+ }))
47
+
48
+ runtime = AgentRuntime(home=agent_home)
49
+ manifest = runtime.awaken()
50
+ assert manifest.name == "Aster"
51
+
31
52
  def test_register_connector(self, initialized_agent_home: Path):
32
53
  """Registering a connector should persist it."""
33
54
  runtime = AgentRuntime(home=initialized_agent_home)
@@ -0,0 +1,130 @@
1
+ """Tests for the native SKCapstone session briefing."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import subprocess
7
+ from pathlib import Path
8
+
9
+ from click.testing import CliRunner
10
+
11
+ from skcapstone.cli import main
12
+ from skcapstone.session_briefing import (
13
+ build_session_briefing,
14
+ format_session_briefing_text,
15
+ load_hammertime_briefing,
16
+ )
17
+
18
+
19
+ def test_load_hammertime_briefing_respects_disable_env(monkeypatch, tmp_path: Path) -> None:
20
+ """It returns None when HammerTime briefing is explicitly disabled."""
21
+ monkeypatch.setenv("SK_INCLUDE_HAMMERTIME_BRIEFING", "0")
22
+ assert load_hammertime_briefing(root=tmp_path) is None
23
+
24
+
25
+ def test_load_hammertime_briefing_parses_json(monkeypatch, tmp_path: Path) -> None:
26
+ """It parses JSON output from the HammerTime briefing script."""
27
+ script = tmp_path / "scripts" / "case-briefing.py"
28
+ script.parent.mkdir(parents=True)
29
+ script.write_text("#!/usr/bin/env python3\n", encoding="utf-8")
30
+
31
+ payload = {"summary": {"queue_size": 2}, "alert_count": 1}
32
+
33
+ def fake_run(*args, **kwargs): # noqa: ANN002, ANN003
34
+ return subprocess.CompletedProcess(
35
+ args=args[0],
36
+ returncode=0,
37
+ stdout=json.dumps(payload),
38
+ stderr="",
39
+ )
40
+
41
+ monkeypatch.delenv("SK_INCLUDE_HAMMERTIME_BRIEFING", raising=False)
42
+ monkeypatch.setattr("skcapstone.session_briefing.subprocess.run", fake_run)
43
+
44
+ assert load_hammertime_briefing(root=tmp_path) == payload
45
+
46
+
47
+ def test_build_session_briefing_includes_skcapstone_and_hammertime(monkeypatch, tmp_path: Path) -> None:
48
+ """It builds a combined payload for startup consumers."""
49
+ ctx = {"agent": {"name": "Aster"}, "memories": []}
50
+ briefing = {"summary": {"queue_size": 1}, "alert_count": 0}
51
+
52
+ monkeypatch.setattr("skcapstone.session_briefing.gather_context", lambda home, memory_limit=10: ctx)
53
+ monkeypatch.setattr(
54
+ "skcapstone.session_briefing.load_hammertime_briefing",
55
+ lambda python_bin=None: briefing,
56
+ )
57
+
58
+ payload = build_session_briefing(tmp_path, memory_limit=3)
59
+
60
+ assert payload["agent_home"] == str(tmp_path)
61
+ assert payload["skcapstone_context"] == ctx
62
+ assert payload["hammertime_briefing"] == briefing
63
+ assert "generated_at" in payload
64
+
65
+
66
+ def test_format_session_briefing_text_contains_hammertime_section() -> None:
67
+ """It renders a readable summary including the HammerTime section."""
68
+ payload = {
69
+ "generated_at": "2026-04-09T00:00:00+00:00",
70
+ "agent_home": "/tmp/aster",
71
+ "skcapstone_context": {
72
+ "agent": {"name": "Aster", "is_conscious": True, "fingerprint": "abc123"},
73
+ "pillars": {},
74
+ "board": {"total": 0},
75
+ "memories": [],
76
+ "soul": {"active": None},
77
+ "mcp": {"available": False},
78
+ "gathered_at": "2026-04-09T00:00:00+00:00",
79
+ },
80
+ "hammertime_briefing": {
81
+ "alert_count": 1,
82
+ "summary": {"queue_size": 2},
83
+ "top_priority": {
84
+ "incident_id": "INC-001",
85
+ "problem_slug": "example-problem",
86
+ "action": "File claim of exemption",
87
+ "status": "in-progress",
88
+ },
89
+ "focus_items": [
90
+ {
91
+ "incident_id": "INC-001",
92
+ "action": "Review preferred filing",
93
+ "status": "in-progress",
94
+ }
95
+ ],
96
+ },
97
+ }
98
+
99
+ output = format_session_briefing_text(payload)
100
+
101
+ assert "# SKCapstone Session Briefing" in output
102
+ assert "## hammertime briefing" in output
103
+ assert "INC-001" in output
104
+ assert "File claim of exemption" in output
105
+
106
+
107
+ def test_session_briefing_cli_json(monkeypatch, tmp_path: Path) -> None:
108
+ """The CLI exposes the combined payload as JSON."""
109
+ runner = CliRunner()
110
+ payload = {
111
+ "generated_at": "2026-04-09T00:00:00+00:00",
112
+ "agent_home": str(tmp_path),
113
+ "skcapstone_context": {"agent": {"name": "Aster"}},
114
+ "hammertime_briefing": {"summary": {"queue_size": 1}, "alert_count": 0},
115
+ }
116
+
117
+ monkeypatch.setattr(
118
+ "skcapstone.session_briefing.build_session_briefing",
119
+ lambda home, memory_limit=10: payload,
120
+ )
121
+
122
+ result = runner.invoke(
123
+ main,
124
+ ["session", "briefing", "--home", str(tmp_path), "--format", "json"],
125
+ )
126
+
127
+ assert result.exit_code == 0
128
+ parsed = json.loads(result.output)
129
+ assert parsed["skcapstone_context"]["agent"]["name"] == "Aster"
130
+ assert parsed["hammertime_briefing"]["summary"]["queue_size"] == 1
@@ -121,6 +121,24 @@ class TestBuildGraph:
121
121
  sync_edges = [e for e in graph.edges if e.edge_type == "sync"]
122
122
  assert len(sync_edges) >= 1
123
123
 
124
+ def test_manifest_operator_creates_human_link(self, tmp_agent_home: Path):
125
+ """Manifest operator metadata appears as an explicit trust relationship."""
126
+ _init_agent(tmp_agent_home, "operator-graph")
127
+ manifest_path = tmp_agent_home / "manifest.json"
128
+ manifest = json.loads(manifest_path.read_text())
129
+ manifest["operator"] = {
130
+ "name": "Casey",
131
+ "fingerprint": "FP1234567890",
132
+ "relationship": "human-operator",
133
+ "entity_type": "human",
134
+ }
135
+ manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
136
+
137
+ graph = build_trust_graph(tmp_agent_home)
138
+
139
+ assert any(n.label == "Casey" for n in graph.nodes)
140
+ assert any(e.edge_type == "operator" and e.label == "human-operator" for e in graph.edges)
141
+
124
142
 
125
143
  class TestFormatDot:
126
144
  """Tests for DOT format output."""