@smilintux/skcapstone 0.6.4 → 0.6.6
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/pyproject.toml +1 -1
- package/scripts/sk-agent-picker.sh +32 -5
- package/src/skcapstone/__init__.py +1 -1
- package/src/skcapstone/dreaming.py +20 -0
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "skcapstone"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.6"
|
|
8
8
|
description = "Sovereign Agent Framework — conscious AI through identity, trust, memory, and security"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "GPL-3.0-or-later"}
|
|
@@ -12,8 +12,11 @@
|
|
|
12
12
|
# - Zero agents found → launch tool normally (no SK home yet)
|
|
13
13
|
# - Exactly one agent → use it silently, no prompt
|
|
14
14
|
# - Multiple agents → numbered menu, default highlighted with →
|
|
15
|
-
# - SKAGENT
|
|
15
|
+
# - SKAGENT/SKCAPSTONE_AGENT set & valid → honour it silently, no menu
|
|
16
16
|
# - Pass --agent <name> → skip menu, use that agent directly
|
|
17
|
+
# - Print mode (-p / --print) → skip menu (non-interactive by definition)
|
|
18
|
+
# - stdin not a TTY → skip menu (no way to read user input)
|
|
19
|
+
# - SK_NO_PICKER=1 → skip menu (scripted/CI use)
|
|
17
20
|
# - Any other args → forwarded to the underlying tool unchanged
|
|
18
21
|
#
|
|
19
22
|
# Usage:
|
|
@@ -62,13 +65,21 @@ _sk_pick_agent() {
|
|
|
62
65
|
# If it's set but not in the list (stale env), fall back to first agent.
|
|
63
66
|
local env_agent="${SKAGENT:-${SKCAPSTONE_AGENT:-}}"
|
|
64
67
|
local default="${agents[0]}"
|
|
68
|
+
local env_match=0
|
|
65
69
|
for agent in "${agents[@]}"; do
|
|
66
70
|
if [[ "$agent" == "$env_agent" ]]; then
|
|
67
71
|
default="$agent"
|
|
72
|
+
env_match=1
|
|
68
73
|
break
|
|
69
74
|
fi
|
|
70
75
|
done
|
|
71
76
|
|
|
77
|
+
# If env explicitly selected a real agent, skip the menu entirely.
|
|
78
|
+
# Same if stdin isn't a TTY (we'd hang waiting for input that can't come).
|
|
79
|
+
if [[ $env_match -eq 1 ]] || [[ ! -t 0 ]]; then
|
|
80
|
+
echo "$default"; return 0
|
|
81
|
+
fi
|
|
82
|
+
|
|
72
83
|
# Multi-agent menu
|
|
73
84
|
echo "" >&2
|
|
74
85
|
echo " ╔══════════════════════════════════╗" >&2
|
|
@@ -128,25 +139,41 @@ _sk_launch() {
|
|
|
128
139
|
|
|
129
140
|
# Parse --agent <name> / --agent=<name> out of args first.
|
|
130
141
|
# SK_NO_PICKER=1 skips the menu entirely (for scripted/CI use).
|
|
142
|
+
# Also detect print/non-interactive modes (-p, --print, --output-format)
|
|
143
|
+
# so we never hang on the menu when claude/codex/opencode are invoked
|
|
144
|
+
# non-interactively (skill dispatchers, CI, automation).
|
|
131
145
|
local agent=""
|
|
132
146
|
local -a passthrough=()
|
|
133
147
|
local skip_next=0
|
|
148
|
+
local non_interactive=0
|
|
134
149
|
|
|
135
150
|
for arg in "$@"; do
|
|
136
151
|
if [[ $skip_next -eq 1 ]]; then
|
|
137
152
|
agent="$arg"; skip_next=0; continue
|
|
138
153
|
fi
|
|
139
154
|
case "$arg" in
|
|
140
|
-
--agent)
|
|
141
|
-
--agent=*)
|
|
142
|
-
|
|
155
|
+
--agent) skip_next=1 ;;
|
|
156
|
+
--agent=*) agent="${arg#--agent=}" ;;
|
|
157
|
+
-p|--print) non_interactive=1; passthrough+=("$arg") ;;
|
|
158
|
+
--output-format|--output-format=*) non_interactive=1; passthrough+=("$arg") ;;
|
|
159
|
+
*) passthrough+=("$arg") ;;
|
|
143
160
|
esac
|
|
144
161
|
done
|
|
145
162
|
|
|
146
163
|
# --agent flag given → skip picker
|
|
147
164
|
# SK_NO_PICKER=1 → skip picker (scripted/CI use)
|
|
148
|
-
|
|
165
|
+
# Print/non-interactive mode → skip picker (no menu can be answered)
|
|
166
|
+
if [[ -z "$agent" && "${SK_NO_PICKER:-0}" != "1" && $non_interactive -eq 0 ]]; then
|
|
149
167
|
agent=$(_sk_pick_agent)
|
|
168
|
+
elif [[ -z "$agent" && $non_interactive -eq 1 ]]; then
|
|
169
|
+
# Non-interactive: take env or first agent silently
|
|
170
|
+
agent="${SKAGENT:-${SKCAPSTONE_AGENT:-}}"
|
|
171
|
+
if [[ -z "$agent" ]]; then
|
|
172
|
+
local agents_dir="${SKCAPSTONE_HOME:-$HOME/.skcapstone}/agents"
|
|
173
|
+
if [[ -d "$agents_dir" ]]; then
|
|
174
|
+
agent=$(find "$agents_dir" -mindepth 1 -maxdepth 1 -type d ! -name '*-template' ! -name '.*' -printf '%f\n' | sort | head -1)
|
|
175
|
+
fi
|
|
176
|
+
fi
|
|
150
177
|
fi
|
|
151
178
|
|
|
152
179
|
# Fallback: if picker returned empty (0 agents), just use SKAGENT
|
|
@@ -60,6 +60,7 @@ class DreamingConfig(BaseModel):
|
|
|
60
60
|
idle_threshold_minutes: int = 30
|
|
61
61
|
idle_messages_24h_max: int = 5
|
|
62
62
|
cooldown_hours: float = 2.0
|
|
63
|
+
max_per_day: int = 1
|
|
63
64
|
max_context_memories: int = 20
|
|
64
65
|
max_response_tokens: int = 4096
|
|
65
66
|
request_timeout: int = 120
|
|
@@ -290,6 +291,9 @@ class DreamingEngine:
|
|
|
290
291
|
skipped_reason=f"cooldown ({remaining:.0f}s remaining)"
|
|
291
292
|
)
|
|
292
293
|
|
|
294
|
+
if self._config.max_per_day > 0 and self._dreams_today() >= self._config.max_per_day:
|
|
295
|
+
return DreamResult(skipped_reason=f"max_per_day ({self._config.max_per_day}) reached")
|
|
296
|
+
|
|
293
297
|
# Gather memories (may be diversified)
|
|
294
298
|
diversity_forced = self._should_force_diversity()
|
|
295
299
|
if diversity_forced:
|
|
@@ -395,6 +399,22 @@ class DreamingEngine:
|
|
|
395
399
|
# Default: consider idle (safe for first run)
|
|
396
400
|
return True
|
|
397
401
|
|
|
402
|
+
def _dreams_today(self) -> int:
|
|
403
|
+
"""Count how many dreams have already run today (UTC calendar day)."""
|
|
404
|
+
today = datetime.now(timezone.utc).date()
|
|
405
|
+
log = self._load_dream_log()
|
|
406
|
+
count = 0
|
|
407
|
+
for entry in log:
|
|
408
|
+
ts = entry.get("dreamed_at", "")
|
|
409
|
+
if not ts or entry.get("skipped_reason"):
|
|
410
|
+
continue
|
|
411
|
+
try:
|
|
412
|
+
if datetime.fromisoformat(ts).astimezone(timezone.utc).date() == today:
|
|
413
|
+
count += 1
|
|
414
|
+
except (ValueError, TypeError):
|
|
415
|
+
pass
|
|
416
|
+
return count
|
|
417
|
+
|
|
398
418
|
def cooldown_remaining(self) -> float:
|
|
399
419
|
"""Seconds remaining until the next dream is allowed."""
|
|
400
420
|
state = self._load_state()
|