@smilintux/skcapstone 0.5.8 → 0.5.10
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/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 +19 -7
- package/src/skcapstone/pillars/consciousness.py +12 -3
- package/src/skcapstone/systemd.py +49 -15
package/package.json
CHANGED
|
@@ -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
|
|
@@ -1008,7 +1008,7 @@ def _step_autostart_service(agent_name: str = "sovereign") -> bool:
|
|
|
1008
1008
|
system = platform.system()
|
|
1009
1009
|
|
|
1010
1010
|
if system == "Linux":
|
|
1011
|
-
return _step_systemd_service_linux()
|
|
1011
|
+
return _step_systemd_service_linux(agent_name)
|
|
1012
1012
|
elif system == "Darwin":
|
|
1013
1013
|
return _step_launchd_service_macos(agent_name)
|
|
1014
1014
|
else:
|
|
@@ -1019,8 +1019,17 @@ def _step_autostart_service(agent_name: str = "sovereign") -> bool:
|
|
|
1019
1019
|
return False
|
|
1020
1020
|
|
|
1021
1021
|
|
|
1022
|
-
def _step_systemd_service_linux() -> bool:
|
|
1023
|
-
"""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"
|
|
1024
1033
|
if not click.confirm(" Install systemd user service for auto-start at login?", default=False):
|
|
1025
1034
|
click.echo(
|
|
1026
1035
|
click.style(" ↷ ", fg="bright_black")
|
|
@@ -1036,12 +1045,15 @@ def _step_systemd_service_linux() -> bool:
|
|
|
1036
1045
|
click.echo(click.style(" ", fg="bright_black") + "Try: systemctl --user status")
|
|
1037
1046
|
return False
|
|
1038
1047
|
|
|
1039
|
-
result = install_service(enable=True, start=False)
|
|
1048
|
+
result = install_service(agent_name=agent_name, enable=True, start=False)
|
|
1040
1049
|
if result.get("installed"):
|
|
1041
|
-
click.echo(click.style(" ✓ ", fg="green") + "Systemd service installed")
|
|
1050
|
+
click.echo(click.style(" ✓ ", fg="green") + f"Systemd service installed")
|
|
1042
1051
|
if result.get("enabled"):
|
|
1043
|
-
click.echo(click.style(" ✓ ", fg="green") + "Service enabled — auto-starts at login")
|
|
1044
|
-
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
|
+
)
|
|
1045
1057
|
return True
|
|
1046
1058
|
else:
|
|
1047
1059
|
click.echo(click.style(" ✗ ", fg="red") + "Service install failed")
|
|
@@ -28,7 +28,11 @@ def initialize_consciousness(home: Path) -> ConsciousnessState:
|
|
|
28
28
|
ConsciousnessState with current status.
|
|
29
29
|
"""
|
|
30
30
|
agent_name = os.environ.get("SKCAPSTONE_AGENT", "lumina")
|
|
31
|
-
|
|
31
|
+
# home may be the agent dir (~/.skcapstone/agents/jarvis/) or the
|
|
32
|
+
# shared root (~/.skcapstone/). Check for skwhisper/ directly first.
|
|
33
|
+
whisper_dir = home / "skwhisper"
|
|
34
|
+
if not whisper_dir.exists():
|
|
35
|
+
whisper_dir = home / "agents" / agent_name / "skwhisper"
|
|
32
36
|
|
|
33
37
|
state = ConsciousnessState()
|
|
34
38
|
|
|
@@ -81,11 +85,16 @@ def initialize_consciousness(home: Path) -> ConsciousnessState:
|
|
|
81
85
|
pass
|
|
82
86
|
|
|
83
87
|
# Check if consciousness daemon is running (systemd)
|
|
84
|
-
#
|
|
88
|
+
# Check template instance (skcapstone@<agent>), legacy single-agent, and skwhisper
|
|
85
89
|
try:
|
|
86
90
|
import subprocess
|
|
87
91
|
|
|
88
|
-
|
|
92
|
+
service_candidates = [
|
|
93
|
+
f"skcapstone@{agent_name}", # multi-agent template unit
|
|
94
|
+
"skcapstone", # legacy single-agent unit
|
|
95
|
+
"skwhisper", # standalone skwhisper daemon
|
|
96
|
+
]
|
|
97
|
+
for service_name in service_candidates:
|
|
89
98
|
result = subprocess.run(
|
|
90
99
|
["systemctl", "--user", "is-active", service_name],
|
|
91
100
|
capture_output=True,
|
|
@@ -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:
|