@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smilintux/skcapstone",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "description": "SKCapstone - The sovereign agent framework. CapAuth identity, Cloud 9 trust, SKMemory persistence.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -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/.skenv/bin/skcapstone daemon start --foreground
10
- ExecStop=%h/.skenv/bin/skcapstone daemon stop
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=lumina
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=skcapstone daemon start --agent %i --foreground
24
- ExecStop=skcapstone daemon stop --agent %i
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(click.style(" ", fg="bright_black") + "Start now: systemctl --user start skcapstone")
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
- Copies unit files to ~/.config/systemd/user/, reloads the
132
- daemon, and optionally enables + starts the service.
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 = {"installed": False, "enabled": False, "started": False, "timers_enabled": False}
150
-
151
- service_src = source / SERVICE_NAME
152
- if not service_src.exists():
153
- logger.error("Service unit not found: %s", service_src)
154
- return result
155
-
156
- copied = 0
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", SERVICE_NAME)
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", SERVICE_NAME)
214
+ r = _systemctl("start", service_unit)
181
215
  result["started"] = r.returncode == 0
182
216
 
183
217
  for timer in TIMER_UNITS: