loki-mode 5.50.0 → 5.52.0

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 (73) hide show
  1. package/README.md +2 -2
  2. package/SKILL.md +2 -2
  3. package/VERSION +1 -1
  4. package/autonomy/completion-council.sh +13 -0
  5. package/autonomy/council-v2.sh +302 -0
  6. package/autonomy/loki +92 -64
  7. package/autonomy/run.sh +228 -16
  8. package/dashboard/__init__.py +1 -1
  9. package/dashboard/api_keys.py +363 -0
  10. package/dashboard/api_v2.py +596 -0
  11. package/dashboard/models.py +88 -0
  12. package/dashboard/requirements.txt +8 -7
  13. package/dashboard/runs.py +384 -0
  14. package/dashboard/server.py +4 -0
  15. package/dashboard/tenants.py +281 -0
  16. package/docs/INSTALLATION.md +1 -1
  17. package/docs/alternative-installations.md +3 -3
  18. package/docs/certification/01-core-concepts/lab.md +174 -0
  19. package/docs/certification/01-core-concepts/lesson.md +182 -0
  20. package/docs/certification/01-core-concepts/quiz.md +93 -0
  21. package/docs/certification/02-enterprise-features/lab.md +154 -0
  22. package/docs/certification/02-enterprise-features/lesson.md +202 -0
  23. package/docs/certification/02-enterprise-features/quiz.md +93 -0
  24. package/docs/certification/03-advanced-patterns/lab.md +138 -0
  25. package/docs/certification/03-advanced-patterns/lesson.md +199 -0
  26. package/docs/certification/03-advanced-patterns/quiz.md +93 -0
  27. package/docs/certification/04-production-deployment/lab.md +160 -0
  28. package/docs/certification/04-production-deployment/lesson.md +261 -0
  29. package/docs/certification/04-production-deployment/quiz.md +93 -0
  30. package/docs/certification/05-troubleshooting/lab.md +254 -0
  31. package/docs/certification/05-troubleshooting/lesson.md +266 -0
  32. package/docs/certification/05-troubleshooting/quiz.md +93 -0
  33. package/docs/certification/README.md +80 -0
  34. package/docs/certification/answer-key.md +117 -0
  35. package/docs/certification/certification-exam.md +471 -0
  36. package/docs/certification/sample-prds/microservices-platform.md +100 -0
  37. package/docs/certification/sample-prds/saas-dashboard.md +60 -0
  38. package/docs/certification/sample-prds/todo-app.md +44 -0
  39. package/docs/enterprise/architecture.md +324 -0
  40. package/docs/enterprise/integration-cookbook.md +485 -0
  41. package/docs/enterprise/migration.md +268 -0
  42. package/docs/enterprise/performance.md +290 -0
  43. package/docs/enterprise/sdk-guide.md +557 -0
  44. package/docs/enterprise/security.md +390 -0
  45. package/mcp/__init__.py +1 -1
  46. package/memory/cross_project.py +108 -0
  47. package/memory/knowledge_graph.py +197 -0
  48. package/memory/rag_injector.py +81 -0
  49. package/package.json +2 -2
  50. package/src/audit/subscriber.js +100 -0
  51. package/src/integrations/slack/adapter.js +142 -0
  52. package/src/integrations/slack/blocks.js +87 -0
  53. package/src/integrations/slack/commands.js +58 -0
  54. package/src/integrations/slack/index.js +2 -0
  55. package/src/integrations/slack/webhook-handler.js +32 -0
  56. package/src/integrations/sync-subscriber.js +249 -0
  57. package/src/integrations/teams/adapter.js +154 -0
  58. package/src/integrations/teams/cards.js +165 -0
  59. package/src/integrations/teams/index.js +2 -0
  60. package/src/integrations/teams/webhook.js +15 -0
  61. package/src/observability/otel-bridge.js +297 -0
  62. package/src/plugins/agent-plugin.js +123 -0
  63. package/src/plugins/gate-plugin.js +153 -0
  64. package/src/plugins/index.js +116 -0
  65. package/src/plugins/integration-plugin.js +174 -0
  66. package/src/plugins/loader.js +275 -0
  67. package/src/plugins/mcp-plugin.js +190 -0
  68. package/src/plugins/schemas/agent.json +59 -0
  69. package/src/plugins/schemas/integration.json +62 -0
  70. package/src/plugins/schemas/mcp_tool.json +73 -0
  71. package/src/plugins/schemas/quality_gate.json +52 -0
  72. package/src/plugins/validator.js +297 -0
  73. package/src/policies/check.js +38 -0
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  [![Agent Types](https://img.shields.io/badge/Agent%20Types-41-blue)]()
12
12
  [![Benchmarks](https://img.shields.io/badge/Benchmarks-Infrastructure%20Ready-blue)](benchmarks/)
13
13
 
14
- **Current Version: v5.50.0**
14
+ **Current Version: v5.52.0**
15
15
 
16
16
  **[Autonomi](https://www.autonomi.dev/)** | **[Documentation](https://www.autonomi.dev/docs)** | **[GitHub](https://github.com/asklokesh/loki-mode)**
17
17
 
@@ -85,7 +85,7 @@ gemini
85
85
  ### Verify Installation
86
86
 
87
87
  ```bash
88
- loki --version # Should print 5.50.0
88
+ loki --version # Should print 5.52.0
89
89
  loki doctor # Check skill symlinks and provider availability
90
90
  ```
91
91
 
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes PRD to deployed product with minimal human intervention. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v5.50.0
6
+ # Loki Mode v5.52.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -263,4 +263,4 @@ The following features are documented in skill modules but not yet fully automat
263
263
  | Quality gates 3-reviewer system | Implemented (v5.35.0) | 5 specialist reviewers in `skills/quality-gates.md`; execution in run.sh |
264
264
  | Benchmarks (HumanEval, SWE-bench) | Infrastructure only | Runner scripts and datasets exist in `benchmarks/`; no published results |
265
265
 
266
- **v5.50.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
266
+ **v5.52.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 5.50.0
1
+ 5.52.0
@@ -216,6 +216,19 @@ council_vote() {
216
216
  local vote_dir="$COUNCIL_STATE_DIR/votes/iteration-$ITERATION_COUNT"
217
217
  mkdir -p "$vote_dir"
218
218
 
219
+ # Council v2: true blind review with sycophancy detection
220
+ if [ "${LOKI_COUNCIL_VERSION:-1}" = "2" ]; then
221
+ # Source council v2 if not already loaded
222
+ if ! type council_v2_vote &>/dev/null; then
223
+ source "${BASH_SOURCE[0]%/*}/council-v2.sh"
224
+ fi
225
+ # Gather evidence first (shared function)
226
+ local evidence_file="$vote_dir/evidence.md"
227
+ council_gather_evidence "$evidence_file" "$prd_path"
228
+ council_v2_vote "$prd_path" "$evidence_file" "$vote_dir" "${ITERATION_COUNT:-0}"
229
+ return $?
230
+ fi
231
+
219
232
  log_header "COMPLETION COUNCIL - Iteration $ITERATION_COUNT"
220
233
  log_info "Convening ${COUNCIL_SIZE}-member council..."
221
234
 
@@ -0,0 +1,302 @@
1
+ #!/usr/bin/env bash
2
+ #===============================================================================
3
+ # Council v2 - True blind review with sycophancy detection
4
+ #
5
+ # Upgraded council review system that provides:
6
+ # 1. True blind review (isolated evidence packages, no cross-contamination)
7
+ # 2. Sycophancy detection via statistical analysis
8
+ # 3. Reviewer calibration tracking over time
9
+ # 4. Anti-sycophancy devil's advocate on high sycophancy scores
10
+ #
11
+ # Activated via: LOKI_COUNCIL_VERSION=2
12
+ #
13
+ # Environment Variables:
14
+ # LOKI_COUNCIL_SYCOPHANCY_THRESHOLD - Score above which to trigger devil's advocate (default: 0.6)
15
+ # LOKI_COUNCIL_VERSION - Set to "2" to activate this module
16
+ #
17
+ # Dependencies:
18
+ # - completion-council.sh (sourced by caller for shared functions)
19
+ # - swarm/sycophancy.py (sycophancy detection)
20
+ # - swarm/calibration.py (reviewer calibration)
21
+ #
22
+ #===============================================================================
23
+
24
+ COUNCIL_V2_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
25
+
26
+ #===============================================================================
27
+ # council_v2_vote() -- Main entry point for v2 voting
28
+ #
29
+ # 1. Create isolated evidence packages (no cross-contamination)
30
+ # 2. Launch parallel reviewers with isolated evidence
31
+ # 3. Collect votes independently
32
+ # 4. Run sycophancy detection via Python
33
+ # 5. Run calibration tracking
34
+ # 6. Apply anti-sycophancy if needed (devil's advocate)
35
+ # 7. Return final verdict
36
+ #===============================================================================
37
+
38
+ council_v2_vote() {
39
+ local prd_path="$1"
40
+ local evidence_file="$2"
41
+ local vote_dir="$3"
42
+ local iteration="${4:-0}"
43
+
44
+ local council_size="${COUNCIL_SIZE:-3}"
45
+
46
+ log_header "COMPLETION COUNCIL v2 - Iteration $iteration"
47
+ log_info "Convening ${council_size}-member blind review council..."
48
+
49
+ # Step 1: Create isolated evidence packages
50
+ local review_dirs=()
51
+ for i in $(seq 1 "$council_size"); do
52
+ local review_dir
53
+ review_dir=$(mktemp -d)
54
+ cp "$evidence_file" "$review_dir/evidence.md"
55
+ if [ -n "$prd_path" ] && [ -f "$prd_path" ]; then
56
+ cp "$prd_path" "$review_dir/prd.md"
57
+ fi
58
+ review_dirs+=("$review_dir")
59
+ done
60
+
61
+ # Step 2: Launch parallel reviewers
62
+ local vote_files=()
63
+ for i in $(seq 0 $((council_size - 1))); do
64
+ local role
65
+ case $i in
66
+ 0) role="requirements_verifier" ;;
67
+ 1) role="test_auditor" ;;
68
+ *) role="code_quality_reviewer" ;;
69
+ esac
70
+ local vote_file="${review_dirs[$i]}/vote.json"
71
+ vote_files+=("$vote_file")
72
+
73
+ # Each reviewer gets ONLY their evidence package (no other votes visible)
74
+ council_v2_run_reviewer "$role" "${review_dirs[$i]}" "$vote_file" &
75
+ done
76
+
77
+ # Wait for all reviewers to complete
78
+ wait
79
+
80
+ # Step 3: Collect votes
81
+ local votes_json="["
82
+ local first=true
83
+ local approve_count=0
84
+ local reject_count=0
85
+ for vote_file in "${vote_files[@]}"; do
86
+ if [ -f "$vote_file" ]; then
87
+ local vote_content
88
+ vote_content=$(cat "$vote_file")
89
+ if [ "$first" = "true" ]; then
90
+ first=false
91
+ else
92
+ votes_json="$votes_json,"
93
+ fi
94
+ votes_json="$votes_json$vote_content"
95
+
96
+ local verdict
97
+ verdict=$(echo "$vote_content" | python3 -c "import sys,json; print(json.load(sys.stdin).get('verdict','').upper())" 2>/dev/null || echo "UNKNOWN")
98
+ if [ "$verdict" = "APPROVE" ]; then
99
+ ((approve_count++))
100
+ else
101
+ ((reject_count++))
102
+ fi
103
+ fi
104
+ done
105
+ votes_json="$votes_json]"
106
+
107
+ log_info "Blind review results: $approve_count APPROVE / $reject_count REJECT"
108
+
109
+ # Step 4: Sycophancy detection
110
+ local sycophancy_score
111
+ sycophancy_score=$(python3 -c "
112
+ import sys
113
+ sys.path.insert(0, '${COUNCIL_V2_DIR}/../swarm')
114
+ from sycophancy import detect_sycophancy
115
+ import json
116
+ votes = json.loads(sys.argv[1])
117
+ print('{:.3f}'.format(detect_sycophancy(votes)))
118
+ " "$votes_json" 2>/dev/null || echo "0.000")
119
+
120
+ log_info "Sycophancy score: $sycophancy_score"
121
+
122
+ # Step 5: Anti-sycophancy check
123
+ if [ "$approve_count" -eq "$council_size" ]; then
124
+ local threshold="${LOKI_COUNCIL_SYCOPHANCY_THRESHOLD:-0.6}"
125
+ local should_challenge
126
+ should_challenge=$(python3 -c "print('yes' if float('$sycophancy_score') >= float('$threshold') else 'no')" 2>/dev/null || echo "no")
127
+
128
+ if [ "$should_challenge" = "yes" ]; then
129
+ log_warn "Sycophancy score $sycophancy_score >= $threshold -- adding devil's advocate"
130
+ # Run devil's advocate with fresh evidence (no visibility of other votes)
131
+ local da_dir
132
+ da_dir=$(mktemp -d)
133
+ cp "$evidence_file" "$da_dir/evidence.md"
134
+ [ -n "$prd_path" ] && [ -f "$prd_path" ] && cp "$prd_path" "$da_dir/prd.md"
135
+ local da_vote="$da_dir/vote.json"
136
+ council_v2_run_reviewer "devils_advocate" "$da_dir" "$da_vote"
137
+
138
+ if [ -f "$da_vote" ]; then
139
+ local da_verdict
140
+ da_verdict=$(cat "$da_vote" | python3 -c "import sys,json; print(json.load(sys.stdin).get('verdict','').upper())" 2>/dev/null || echo "REJECT")
141
+ if [ "$da_verdict" = "REJECT" ]; then
142
+ log_warn "Devil's advocate REJECTED unanimous approval"
143
+ approve_count=$((approve_count - 1))
144
+ reject_count=$((reject_count + 1))
145
+ else
146
+ log_info "Devil's advocate confirmed unanimous approval"
147
+ fi
148
+ fi
149
+ rm -rf "$da_dir"
150
+ fi
151
+ fi
152
+
153
+ # Step 6: Calibration tracking
154
+ local final_decision
155
+ if [ "$approve_count" -ge "${COUNCIL_THRESHOLD:-2}" ]; then
156
+ final_decision="approve"
157
+ else
158
+ final_decision="reject"
159
+ fi
160
+
161
+ python3 -c "
162
+ import sys
163
+ sys.path.insert(0, '${COUNCIL_V2_DIR}/../swarm')
164
+ from calibration import CalibrationTracker
165
+ import json
166
+ tracker = CalibrationTracker('.loki/council/calibration.json')
167
+ votes = json.loads(sys.argv[1])
168
+ for i, v in enumerate(votes):
169
+ v['reviewer_id'] = 'reviewer-' + str(i + 1)
170
+ tracker.record_round(int(sys.argv[2]), votes, sys.argv[3])
171
+ tracker.save()
172
+ " "$votes_json" "$iteration" "$final_decision" 2>/dev/null || true
173
+
174
+ # Step 7: Save results
175
+ mkdir -p "$vote_dir"
176
+ echo "$votes_json" > "$vote_dir/all-votes.json"
177
+ cat > "$vote_dir/summary.json" << SUMMARY_EOF
178
+ {
179
+ "version": 2,
180
+ "approve": $approve_count,
181
+ "reject": $reject_count,
182
+ "sycophancy_score": $sycophancy_score,
183
+ "decision": "$final_decision",
184
+ "council_size": $council_size,
185
+ "iteration": $iteration
186
+ }
187
+ SUMMARY_EOF
188
+
189
+ log_info "Council v2 verdict: $approve_count APPROVE / $reject_count REJECT -> $final_decision (sycophancy: $sycophancy_score)"
190
+
191
+ # Emit event for dashboard
192
+ emit_event_json "council_vote" \
193
+ "version=2" \
194
+ "iteration=$iteration" \
195
+ "approve=$approve_count" \
196
+ "reject=$reject_count" \
197
+ "threshold=${COUNCIL_THRESHOLD:-2}" \
198
+ "sycophancy_score=$sycophancy_score" \
199
+ "result=$(echo "$final_decision" | tr '[:lower:]' '[:upper:]')" 2>/dev/null || true
200
+
201
+ # Cleanup isolated review directories
202
+ for dir in "${review_dirs[@]}"; do
203
+ rm -rf "$dir"
204
+ done
205
+
206
+ # Return result
207
+ if [ "$final_decision" = "approve" ]; then
208
+ return 0
209
+ else
210
+ return 1
211
+ fi
212
+ }
213
+
214
+ #===============================================================================
215
+ # council_v2_run_reviewer() -- Run a single isolated reviewer
216
+ #
217
+ # Each reviewer receives only their evidence package and produces a JSON vote.
218
+ # No reviewer can see another reviewer's output (true blind review).
219
+ #===============================================================================
220
+
221
+ council_v2_run_reviewer() {
222
+ local role="$1"
223
+ local review_dir="$2"
224
+ local output_file="$3"
225
+
226
+ # Build review prompt based on role
227
+ local prompt
228
+ case "$role" in
229
+ requirements_verifier)
230
+ prompt="You are a requirements verification reviewer. Review the evidence and PRD below. Check if all requirements are implemented. Output a JSON object with keys: verdict (APPROVE or REJECT), reasoning (string), issues (array of {severity, description})."
231
+ ;;
232
+ test_auditor)
233
+ prompt="You are a test auditor reviewer. Review the evidence below. Check test coverage, test quality, and assertion density. Output a JSON object with keys: verdict (APPROVE or REJECT), reasoning (string), issues (array of {severity, description})."
234
+ ;;
235
+ code_quality_reviewer)
236
+ prompt="You are a code quality reviewer. Review the evidence below. Check for SOLID violations, security issues, performance problems. Output a JSON object with keys: verdict (APPROVE or REJECT), reasoning (string), issues (array of {severity, description})."
237
+ ;;
238
+ devils_advocate)
239
+ prompt="You are a devil's advocate reviewer. Your job is to find reasons this code should NOT be approved. Be skeptical and contrarian. Output a JSON object with keys: verdict (APPROVE or REJECT), reasoning (string), issues (array of {severity, description})."
240
+ ;;
241
+ *)
242
+ prompt="You are a general reviewer. Evaluate project completion. Output a JSON object with keys: verdict (APPROVE or REJECT), reasoning (string), issues (array of {severity, description})."
243
+ ;;
244
+ esac
245
+
246
+ local evidence=""
247
+ [ -f "$review_dir/evidence.md" ] && evidence=$(cat "$review_dir/evidence.md")
248
+ local prd=""
249
+ [ -f "$review_dir/prd.md" ] && prd=$(head -100 "$review_dir/prd.md")
250
+
251
+ # Use the current provider to run the review
252
+ local full_prompt="$prompt
253
+
254
+ ## Evidence
255
+ $evidence
256
+
257
+ ## PRD (first 100 lines)
258
+ $prd
259
+
260
+ Respond ONLY with a valid JSON object. No markdown fencing."
261
+
262
+ local result
263
+ case "${PROVIDER_NAME:-claude}" in
264
+ claude)
265
+ if command -v claude &>/dev/null; then
266
+ result=$(echo "$full_prompt" | claude --model haiku -p 2>/dev/null || echo '{"verdict":"REJECT","reasoning":"review execution failed","issues":[]}')
267
+ else
268
+ result='{"verdict":"REJECT","reasoning":"reviewer CLI unavailable","issues":[]}'
269
+ fi
270
+ ;;
271
+ codex)
272
+ if command -v codex &>/dev/null; then
273
+ result=$(codex exec -q "$full_prompt" 2>/dev/null || echo '{"verdict":"REJECT","reasoning":"review execution failed","issues":[]}')
274
+ else
275
+ result='{"verdict":"REJECT","reasoning":"reviewer CLI unavailable","issues":[]}'
276
+ fi
277
+ ;;
278
+ gemini)
279
+ if command -v gemini &>/dev/null; then
280
+ result=$(echo "$full_prompt" | gemini 2>/dev/null || echo '{"verdict":"REJECT","reasoning":"review execution failed","issues":[]}')
281
+ else
282
+ result='{"verdict":"REJECT","reasoning":"reviewer CLI unavailable","issues":[]}'
283
+ fi
284
+ ;;
285
+ *)
286
+ result='{"verdict":"REJECT","reasoning":"review not supported for this provider","issues":[]}'
287
+ ;;
288
+ esac
289
+
290
+ # Extract JSON from result (handle markdown fencing)
291
+ local extracted
292
+ extracted=$(echo "$result" | sed -n '/^{/,/^}/p' | head -50)
293
+ if [ -z "$extracted" ]; then
294
+ # Try removing markdown fencing
295
+ extracted=$(echo "$result" | sed 's/^```json//;s/^```//' | sed -n '/^{/,/^}/p' | head -50)
296
+ fi
297
+ if [ -z "$extracted" ]; then
298
+ extracted='{"verdict":"REJECT","reasoning":"failed to parse review output","issues":[]}'
299
+ fi
300
+
301
+ echo "$extracted" > "$output_file"
302
+ }
package/autonomy/loki CHANGED
@@ -130,6 +130,81 @@ get_version() {
130
130
  fi
131
131
  }
132
132
 
133
+ # Ensure dashboard Python venv with all deps installed.
134
+ # Uses ~/.loki/dashboard-venv (persistent, writable, survives npm/brew upgrades).
135
+ # Sets DASHBOARD_PYTHON to the venv python3 path on success.
136
+ # Returns 0 on success, 1 on failure.
137
+ ensure_dashboard_venv() {
138
+ local dashboard_venv="$HOME/.loki/dashboard-venv"
139
+ DASHBOARD_PYTHON="python3"
140
+
141
+ # Use existing venv python if available
142
+ if [ -x "${dashboard_venv}/bin/python3" ]; then
143
+ DASHBOARD_PYTHON="${dashboard_venv}/bin/python3"
144
+ fi
145
+
146
+ # Check all required imports
147
+ if "$DASHBOARD_PYTHON" -c "import fastapi; import sqlalchemy; import aiosqlite" 2>/dev/null; then
148
+ return 0
149
+ fi
150
+
151
+ echo -e "${YELLOW}Setting up dashboard virtualenv...${NC}"
152
+
153
+ # Create venv if missing or broken
154
+ if ! [ -x "${dashboard_venv}/bin/python3" ]; then
155
+ # Remove broken venv if exists
156
+ if [ -d "$dashboard_venv" ]; then
157
+ echo -e "${YELLOW}Removing broken dashboard venv...${NC}"
158
+ rm -rf "$dashboard_venv"
159
+ fi
160
+ mkdir -p "$HOME/.loki"
161
+ python3 -m venv "$dashboard_venv" 2>/dev/null || python3.13 -m venv "$dashboard_venv" 2>/dev/null || {
162
+ echo -e "${RED}Failed to create dashboard virtualenv${NC}"
163
+ echo "You may need to install python3-venv:"
164
+ echo " sudo apt install python3-venv (Debian/Ubuntu)"
165
+ echo " brew install python3 (macOS)"
166
+ return 1
167
+ }
168
+ fi
169
+
170
+ DASHBOARD_PYTHON="${dashboard_venv}/bin/python3"
171
+ echo -e "${YELLOW}Installing dashboard dependencies...${NC}"
172
+
173
+ # Try pinned requirements first, then unpinned fallback
174
+ local req_file="${SKILL_DIR}/dashboard/requirements.txt"
175
+ local installed=false
176
+ if [ -f "$req_file" ]; then
177
+ if "${dashboard_venv}/bin/pip" install -r "$req_file" 2>&1 | tail -1; then
178
+ installed=true
179
+ else
180
+ echo -e "${YELLOW}Pinned deps failed, trying unpinned...${NC}"
181
+ fi
182
+ fi
183
+
184
+ if [ "$installed" = false ]; then
185
+ # Install without greenlet first (it may need a C compiler).
186
+ # SQLAlchemy only requires greenlet for sync-in-async; aiosqlite is pure async.
187
+ if ! "${dashboard_venv}/bin/pip" install fastapi uvicorn pydantic websockets sqlalchemy aiosqlite 2>&1 | tail -1; then
188
+ echo -e "${RED}Failed to install dashboard dependencies${NC}"
189
+ echo "Try manually: ${dashboard_venv}/bin/pip install fastapi uvicorn sqlalchemy aiosqlite"
190
+ return 1
191
+ fi
192
+ # Try greenlet separately (optional, needs C compiler on some platforms)
193
+ "${dashboard_venv}/bin/pip" install greenlet 2>/dev/null || true
194
+ fi
195
+
196
+ # Verify imports work after install
197
+ if ! "$DASHBOARD_PYTHON" -c "import fastapi; import sqlalchemy; import aiosqlite" 2>/dev/null; then
198
+ echo -e "${RED}Dashboard dependencies installed but imports still fail${NC}"
199
+ echo "Try removing the venv and retrying:"
200
+ echo " rm -rf ${dashboard_venv}"
201
+ echo " loki dashboard start"
202
+ return 1
203
+ fi
204
+
205
+ return 0
206
+ }
207
+
133
208
  # Check if jq is available (called by functions that need it)
134
209
  require_jq() {
135
210
  if ! command -v jq &> /dev/null; then
@@ -1565,14 +1640,12 @@ cmd_dashboard_start() {
1565
1640
  exit 1
1566
1641
  fi
1567
1642
 
1568
- # Determine python command -- prefer dashboard venv if available
1569
- local python_cmd="python3"
1570
- local dashboard_venv="${SKILL_DIR}/dashboard/.venv"
1571
- if [ -x "${dashboard_venv}/bin/python3" ]; then
1572
- python_cmd="${dashboard_venv}/bin/python3"
1573
- elif ! command -v python3 &> /dev/null; then
1574
- python_cmd="python"
1643
+ # Set up dashboard venv and python command
1644
+ if ! ensure_dashboard_venv; then
1645
+ echo -e "${RED}Cannot start dashboard without Python dependencies${NC}"
1646
+ return 1
1575
1647
  fi
1648
+ local python_cmd="$DASHBOARD_PYTHON"
1576
1649
 
1577
1650
  # Check if already running
1578
1651
  if [ -f "$DASHBOARD_PID_FILE" ]; then
@@ -1618,34 +1691,6 @@ cmd_dashboard_start() {
1618
1691
  tls_info=" (TLS enabled)"
1619
1692
  fi
1620
1693
 
1621
- # Ensure dashboard Python dependencies via virtualenv (PEP 668 safe)
1622
- if ! "$python_cmd" -c "import fastapi" 2>/dev/null; then
1623
- echo -e "${YELLOW}Setting up dashboard virtualenv...${NC}"
1624
- local req_file="${SKILL_DIR}/dashboard/requirements.txt"
1625
- if ! [ -d "$dashboard_venv" ]; then
1626
- python3 -m venv "$dashboard_venv" 2>/dev/null || python3.13 -m venv "$dashboard_venv" 2>/dev/null || true
1627
- fi
1628
- if [ -x "${dashboard_venv}/bin/python3" ]; then
1629
- python_cmd="${dashboard_venv}/bin/python3"
1630
- echo -e "${YELLOW}Installing dashboard dependencies into venv...${NC}"
1631
- if [ -f "$req_file" ]; then
1632
- "${dashboard_venv}/bin/pip" install -q -r "$req_file" 2>/dev/null || {
1633
- echo -e "${YELLOW}Pinned deps failed, installing core deps...${NC}"
1634
- "${dashboard_venv}/bin/pip" install -q fastapi uvicorn pydantic websockets 2>/dev/null || true
1635
- }
1636
- else
1637
- "${dashboard_venv}/bin/pip" install -q fastapi uvicorn pydantic websockets 2>/dev/null || true
1638
- fi
1639
- else
1640
- # Fallback: try direct pip (may fail on PEP 668 systems)
1641
- pip3 install -q fastapi uvicorn pydantic websockets 2>/dev/null || pip install -q fastapi uvicorn pydantic websockets 2>/dev/null || {
1642
- echo -e "${RED}Failed to install dashboard dependencies${NC}"
1643
- echo "Run manually: python3 -m venv ${dashboard_venv} && ${dashboard_venv}/bin/pip install fastapi uvicorn pydantic websockets"
1644
- exit 1
1645
- }
1646
- fi
1647
- fi
1648
-
1649
1694
  echo -e "${GREEN}Starting dashboard server...${NC}"
1650
1695
  echo -e "${CYAN}Host:${NC} $host"
1651
1696
  echo -e "${CYAN}Port:${NC} $port"
@@ -3347,35 +3392,12 @@ cmd_api() {
3347
3392
  fi
3348
3393
  fi
3349
3394
 
3350
- # Ensure dashboard Python dependencies via virtualenv (PEP 668 safe)
3351
- local dashboard_venv="${SKILL_DIR}/dashboard/.venv"
3352
- local api_python="python3"
3353
- if [ -x "${dashboard_venv}/bin/python3" ]; then
3354
- api_python="${dashboard_venv}/bin/python3"
3355
- fi
3356
- if ! "$api_python" -c "import fastapi" 2>/dev/null; then
3357
- echo -e "${YELLOW}Setting up dashboard virtualenv...${NC}"
3358
- if ! [ -d "$dashboard_venv" ]; then
3359
- python3 -m venv "$dashboard_venv" 2>/dev/null || python3.13 -m venv "$dashboard_venv" 2>/dev/null || true
3360
- fi
3361
- if [ -x "${dashboard_venv}/bin/python3" ]; then
3362
- api_python="${dashboard_venv}/bin/python3"
3363
- local req_file="${SKILL_DIR}/dashboard/requirements.txt"
3364
- if [ -f "$req_file" ]; then
3365
- "${dashboard_venv}/bin/pip" install -q -r "$req_file" 2>/dev/null || {
3366
- "${dashboard_venv}/bin/pip" install -q fastapi uvicorn pydantic websockets 2>/dev/null || true
3367
- }
3368
- else
3369
- "${dashboard_venv}/bin/pip" install -q fastapi uvicorn pydantic websockets 2>/dev/null || true
3370
- fi
3371
- else
3372
- pip3 install -q fastapi uvicorn pydantic websockets 2>/dev/null || {
3373
- echo -e "${RED}Failed to install dashboard dependencies${NC}"
3374
- echo "Run manually: python3 -m venv ${dashboard_venv} && ${dashboard_venv}/bin/pip install fastapi uvicorn pydantic websockets"
3375
- exit 1
3376
- }
3377
- fi
3395
+ # Ensure dashboard Python dependencies
3396
+ if ! ensure_dashboard_venv; then
3397
+ echo -e "${RED}Cannot start dashboard without Python dependencies${NC}"
3398
+ exit 1
3378
3399
  fi
3400
+ local api_python="$DASHBOARD_PYTHON"
3379
3401
 
3380
3402
  # Start server
3381
3403
  mkdir -p "$LOKI_DIR/logs" "$LOKI_DIR/dashboard"
@@ -3941,7 +3963,13 @@ SESSEOF
3941
3963
  sleep 1
3942
3964
  fi
3943
3965
 
3944
- LOKI_DIR="$LOKI_DIR" PYTHONPATH="$SKILL_DIR" python3 -m uvicorn dashboard.server:app \
3966
+ # Set up dashboard venv
3967
+ if ! ensure_dashboard_venv; then
3968
+ echo -e "${YELLOW}Warning: Dashboard dependencies not available, continuing without dashboard${NC}"
3969
+ fi
3970
+ local demo_python="$DASHBOARD_PYTHON"
3971
+
3972
+ LOKI_DIR="$LOKI_DIR" PYTHONPATH="$SKILL_DIR" "$demo_python" -m uvicorn dashboard.server:app \
3945
3973
  --host 127.0.0.1 --port 57374 --log-level warning &>/dev/null &
3946
3974
  local dash_pid=$!
3947
3975
  echo "$dash_pid" > "$LOKI_DIR/dashboard/dashboard.pid"