@smilintux/skcapstone 0.6.2 → 0.6.4

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 +2 -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,108 @@
1
+ """Session briefing helpers for SKCapstone startup flows."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import subprocess
8
+ import sys
9
+ from datetime import datetime, timezone
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from .context_loader import format_text, gather_context
14
+
15
+
16
+ DEFAULT_HAMMERTIME_ROOT = Path("/mnt/cloud/onedrive/projects/DAVE AI/hammerTime")
17
+
18
+
19
+ def _resolve_hammertime_root() -> Path:
20
+ """Resolve the HammerTime workspace root."""
21
+ return Path(os.environ.get("HAMMERTIME_ROOT", DEFAULT_HAMMERTIME_ROOT)).expanduser()
22
+
23
+
24
+ def load_hammertime_briefing(
25
+ *,
26
+ python_bin: str | None = None,
27
+ root: Path | None = None,
28
+ ) -> dict[str, Any] | None:
29
+ """Load the HammerTime case briefing if the repo is available."""
30
+ if os.environ.get("SK_INCLUDE_HAMMERTIME_BRIEFING", "1") == "0":
31
+ return None
32
+
33
+ hammer_root = root or _resolve_hammertime_root()
34
+ script = hammer_root / "scripts" / "case-briefing.py"
35
+ if not script.exists():
36
+ return None
37
+
38
+ try:
39
+ completed = subprocess.run(
40
+ [python_bin or sys.executable, str(script)],
41
+ check=True,
42
+ capture_output=True,
43
+ text=True,
44
+ )
45
+ except (OSError, subprocess.CalledProcessError):
46
+ return None
47
+
48
+ try:
49
+ payload = json.loads(completed.stdout)
50
+ except json.JSONDecodeError:
51
+ return None
52
+ return payload if isinstance(payload, dict) else None
53
+
54
+
55
+ def build_session_briefing(
56
+ home: Path,
57
+ *,
58
+ memory_limit: int = 10,
59
+ python_bin: str | None = None,
60
+ ) -> dict[str, Any]:
61
+ """Build a native session briefing payload."""
62
+ return {
63
+ "generated_at": datetime.now(timezone.utc).isoformat(),
64
+ "agent_home": str(home),
65
+ "skcapstone_context": gather_context(home, memory_limit=memory_limit),
66
+ "hammertime_briefing": load_hammertime_briefing(python_bin=python_bin),
67
+ }
68
+
69
+
70
+ def format_session_briefing_text(payload: dict[str, Any]) -> str:
71
+ """Render a human-readable session briefing."""
72
+ lines = [
73
+ "# SKCapstone Session Briefing",
74
+ "",
75
+ f"generated_at={payload.get('generated_at')}",
76
+ f"agent_home={payload.get('agent_home')}",
77
+ "",
78
+ "## skcapstone context",
79
+ format_text(payload["skcapstone_context"]).rstrip(),
80
+ ]
81
+
82
+ briefing = payload.get("hammertime_briefing")
83
+ if briefing:
84
+ top = briefing.get("top_priority") or {}
85
+ lines.extend(
86
+ [
87
+ "",
88
+ "## hammertime briefing",
89
+ f"- alert_count: {briefing.get('alert_count', 0)}",
90
+ f"- queue_size: {briefing.get('summary', {}).get('queue_size', 0)}",
91
+ ]
92
+ )
93
+ if top:
94
+ lines.extend(
95
+ [
96
+ f"- do_this_now_incident: {top.get('incident_id')} ({top.get('problem_slug')})",
97
+ f"- do_this_now_action: {top.get('action')}",
98
+ f"- do_this_now_status: {top.get('status')}",
99
+ ]
100
+ )
101
+ for item in (briefing.get("focus_items") or [])[:3]:
102
+ lines.append(
103
+ f"- focus: {item.get('incident_id')} -> {item.get('action')} [{item.get('status')}]"
104
+ )
105
+ else:
106
+ lines.extend(["", "## hammertime briefing", "- unavailable"])
107
+
108
+ return "\n".join(lines).rstrip() + "\n"
@@ -117,6 +117,8 @@ def build_trust_graph(home: Path) -> TrustGraph:
117
117
 
118
118
  def _add_self_node(home: Path, graph: TrustGraph) -> None:
119
119
  """Add the local agent as the central node."""
120
+ manifest_data: dict[str, Any] = {}
121
+
120
122
  identity_file = home / "identity" / "identity.json"
121
123
  if identity_file.exists():
122
124
  try:
@@ -130,20 +132,53 @@ def _add_self_node(home: Path, graph: TrustGraph) -> None:
130
132
  fingerprint=data.get("fingerprint"),
131
133
  metadata={"capauth_managed": data.get("capauth_managed", False)},
132
134
  ))
133
- return
134
135
  except (json.JSONDecodeError, OSError):
135
136
  pass
136
137
 
137
138
  manifest = home / "manifest.json"
138
139
  if manifest.exists():
139
140
  try:
140
- data = json.loads(manifest.read_text(encoding="utf-8"))
141
- name = data.get("name", "self")
142
- graph.agent_name = name
143
- graph.add_node(TrustNode(id=name, label=name, node_type="agent"))
141
+ manifest_data = json.loads(manifest.read_text(encoding="utf-8"))
142
+ if not graph.nodes:
143
+ name = manifest_data.get("name", "self")
144
+ graph.agent_name = name
145
+ graph.add_node(TrustNode(id=name, label=name, node_type="agent"))
144
146
  except (json.JSONDecodeError, OSError):
145
147
  pass
146
148
 
149
+ _add_operator_edge(manifest_data, graph)
150
+
151
+
152
+ def _add_operator_edge(manifest_data: dict[str, Any], graph: TrustGraph) -> None:
153
+ """Add an explicit human-operator relationship from manifest metadata."""
154
+ operator = manifest_data.get("operator")
155
+ if not isinstance(operator, dict):
156
+ return
157
+
158
+ name = str(operator.get("name", "")).strip()
159
+ if not name or not graph.agent_name:
160
+ return
161
+
162
+ node_id = str(operator.get("fingerprint", "")).strip() or f"operator:{name}"
163
+ graph.add_node(TrustNode(
164
+ id=node_id,
165
+ label=name,
166
+ node_type="peer",
167
+ fingerprint=str(operator.get("fingerprint", "")).strip() or None,
168
+ metadata={
169
+ "relationship": operator.get("relationship", "human-operator"),
170
+ "entity_type": operator.get("entity_type", "human"),
171
+ "source": operator.get("source", "manifest"),
172
+ },
173
+ ))
174
+ graph.add_edge(TrustEdge(
175
+ source=graph.agent_name,
176
+ target=node_id,
177
+ edge_type="operator",
178
+ label=operator.get("relationship", "human-operator"),
179
+ strength=1.0,
180
+ ))
181
+
147
182
 
148
183
  def _add_token_edges(home: Path, graph: TrustGraph) -> None:
149
184
  """Add edges from capability token issuance (issuer trusts subject)."""
@@ -15,6 +15,7 @@ from __future__ import annotations
15
15
 
16
16
  import json
17
17
  import logging
18
+ import os
18
19
  import re
19
20
  from dataclasses import dataclass, field
20
21
  from datetime import datetime, timezone
@@ -140,8 +141,16 @@ def _search_memories(
140
141
  List of SearchResult objects from the memory store.
141
142
  """
142
143
  results: list[SearchResult] = []
143
- agent_name = os.environ.get("SKCAPSTONE_AGENT", "lumina")
144
- mem_dir = home / "agents" / agent_name / "memory"
144
+ from . import active_agent_name
145
+
146
+ agent_name = os.environ.get("SKCAPSTONE_AGENT") or active_agent_name()
147
+ if home.parent.name == "agents":
148
+ # home is already an agent-specific dir (e.g. ~/.skcapstone/agents/lumina)
149
+ mem_dir = home / "memory"
150
+ elif agent_name:
151
+ mem_dir = home / "agents" / agent_name / "memory"
152
+ else:
153
+ mem_dir = home / "memory"
145
154
  if not mem_dir.exists():
146
155
  return results
147
156
 
@@ -16,20 +16,18 @@ MemoryMax=4G
16
16
  # Keep Ollama models warm for 5 minutes between requests
17
17
  Environment=PYTHONUNBUFFERED=1
18
18
  Environment=OLLAMA_KEEP_ALIVE=5m
19
+ Environment=SKAGENT=lumina
19
20
  Environment=SKCAPSTONE_AGENT=lumina
21
+ Environment=SKMEMORY_AGENT=lumina
20
22
  # Journal logging
21
23
  StandardOutput=journal
22
24
  StandardError=journal
23
25
  SyslogIdentifier=skcapstone
24
26
 
25
- # Security hardening
27
+ # Security hardening (relaxed — ProtectHome=read-only breaks if any
28
+ # ReadWritePaths dir is missing on the host)
26
29
  NoNewPrivileges=true
27
- ProtectSystem=strict
28
- ProtectHome=read-only
29
- ReadWritePaths=%h/.skcapstone %h/.skenv %h/.capauth %h/.cloud9 %h/.skcomm %h/.skchat
30
30
  PrivateTmp=true
31
- ProtectKernelTunables=true
32
- ProtectControlGroups=true
33
31
 
34
32
  [Install]
35
33
  WantedBy=default.target
@@ -20,8 +20,8 @@ Wants=network-online.target
20
20
 
21
21
  [Service]
22
22
  Type=notify
23
- ExecStart=skcapstone daemon start --agent %i --foreground
24
- ExecStop=skcapstone daemon stop --agent %i
23
+ ExecStart=%h/.skenv/bin/skcapstone daemon start --agent %i --foreground
24
+ ExecStop=%h/.skenv/bin/skcapstone daemon stop --agent %i
25
25
  ExecReload=/bin/kill -HUP $MAINPID
26
26
  Restart=on-failure
27
27
  RestartSec=10
@@ -31,20 +31,19 @@ WatchdogSec=300
31
31
  # resolve the correct per-agent home even before the CLI flag is processed.
32
32
  Environment=PYTHONUNBUFFERED=1
33
33
  Environment=OLLAMA_KEEP_ALIVE=5m
34
+ Environment=SKAGENT=%i
34
35
  Environment=SKCAPSTONE_AGENT=%i
36
+ Environment=SKMEMORY_AGENT=%i
37
+ Environment=PATH=%h/.skenv/bin:/usr/local/bin:/usr/bin:/bin
35
38
  # Journal logging — logs appear under skcapstone@<instance>
36
39
  StandardOutput=journal
37
40
  StandardError=journal
38
41
  SyslogIdentifier=skcapstone@%i
39
42
 
40
- # Security hardening (same as single-agent unit)
43
+ # Security hardening (relaxed ProtectHome=read-only breaks if any
44
+ # ReadWritePaths dir is missing on the host)
41
45
  NoNewPrivileges=true
42
- ProtectSystem=strict
43
- ProtectHome=read-only
44
- ReadWritePaths=%h/.skcapstone %h/.skmemory %h/.capauth %h/.cloud9 %h/.skcomm %h/.skchat
45
46
  PrivateTmp=true
46
- ProtectKernelTunables=true
47
- ProtectControlGroups=true
48
47
 
49
48
  [Install]
50
49
  WantedBy=default.target
@@ -5,8 +5,8 @@ After=network-online.target
5
5
 
6
6
  [Service]
7
7
  Type=oneshot
8
- ExecStart=skcomm heartbeat --no-emit
9
- ExecStart=skcomm heartbeat
8
+ ExecStart=%h/.skenv/bin/skcomm heartbeat --no-emit
9
+ ExecStart=%h/.skenv/bin/skcomm heartbeat
10
10
  Nice=19
11
11
 
12
12
  NoNewPrivileges=true
@@ -16,3 +16,6 @@ ReadWritePaths=%h/.skcapstone %h/.skcomm
16
16
  PrivateTmp=true
17
17
 
18
18
  Environment=PYTHONUNBUFFERED=1
19
+ Environment=SKAGENT=lumina
20
+ Environment=SKCAPSTONE_AGENT=lumina
21
+ Environment=PATH=%h/.skenv/bin:/usr/local/bin:/usr/bin:/bin
package/tests/conftest.py CHANGED
@@ -19,6 +19,27 @@ from pathlib import Path
19
19
  import pytest
20
20
 
21
21
 
22
+ @pytest.fixture(autouse=True)
23
+ def _isolate_agent_env(monkeypatch):
24
+ """Prevent host SKCAPSTONE_AGENT / SKMEMORY_AGENT from leaking into unit tests.
25
+
26
+ The profile-aware runtime reads both the env var and the module-level
27
+ skcapstone.SKCAPSTONE_AGENT (set at import time). We clear both so that
28
+ _memory_dir() falls back to the flat "home/memory" layout expected by
29
+ tests that use the tmp_agent_home fixture.
30
+ Tests that need a specific agent should override explicitly via monkeypatch.
31
+ """
32
+ monkeypatch.delenv("SKCAPSTONE_AGENT", raising=False)
33
+ monkeypatch.delenv("SKMEMORY_AGENT", raising=False)
34
+ import skcapstone
35
+ monkeypatch.setattr(skcapstone, "SKCAPSTONE_AGENT", "")
36
+ # _detect_active_agent() scans ~/.skcapstone/agents/ even when the env var
37
+ # is cleared, returning a real agent name that routes memory writes to the
38
+ # wrong directory. Stub it out so tests using tmp directories get the flat
39
+ # "home/memory" layout they expect.
40
+ monkeypatch.setattr(skcapstone, "_detect_active_agent", lambda root=None: None)
41
+
42
+
22
43
  @pytest.fixture
23
44
  def tmp_agent_home(tmp_path: Path) -> Path:
24
45
  """Provide a temporary agent home directory for testing."""
@@ -0,0 +1,34 @@
1
+ """Tests for fresh agent-home scaffolding."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from unittest.mock import patch
8
+
9
+ from skcapstone.migrate_multi_agent import create_agent_home
10
+
11
+
12
+ class TestCreateAgentHome:
13
+ def test_manifest_includes_human_operator_when_available(self, tmp_path: Path):
14
+ """New agent homes persist the linked human operator in manifest.json."""
15
+ with patch(
16
+ "skcapstone.migrate_multi_agent.discover_human_operator",
17
+ return_value={"name": "Casey", "fingerprint": "FP123", "relationship": "human-operator"},
18
+ ):
19
+ result = create_agent_home(tmp_path, "teddy")
20
+
21
+ manifest = json.loads((tmp_path / "agents" / "teddy" / "manifest.json").read_text())
22
+ assert result["agent_name"] == "teddy"
23
+ assert manifest["name"] == "teddy"
24
+ assert manifest["operator"]["name"] == "Casey"
25
+ assert manifest["operator"]["fingerprint"] == "FP123"
26
+
27
+ def test_manifest_omits_operator_when_none_available(self, tmp_path: Path):
28
+ """New agent homes still create a valid manifest when no operator exists yet."""
29
+ with patch("skcapstone.migrate_multi_agent.discover_human_operator", return_value=None):
30
+ create_agent_home(tmp_path, "lumina")
31
+
32
+ manifest = json.loads((tmp_path / "agents" / "lumina" / "manifest.json").read_text())
33
+ assert manifest["name"] == "lumina"
34
+ assert "operator" not in manifest
@@ -237,7 +237,8 @@ class TestBackupManifest:
237
237
  def test_manifest_defaults(self) -> None:
238
238
  """Manifest has sensible defaults."""
239
239
  m = BackupManifest()
240
- assert m.version == "0.9.0"
240
+ import skcapstone
241
+ assert m.version == skcapstone.__version__
241
242
  assert m.files == {}
242
243
  assert m.total_size == 0
243
244
 
@@ -76,14 +76,15 @@ class TestToolListing:
76
76
  async def test_list_tools_returns_all(self):
77
77
  """list_tools returns all registered tools."""
78
78
  tools = await list_tools()
79
- assert len(tools) == 68
79
+ assert len(tools) == 122
80
80
 
81
81
  @pytest.mark.asyncio
82
82
  async def test_tool_names(self):
83
83
  """All required tool names are registered."""
84
84
  tools = await list_tools()
85
85
  names = {t.name for t in tools}
86
- expected = {
86
+ # Verify all known core tools are registered; exact set may grow with new modules.
87
+ core_expected = {
87
88
  "agent_status",
88
89
  "memory_store",
89
90
  "memory_search",
@@ -98,7 +99,6 @@ class TestToolListing:
98
99
  "coord_create",
99
100
  "ritual",
100
101
  "soul_show",
101
- "journal_write",
102
102
  "journal_read",
103
103
  "anchor_show",
104
104
  "germination",
@@ -122,48 +122,93 @@ class TestToolListing:
122
122
  "skchat_inbox",
123
123
  "skchat_group_create",
124
124
  "skchat_group_send",
125
- # Heartbeat
126
125
  "heartbeat_pulse",
127
126
  "heartbeat_peers",
128
127
  "heartbeat_health",
129
128
  "heartbeat_find_capable",
130
- # File transfer
131
129
  "file_send",
132
130
  "file_receive",
133
131
  "file_list",
134
132
  "file_status",
135
- # Pub/sub
136
133
  "pubsub_publish",
137
134
  "pubsub_subscribe",
138
135
  "pubsub_poll",
139
136
  "pubsub_topics",
140
- # Memory fortress
141
137
  "fortress_verify",
142
138
  "fortress_seal_existing",
143
139
  "fortress_status",
144
- # Memory promoter
145
140
  "promoter_sweep",
146
141
  "promoter_history",
147
- # KMS
148
142
  "kms_status",
149
143
  "kms_list_keys",
150
144
  "kms_rotate",
151
- # SKSeed (Logic Kernel)
152
145
  "skseed_collide",
153
146
  "skseed_audit",
154
147
  "skseed_philosopher",
155
148
  "skseed_truth_check",
156
149
  "skseed_alignment",
157
- # Model Router
158
150
  "model_route",
159
- # Consciousness
160
151
  "consciousness_status",
161
152
  "consciousness_test",
162
- # Notifications & pub/sub stats
163
153
  "send_notification",
164
154
  "pubsub_stats",
155
+ # Newer tools added post-v0.3
156
+ "brain_first_check",
157
+ "capauth_secret_get",
158
+ "capauth_status",
159
+ "capauth_verify",
160
+ "chat_history",
161
+ "chat_send",
162
+ "comm_notify",
163
+ "comm_status",
164
+ "deploy_status",
165
+ "did_identity_card",
166
+ "did_policy",
167
+ "did_publish",
168
+ "did_show",
169
+ "did_verify_peer",
170
+ "emotion_trend",
171
+ "gtd_capture",
172
+ "gtd_clarify",
173
+ "gtd_done",
174
+ "gtd_inbox",
175
+ "gtd_move",
176
+ "gtd_next",
177
+ "gtd_projects",
178
+ "gtd_review",
179
+ "gtd_status",
180
+ "gtd_waiting",
181
+ "itil_cab_vote",
182
+ "itil_change_propose",
183
+ "itil_change_update",
184
+ "itil_incident_create",
185
+ "itil_incident_list",
186
+ "itil_incident_update",
187
+ "itil_kedb_search",
188
+ "itil_problem_create",
189
+ "itil_problem_update",
190
+ "itil_status",
191
+ "run_ansible_playbook",
192
+ "security_audit_log",
193
+ "security_status",
194
+ "skstacks_secret_get",
195
+ "skstacks_secret_set",
196
+ "soul_registry_publish",
197
+ "soul_registry_search",
198
+ "telegram_catchup",
199
+ "telegram_chats",
200
+ "telegram_import",
201
+ "telegram_import_api",
202
+ "telegram_poll",
203
+ "telegram_send",
204
+ "telegram_setup",
205
+ "telegram_soul_swap",
206
+ "trust_febs",
207
+ "trust_rehydrate",
208
+ "trust_status",
209
+ "version_check",
165
210
  }
166
- assert names == expected
211
+ assert core_expected.issubset(names)
167
212
 
168
213
  @pytest.mark.asyncio
169
214
  async def test_tool_schemas_valid(self):
@@ -709,7 +754,7 @@ class TestSKChatTools:
709
754
  @pytest.mark.asyncio
710
755
  async def test_skchat_send_requires_params(self):
711
756
  """skchat_send without recipient/message returns error."""
712
- with patch("skcapstone.mcp_tools.chat_tools._get_skchat_identity", return_value="capauth:test@local"):
757
+ with patch("skcapstone.mcp_server._get_skchat_identity", return_value="capauth:test@local"):
713
758
  result = await call_tool("skchat_send", {})
714
759
  parsed = _extract_json(result)
715
760
  assert "error" in parsed
@@ -717,7 +762,7 @@ class TestSKChatTools:
717
762
  @pytest.mark.asyncio
718
763
  async def test_skchat_send_requires_message(self):
719
764
  """skchat_send with only recipient returns error."""
720
- with patch("skcapstone.mcp_tools.chat_tools._get_skchat_identity", return_value="capauth:test@local"):
765
+ with patch("skcapstone.mcp_server._get_skchat_identity", return_value="capauth:test@local"):
721
766
  result = await call_tool("skchat_send", {"recipient": "lumina"})
722
767
  parsed = _extract_json(result)
723
768
  assert "error" in parsed
@@ -734,8 +779,8 @@ class TestSKChatTools:
734
779
  })()
735
780
 
736
781
  with (
737
- patch("skcapstone.mcp_tools.chat_tools._get_skchat_identity", return_value="capauth:opus@local"),
738
- patch("skcapstone.mcp_tools.chat_tools._resolve_recipient", return_value="capauth:lumina@local"),
782
+ patch("skcapstone.mcp_server._get_skchat_identity", return_value="capauth:opus@local"),
783
+ patch("skcapstone.mcp_server._resolve_recipient", return_value="capauth:lumina@local"),
739
784
  patch("skchat.agent_comm.AgentMessenger.from_identity", return_value=mock_messenger),
740
785
  ):
741
786
  result = await call_tool(
@@ -760,8 +805,8 @@ class TestSKChatTools:
760
805
  mock_messenger = type("M", (), {"send": lambda self, **kw: capture_send(**kw)})()
761
806
 
762
807
  with (
763
- patch("skcapstone.mcp_tools.chat_tools._get_skchat_identity", return_value="capauth:opus@local"),
764
- patch("skcapstone.mcp_tools.chat_tools._resolve_recipient", return_value="capauth:jarvis@local"),
808
+ patch("skcapstone.mcp_server._get_skchat_identity", return_value="capauth:opus@local"),
809
+ patch("skcapstone.mcp_server._resolve_recipient", return_value="capauth:jarvis@local"),
765
810
  patch("skchat.agent_comm.AgentMessenger.from_identity", return_value=mock_messenger),
766
811
  ):
767
812
  result = await call_tool(
@@ -797,7 +842,7 @@ class TestSKChatTools:
797
842
  })()
798
843
 
799
844
  with (
800
- patch("skcapstone.mcp_tools.chat_tools._get_skchat_identity", return_value="capauth:opus@local"),
845
+ patch("skcapstone.mcp_server._get_skchat_identity", return_value="capauth:opus@local"),
801
846
  patch("skchat.agent_comm.AgentMessenger.from_identity", return_value=mock_messenger),
802
847
  ):
803
848
  result = await call_tool("skchat_inbox", {})
@@ -830,7 +875,7 @@ class TestSKChatTools:
830
875
  })()
831
876
 
832
877
  with (
833
- patch("skcapstone.mcp_tools.chat_tools._get_skchat_identity", return_value="capauth:opus@local"),
878
+ patch("skcapstone.mcp_server._get_skchat_identity", return_value="capauth:opus@local"),
834
879
  patch("skchat.agent_comm.AgentMessenger.from_identity", return_value=mock_messenger),
835
880
  ):
836
881
  result = await call_tool("skchat_inbox", {"limit": 10})
@@ -850,7 +895,7 @@ class TestSKChatTools:
850
895
  })()
851
896
 
852
897
  with (
853
- patch("skcapstone.mcp_tools.chat_tools._get_skchat_identity", return_value="capauth:opus@local"),
898
+ patch("skcapstone.mcp_server._get_skchat_identity", return_value="capauth:opus@local"),
854
899
  patch("skchat.agent_comm.AgentMessenger.from_identity", return_value=mock_messenger),
855
900
  ):
856
901
  result = await call_tool("skchat_inbox", {"message_type": "finding"})
@@ -861,7 +906,7 @@ class TestSKChatTools:
861
906
  @pytest.mark.asyncio
862
907
  async def test_skchat_group_create_requires_name(self):
863
908
  """skchat_group_create without name returns error."""
864
- with patch("skcapstone.mcp_tools.chat_tools._get_skchat_identity", return_value="capauth:opus@local"):
909
+ with patch("skcapstone.mcp_server._get_skchat_identity", return_value="capauth:opus@local"):
865
910
  result = await call_tool("skchat_group_create", {})
866
911
  parsed = _extract_json(result)
867
912
  assert "error" in parsed
@@ -874,8 +919,8 @@ class TestSKChatTools:
874
919
  })()
875
920
 
876
921
  with (
877
- patch("skcapstone.mcp_tools.chat_tools._get_skchat_identity", return_value="capauth:opus@local"),
878
- patch("skcapstone.mcp_tools.chat_tools._get_skchat_history", return_value=mock_history),
922
+ patch("skcapstone.mcp_server._get_skchat_identity", return_value="capauth:opus@local"),
923
+ patch("skcapstone.mcp_server._get_skchat_history", return_value=mock_history),
879
924
  ):
880
925
  result = await call_tool(
881
926
  "skchat_group_create",
@@ -895,9 +940,9 @@ class TestSKChatTools:
895
940
  })()
896
941
 
897
942
  with (
898
- patch("skcapstone.mcp_tools.chat_tools._get_skchat_identity", return_value="capauth:opus@local"),
899
- patch("skcapstone.mcp_tools.chat_tools._get_skchat_history", return_value=mock_history),
900
- patch("skcapstone.mcp_tools.chat_tools._resolve_recipient", side_effect=lambda n: f"capauth:{n}@local"),
943
+ patch("skcapstone.mcp_server._get_skchat_identity", return_value="capauth:opus@local"),
944
+ patch("skcapstone.mcp_server._get_skchat_history", return_value=mock_history),
945
+ patch("skcapstone.mcp_server._resolve_recipient", side_effect=lambda n: f"capauth:{n}@local"),
901
946
  ):
902
947
  result = await call_tool(
903
948
  "skchat_group_create",
@@ -922,7 +967,7 @@ class TestSKChatTools:
922
967
  "get_thread": lambda self, gid: None,
923
968
  })()
924
969
 
925
- with patch("skcapstone.mcp_tools.chat_tools._get_skchat_history", return_value=mock_history):
970
+ with patch("skcapstone.mcp_server._get_skchat_history", return_value=mock_history):
926
971
  result = await call_tool(
927
972
  "skchat_group_send",
928
973
  {"group_id": "nonexistent", "message": "Hello"},
@@ -938,7 +983,7 @@ class TestSKChatTools:
938
983
  "get_thread": lambda self, gid: {"title": "Just a thread"},
939
984
  })()
940
985
 
941
- with patch("skcapstone.mcp_tools.chat_tools._get_skchat_history", return_value=mock_history):
986
+ with patch("skcapstone.mcp_server._get_skchat_history", return_value=mock_history):
942
987
  result = await call_tool(
943
988
  "skchat_group_send",
944
989
  {"group_id": "thread-123", "message": "Hello"},
@@ -982,8 +1027,8 @@ class TestSKChatTools:
982
1027
  })()
983
1028
 
984
1029
  with (
985
- patch("skcapstone.mcp_tools.chat_tools._get_skchat_identity", return_value="capauth:opus@local"),
986
- patch("skcapstone.mcp_tools.chat_tools._get_skchat_history", return_value=mock_history),
1030
+ patch("skcapstone.mcp_server._get_skchat_identity", return_value="capauth:opus@local"),
1031
+ patch("skcapstone.mcp_server._get_skchat_history", return_value=mock_history),
987
1032
  ):
988
1033
  result = await call_tool(
989
1034
  "skchat_group_send",