loki-mode 5.35.0 → 5.37.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.
package/autonomy/run.sh CHANGED
@@ -22,20 +22,32 @@
22
22
  # LOKI_SKIP_PREREQS - Skip prerequisite checks (default: false)
23
23
  # LOKI_DASHBOARD - Enable web dashboard (default: true)
24
24
  # LOKI_DASHBOARD_PORT - Dashboard port (default: 57374)
25
+ # LOKI_TLS_CERT - Path to PEM certificate (enables HTTPS for dashboard)
26
+ # LOKI_TLS_KEY - Path to PEM private key (enables HTTPS for dashboard)
25
27
  #
26
28
  # Resource Monitoring (prevents system overload):
27
29
  # LOKI_RESOURCE_CHECK_INTERVAL - Check resources every N seconds (default: 300 = 5min)
28
30
  # LOKI_RESOURCE_CPU_THRESHOLD - CPU % threshold to warn (default: 80)
29
31
  # LOKI_RESOURCE_MEM_THRESHOLD - Memory % threshold to warn (default: 80)
30
32
  #
33
+ # Budget / Cost Limits (opt-in):
34
+ # LOKI_BUDGET_LIMIT - Max USD spend before auto-pause (default: empty = unlimited)
35
+ # Example: "50.00" pauses session when estimated cost >= $50
36
+ #
31
37
  # Security & Autonomy Controls (Enterprise):
32
38
  # LOKI_STAGED_AUTONOMY - Require approval before execution (default: false)
33
- # LOKI_AUDIT_LOG - Enable audit logging (default: false)
39
+ # LOKI_AUDIT_LOG - Enable audit logging (default: true)
40
+ # LOKI_AUDIT_DISABLED - Disable audit logging (default: false)
34
41
  # LOKI_MAX_PARALLEL_AGENTS - Limit concurrent agent spawning (default: 10)
35
42
  # LOKI_SANDBOX_MODE - Run in sandboxed container (default: false, requires Docker)
36
43
  # LOKI_ALLOWED_PATHS - Comma-separated paths agents can modify (default: all)
37
44
  # LOKI_BLOCKED_COMMANDS - Comma-separated blocked shell commands (default: rm -rf /)
38
45
  #
46
+ # OIDC / SSO Authentication (optional, works alongside token auth):
47
+ # LOKI_OIDC_ISSUER - OIDC issuer URL (e.g., https://accounts.google.com)
48
+ # LOKI_OIDC_CLIENT_ID - OIDC client/application ID
49
+ # LOKI_OIDC_AUDIENCE - Expected JWT audience (default: same as client_id)
50
+ #
39
51
  # SDLC Phase Controls (all enabled by default, set to 'false' to skip):
40
52
  # LOKI_PHASE_UNIT_TESTS - Run unit tests (default: true)
41
53
  # LOKI_PHASE_API_TESTS - Functional API testing (default: true)
@@ -122,6 +134,10 @@
122
134
  # Security (Enterprise):
123
135
  # LOKI_PROMPT_INJECTION - Enable HUMAN_INPUT.md processing (default: false)
124
136
  # Set to "true" only in trusted environments
137
+ #
138
+ # Process Supervision (opt-in):
139
+ # LOKI_WATCHDOG - Enable process health monitoring (default: false)
140
+ # LOKI_WATCHDOG_INTERVAL - Check interval in seconds (default: 30)
125
141
  #===============================================================================
126
142
  #
127
143
  # Compatibility: bash 3.2+ (macOS default), bash 4+ (Linux), WSL
@@ -461,17 +477,25 @@ RESOURCE_CHECK_INTERVAL=${LOKI_RESOURCE_CHECK_INTERVAL:-300} # Check every 5 mi
461
477
  RESOURCE_CPU_THRESHOLD=${LOKI_RESOURCE_CPU_THRESHOLD:-80} # CPU % threshold
462
478
  RESOURCE_MEM_THRESHOLD=${LOKI_RESOURCE_MEM_THRESHOLD:-80} # Memory % threshold
463
479
 
480
+ # Budget / Cost Limit (opt-in, empty = unlimited)
481
+ BUDGET_LIMIT=${LOKI_BUDGET_LIMIT:-""} # USD amount, e.g., "50.00"
482
+
464
483
  # Background Mode
465
484
  BACKGROUND_MODE=${LOKI_BACKGROUND:-false} # Run in background
466
485
 
467
486
  # Security & Autonomy Controls
468
487
  STAGED_AUTONOMY=${LOKI_STAGED_AUTONOMY:-false} # Require plan approval
469
- AUDIT_LOG_ENABLED=${LOKI_AUDIT_LOG:-false} # Enable audit logging
488
+ AUDIT_LOG_ENABLED=${LOKI_AUDIT_LOG:-true} # Enable audit logging (on by default)
470
489
  MAX_PARALLEL_AGENTS=${LOKI_MAX_PARALLEL_AGENTS:-10} # Limit concurrent agents
471
490
  SANDBOX_MODE=${LOKI_SANDBOX_MODE:-false} # Docker sandbox mode
472
491
  ALLOWED_PATHS=${LOKI_ALLOWED_PATHS:-""} # Empty = all paths allowed
473
492
  BLOCKED_COMMANDS=${LOKI_BLOCKED_COMMANDS:-"rm -rf /,dd if=,mkfs,:(){ :|:& };:"}
474
493
 
494
+ # Process Supervision (opt-in)
495
+ WATCHDOG_ENABLED=${LOKI_WATCHDOG:-"false"} # Enable process health monitoring
496
+ WATCHDOG_INTERVAL=${LOKI_WATCHDOG_INTERVAL:-30} # Check interval in seconds
497
+ LAST_WATCHDOG_CHECK=0
498
+
475
499
  STATUS_MONITOR_PID=""
476
500
  DASHBOARD_PID=""
477
501
  RESOURCE_MONITOR_PID=""
@@ -740,6 +764,57 @@ get_iteration_duration_ms() {
740
764
  fi
741
765
  }
742
766
 
767
+ #===============================================================================
768
+ # API Key Validation
769
+ # Validates required API key is set for the selected provider.
770
+ # Supports Docker/K8s secret file mounts as fallback.
771
+ #===============================================================================
772
+
773
+ validate_api_keys() {
774
+ local provider="${LOKI_PROVIDER:-claude}"
775
+ local key_var=""
776
+
777
+ case "$provider" in
778
+ claude) key_var="ANTHROPIC_API_KEY" ;;
779
+ codex) key_var="OPENAI_API_KEY" ;;
780
+ gemini) key_var="GOOGLE_API_KEY" ;;
781
+ esac
782
+
783
+ if [[ -z "$key_var" ]]; then
784
+ return 0
785
+ fi
786
+
787
+ local key_value="${!key_var:-}"
788
+
789
+ # Try loading from secret file mounts (Docker/K8s)
790
+ if [[ -z "$key_value" ]]; then
791
+ local lower_name
792
+ lower_name=$(echo "$key_var" | tr '[:upper:]' '[:lower:]')
793
+ for mount_path in /run/secrets /var/run/secrets; do
794
+ if [[ -f "$mount_path/$lower_name" ]]; then
795
+ key_value=$(cat "$mount_path/$lower_name" 2>/dev/null | tr -d '[:space:]')
796
+ if [[ -n "$key_value" ]]; then
797
+ export "$key_var=$key_value"
798
+ log_info "Loaded $key_var from secret file: $mount_path/$lower_name"
799
+ break
800
+ fi
801
+ fi
802
+ done
803
+ fi
804
+
805
+ if [[ -z "$key_value" ]]; then
806
+ log_error "Required API key $key_var is not set for provider $provider"
807
+ log_error "Set via environment variable or Docker/K8s secret mount"
808
+ return 1
809
+ fi
810
+
811
+ # Log masked key for debugging
812
+ local masked="${key_value:0:8}...${key_value: -4}"
813
+ log_info "API key $key_var: $masked (${#key_value} chars)"
814
+
815
+ return 0
816
+ }
817
+
743
818
  #===============================================================================
744
819
  # Complexity Tier Detection (Auto-Claude pattern)
745
820
  #===============================================================================
@@ -2097,6 +2172,19 @@ EOF
2097
2172
  # Write pricing.json with provider-specific model rates
2098
2173
  _write_pricing_json
2099
2174
 
2175
+ # Write budget.json if a budget limit is configured
2176
+ if [ -n "$BUDGET_LIMIT" ]; then
2177
+ cat > ".loki/metrics/budget.json" << BUDGET_EOF
2178
+ {
2179
+ "limit": $BUDGET_LIMIT,
2180
+ "budget_limit": $BUDGET_LIMIT,
2181
+ "budget_used": 0,
2182
+ "created_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
2183
+ }
2184
+ BUDGET_EOF
2185
+ log_info "Budget limit set: \$$BUDGET_LIMIT"
2186
+ fi
2187
+
2100
2188
  log_info "Loki directory initialized: .loki/"
2101
2189
  }
2102
2190
 
@@ -2412,6 +2500,12 @@ write_dashboard_state() {
2412
2500
  council_state=$(cat ".loki/council/state.json" 2>/dev/null || echo '{"enabled":false}')
2413
2501
  fi
2414
2502
 
2503
+ # Get budget status (if configured)
2504
+ local budget_json="null"
2505
+ if [ -f ".loki/metrics/budget.json" ]; then
2506
+ budget_json=$(cat ".loki/metrics/budget.json" 2>/dev/null || echo "null")
2507
+ fi
2508
+
2415
2509
  # Write comprehensive JSON state (atomic via temp file + mv)
2416
2510
  local project_name=$(basename "$(pwd)")
2417
2511
  local project_path=$(pwd)
@@ -2463,7 +2557,8 @@ write_dashboard_state() {
2463
2557
  "procedural": $procedural_count
2464
2558
  },
2465
2559
  "qualityGates": $quality_gates,
2466
- "council": $council_state
2560
+ "council": $council_state,
2561
+ "budget": $budget_json
2467
2562
  }
2468
2563
  EOF
2469
2564
  mv "$_tmp_state" "$output_file"
@@ -3333,7 +3428,24 @@ check_staged_autonomy() {
3333
3428
  }
3334
3429
 
3335
3430
  check_command_allowed() {
3336
- # Check if a command is in the blocked list
3431
+ # Check if a command string contains any blocked patterns from BLOCKED_COMMANDS.
3432
+ #
3433
+ # SECURITY NOTE: This function is intentionally NOT called by run.sh because
3434
+ # run.sh does not directly execute arbitrary shell commands from user or agent
3435
+ # input. Command execution is handled by the AI CLI's own permission model:
3436
+ # - Claude Code: --dangerously-skip-permissions (with its own allowlist)
3437
+ # - Codex CLI: --full-auto or exec --dangerously-bypass-approvals-and-sandbox
3438
+ # - Gemini CLI: --approval-mode=yolo
3439
+ #
3440
+ # HUMAN_INPUT.md content is injected as a text prompt to the AI agent (not
3441
+ # executed as a shell command), and is already guarded by:
3442
+ # - LOKI_PROMPT_INJECTION=false by default (disabled unless explicitly enabled)
3443
+ # - Symlink rejection (prevents path traversal attacks)
3444
+ # - 1MB file size limit
3445
+ #
3446
+ # This function is retained as a utility for external callers (sandbox.sh,
3447
+ # custom hooks, or user scripts) that may need to validate commands against
3448
+ # the BLOCKED_COMMANDS list before execution.
3337
3449
  local command="$1"
3338
3450
 
3339
3451
  IFS=',' read -ra BLOCKED_ARRAY <<< "$BLOCKED_COMMANDS"
@@ -4623,10 +4735,20 @@ start_dashboard() {
4623
4735
  export LOKI_DASHBOARD_HOST="127.0.0.1"
4624
4736
  export LOKI_PROJECT_PATH="$project_path"
4625
4737
 
4738
+ # Determine URL scheme based on TLS configuration
4739
+ local url_scheme="http"
4740
+ local tls_env=""
4741
+ if [ -n "${LOKI_TLS_CERT:-}" ] && [ -n "${LOKI_TLS_KEY:-}" ]; then
4742
+ url_scheme="https"
4743
+ tls_env="LOKI_TLS_CERT=${LOKI_TLS_CERT} LOKI_TLS_KEY=${LOKI_TLS_KEY}"
4744
+ log_info "TLS enabled for dashboard"
4745
+ fi
4746
+
4626
4747
  # Start the FastAPI dashboard server
4627
4748
  # Dashboard module is at project root (parent of autonomy/)
4628
4749
  # LOKI_SKILL_DIR tells server.py where to find static files
4629
- LOKI_SKILL_DIR="${SCRIPT_DIR%/*}" PYTHONPATH="${SCRIPT_DIR%/*}" nohup python3 -m dashboard.server > "$log_file" 2>&1 &
4750
+ LOKI_TLS_CERT="${LOKI_TLS_CERT:-}" LOKI_TLS_KEY="${LOKI_TLS_KEY:-}" \
4751
+ LOKI_SKILL_DIR="${SCRIPT_DIR%/*}" PYTHONPATH="${SCRIPT_DIR%/*}" nohup python3 -m dashboard.server > "$log_file" 2>&1 &
4630
4752
  DASHBOARD_PID=$!
4631
4753
 
4632
4754
  # Save PID for later cleanup
@@ -4641,11 +4763,11 @@ start_dashboard() {
4641
4763
 
4642
4764
  if kill -0 "$DASHBOARD_PID" 2>/dev/null; then
4643
4765
  log_info "Dashboard started (PID: $DASHBOARD_PID)"
4644
- log_info "Dashboard: ${CYAN}http://127.0.0.1:$DASHBOARD_PORT/${NC}"
4766
+ log_info "Dashboard: ${CYAN}${url_scheme}://127.0.0.1:$DASHBOARD_PORT/${NC}"
4645
4767
 
4646
4768
  # Open in browser (macOS)
4647
4769
  if [[ "$OSTYPE" == "darwin"* ]]; then
4648
- open "http://127.0.0.1:$DASHBOARD_PORT/" 2>/dev/null || true
4770
+ open "${url_scheme}://127.0.0.1:$DASHBOARD_PORT/" 2>/dev/null || true
4649
4771
  fi
4650
4772
  return 0
4651
4773
  else
@@ -4882,6 +5004,180 @@ is_completed() {
4882
5004
  return 1
4883
5005
  }
4884
5006
 
5007
+ # Check if estimated cost has exceeded the budget limit
5008
+ # Returns 0 (exceeded) or 1 (within budget / no limit set)
5009
+ check_budget_limit() {
5010
+ [[ -z "$BUDGET_LIMIT" ]] && return 1 # No limit set
5011
+
5012
+ # Validate BUDGET_LIMIT is a valid number (prevent shell injection)
5013
+ if ! python3 -c "float('${BUDGET_LIMIT//[^0-9.]/}')" 2>/dev/null; then
5014
+ log_error "BUDGET_LIMIT is not a valid number: $BUDGET_LIMIT"
5015
+ return 1
5016
+ fi
5017
+
5018
+ local current_cost=0
5019
+ local efficiency_dir=".loki/metrics/efficiency"
5020
+
5021
+ # Calculate cost from per-iteration efficiency files (same source as /api/cost)
5022
+ if [ -d "$efficiency_dir" ]; then
5023
+ current_cost=$(python3 -c "
5024
+ import json, glob
5025
+ total = 0.0
5026
+ pricing = {
5027
+ 'opus': {'input': 5.00, 'output': 25.00},
5028
+ 'sonnet': {'input': 3.00, 'output': 15.00},
5029
+ 'haiku': {'input': 1.00, 'output': 5.00},
5030
+ 'gpt-5.3-codex': {'input': 1.50, 'output': 12.00},
5031
+ 'gemini-3-pro': {'input': 1.25, 'output': 10.00},
5032
+ 'gemini-3-flash': {'input': 0.10, 'output': 0.40},
5033
+ }
5034
+ for f in glob.glob('${efficiency_dir}/*.json'):
5035
+ try:
5036
+ d = json.load(open(f))
5037
+ cost = d.get('cost_usd')
5038
+ if cost is not None:
5039
+ total += float(cost)
5040
+ else:
5041
+ model = d.get('model', 'sonnet').lower()
5042
+ p = pricing.get(model, pricing['sonnet'])
5043
+ inp = d.get('input_tokens', 0)
5044
+ out = d.get('output_tokens', 0)
5045
+ total += (inp / 1_000_000) * p['input'] + (out / 1_000_000) * p['output']
5046
+ except: pass
5047
+ print(round(total, 4))
5048
+ " 2>/dev/null || echo "0")
5049
+ fi
5050
+
5051
+ # Compare against limit
5052
+ local exceeded
5053
+ exceeded=$(python3 -c "
5054
+ import sys
5055
+ try:
5056
+ cost = float(sys.argv[1])
5057
+ limit = float(sys.argv[2])
5058
+ print(1 if cost >= limit else 0)
5059
+ except (ValueError, IndexError):
5060
+ print(0)
5061
+ " "$current_cost" "$BUDGET_LIMIT" 2>/dev/null || echo "0")
5062
+
5063
+ if [[ "$exceeded" == "1" ]]; then
5064
+ log_warn "BUDGET LIMIT REACHED: \$${current_cost} >= \$${BUDGET_LIMIT}"
5065
+ touch ".loki/PAUSE"
5066
+ mkdir -p ".loki/signals"
5067
+ echo "{\"type\":\"BUDGET_EXCEEDED\",\"limit\":${BUDGET_LIMIT},\"current\":${current_cost},\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" > ".loki/signals/BUDGET_EXCEEDED"
5068
+ # Update budget.json with latest usage
5069
+ cat > ".loki/metrics/budget.json" << BUDGETUPD_EOF
5070
+ {
5071
+ "limit": $BUDGET_LIMIT,
5072
+ "budget_limit": $BUDGET_LIMIT,
5073
+ "budget_used": $current_cost,
5074
+ "exceeded": true,
5075
+ "exceeded_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
5076
+ }
5077
+ BUDGETUPD_EOF
5078
+ emit_event_json "budget_exceeded" \
5079
+ "limit=${BUDGET_LIMIT}" \
5080
+ "current=${current_cost}" \
5081
+ "iteration=$ITERATION_COUNT"
5082
+ return 0
5083
+ fi
5084
+
5085
+ # Update budget.json with current usage (not exceeded)
5086
+ if [ -n "$current_cost" ] && [ "$current_cost" != "0" ]; then
5087
+ cat > ".loki/metrics/budget.json" << BUDGETUPD_EOF
5088
+ {
5089
+ "limit": $BUDGET_LIMIT,
5090
+ "budget_limit": $BUDGET_LIMIT,
5091
+ "budget_used": $current_cost,
5092
+ "exceeded": false
5093
+ }
5094
+ BUDGETUPD_EOF
5095
+ fi
5096
+
5097
+ return 1
5098
+ }
5099
+
5100
+ #===============================================================================
5101
+ # Watchdog: Process Supervision and Health Monitoring
5102
+ # Opt-in via LOKI_WATCHDOG=true. Detects crashed dashboard and agent processes.
5103
+ #===============================================================================
5104
+
5105
+ watchdog_check() {
5106
+ [[ "$WATCHDOG_ENABLED" != "true" ]] && return 0
5107
+
5108
+ # Check dashboard health
5109
+ local dashboard_pid_file=".loki/dashboard/dashboard.pid"
5110
+ if [[ -f "$dashboard_pid_file" ]]; then
5111
+ local dpid
5112
+ dpid=$(cat "$dashboard_pid_file" 2>/dev/null)
5113
+ if [[ -n "$dpid" ]] && ! kill -0 "$dpid" 2>/dev/null; then
5114
+ log_warn "WATCHDOG: Dashboard process $dpid is dead"
5115
+ emit_event_json "watchdog_alert" \
5116
+ "process=dashboard" \
5117
+ "pid=$dpid" \
5118
+ "action=detected_dead"
5119
+
5120
+ # Auto-restart dashboard if it was previously running
5121
+ if [[ "${ENABLE_DASHBOARD:-true}" == "true" ]]; then
5122
+ log_info "WATCHDOG: Restarting dashboard..."
5123
+ start_dashboard
5124
+ fi
5125
+ fi
5126
+ fi
5127
+
5128
+ # Check for zombie/dead agents
5129
+ local agents_file=".loki/state/agents.json"
5130
+ if [[ -f "$agents_file" ]]; then
5131
+ local dead_count=0
5132
+ local agent_pids
5133
+ agent_pids=$(python3 -c "
5134
+ import json, sys
5135
+ try:
5136
+ agents = json.load(open('$agents_file'))
5137
+ for a in agents:
5138
+ pid = a.get('pid')
5139
+ status = a.get('status', '')
5140
+ if pid and status not in ('terminated', 'completed', 'failed', 'crashed'):
5141
+ print(f\"{pid}:{a.get('id','unknown')}\")
5142
+ except Exception:
5143
+ pass
5144
+ " 2>/dev/null || true)
5145
+
5146
+ if [[ -n "$agent_pids" ]]; then
5147
+ while IFS=: read -r apid aid; do
5148
+ [[ -z "$apid" ]] && continue
5149
+ if ! kill -0 "$apid" 2>/dev/null; then
5150
+ dead_count=$((dead_count + 1))
5151
+ log_warn "WATCHDOG: Agent $aid (PID $apid) is dead"
5152
+ # Update agent status in agents.json
5153
+ python3 -c "
5154
+ import json
5155
+ try:
5156
+ with open('$agents_file', 'r') as f:
5157
+ agents = json.load(f)
5158
+ for a in agents:
5159
+ if str(a.get('pid')) == '$apid':
5160
+ a['status'] = 'crashed'
5161
+ a['crashed_at'] = '$(date -u +%Y-%m-%dT%H:%M:%SZ)'
5162
+ with open('$agents_file', 'w') as f:
5163
+ json.dump(agents, f, indent=2)
5164
+ except Exception:
5165
+ pass
5166
+ " 2>/dev/null || true
5167
+ fi
5168
+ done <<< "$agent_pids"
5169
+
5170
+ if [[ $dead_count -gt 0 ]]; then
5171
+ emit_event_json "watchdog_alert" \
5172
+ "process=agents" \
5173
+ "dead_count=$dead_count"
5174
+ fi
5175
+ fi
5176
+ fi
5177
+
5178
+ return 0
5179
+ }
5180
+
4885
5181
  # Check if completion promise is fulfilled in log output
4886
5182
  check_completion_promise() {
4887
5183
  local log_file="$1"
@@ -5454,6 +5750,9 @@ run_autonomous() {
5454
5750
  log_info "Base wait: ${BASE_WAIT}s"
5455
5751
  log_info "Max wait: ${MAX_WAIT}s"
5456
5752
  log_info "Autonomy mode: $AUTONOMY_MODE"
5753
+ if [ -n "$BUDGET_LIMIT" ]; then
5754
+ log_info "Budget limit: \$$BUDGET_LIMIT"
5755
+ fi
5457
5756
  # Only show Claude-specific features for Claude provider
5458
5757
  if [ "${PROVIDER_NAME:-claude}" = "claude" ]; then
5459
5758
  log_info "Prompt repetition (Haiku): $PROMPT_REPETITION"
@@ -5493,6 +5792,23 @@ run_autonomous() {
5493
5792
  2) return 0 ;; # STOP requested
5494
5793
  esac
5495
5794
 
5795
+ # Check budget limit (creates PAUSE file if exceeded)
5796
+ if check_budget_limit; then
5797
+ log_warn "Session paused due to budget limit. Remove .loki/PAUSE to resume."
5798
+ save_state $retry "budget_exceeded" 0
5799
+ continue # Will hit PAUSE check on next iteration
5800
+ fi
5801
+
5802
+ # Watchdog: periodic process health check (opt-in via LOKI_WATCHDOG=true)
5803
+ if [[ "$WATCHDOG_ENABLED" == "true" ]]; then
5804
+ local now_epoch
5805
+ now_epoch=$(date +%s)
5806
+ if (( now_epoch - LAST_WATCHDOG_CHECK >= WATCHDOG_INTERVAL )); then
5807
+ watchdog_check
5808
+ LAST_WATCHDOG_CHECK=$now_epoch
5809
+ fi
5810
+ fi
5811
+
5496
5812
  # Auto-track iteration start (for dashboard task queue)
5497
5813
  track_iteration_start "$ITERATION_COUNT" "$prd_path"
5498
5814
 
@@ -6372,6 +6688,11 @@ main() {
6372
6688
  fi
6373
6689
  fi
6374
6690
 
6691
+ # Validate API keys for the selected provider
6692
+ if ! validate_api_keys; then
6693
+ exit 1
6694
+ fi
6695
+
6375
6696
  # Check prerequisites (unless skipped)
6376
6697
  if [ "$SKIP_PREREQS" != "true" ]; then
6377
6698
  if ! check_prerequisites; then
@@ -727,7 +727,7 @@ docker_desktop_sandbox_prompt() {
727
727
  docker sandbox exec -w "$PROJECT_DIR" \
728
728
  ${DESKTOP_ENV_ARGS[@]+"${DESKTOP_ENV_ARGS[@]}"} \
729
729
  "$DESKTOP_SANDBOX_NAME" \
730
- bash -c "echo '$message' > ${PROJECT_DIR}/.loki/HUMAN_INPUT.md"
730
+ bash -c "printf '%s\n' \"\$1\" > ${PROJECT_DIR}/.loki/HUMAN_INPUT.md" -- "$message"
731
731
 
732
732
  log_success "Prompt sent to sandbox"
733
733
  }
package/autonomy/serve.sh CHANGED
@@ -19,6 +19,8 @@
19
19
  # LOKI_DASHBOARD_PORT Port (default: 57374)
20
20
  # LOKI_DASHBOARD_HOST Host (default: localhost)
21
21
  # LOKI_API_TOKEN API token for remote access
22
+ # LOKI_TLS_CERT Path to PEM certificate (enables HTTPS)
23
+ # LOKI_TLS_KEY Path to PEM private key (enables HTTPS)
22
24
  #===============================================================================
23
25
 
24
26
  set -euo pipefail
@@ -30,6 +32,8 @@ API_DIR="$PROJECT_DIR/api"
30
32
  # Default configuration
31
33
  PORT="${LOKI_DASHBOARD_PORT:-57374}"
32
34
  HOST="${LOKI_DASHBOARD_HOST:-localhost}"
35
+ TLS_CERT="${LOKI_TLS_CERT:-}"
36
+ TLS_KEY="${LOKI_TLS_KEY:-}"
33
37
  CORS="true"
34
38
  AUTH="true"
35
39
 
@@ -65,6 +69,8 @@ Usage:
65
69
  Options:
66
70
  --port, -p <port> Port to listen on (default: 57374)
67
71
  --host <host> Host to bind to (default: localhost)
72
+ --tls-cert <path> Path to PEM certificate (enables HTTPS)
73
+ --tls-key <path> Path to PEM private key (enables HTTPS)
68
74
  --no-cors Disable CORS
69
75
  --no-auth Disable authentication
70
76
  --generate-token Generate a new API token
@@ -74,6 +80,8 @@ Environment Variables:
74
80
  LOKI_DASHBOARD_PORT Port (overridden by --port)
75
81
  LOKI_DASHBOARD_HOST Host (overridden by --host)
76
82
  LOKI_API_TOKEN API token for remote access
83
+ LOKI_TLS_CERT Path to PEM certificate (overridden by --tls-cert)
84
+ LOKI_TLS_KEY Path to PEM private key (overridden by --tls-key)
77
85
  LOKI_DIR Loki installation directory
78
86
  LOKI_DEBUG Enable debug output
79
87
 
@@ -88,6 +96,9 @@ Examples:
88
96
  export LOKI_API_TOKEN=\$(loki serve --generate-token)
89
97
  loki serve --host 0.0.0.0
90
98
 
99
+ # Enable HTTPS with TLS
100
+ loki serve --tls-cert /path/to/cert.pem --tls-key /path/to/key.pem
101
+
91
102
  # Connect from another machine
92
103
  curl -H "Authorization: Bearer \$TOKEN" http://server:57374/health
93
104
 
@@ -131,6 +142,14 @@ main() {
131
142
  HOST="$2"
132
143
  shift 2
133
144
  ;;
145
+ --tls-cert)
146
+ TLS_CERT="$2"
147
+ shift 2
148
+ ;;
149
+ --tls-key)
150
+ TLS_KEY="$2"
151
+ shift 2
152
+ ;;
134
153
  --no-cors)
135
154
  CORS="false"
136
155
  shift
@@ -195,6 +214,12 @@ main() {
195
214
  [ "$CORS" = "false" ] && server_args+=("--no-cors")
196
215
  [ "$AUTH" = "false" ] && server_args+=("--no-auth")
197
216
 
217
+ # Pass TLS options if configured
218
+ if [ -n "$TLS_CERT" ] && [ -n "$TLS_KEY" ]; then
219
+ server_args+=("--tls-cert" "$TLS_CERT" "--tls-key" "$TLS_KEY")
220
+ log_info "TLS enabled: cert=$TLS_CERT key=$TLS_KEY"
221
+ fi
222
+
198
223
  # Export environment variables
199
224
  export LOKI_DIR="$PROJECT_DIR"
200
225
  export LOKI_VERSION="$(cat "$PROJECT_DIR/VERSION" 2>/dev/null || echo "dev")"
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "5.35.0"
10
+ __version__ = "5.37.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -1,8 +1,8 @@
1
1
  """
2
- Optional Audit Logging Module for Loki Mode Dashboard.
2
+ Audit Logging Module for Loki Mode Dashboard.
3
3
 
4
- Enterprise feature - disabled by default.
5
- Enable with LOKI_ENTERPRISE_AUDIT=true environment variable.
4
+ Enabled by default. Disable with LOKI_AUDIT_DISABLED=true environment variable.
5
+ Legacy env var LOKI_ENTERPRISE_AUDIT=true always enables audit (backward compat).
6
6
 
7
7
  Audit logs: ~/.loki/dashboard/audit/
8
8
  """
@@ -14,7 +14,11 @@ from pathlib import Path
14
14
  from typing import Any, Optional
15
15
 
16
16
  # Configuration
17
- ENTERPRISE_AUDIT_ENABLED = os.environ.get("LOKI_ENTERPRISE_AUDIT", "").lower() in ("true", "1", "yes")
17
+ # Audit is ON by default. Disable with LOKI_AUDIT_DISABLED=true.
18
+ # Backward compat: LOKI_ENTERPRISE_AUDIT=true always forces audit ON.
19
+ _audit_disabled = os.environ.get("LOKI_AUDIT_DISABLED", "").lower() in ("true", "1", "yes")
20
+ _enterprise_force_on = os.environ.get("LOKI_ENTERPRISE_AUDIT", "").lower() in ("true", "1", "yes")
21
+ ENTERPRISE_AUDIT_ENABLED = _enterprise_force_on or (not _audit_disabled)
18
22
  AUDIT_DIR = Path.home() / ".loki" / "dashboard" / "audit"
19
23
 
20
24
  # Log rotation settings
@@ -251,5 +255,5 @@ def get_audit_summary(days: int = 7) -> dict:
251
255
 
252
256
 
253
257
  def is_audit_enabled() -> bool:
254
- """Check if audit logging is enabled."""
258
+ """Check if audit logging is enabled (on by default, disable with LOKI_AUDIT_DISABLED=true)."""
255
259
  return ENTERPRISE_AUDIT_ENABLED