@smilintux/skcapstone 0.5.7 → 0.5.9
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/package.json +1 -1
- package/scripts/install.sh +1 -0
- package/src/skcapstone/cli/daemon.py +9 -6
- package/src/skcapstone/data/systemd/skcapstone.service +5 -4
- package/src/skcapstone/data/systemd/skcapstone@.service +3 -3
- package/src/skcapstone/onboard.py +20 -7
- package/src/skcapstone/pillars/consciousness.py +11 -0
- package/src/skcapstone/systemd.py +49 -15
package/package.json
CHANGED
package/scripts/install.sh
CHANGED
|
@@ -120,6 +120,7 @@ install_pkg "skseal" "" "$PARENT/skseal"
|
|
|
120
120
|
install_pkg "skskills" "" "$PARENT/skskills"
|
|
121
121
|
install_pkg "sksecurity" "" "$PARENT/sksecurity $PILLAR/SKSecurity $PARENT/SKSecurity"
|
|
122
122
|
install_pkg "skseed" "" "$PILLAR/skseed $PARENT/skseed"
|
|
123
|
+
install_pkg "skwhisper" "" "$PARENT/skwhisper-dev $PILLAR/skwhisper $PARENT/skwhisper"
|
|
123
124
|
|
|
124
125
|
# ---------------------------------------------------------------------------
|
|
125
126
|
# Step 4: Dev tools (optional)
|
|
@@ -314,22 +314,25 @@ def register_daemon_commands(main: click.Group) -> None:
|
|
|
314
314
|
console.print()
|
|
315
315
|
|
|
316
316
|
elif platform.system() == "Linux":
|
|
317
|
-
from ..systemd import install_service, systemd_available
|
|
317
|
+
from ..systemd import install_service, systemd_available, SERVICE_NAME
|
|
318
318
|
|
|
319
319
|
if not systemd_available():
|
|
320
320
|
console.print("[red]systemd user session not available.[/]")
|
|
321
321
|
console.print("[dim]This command requires a Linux system with systemd.[/]")
|
|
322
322
|
raise SystemExit(1)
|
|
323
323
|
|
|
324
|
-
console.print("\n[cyan]Installing skcapstone systemd service...[/]")
|
|
325
|
-
result = install_service(start=start)
|
|
324
|
+
console.print(f"\n[cyan]Installing skcapstone systemd service for agent '{effective_agent}'...[/]")
|
|
325
|
+
result = install_service(agent_name=effective_agent, start=start)
|
|
326
|
+
svc_name = result.get("service_name", SERVICE_NAME)
|
|
326
327
|
|
|
327
328
|
if result["installed"]:
|
|
328
|
-
console.print("[green] Unit files installed.[/]")
|
|
329
|
+
console.print(f"[green] Unit files installed ({svc_name}).[/]")
|
|
329
330
|
if result["enabled"]:
|
|
330
|
-
console.print("[green] Service enabled at login.[/]")
|
|
331
|
+
console.print(f"[green] Service enabled at login.[/]")
|
|
331
332
|
if result.get("started"):
|
|
332
|
-
console.print("[green] Service started.[/]")
|
|
333
|
+
console.print(f"[green] Service started.[/]")
|
|
334
|
+
else:
|
|
335
|
+
console.print(f"[dim] Start: systemctl --user start {svc_name}[/]")
|
|
333
336
|
console.print()
|
|
334
337
|
|
|
335
338
|
if not result["installed"]:
|
|
@@ -6,8 +6,8 @@ Wants=network-online.target
|
|
|
6
6
|
|
|
7
7
|
[Service]
|
|
8
8
|
Type=simple
|
|
9
|
-
ExecStart=%h/.
|
|
10
|
-
ExecStop=%h/.
|
|
9
|
+
ExecStart=%h/.local/bin/skcapstone daemon start --foreground
|
|
10
|
+
ExecStop=%h/.local/bin/skcapstone daemon stop
|
|
11
11
|
ExecReload=/bin/kill -HUP $MAINPID
|
|
12
12
|
Restart=on-failure
|
|
13
13
|
RestartSec=10
|
|
@@ -16,7 +16,8 @@ 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=SKCAPSTONE_AGENT=
|
|
19
|
+
Environment=SKCAPSTONE_AGENT=sovereign
|
|
20
|
+
Environment=SKCAPSTONE_HOME=%h/.skcapstone
|
|
20
21
|
# Journal logging
|
|
21
22
|
StandardOutput=journal
|
|
22
23
|
StandardError=journal
|
|
@@ -26,7 +27,7 @@ SyslogIdentifier=skcapstone
|
|
|
26
27
|
NoNewPrivileges=true
|
|
27
28
|
ProtectSystem=strict
|
|
28
29
|
ProtectHome=read-only
|
|
29
|
-
ReadWritePaths=%h/.skcapstone %h/.skenv %h/.capauth %h/.cloud9 %h/.skcomm %h/.skchat
|
|
30
|
+
ReadWritePaths=%h/.skcapstone %h/.skenv %h/.local %h/.config %h/.capauth %h/.cloud9 %h/.skcomm %h/.skchat
|
|
30
31
|
PrivateTmp=true
|
|
31
32
|
ProtectKernelTunables=true
|
|
32
33
|
ProtectControlGroups=true
|
|
@@ -20,8 +20,8 @@ Wants=network-online.target
|
|
|
20
20
|
|
|
21
21
|
[Service]
|
|
22
22
|
Type=notify
|
|
23
|
-
ExecStart
|
|
24
|
-
ExecStop
|
|
23
|
+
ExecStart=%h/.local/bin/skcapstone daemon start --agent %i --foreground
|
|
24
|
+
ExecStop=%h/.local/bin/skcapstone daemon stop --agent %i
|
|
25
25
|
ExecReload=/bin/kill -HUP $MAINPID
|
|
26
26
|
Restart=on-failure
|
|
27
27
|
RestartSec=10
|
|
@@ -41,7 +41,7 @@ SyslogIdentifier=skcapstone@%i
|
|
|
41
41
|
NoNewPrivileges=true
|
|
42
42
|
ProtectSystem=strict
|
|
43
43
|
ProtectHome=read-only
|
|
44
|
-
ReadWritePaths=%h/.skcapstone %h/.skmemory %h/.capauth %h/.cloud9 %h/.skcomm %h/.skchat
|
|
44
|
+
ReadWritePaths=%h/.skcapstone %h/.skmemory %h/.local %h/.config %h/.capauth %h/.cloud9 %h/.skcomm %h/.skchat
|
|
45
45
|
PrivateTmp=true
|
|
46
46
|
ProtectKernelTunables=true
|
|
47
47
|
ProtectControlGroups=true
|
|
@@ -487,6 +487,7 @@ _PILLAR_PACKAGES = [
|
|
|
487
487
|
("skseed", "skseed", "Cloud 9 seeds & LLM callbacks"),
|
|
488
488
|
("sksecurity", "sksecurity", "Audit logging & threat detection"),
|
|
489
489
|
("pgpy", "pgpy", "PGP cryptography (PGPy backend)"),
|
|
490
|
+
("skwhisper", "skwhisper", "Subconscious memory layer (session digester)"),
|
|
490
491
|
]
|
|
491
492
|
|
|
492
493
|
|
|
@@ -1007,7 +1008,7 @@ def _step_autostart_service(agent_name: str = "sovereign") -> bool:
|
|
|
1007
1008
|
system = platform.system()
|
|
1008
1009
|
|
|
1009
1010
|
if system == "Linux":
|
|
1010
|
-
return _step_systemd_service_linux()
|
|
1011
|
+
return _step_systemd_service_linux(agent_name)
|
|
1011
1012
|
elif system == "Darwin":
|
|
1012
1013
|
return _step_launchd_service_macos(agent_name)
|
|
1013
1014
|
else:
|
|
@@ -1018,8 +1019,17 @@ def _step_autostart_service(agent_name: str = "sovereign") -> bool:
|
|
|
1018
1019
|
return False
|
|
1019
1020
|
|
|
1020
1021
|
|
|
1021
|
-
def _step_systemd_service_linux() -> bool:
|
|
1022
|
-
"""Install systemd user service (Linux only).
|
|
1022
|
+
def _step_systemd_service_linux(agent_name: str = "sovereign") -> bool:
|
|
1023
|
+
"""Install systemd user service for an agent (Linux only).
|
|
1024
|
+
|
|
1025
|
+
Uses the template unit ``skcapstone@.service`` so each agent
|
|
1026
|
+
gets its own independent service instance. Multiple agents can
|
|
1027
|
+
run simultaneously on the same machine.
|
|
1028
|
+
|
|
1029
|
+
Args:
|
|
1030
|
+
agent_name: Agent slug from onboarding (e.g. "jarvis").
|
|
1031
|
+
"""
|
|
1032
|
+
service_name = f"skcapstone@{agent_name}.service"
|
|
1023
1033
|
if not click.confirm(" Install systemd user service for auto-start at login?", default=False):
|
|
1024
1034
|
click.echo(
|
|
1025
1035
|
click.style(" ↷ ", fg="bright_black")
|
|
@@ -1035,12 +1045,15 @@ def _step_systemd_service_linux() -> bool:
|
|
|
1035
1045
|
click.echo(click.style(" ", fg="bright_black") + "Try: systemctl --user status")
|
|
1036
1046
|
return False
|
|
1037
1047
|
|
|
1038
|
-
result = install_service(enable=True, start=False)
|
|
1048
|
+
result = install_service(agent_name=agent_name, enable=True, start=False)
|
|
1039
1049
|
if result.get("installed"):
|
|
1040
|
-
click.echo(click.style(" ✓ ", fg="green") + "Systemd service installed")
|
|
1050
|
+
click.echo(click.style(" ✓ ", fg="green") + f"Systemd service installed")
|
|
1041
1051
|
if result.get("enabled"):
|
|
1042
|
-
click.echo(click.style(" ✓ ", fg="green") + "Service enabled — auto-starts at login")
|
|
1043
|
-
click.echo(
|
|
1052
|
+
click.echo(click.style(" ✓ ", fg="green") + f"Service enabled — auto-starts at login")
|
|
1053
|
+
click.echo(
|
|
1054
|
+
click.style(" ", fg="bright_black")
|
|
1055
|
+
+ f"Start now: systemctl --user start {service_name}"
|
|
1056
|
+
)
|
|
1044
1057
|
return True
|
|
1045
1058
|
else:
|
|
1046
1059
|
click.echo(click.style(" ✗ ", fg="red") + "Service install failed")
|
|
@@ -105,6 +105,14 @@ def initialize_consciousness(home: Path) -> ConsciousnessState:
|
|
|
105
105
|
if trip_dir.exists():
|
|
106
106
|
state.trip_sessions = len(list(trip_dir.glob("*.json")))
|
|
107
107
|
|
|
108
|
+
# Check if skwhisper package is importable (installed)
|
|
109
|
+
skwhisper_installed = False
|
|
110
|
+
try:
|
|
111
|
+
import importlib.util
|
|
112
|
+
skwhisper_installed = importlib.util.find_spec("skwhisper") is not None
|
|
113
|
+
except (ImportError, ValueError):
|
|
114
|
+
skwhisper_installed = False
|
|
115
|
+
|
|
108
116
|
# Determine status
|
|
109
117
|
if state.whisper_active and state.sessions_digested > 0 and state.whisper_md is not None:
|
|
110
118
|
if state.whisper_md_age_hours < 24:
|
|
@@ -116,6 +124,9 @@ def initialize_consciousness(home: Path) -> ConsciousnessState:
|
|
|
116
124
|
state.status = PillarStatus.DEGRADED
|
|
117
125
|
elif state.sessions_digested > 0 or state.whisper_md is not None:
|
|
118
126
|
state.status = PillarStatus.DEGRADED
|
|
127
|
+
elif skwhisper_installed:
|
|
128
|
+
# Package is installed but service not running and no data yet — at least DEGRADED
|
|
129
|
+
state.status = PillarStatus.DEGRADED
|
|
119
130
|
else:
|
|
120
131
|
state.status = PillarStatus.MISSING
|
|
121
132
|
|
|
@@ -121,24 +121,32 @@ def systemd_available() -> bool:
|
|
|
121
121
|
|
|
122
122
|
|
|
123
123
|
def install_service(
|
|
124
|
+
agent_name: Optional[str] = None,
|
|
124
125
|
unit_dir: Optional[Path] = None,
|
|
125
126
|
source_dir: Optional[Path] = None,
|
|
126
127
|
enable: bool = True,
|
|
127
128
|
start: bool = True,
|
|
128
129
|
) -> dict:
|
|
129
|
-
"""Install the skcapstone systemd user service.
|
|
130
|
+
"""Install the skcapstone systemd user service for an agent.
|
|
130
131
|
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
When *agent_name* is provided, installs the template unit
|
|
133
|
+
``skcapstone@.service`` and enables ``skcapstone@<agent>.service``.
|
|
134
|
+
This supports multiple agents running as independent services on
|
|
135
|
+
the same machine.
|
|
136
|
+
|
|
137
|
+
When *agent_name* is None, falls back to the single-agent
|
|
138
|
+
``skcapstone.service`` unit for backwards compatibility.
|
|
133
139
|
|
|
134
140
|
Args:
|
|
141
|
+
agent_name: Agent name (e.g. "jarvis"). Uses template unit.
|
|
135
142
|
unit_dir: Target directory for unit files.
|
|
136
143
|
source_dir: Directory containing the .service/.socket files.
|
|
137
144
|
enable: Whether to enable the service at login.
|
|
138
145
|
start: Whether to start the service immediately.
|
|
139
146
|
|
|
140
147
|
Returns:
|
|
141
|
-
dict: Result with 'installed', 'enabled', 'started' bools
|
|
148
|
+
dict: Result with 'installed', 'enabled', 'started' bools
|
|
149
|
+
and 'service_name' for the installed unit.
|
|
142
150
|
"""
|
|
143
151
|
_require_linux()
|
|
144
152
|
target = unit_dir or SYSTEMD_USER_DIR
|
|
@@ -146,15 +154,41 @@ def install_service(
|
|
|
146
154
|
|
|
147
155
|
target.mkdir(parents=True, exist_ok=True)
|
|
148
156
|
|
|
149
|
-
result = {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
+
result = {
|
|
158
|
+
"installed": False,
|
|
159
|
+
"enabled": False,
|
|
160
|
+
"started": False,
|
|
161
|
+
"timers_enabled": False,
|
|
162
|
+
"service_name": "",
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# Determine which unit to use
|
|
166
|
+
if agent_name:
|
|
167
|
+
# Multi-agent: use template unit skcapstone@.service
|
|
168
|
+
template_name = "skcapstone@.service"
|
|
169
|
+
instance_name = f"skcapstone@{agent_name}.service"
|
|
170
|
+
template_src = source / template_name
|
|
171
|
+
if not template_src.exists():
|
|
172
|
+
logger.error("Template unit not found: %s", template_src)
|
|
173
|
+
return result
|
|
174
|
+
shutil.copy2(template_src, target / template_name)
|
|
175
|
+
service_unit = instance_name
|
|
176
|
+
result["service_name"] = instance_name
|
|
177
|
+
else:
|
|
178
|
+
# Single-agent fallback
|
|
179
|
+
service_src = source / SERVICE_NAME
|
|
180
|
+
if not service_src.exists():
|
|
181
|
+
logger.error("Service unit not found: %s", service_src)
|
|
182
|
+
return result
|
|
183
|
+
shutil.copy2(service_src, target / SERVICE_NAME)
|
|
184
|
+
service_unit = SERVICE_NAME
|
|
185
|
+
result["service_name"] = SERVICE_NAME
|
|
186
|
+
|
|
187
|
+
# Copy ancillary units (socket, timers, etc.)
|
|
188
|
+
copied = 1 # already copied the main unit
|
|
157
189
|
for unit_name in ALL_UNITS:
|
|
190
|
+
if unit_name == SERVICE_NAME:
|
|
191
|
+
continue # already handled above
|
|
158
192
|
src = source / unit_name
|
|
159
193
|
if src.exists():
|
|
160
194
|
shutil.copy2(src, target / unit_name)
|
|
@@ -162,10 +196,10 @@ def install_service(
|
|
|
162
196
|
|
|
163
197
|
_systemctl("daemon-reload")
|
|
164
198
|
result["installed"] = True
|
|
165
|
-
logger.info("Installed %d unit file(s) to %s", copied, target)
|
|
199
|
+
logger.info("Installed %d unit file(s) to %s (service: %s)", copied, target, service_unit)
|
|
166
200
|
|
|
167
201
|
if enable:
|
|
168
|
-
r = _systemctl("enable",
|
|
202
|
+
r = _systemctl("enable", service_unit)
|
|
169
203
|
result["enabled"] = r.returncode == 0
|
|
170
204
|
|
|
171
205
|
timers_ok = True
|
|
@@ -177,7 +211,7 @@ def install_service(
|
|
|
177
211
|
result["timers_enabled"] = timers_ok
|
|
178
212
|
|
|
179
213
|
if start:
|
|
180
|
-
r = _systemctl("start",
|
|
214
|
+
r = _systemctl("start", service_unit)
|
|
181
215
|
result["started"] = r.returncode == 0
|
|
182
216
|
|
|
183
217
|
for timer in TIMER_UNITS:
|