loki-mode 6.4.0 → 6.6.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/README.md +7 -5
- package/SKILL.md +8 -4
- package/VERSION +1 -1
- package/autonomy/completion-council.sh +24 -0
- package/autonomy/council-v2.sh +14 -0
- package/autonomy/hooks/migration-hooks.sh +6 -2
- package/autonomy/loki +221 -67
- package/autonomy/run.sh +164 -22
- package/autonomy/sandbox.sh +48 -22
- package/autonomy/telemetry.sh +20 -7
- package/dashboard/__init__.py +1 -1
- package/dashboard/control.py +1 -1
- package/dashboard/migration_engine.py +1 -1
- package/dashboard/server.py +27 -12
- package/docs/INSTALLATION.md +2 -2
- package/events/emit.sh +1 -1
- package/mcp/__init__.py +1 -1
- package/mcp/server.py +11 -6
- package/memory/embeddings.py +15 -6
- package/memory/engine.py +56 -0
- package/memory/schemas.py +6 -2
- package/memory/storage.py +5 -2
- package/memory/token_economics.py +4 -1
- package/package.json +1 -1
- package/providers/aider.sh +129 -0
- package/providers/cline.sh +122 -0
- package/providers/loader.sh +1 -1
- package/skills/model-selection.md +3 -1
- package/skills/providers.md +140 -17
package/autonomy/run.sh
CHANGED
|
@@ -1137,6 +1137,20 @@ validate_api_keys() {
|
|
|
1137
1137
|
claude) key_var="ANTHROPIC_API_KEY" ;;
|
|
1138
1138
|
codex) key_var="OPENAI_API_KEY" ;;
|
|
1139
1139
|
gemini) key_var="GOOGLE_API_KEY" ;;
|
|
1140
|
+
cline) # Cline manages its own keys via `cline auth`
|
|
1141
|
+
if ! command -v cline &>/dev/null; then
|
|
1142
|
+
log_error "Cline CLI not found. Install: npm install -g cline"
|
|
1143
|
+
return 1
|
|
1144
|
+
fi
|
|
1145
|
+
return 0
|
|
1146
|
+
;;
|
|
1147
|
+
aider) # Aider manages keys via env vars or .aider.conf.yml
|
|
1148
|
+
if ! command -v aider &>/dev/null; then
|
|
1149
|
+
log_error "Aider not found. Install: pip install aider-chat"
|
|
1150
|
+
return 1
|
|
1151
|
+
fi
|
|
1152
|
+
return 0
|
|
1153
|
+
;;
|
|
1140
1154
|
esac
|
|
1141
1155
|
|
|
1142
1156
|
if [[ -z "$key_var" ]]; then
|
|
@@ -1390,6 +1404,14 @@ get_provider_tier_param() {
|
|
|
1390
1404
|
*) echo "medium" ;;
|
|
1391
1405
|
esac
|
|
1392
1406
|
;;
|
|
1407
|
+
cline)
|
|
1408
|
+
# Cline uses single externally-configured model
|
|
1409
|
+
echo "${LOKI_CLINE_MODEL:-default}"
|
|
1410
|
+
;;
|
|
1411
|
+
aider)
|
|
1412
|
+
# Aider uses single externally-configured model
|
|
1413
|
+
echo "${LOKI_AIDER_MODEL:-claude-3.7-sonnet}"
|
|
1414
|
+
;;
|
|
1393
1415
|
*)
|
|
1394
1416
|
echo "development"
|
|
1395
1417
|
;;
|
|
@@ -2177,6 +2199,15 @@ spawn_worktree_session() {
|
|
|
2177
2199
|
invoke_gemini "Loki Mode: $task_prompt. Read .loki/CONTINUITY.md for context." \
|
|
2178
2200
|
>> "$log_file" 2>&1
|
|
2179
2201
|
;;
|
|
2202
|
+
cline)
|
|
2203
|
+
# Cline supports parallel instances
|
|
2204
|
+
invoke_cline "Loki Mode: $task_prompt. Read .loki/CONTINUITY.md for context." \
|
|
2205
|
+
>> "$log_file" 2>&1
|
|
2206
|
+
;;
|
|
2207
|
+
aider)
|
|
2208
|
+
# Aider has known issues with parallel - skip
|
|
2209
|
+
log_warn "Aider does not support parallel sessions, skipping"
|
|
2210
|
+
;;
|
|
2180
2211
|
*)
|
|
2181
2212
|
log_error "Unknown provider: ${PROVIDER_NAME}"
|
|
2182
2213
|
return 1
|
|
@@ -2269,6 +2300,12 @@ Output ONLY the resolved file content with no conflict markers. No explanations.
|
|
|
2269
2300
|
# Uses invoke_gemini_capture for rate limit fallback to flash model
|
|
2270
2301
|
resolution=$(invoke_gemini_capture "$conflict_prompt" 2>/dev/null)
|
|
2271
2302
|
;;
|
|
2303
|
+
cline)
|
|
2304
|
+
resolution=$(invoke_cline_capture "$conflict_prompt" 2>/dev/null)
|
|
2305
|
+
;;
|
|
2306
|
+
aider)
|
|
2307
|
+
resolution=$(invoke_aider_capture "$conflict_prompt" 2>/dev/null)
|
|
2308
|
+
;;
|
|
2272
2309
|
*)
|
|
2273
2310
|
log_error "Unknown provider: ${PROVIDER_NAME}"
|
|
2274
2311
|
return 1
|
|
@@ -2515,6 +2552,12 @@ check_prerequisites() {
|
|
|
2515
2552
|
# TODO: Verify official Gemini CLI package name when available
|
|
2516
2553
|
log_info "Install: npm install -g @google/gemini-cli (or visit https://ai.google.dev/)"
|
|
2517
2554
|
;;
|
|
2555
|
+
cline)
|
|
2556
|
+
log_info "Install: npm install -g cline"
|
|
2557
|
+
;;
|
|
2558
|
+
aider)
|
|
2559
|
+
log_info "Install: pip install aider-chat"
|
|
2560
|
+
;;
|
|
2518
2561
|
*)
|
|
2519
2562
|
log_info "Install the $cli_name CLI for your provider"
|
|
2520
2563
|
;;
|
|
@@ -2828,6 +2871,62 @@ invoke_gemini_capture() {
|
|
|
2828
2871
|
echo "$output"
|
|
2829
2872
|
}
|
|
2830
2873
|
|
|
2874
|
+
#===============================================================================
|
|
2875
|
+
# Cline Invocation (Tier 2 - Near-Full)
|
|
2876
|
+
#===============================================================================
|
|
2877
|
+
|
|
2878
|
+
# Invoke Cline CLI in autonomous mode
|
|
2879
|
+
# Usage: invoke_cline "prompt" [additional args...]
|
|
2880
|
+
invoke_cline() {
|
|
2881
|
+
local prompt="$1"
|
|
2882
|
+
shift
|
|
2883
|
+
local model="${LOKI_CLINE_MODEL:-}"
|
|
2884
|
+
local model_flag=""
|
|
2885
|
+
[[ -n "$model" ]] && model_flag="-m $model"
|
|
2886
|
+
# shellcheck disable=SC2086
|
|
2887
|
+
cline -y $model_flag "$prompt" "$@" 2>&1
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
# Invoke Cline and capture output (for variable assignment)
|
|
2891
|
+
# Usage: result=$(invoke_cline_capture "prompt")
|
|
2892
|
+
invoke_cline_capture() {
|
|
2893
|
+
local prompt="$1"
|
|
2894
|
+
shift
|
|
2895
|
+
local model="${LOKI_CLINE_MODEL:-}"
|
|
2896
|
+
local model_flag=""
|
|
2897
|
+
[[ -n "$model" ]] && model_flag="-m $model"
|
|
2898
|
+
# shellcheck disable=SC2086
|
|
2899
|
+
cline -y $model_flag "$prompt" "$@" 2>&1
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
#===============================================================================
|
|
2903
|
+
# Aider Invocation (Tier 3 - Degraded, 18+ Providers)
|
|
2904
|
+
#===============================================================================
|
|
2905
|
+
|
|
2906
|
+
# Invoke Aider in autonomous single-instruction mode
|
|
2907
|
+
# Usage: invoke_aider "prompt" [additional args...]
|
|
2908
|
+
invoke_aider() {
|
|
2909
|
+
local prompt="$1"
|
|
2910
|
+
shift
|
|
2911
|
+
local model="${LOKI_AIDER_MODEL:-claude-3.7-sonnet}"
|
|
2912
|
+
local extra_flags="${LOKI_AIDER_FLAGS:-}"
|
|
2913
|
+
# shellcheck disable=SC2086
|
|
2914
|
+
aider --message "$prompt" --yes-always --no-auto-commits \
|
|
2915
|
+
--model "$model" $extra_flags "$@" 2>&1
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
# Invoke Aider and capture output (for variable assignment)
|
|
2919
|
+
# Usage: result=$(invoke_aider_capture "prompt")
|
|
2920
|
+
invoke_aider_capture() {
|
|
2921
|
+
local prompt="$1"
|
|
2922
|
+
shift
|
|
2923
|
+
local model="${LOKI_AIDER_MODEL:-claude-3.7-sonnet}"
|
|
2924
|
+
local extra_flags="${LOKI_AIDER_FLAGS:-}"
|
|
2925
|
+
# shellcheck disable=SC2086
|
|
2926
|
+
aider --message "$prompt" --yes-always --no-auto-commits \
|
|
2927
|
+
--model "$model" $extra_flags "$@" 2>&1
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2831
2930
|
#===============================================================================
|
|
2832
2931
|
# Copy Skill Files to Project Directory
|
|
2833
2932
|
#===============================================================================
|
|
@@ -3343,6 +3442,10 @@ track_iteration_complete() {
|
|
|
3343
3442
|
model_tier="gpt-5.3-codex"
|
|
3344
3443
|
elif [ "${PROVIDER_NAME:-claude}" = "gemini" ]; then
|
|
3345
3444
|
model_tier="gemini-3-pro"
|
|
3445
|
+
elif [ "${PROVIDER_NAME:-claude}" = "cline" ]; then
|
|
3446
|
+
model_tier="${LOKI_CLINE_MODEL:-sonnet}"
|
|
3447
|
+
elif [ "${PROVIDER_NAME:-claude}" = "aider" ]; then
|
|
3448
|
+
model_tier="${LOKI_AIDER_MODEL:-claude-3.7-sonnet}"
|
|
3346
3449
|
fi
|
|
3347
3450
|
local phase="${LAST_KNOWN_PHASE:-}"
|
|
3348
3451
|
[ -z "$phase" ] && phase=$(python3 -c "import json; print(json.load(open('.loki/state/orchestrator.json')).get('currentPhase', 'unknown'))" 2>/dev/null || echo "unknown")
|
|
@@ -5143,6 +5246,14 @@ BUILD_PROMPT
|
|
|
5143
5246
|
invoke_gemini_capture "$prompt_text" \
|
|
5144
5247
|
> "$review_output" 2>/dev/null
|
|
5145
5248
|
;;
|
|
5249
|
+
cline)
|
|
5250
|
+
invoke_cline_capture "$prompt_text" \
|
|
5251
|
+
> "$review_output" 2>/dev/null
|
|
5252
|
+
;;
|
|
5253
|
+
aider)
|
|
5254
|
+
invoke_aider_capture "$prompt_text" \
|
|
5255
|
+
> "$review_output" 2>/dev/null
|
|
5256
|
+
;;
|
|
5146
5257
|
*)
|
|
5147
5258
|
echo "VERDICT: PASS" > "$review_output"
|
|
5148
5259
|
echo "FINDINGS:" >> "$review_output"
|
|
@@ -5347,6 +5458,18 @@ OVERALL_RISK: HIGH or MEDIUM or LOW"
|
|
|
5347
5458
|
> "$result_file" 2>/dev/null || true
|
|
5348
5459
|
fi
|
|
5349
5460
|
;;
|
|
5461
|
+
cline)
|
|
5462
|
+
if command -v cline &>/dev/null; then
|
|
5463
|
+
invoke_cline_capture "$adversarial_prompt" \
|
|
5464
|
+
> "$result_file" 2>/dev/null || true
|
|
5465
|
+
fi
|
|
5466
|
+
;;
|
|
5467
|
+
aider)
|
|
5468
|
+
if command -v aider &>/dev/null; then
|
|
5469
|
+
invoke_aider_capture "$adversarial_prompt" \
|
|
5470
|
+
> "$result_file" 2>/dev/null || true
|
|
5471
|
+
fi
|
|
5472
|
+
;;
|
|
5350
5473
|
*)
|
|
5351
5474
|
echo "ATTACK_VECTORS: None (unknown provider)" > "$result_file"
|
|
5352
5475
|
echo "OVERALL_RISK: LOW" >> "$result_file"
|
|
@@ -6063,7 +6186,7 @@ detect_rate_limit() {
|
|
|
6063
6186
|
claude)
|
|
6064
6187
|
wait_secs=$(parse_claude_reset_time "$log_file")
|
|
6065
6188
|
;;
|
|
6066
|
-
codex|gemini|*)
|
|
6189
|
+
codex|gemini|cline|aider|*)
|
|
6067
6190
|
# No provider-specific reset time format known
|
|
6068
6191
|
# Fall through to generic parsing
|
|
6069
6192
|
;;
|
|
@@ -7434,12 +7557,13 @@ run_autonomous() {
|
|
|
7434
7557
|
audit_agent_action "cli_invoke" "Starting iteration $ITERATION_COUNT" "provider=${PROVIDER_NAME:-claude},tier=$CURRENT_TIER"
|
|
7435
7558
|
|
|
7436
7559
|
# Provider-specific invocation with dynamic tier selection
|
|
7560
|
+
local exit_code=0
|
|
7437
7561
|
case "${PROVIDER_NAME:-claude}" in
|
|
7438
7562
|
claude)
|
|
7439
7563
|
# Claude: Full features with stream-json output and agent tracking
|
|
7440
7564
|
# Uses dynamic tier for model selection based on RARV phase
|
|
7441
7565
|
# Pass tier to Python via environment for dashboard display
|
|
7442
|
-
LOKI_CURRENT_MODEL="$tier_param" \
|
|
7566
|
+
{ LOKI_CURRENT_MODEL="$tier_param" \
|
|
7443
7567
|
claude --dangerously-skip-permissions --model "$tier_param" -p "$prompt" \
|
|
7444
7568
|
--output-format stream-json --verbose 2>&1 | \
|
|
7445
7569
|
tee -a "$log_file" "$agent_log" | \
|
|
@@ -7647,7 +7771,7 @@ if __name__ == "__main__":
|
|
|
7647
7771
|
except BrokenPipeError:
|
|
7648
7772
|
sys.exit(0)
|
|
7649
7773
|
'
|
|
7650
|
-
|
|
7774
|
+
} && exit_code=0 || exit_code=$?
|
|
7651
7775
|
;;
|
|
7652
7776
|
|
|
7653
7777
|
codex)
|
|
@@ -7655,10 +7779,10 @@ if __name__ == "__main__":
|
|
|
7655
7779
|
# Uses positional prompt after exec subcommand
|
|
7656
7780
|
# Note: Effort is set via env var, not CLI flag
|
|
7657
7781
|
# Uses dynamic tier from RARV phase (tier_param already set above)
|
|
7658
|
-
CODEX_MODEL_REASONING_EFFORT="$tier_param" \
|
|
7782
|
+
{ CODEX_MODEL_REASONING_EFFORT="$tier_param" \
|
|
7659
7783
|
codex exec --full-auto \
|
|
7660
|
-
"$prompt" 2>&1 | tee -a "$log_file" "$agent_log"
|
|
7661
|
-
|
|
7784
|
+
"$prompt" 2>&1 | tee -a "$log_file" "$agent_log"; \
|
|
7785
|
+
} && exit_code=0 || exit_code=$?
|
|
7662
7786
|
;;
|
|
7663
7787
|
|
|
7664
7788
|
gemini)
|
|
@@ -7672,8 +7796,8 @@ if __name__ == "__main__":
|
|
|
7672
7796
|
# Try primary model, fallback on rate limit
|
|
7673
7797
|
local tmp_output
|
|
7674
7798
|
tmp_output=$(mktemp)
|
|
7675
|
-
gemini --approval-mode=yolo --model "$model" "$prompt" < /dev/null 2>&1 | tee "$tmp_output" | tee -a "$log_file" "$agent_log"
|
|
7676
|
-
|
|
7799
|
+
{ gemini --approval-mode=yolo --model "$model" "$prompt" < /dev/null 2>&1 | tee "$tmp_output" | tee -a "$log_file" "$agent_log"; \
|
|
7800
|
+
} && exit_code=0 || exit_code=$?
|
|
7677
7801
|
|
|
7678
7802
|
if [[ $exit_code -ne 0 ]] && grep -qiE "(rate.?limit|429|quota|resource.?exhausted)" "$tmp_output"; then
|
|
7679
7803
|
log_warn "Rate limit hit on $model, falling back to $fallback"
|
|
@@ -7684,6 +7808,21 @@ if __name__ == "__main__":
|
|
|
7684
7808
|
rm -f "$tmp_output"
|
|
7685
7809
|
;;
|
|
7686
7810
|
|
|
7811
|
+
cline)
|
|
7812
|
+
# Cline: Tier 2 - near-full mode with subagents and MCP
|
|
7813
|
+
echo "[loki] Cline model: ${LOKI_CLINE_MODEL:-default}, tier: $tier_param" >> "$log_file"
|
|
7814
|
+
echo "[loki] Cline model: ${LOKI_CLINE_MODEL:-default}, tier: $tier_param" >> "$agent_log"
|
|
7815
|
+
{ invoke_cline "$prompt" 2>&1 | tee -a "$log_file" "$agent_log"; \
|
|
7816
|
+
} && exit_code=0 || exit_code=$?
|
|
7817
|
+
;;
|
|
7818
|
+
aider)
|
|
7819
|
+
# Aider: Tier 3 - degraded mode, 18+ providers
|
|
7820
|
+
echo "[loki] Aider model: ${LOKI_AIDER_MODEL:-claude-3.7-sonnet}, tier: $tier_param" >> "$log_file"
|
|
7821
|
+
echo "[loki] Aider model: ${LOKI_AIDER_MODEL:-claude-3.7-sonnet}, tier: $tier_param" >> "$agent_log"
|
|
7822
|
+
{ invoke_aider "$prompt" 2>&1 | tee -a "$log_file" "$agent_log"; \
|
|
7823
|
+
} && exit_code=0 || exit_code=$?
|
|
7824
|
+
;;
|
|
7825
|
+
|
|
7687
7826
|
*)
|
|
7688
7827
|
log_error "Unknown provider: ${PROVIDER_NAME:-unknown}"
|
|
7689
7828
|
local exit_code=1
|
|
@@ -8143,17 +8282,18 @@ except (json.JSONDecodeError, OSError): pass
|
|
|
8143
8282
|
stop_dashboard
|
|
8144
8283
|
stop_status_monitor
|
|
8145
8284
|
kill_all_registered
|
|
8146
|
-
rm -f
|
|
8285
|
+
rm -f "$loki_dir/loki.pid" "$loki_dir/PAUSE" 2>/dev/null
|
|
8147
8286
|
# Clean up per-session PID file if running with session ID
|
|
8148
8287
|
if [ -n "${LOKI_SESSION_ID:-}" ]; then
|
|
8149
|
-
rm -f "
|
|
8288
|
+
rm -f "$loki_dir/sessions/${LOKI_SESSION_ID}/loki.pid" 2>/dev/null
|
|
8150
8289
|
fi
|
|
8151
8290
|
# Mark session.json as stopped
|
|
8152
|
-
if [ -f "
|
|
8153
|
-
python3 -c "
|
|
8154
|
-
import json
|
|
8291
|
+
if [ -f "$loki_dir/session.json" ]; then
|
|
8292
|
+
_LOKI_SESSION_FILE="$loki_dir/session.json" python3 -c "
|
|
8293
|
+
import json, os
|
|
8294
|
+
sf = os.environ['_LOKI_SESSION_FILE']
|
|
8155
8295
|
try:
|
|
8156
|
-
with open(
|
|
8296
|
+
with open(sf, 'r+') as f:
|
|
8157
8297
|
d = json.load(f); d['status'] = 'stopped'
|
|
8158
8298
|
f.seek(0); f.truncate(); json.dump(d, f)
|
|
8159
8299
|
except (json.JSONDecodeError, OSError): pass
|
|
@@ -8277,7 +8417,7 @@ main() {
|
|
|
8277
8417
|
fi
|
|
8278
8418
|
shift 2
|
|
8279
8419
|
else
|
|
8280
|
-
log_error "--provider requires a value (claude, codex, gemini)"
|
|
8420
|
+
log_error "--provider requires a value (claude, codex, gemini, cline, aider)"
|
|
8281
8421
|
exit 1
|
|
8282
8422
|
fi
|
|
8283
8423
|
;;
|
|
@@ -8311,7 +8451,7 @@ main() {
|
|
|
8311
8451
|
echo "Options:"
|
|
8312
8452
|
echo " --parallel Enable git worktree-based parallel workflows"
|
|
8313
8453
|
echo " --allow-haiku Enable Haiku model for fast tier (default: disabled)"
|
|
8314
|
-
echo " --provider <name> Provider: claude (default), codex, gemini"
|
|
8454
|
+
echo " --provider <name> Provider: claude (default), codex, gemini, cline, aider"
|
|
8315
8455
|
echo " --bg, --background Run in background mode"
|
|
8316
8456
|
echo " --interactive-prd Interactive PRD pre-flight analysis"
|
|
8317
8457
|
echo " --help, -h Show this help message"
|
|
@@ -8736,17 +8876,19 @@ main() {
|
|
|
8736
8876
|
fi
|
|
8737
8877
|
stop_dashboard
|
|
8738
8878
|
stop_status_monitor
|
|
8739
|
-
|
|
8879
|
+
local loki_dir="${TARGET_DIR:-.}/.loki"
|
|
8880
|
+
rm -f "$loki_dir/loki.pid" 2>/dev/null
|
|
8740
8881
|
# Clean up per-session PID file if running with session ID
|
|
8741
8882
|
if [ -n "${LOKI_SESSION_ID:-}" ]; then
|
|
8742
|
-
rm -f "
|
|
8883
|
+
rm -f "$loki_dir/sessions/${LOKI_SESSION_ID}/loki.pid" 2>/dev/null
|
|
8743
8884
|
fi
|
|
8744
8885
|
# Mark session.json as stopped
|
|
8745
|
-
if [ -f "
|
|
8746
|
-
python3 -c "
|
|
8747
|
-
import json
|
|
8886
|
+
if [ -f "$loki_dir/session.json" ]; then
|
|
8887
|
+
_LOKI_SESSION_FILE="$loki_dir/session.json" python3 -c "
|
|
8888
|
+
import json, os
|
|
8889
|
+
sf = os.environ['_LOKI_SESSION_FILE']
|
|
8748
8890
|
try:
|
|
8749
|
-
with open(
|
|
8891
|
+
with open(sf, 'r+') as f:
|
|
8750
8892
|
d = json.load(f); d['status'] = 'stopped'
|
|
8751
8893
|
f.seek(0); f.truncate(); json.dump(d, f)
|
|
8752
8894
|
except (json.JSONDecodeError, OSError): pass
|
package/autonomy/sandbox.sh
CHANGED
|
@@ -68,28 +68,32 @@ PROMPT_INJECTION_ENABLED="${LOKI_PROMPT_INJECTION:-false}"
|
|
|
68
68
|
|
|
69
69
|
# Preset definitions: "host_path:container_path:mode"
|
|
70
70
|
# Container runs as user 'loki' (UID 1000), so mount to /home/loki/
|
|
71
|
-
declare -A
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
)
|
|
71
|
+
# Uses functions instead of declare -A for bash 3.2 compatibility (macOS default)
|
|
72
|
+
_get_mount_preset() {
|
|
73
|
+
case "$1" in
|
|
74
|
+
gh) echo "$HOME/.config/gh:/home/loki/.config/gh:ro" ;;
|
|
75
|
+
git) echo "$HOME/.gitconfig:/home/loki/.gitconfig:ro" ;;
|
|
76
|
+
ssh) echo "$HOME/.ssh/known_hosts:/home/loki/.ssh/known_hosts:ro" ;;
|
|
77
|
+
aws) echo "$HOME/.aws:/home/loki/.aws:ro" ;;
|
|
78
|
+
azure) echo "$HOME/.azure:/home/loki/.azure:ro" ;;
|
|
79
|
+
kube) echo "$HOME/.kube:/home/loki/.kube:ro" ;;
|
|
80
|
+
terraform) echo "$HOME/.terraform.d:/home/loki/.terraform.d:ro" ;;
|
|
81
|
+
gcloud) echo "$HOME/.config/gcloud:/home/loki/.config/gcloud:ro" ;;
|
|
82
|
+
npm) echo "$HOME/.npmrc:/home/loki/.npmrc:ro" ;;
|
|
83
|
+
*) echo "" ;;
|
|
84
|
+
esac
|
|
85
|
+
}
|
|
84
86
|
|
|
85
87
|
# Environment variables auto-passed per preset (comma-separated)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
)
|
|
88
|
+
_get_env_preset() {
|
|
89
|
+
case "$1" in
|
|
90
|
+
aws) echo "AWS_REGION,AWS_PROFILE,AWS_DEFAULT_REGION" ;;
|
|
91
|
+
azure) echo "AZURE_SUBSCRIPTION_ID,AZURE_TENANT_ID" ;;
|
|
92
|
+
gcloud) echo "GOOGLE_PROJECT,GOOGLE_REGION,GCLOUD_PROJECT" ;;
|
|
93
|
+
terraform) echo "TF_VAR_*" ;;
|
|
94
|
+
*) echo "" ;;
|
|
95
|
+
esac
|
|
96
|
+
}
|
|
93
97
|
|
|
94
98
|
#===============================================================================
|
|
95
99
|
# Utility Functions
|
|
@@ -160,6 +164,14 @@ warn_missing_api_keys() {
|
|
|
160
164
|
log_warn "GOOGLE_API_KEY not set - Gemini commands will fail inside container"
|
|
161
165
|
fi
|
|
162
166
|
;;
|
|
167
|
+
cline)
|
|
168
|
+
log_info "Cline manages its own API keys via 'cline auth' - ensure authentication is configured"
|
|
169
|
+
;;
|
|
170
|
+
aider)
|
|
171
|
+
if [[ -z "${OPENAI_API_KEY:-}" ]] && [[ -z "${ANTHROPIC_API_KEY:-}" ]]; then
|
|
172
|
+
log_warn "No API key set for Aider - set OPENAI_API_KEY or ANTHROPIC_API_KEY"
|
|
173
|
+
fi
|
|
174
|
+
;;
|
|
163
175
|
esac
|
|
164
176
|
}
|
|
165
177
|
|
|
@@ -592,6 +604,18 @@ _desktop_install_provider_cli() {
|
|
|
592
604
|
docker sandbox exec "$sandbox_name" npm install -g @google/gemini-cli 2>&1 | tail -1
|
|
593
605
|
fi
|
|
594
606
|
;;
|
|
607
|
+
cline)
|
|
608
|
+
if ! docker sandbox exec "$sandbox_name" which cline &>/dev/null 2>&1; then
|
|
609
|
+
log_info "Installing Cline CLI in sandbox (one-time)..."
|
|
610
|
+
docker sandbox exec "$sandbox_name" npm install -g cline 2>&1 | tail -1
|
|
611
|
+
fi
|
|
612
|
+
;;
|
|
613
|
+
aider)
|
|
614
|
+
if ! docker sandbox exec "$sandbox_name" which aider &>/dev/null 2>&1; then
|
|
615
|
+
log_info "Installing Aider in sandbox (one-time)..."
|
|
616
|
+
docker sandbox exec "$sandbox_name" pip install aider-chat 2>&1 | tail -1
|
|
617
|
+
fi
|
|
618
|
+
;;
|
|
595
619
|
# claude is pre-installed in the sandbox template
|
|
596
620
|
esac
|
|
597
621
|
}
|
|
@@ -836,7 +860,8 @@ except: pass
|
|
|
836
860
|
while IFS= read -r name; do
|
|
837
861
|
[[ -z "$name" ]] && continue
|
|
838
862
|
|
|
839
|
-
local preset_value
|
|
863
|
+
local preset_value
|
|
864
|
+
preset_value="$(_get_mount_preset "$name")"
|
|
840
865
|
if [[ -z "$preset_value" ]]; then
|
|
841
866
|
log_warn "Unknown Docker mount preset: $name"
|
|
842
867
|
continue
|
|
@@ -861,7 +886,8 @@ except: pass
|
|
|
861
886
|
fi
|
|
862
887
|
|
|
863
888
|
# Add associated env vars
|
|
864
|
-
local env_list
|
|
889
|
+
local env_list
|
|
890
|
+
env_list="$(_get_env_preset "$name")"
|
|
865
891
|
if [[ -n "$env_list" ]]; then
|
|
866
892
|
IFS=',' read -ra env_names <<< "$env_list"
|
|
867
893
|
local env_name
|
package/autonomy/telemetry.sh
CHANGED
|
@@ -52,17 +52,30 @@ loki_telemetry() {
|
|
|
52
52
|
os_name=$(uname -s 2>/dev/null || echo "unknown")
|
|
53
53
|
arch=$(uname -m 2>/dev/null || echo "unknown")
|
|
54
54
|
|
|
55
|
-
# Build
|
|
56
|
-
local
|
|
55
|
+
# Build JSON payload safely using Python to prevent injection
|
|
56
|
+
local extra_args=""
|
|
57
57
|
for arg in "$@"; do
|
|
58
|
-
|
|
59
|
-
local val="${arg#*=}"
|
|
60
|
-
extra_props="${extra_props},\"${key}\":\"${val}\""
|
|
58
|
+
extra_args="${extra_args}${extra_args:+ }${arg}"
|
|
61
59
|
done
|
|
62
60
|
|
|
63
61
|
local payload
|
|
64
|
-
payload=$(
|
|
65
|
-
|
|
62
|
+
payload=$(python3 -c "
|
|
63
|
+
import json, sys
|
|
64
|
+
props = {'os': sys.argv[1], 'arch': sys.argv[2], 'version': sys.argv[3], 'channel': sys.argv[4]}
|
|
65
|
+
for arg in sys.argv[5:]:
|
|
66
|
+
if '=' in arg:
|
|
67
|
+
k, v = arg.split('=', 1)
|
|
68
|
+
props[k] = v
|
|
69
|
+
print(json.dumps({'api_key': '$LOKI_POSTHOG_KEY', 'event': sys.argv[5] if len(sys.argv) > 5 else '', 'distinct_id': '$distinct_id', 'properties': props}))
|
|
70
|
+
" "$os_name" "$arch" "$version" "$channel" $extra_args 2>/dev/null) || return 0
|
|
71
|
+
# Re-inject event and distinct_id properly
|
|
72
|
+
payload=$(python3 -c "
|
|
73
|
+
import json, sys
|
|
74
|
+
d = json.loads(sys.argv[1])
|
|
75
|
+
d['event'] = sys.argv[2]
|
|
76
|
+
d['distinct_id'] = sys.argv[3]
|
|
77
|
+
print(json.dumps(d))
|
|
78
|
+
" "$payload" "$event" "$distinct_id" 2>/dev/null) || return 0
|
|
66
79
|
|
|
67
80
|
(curl -sS --max-time 3 -X POST "${LOKI_POSTHOG_HOST}/capture/" \
|
|
68
81
|
-H "Content-Type: application/json" \
|
package/dashboard/__init__.py
CHANGED
package/dashboard/control.py
CHANGED
|
@@ -132,7 +132,7 @@ class StartRequest(BaseModel):
|
|
|
132
132
|
|
|
133
133
|
def validate_provider(self) -> None:
|
|
134
134
|
"""Validate provider is from allowed list."""
|
|
135
|
-
allowed_providers = ["claude", "codex", "gemini"]
|
|
135
|
+
allowed_providers = ["claude", "codex", "gemini", "cline", "aider"]
|
|
136
136
|
if self.provider not in allowed_providers:
|
|
137
137
|
raise ValueError(f"Invalid provider: {self.provider}. Must be one of: {', '.join(allowed_providers)}")
|
|
138
138
|
|
|
@@ -886,7 +886,7 @@ Summary: {summary}
|
|
|
886
886
|
if len(entries) > 50:
|
|
887
887
|
header = entries[0]
|
|
888
888
|
recent = entries[-50:]
|
|
889
|
-
content = header + "\n## Session:".join(recent)
|
|
889
|
+
content = header + "\n## Session:" + "\n## Session:".join(recent)
|
|
890
890
|
else:
|
|
891
891
|
content = existing
|
|
892
892
|
content += entry
|
package/dashboard/server.py
CHANGED
|
@@ -391,7 +391,7 @@ async def agent_card() -> dict:
|
|
|
391
391
|
"agents": 41,
|
|
392
392
|
"swarms": 8,
|
|
393
393
|
"quality_gates": 9,
|
|
394
|
-
"providers": ["claude", "codex", "gemini"],
|
|
394
|
+
"providers": ["claude", "codex", "gemini", "cline", "aider"],
|
|
395
395
|
"streaming": True,
|
|
396
396
|
"pushNotifications": False,
|
|
397
397
|
"stateTransitionHistory": True,
|
|
@@ -536,9 +536,13 @@ async def get_status() -> StatusResponse:
|
|
|
536
536
|
|
|
537
537
|
# Global session
|
|
538
538
|
if running:
|
|
539
|
+
try:
|
|
540
|
+
_global_pid = int(pid_str) if pid_str else 0
|
|
541
|
+
except (ValueError, TypeError):
|
|
542
|
+
_global_pid = 0
|
|
539
543
|
active_session_list.append(SessionInfo(
|
|
540
544
|
session_id="global",
|
|
541
|
-
pid=
|
|
545
|
+
pid=_global_pid,
|
|
542
546
|
status=status,
|
|
543
547
|
))
|
|
544
548
|
|
|
@@ -1441,7 +1445,7 @@ async def query_audit_logs(
|
|
|
1441
1445
|
action: Optional[str] = None,
|
|
1442
1446
|
resource_type: Optional[str] = None,
|
|
1443
1447
|
resource_id: Optional[str] = None,
|
|
1444
|
-
limit: int = 100,
|
|
1448
|
+
limit: int = Query(default=100, ge=1, le=1000),
|
|
1445
1449
|
offset: int = 0,
|
|
1446
1450
|
):
|
|
1447
1451
|
"""
|
|
@@ -1561,7 +1565,7 @@ async def get_memory_summary():
|
|
|
1561
1565
|
|
|
1562
1566
|
|
|
1563
1567
|
@app.get("/api/memory/episodes")
|
|
1564
|
-
async def list_episodes(limit: int = 50):
|
|
1568
|
+
async def list_episodes(limit: int = Query(default=50, ge=1, le=1000)):
|
|
1565
1569
|
"""List episodic memory entries."""
|
|
1566
1570
|
ep_dir = _get_loki_dir() / "memory" / "episodic"
|
|
1567
1571
|
episodes = []
|
|
@@ -1578,11 +1582,15 @@ async def list_episodes(limit: int = 50):
|
|
|
1578
1582
|
@app.get("/api/memory/episodes/{episode_id}")
|
|
1579
1583
|
async def get_episode(episode_id: str):
|
|
1580
1584
|
"""Get a specific episodic memory entry."""
|
|
1581
|
-
|
|
1585
|
+
loki_dir = _get_loki_dir()
|
|
1586
|
+
ep_dir = loki_dir / "memory" / "episodic"
|
|
1582
1587
|
if not ep_dir.exists():
|
|
1583
1588
|
raise HTTPException(status_code=404, detail="Episode not found")
|
|
1584
1589
|
# Try direct filename match
|
|
1585
1590
|
for f in ep_dir.glob("*.json"):
|
|
1591
|
+
resolved = os.path.realpath(f)
|
|
1592
|
+
if not resolved.startswith(os.path.realpath(str(loki_dir))):
|
|
1593
|
+
raise HTTPException(status_code=403, detail="Access denied")
|
|
1586
1594
|
try:
|
|
1587
1595
|
data = json.loads(f.read_text())
|
|
1588
1596
|
if data.get("id") == episode_id or f.stem == episode_id:
|
|
@@ -1633,10 +1641,14 @@ async def list_skills():
|
|
|
1633
1641
|
@app.get("/api/memory/skills/{skill_id}")
|
|
1634
1642
|
async def get_skill(skill_id: str):
|
|
1635
1643
|
"""Get a specific procedural skill."""
|
|
1636
|
-
|
|
1644
|
+
loki_dir = _get_loki_dir()
|
|
1645
|
+
skills_dir = loki_dir / "memory" / "skills"
|
|
1637
1646
|
if not skills_dir.exists():
|
|
1638
1647
|
raise HTTPException(status_code=404, detail="Skill not found")
|
|
1639
1648
|
for f in skills_dir.glob("*.json"):
|
|
1649
|
+
resolved = os.path.realpath(f)
|
|
1650
|
+
if not resolved.startswith(os.path.realpath(str(loki_dir))):
|
|
1651
|
+
raise HTTPException(status_code=403, detail="Access denied")
|
|
1640
1652
|
try:
|
|
1641
1653
|
data = json.loads(f.read_text())
|
|
1642
1654
|
if data.get("id") == skill_id or f.stem == skill_id:
|
|
@@ -1706,6 +1718,7 @@ def _read_learning_signals(signal_type: Optional[str] = None, limit: int = 50) -
|
|
|
1706
1718
|
(learning/emitter.py). Each file contains a single signal object with fields:
|
|
1707
1719
|
id, type, source, action, timestamp, confidence, outcome, data, context.
|
|
1708
1720
|
"""
|
|
1721
|
+
limit = min(limit, 1000)
|
|
1709
1722
|
signals_dir = _get_loki_dir() / "learning" / "signals"
|
|
1710
1723
|
if not signals_dir.exists() or not signals_dir.is_dir():
|
|
1711
1724
|
return []
|
|
@@ -1825,7 +1838,7 @@ async def get_learning_signals(
|
|
|
1825
1838
|
timeRange: str = "7d",
|
|
1826
1839
|
signalType: Optional[str] = None,
|
|
1827
1840
|
source: Optional[str] = None,
|
|
1828
|
-
limit: int = 50,
|
|
1841
|
+
limit: int = Query(default=50, ge=1, le=1000),
|
|
1829
1842
|
offset: int = 0,
|
|
1830
1843
|
):
|
|
1831
1844
|
"""Get raw learning signals from both events.jsonl and learning signals directory."""
|
|
@@ -2030,7 +2043,7 @@ async def trigger_aggregation():
|
|
|
2030
2043
|
|
|
2031
2044
|
|
|
2032
2045
|
@app.get("/api/learning/preferences")
|
|
2033
|
-
async def get_learning_preferences(limit: int = 50):
|
|
2046
|
+
async def get_learning_preferences(limit: int = Query(default=50, ge=1, le=1000)):
|
|
2034
2047
|
"""Get aggregated user preferences from events and learning signals directory."""
|
|
2035
2048
|
events = _read_events("30d")
|
|
2036
2049
|
prefs = [e for e in events if e.get("type") == "user_preference"]
|
|
@@ -2042,7 +2055,7 @@ async def get_learning_preferences(limit: int = 50):
|
|
|
2042
2055
|
|
|
2043
2056
|
|
|
2044
2057
|
@app.get("/api/learning/errors")
|
|
2045
|
-
async def get_learning_errors(limit: int = 50):
|
|
2058
|
+
async def get_learning_errors(limit: int = Query(default=50, ge=1, le=1000)):
|
|
2046
2059
|
"""Get aggregated error patterns from events and learning signals directory."""
|
|
2047
2060
|
events = _read_events("30d")
|
|
2048
2061
|
errors = [e for e in events if e.get("type") == "error_pattern"]
|
|
@@ -2054,7 +2067,7 @@ async def get_learning_errors(limit: int = 50):
|
|
|
2054
2067
|
|
|
2055
2068
|
|
|
2056
2069
|
@app.get("/api/learning/success")
|
|
2057
|
-
async def get_learning_success(limit: int = 50):
|
|
2070
|
+
async def get_learning_success(limit: int = Query(default=50, ge=1, le=1000)):
|
|
2058
2071
|
"""Get aggregated success patterns from events and learning signals directory."""
|
|
2059
2072
|
events = _read_events("30d")
|
|
2060
2073
|
successes = [e for e in events if e.get("type") == "success_pattern"]
|
|
@@ -2066,7 +2079,7 @@ async def get_learning_success(limit: int = 50):
|
|
|
2066
2079
|
|
|
2067
2080
|
|
|
2068
2081
|
@app.get("/api/learning/tools")
|
|
2069
|
-
async def get_tool_efficiency(limit: int = 50):
|
|
2082
|
+
async def get_tool_efficiency(limit: int = Query(default=50, ge=1, le=1000)):
|
|
2070
2083
|
"""Get tool efficiency rankings from events and learning signals directory."""
|
|
2071
2084
|
events = _read_events("30d")
|
|
2072
2085
|
tools = [e for e in events if e.get("type") == "tool_efficiency"]
|
|
@@ -2442,6 +2455,8 @@ _MODEL_PROVIDERS = {
|
|
|
2442
2455
|
"gpt-5.3-codex": "codex",
|
|
2443
2456
|
"gemini-3-pro": "gemini",
|
|
2444
2457
|
"gemini-3-flash": "gemini",
|
|
2458
|
+
"cline-default": "cline",
|
|
2459
|
+
"aider-default": "aider",
|
|
2445
2460
|
}
|
|
2446
2461
|
|
|
2447
2462
|
|
|
@@ -2505,7 +2520,7 @@ async def get_council_state():
|
|
|
2505
2520
|
|
|
2506
2521
|
|
|
2507
2522
|
@app.get("/api/council/verdicts")
|
|
2508
|
-
async def get_council_verdicts(limit: int = 20):
|
|
2523
|
+
async def get_council_verdicts(limit: int = Query(default=20, ge=1, le=1000)):
|
|
2509
2524
|
"""Get council vote history (decision log)."""
|
|
2510
2525
|
state_file = _get_loki_dir() / "council" / "state.json"
|
|
2511
2526
|
verdicts = []
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
The flagship product of [Autonomi](https://www.autonomi.dev/). Complete installation instructions for all platforms and use cases.
|
|
4
4
|
|
|
5
|
-
**Version:** v6.
|
|
5
|
+
**Version:** v6.6.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
## What's New in v6.
|
|
9
|
+
## What's New in v6.6.0
|
|
10
10
|
|
|
11
11
|
### Dual-Mode Architecture (v6.0.0)
|
|
12
12
|
- `loki run` command for direct autonomous execution
|