loki-mode 6.3.1 → 6.5.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 +1 -1
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/hooks/migration-hooks.sh +6 -2
- package/autonomy/loki +299 -55
- package/autonomy/run.sh +119 -49
- package/autonomy/sandbox.sh +28 -22
- package/autonomy/telemetry.sh +20 -7
- package/dashboard/__init__.py +1 -1
- package/dashboard/migration_engine.py +1 -1
- package/dashboard/server.py +97 -11
- 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/README.md
CHANGED
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 v6.
|
|
6
|
+
# Loki Mode v6.5.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
|
-
**v6.
|
|
266
|
+
**v6.5.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
6.
|
|
1
|
+
6.5.0
|
|
@@ -52,7 +52,7 @@ load_migration_hook_config() {
|
|
|
52
52
|
# Parse YAML config safely using read/declare instead of eval
|
|
53
53
|
while IFS='=' read -r key val; do
|
|
54
54
|
case "$key" in
|
|
55
|
-
HOOK_*)
|
|
55
|
+
HOOK_*) printf -v "$key" '%s' "$val" ;;
|
|
56
56
|
esac
|
|
57
57
|
done < <(python3 -c "
|
|
58
58
|
import sys
|
|
@@ -224,7 +224,7 @@ try:
|
|
|
224
224
|
print(len([s for s in steps if s.get('status') != 'completed']))
|
|
225
225
|
except: print(-1)
|
|
226
226
|
" 2>/dev/null || echo -1)
|
|
227
|
-
[[ "$pending" -
|
|
227
|
+
[[ "$pending" -ne 0 ]] && echo "GATE_BLOCKED: ${pending} steps still pending (or plan missing)" && return 1
|
|
228
228
|
;;
|
|
229
229
|
esac
|
|
230
230
|
|
|
@@ -236,6 +236,10 @@ hook_on_agent_stop() {
|
|
|
236
236
|
local features_path="${LOKI_FEATURES_PATH:-}"
|
|
237
237
|
|
|
238
238
|
[[ "$HOOK_ON_AGENT_STOP_ENABLED" != "true" ]] && return 0
|
|
239
|
+
if [[ -z "$features_path" ]]; then
|
|
240
|
+
echo "HOOK_BLOCKED: LOKI_FEATURES_PATH not set. Cannot verify features."
|
|
241
|
+
return 1
|
|
242
|
+
fi
|
|
239
243
|
[[ ! -f "$features_path" ]] && return 0
|
|
240
244
|
|
|
241
245
|
local failing
|
package/autonomy/loki
CHANGED
|
@@ -806,8 +806,35 @@ cmd_start() {
|
|
|
806
806
|
}
|
|
807
807
|
|
|
808
808
|
# Check if session is running
|
|
809
|
+
# Usage: is_session_running [session_id]
|
|
810
|
+
# Without args: returns true if ANY session (global or per-session) is running
|
|
811
|
+
# With session_id: returns true only if that specific session is running
|
|
809
812
|
is_session_running() {
|
|
810
|
-
|
|
813
|
+
local target_session="${1:-}"
|
|
814
|
+
|
|
815
|
+
if [ -n "$target_session" ]; then
|
|
816
|
+
# Check specific session
|
|
817
|
+
local session_pid_file="$LOKI_DIR/sessions/$target_session/loki.pid"
|
|
818
|
+
if [ -f "$session_pid_file" ]; then
|
|
819
|
+
local pid
|
|
820
|
+
pid=$(cat "$session_pid_file" 2>/dev/null)
|
|
821
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
822
|
+
return 0
|
|
823
|
+
fi
|
|
824
|
+
fi
|
|
825
|
+
# Also check legacy per-run PID file (run-<number>.pid)
|
|
826
|
+
local run_pid_file="$LOKI_DIR/run-${target_session}.pid"
|
|
827
|
+
if [ -f "$run_pid_file" ]; then
|
|
828
|
+
local pid
|
|
829
|
+
pid=$(cat "$run_pid_file" 2>/dev/null)
|
|
830
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
831
|
+
return 0
|
|
832
|
+
fi
|
|
833
|
+
fi
|
|
834
|
+
return 1
|
|
835
|
+
fi
|
|
836
|
+
|
|
837
|
+
# Check global PID files (loki.pid from run.sh, run.pid legacy)
|
|
811
838
|
local pid_files=("$LOKI_DIR/loki.pid" "$LOKI_DIR/run.pid")
|
|
812
839
|
for pid_file in "${pid_files[@]}"; do
|
|
813
840
|
if [ -f "$pid_file" ]; then
|
|
@@ -818,15 +845,151 @@ is_session_running() {
|
|
|
818
845
|
fi
|
|
819
846
|
fi
|
|
820
847
|
done
|
|
848
|
+
|
|
849
|
+
# Check per-session PIDs
|
|
850
|
+
if [ -d "$LOKI_DIR/sessions" ]; then
|
|
851
|
+
for session_dir in "$LOKI_DIR/sessions"/*/; do
|
|
852
|
+
[ -d "$session_dir" ] || continue
|
|
853
|
+
local pid_file="${session_dir}loki.pid"
|
|
854
|
+
if [ -f "$pid_file" ]; then
|
|
855
|
+
local pid
|
|
856
|
+
pid=$(cat "$pid_file" 2>/dev/null)
|
|
857
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
858
|
+
return 0
|
|
859
|
+
fi
|
|
860
|
+
fi
|
|
861
|
+
done
|
|
862
|
+
fi
|
|
821
863
|
return 1
|
|
822
864
|
}
|
|
823
865
|
|
|
824
|
-
#
|
|
866
|
+
# List all running session IDs
|
|
867
|
+
list_running_sessions() {
|
|
868
|
+
local sessions=()
|
|
869
|
+
# Check global session
|
|
870
|
+
if [ -f "$LOKI_DIR/loki.pid" ]; then
|
|
871
|
+
local pid
|
|
872
|
+
pid=$(cat "$LOKI_DIR/loki.pid" 2>/dev/null)
|
|
873
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
874
|
+
sessions+=("global:$pid")
|
|
875
|
+
fi
|
|
876
|
+
fi
|
|
877
|
+
# Check per-session PIDs
|
|
878
|
+
if [ -d "$LOKI_DIR/sessions" ]; then
|
|
879
|
+
for session_dir in "$LOKI_DIR/sessions"/*/; do
|
|
880
|
+
[ -d "$session_dir" ] || continue
|
|
881
|
+
local sid pid_file pid
|
|
882
|
+
sid=$(basename "$session_dir")
|
|
883
|
+
pid_file="${session_dir}loki.pid"
|
|
884
|
+
if [ -f "$pid_file" ]; then
|
|
885
|
+
pid=$(cat "$pid_file" 2>/dev/null)
|
|
886
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
887
|
+
sessions+=("$sid:$pid")
|
|
888
|
+
fi
|
|
889
|
+
fi
|
|
890
|
+
done
|
|
891
|
+
fi
|
|
892
|
+
# Check legacy run-*.pid files
|
|
893
|
+
for run_pid_file in "$LOKI_DIR"/run-*.pid; do
|
|
894
|
+
[ -f "$run_pid_file" ] || continue
|
|
895
|
+
local sid pid
|
|
896
|
+
sid=$(basename "$run_pid_file" .pid)
|
|
897
|
+
sid="${sid#run-}"
|
|
898
|
+
pid=$(cat "$run_pid_file" 2>/dev/null)
|
|
899
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
900
|
+
# Avoid duplicates (if also in sessions/)
|
|
901
|
+
local dup=false
|
|
902
|
+
for s in "${sessions[@]}"; do
|
|
903
|
+
if [[ "$s" == "$sid:"* ]]; then dup=true; break; fi
|
|
904
|
+
done
|
|
905
|
+
if ! $dup; then
|
|
906
|
+
sessions+=("$sid:$pid")
|
|
907
|
+
fi
|
|
908
|
+
fi
|
|
909
|
+
done
|
|
910
|
+
printf '%s\n' "${sessions[@]}"
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
# Kill a PID with SIGTERM, wait, then SIGKILL if needed
|
|
914
|
+
_kill_pid() {
|
|
915
|
+
local pid="$1"
|
|
916
|
+
pkill -P "$pid" 2>/dev/null || true
|
|
917
|
+
kill "$pid" 2>/dev/null || true
|
|
918
|
+
sleep 1
|
|
919
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
920
|
+
pkill -9 -P "$pid" 2>/dev/null || true
|
|
921
|
+
kill -9 "$pid" 2>/dev/null || true
|
|
922
|
+
fi
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
# Stop a specific session by its session ID
|
|
926
|
+
_stop_session_by_id() {
|
|
927
|
+
local sid="$1"
|
|
928
|
+
local session_dir="$LOKI_DIR/sessions/$sid"
|
|
929
|
+
local pid_file="$session_dir/loki.pid"
|
|
930
|
+
|
|
931
|
+
if [ -f "$pid_file" ]; then
|
|
932
|
+
local pid
|
|
933
|
+
pid=$(cat "$pid_file" 2>/dev/null)
|
|
934
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
935
|
+
_kill_pid "$pid"
|
|
936
|
+
echo -e "${RED}Stopped session $sid (PID: $pid)${NC}"
|
|
937
|
+
fi
|
|
938
|
+
rm -f "$pid_file"
|
|
939
|
+
fi
|
|
940
|
+
|
|
941
|
+
# Also check legacy run-<id>.pid
|
|
942
|
+
local run_pid_file="$LOKI_DIR/run-${sid}.pid"
|
|
943
|
+
if [ -f "$run_pid_file" ]; then
|
|
944
|
+
local pid
|
|
945
|
+
pid=$(cat "$run_pid_file" 2>/dev/null)
|
|
946
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
947
|
+
_kill_pid "$pid"
|
|
948
|
+
fi
|
|
949
|
+
rm -f "$run_pid_file"
|
|
950
|
+
fi
|
|
951
|
+
|
|
952
|
+
# Clean up session lock file
|
|
953
|
+
rm -f "$session_dir/session.lock" 2>/dev/null
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
# Stop a specific session or all sessions
|
|
957
|
+
# Usage: loki stop [session_id]
|
|
958
|
+
# Without args: stops all running sessions
|
|
959
|
+
# With session_id: stops only that specific session
|
|
825
960
|
cmd_stop() {
|
|
961
|
+
local target_session=""
|
|
962
|
+
|
|
963
|
+
# Parse arguments
|
|
964
|
+
while [[ $# -gt 0 ]]; do
|
|
965
|
+
case "$1" in
|
|
966
|
+
--help|-h)
|
|
967
|
+
echo -e "${BOLD}loki stop${NC} - Stop running sessions (v6.4.0)"
|
|
968
|
+
echo ""
|
|
969
|
+
echo "Usage: loki stop [session-id]"
|
|
970
|
+
echo ""
|
|
971
|
+
echo " loki stop Stop all running sessions"
|
|
972
|
+
echo " loki stop 52 Stop only session #52"
|
|
973
|
+
echo " loki stop 54 Stop only session #54"
|
|
974
|
+
echo ""
|
|
975
|
+
echo "Use 'loki status' to see all running sessions."
|
|
976
|
+
exit 0
|
|
977
|
+
;;
|
|
978
|
+
-*)
|
|
979
|
+
echo -e "${RED}Unknown option: $1${NC}"
|
|
980
|
+
echo "Run 'loki stop --help' for usage."
|
|
981
|
+
exit 1
|
|
982
|
+
;;
|
|
983
|
+
*)
|
|
984
|
+
target_session="$1"
|
|
985
|
+
shift
|
|
986
|
+
;;
|
|
987
|
+
esac
|
|
988
|
+
done
|
|
989
|
+
|
|
826
990
|
if [ ! -d "$LOKI_DIR" ]; then
|
|
827
991
|
echo -e "${YELLOW}No .loki directory found.${NC}"
|
|
828
992
|
echo "No active session to stop."
|
|
829
|
-
# Emit error pattern for stop without session (SYN-018)
|
|
830
993
|
emit_learning_signal error_pattern \
|
|
831
994
|
--source cli \
|
|
832
995
|
--action "cmd_stop" \
|
|
@@ -837,28 +1000,54 @@ cmd_stop() {
|
|
|
837
1000
|
exit 0
|
|
838
1001
|
fi
|
|
839
1002
|
|
|
1003
|
+
# Stop a specific session by ID
|
|
1004
|
+
if [ -n "$target_session" ]; then
|
|
1005
|
+
if is_session_running "$target_session"; then
|
|
1006
|
+
_stop_session_by_id "$target_session"
|
|
1007
|
+
else
|
|
1008
|
+
echo -e "${YELLOW}Session '$target_session' is not running.${NC}"
|
|
1009
|
+
fi
|
|
1010
|
+
return 0
|
|
1011
|
+
fi
|
|
1012
|
+
|
|
1013
|
+
# No session ID given -- stop all running sessions
|
|
840
1014
|
if is_session_running; then
|
|
1015
|
+
# Stop per-session PIDs first
|
|
1016
|
+
if [ -d "$LOKI_DIR/sessions" ]; then
|
|
1017
|
+
for session_dir in "$LOKI_DIR/sessions"/*/; do
|
|
1018
|
+
[ -d "$session_dir" ] || continue
|
|
1019
|
+
local sid
|
|
1020
|
+
sid=$(basename "$session_dir")
|
|
1021
|
+
if is_session_running "$sid"; then
|
|
1022
|
+
_stop_session_by_id "$sid"
|
|
1023
|
+
fi
|
|
1024
|
+
done
|
|
1025
|
+
fi
|
|
1026
|
+
# Stop legacy run-*.pid sessions
|
|
1027
|
+
for run_pid_file in "$LOKI_DIR"/run-*.pid; do
|
|
1028
|
+
[ -f "$run_pid_file" ] || continue
|
|
1029
|
+
local pid
|
|
1030
|
+
pid=$(cat "$run_pid_file" 2>/dev/null)
|
|
1031
|
+
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
1032
|
+
_kill_pid "$pid"
|
|
1033
|
+
local sid
|
|
1034
|
+
sid=$(basename "$run_pid_file" .pid)
|
|
1035
|
+
echo -e "${RED}Stopped session ${sid#run-} (PID: $pid)${NC}"
|
|
1036
|
+
fi
|
|
1037
|
+
rm -f "$run_pid_file"
|
|
1038
|
+
done
|
|
1039
|
+
|
|
1040
|
+
# Stop global session
|
|
841
1041
|
touch "$LOKI_DIR/STOP"
|
|
842
1042
|
|
|
843
|
-
# Kill the background process and all its children
|
|
844
1043
|
local killed_pid=""
|
|
845
1044
|
for pid_file in "$LOKI_DIR/loki.pid" "$LOKI_DIR/run.pid"; do
|
|
846
1045
|
if [ -f "$pid_file" ]; then
|
|
847
1046
|
local pid
|
|
848
1047
|
pid=$(cat "$pid_file" 2>/dev/null)
|
|
849
1048
|
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
850
|
-
|
|
851
|
-
pkill -P "$pid" 2>/dev/null || true
|
|
852
|
-
# Then kill the parent (SIGTERM)
|
|
853
|
-
kill "$pid" 2>/dev/null || true
|
|
1049
|
+
_kill_pid "$pid"
|
|
854
1050
|
killed_pid="$pid"
|
|
855
|
-
|
|
856
|
-
# Wait briefly, then SIGKILL if still alive
|
|
857
|
-
sleep 1
|
|
858
|
-
if kill -0 "$pid" 2>/dev/null; then
|
|
859
|
-
pkill -9 -P "$pid" 2>/dev/null || true
|
|
860
|
-
kill -9 "$pid" 2>/dev/null || true
|
|
861
|
-
fi
|
|
862
1051
|
fi
|
|
863
1052
|
rm -f "$pid_file"
|
|
864
1053
|
fi
|
|
@@ -871,7 +1060,6 @@ cmd_stop() {
|
|
|
871
1060
|
|
|
872
1061
|
# Mark session.json as stopped (skill-invoked sessions)
|
|
873
1062
|
if [ -f "$LOKI_DIR/session.json" ]; then
|
|
874
|
-
# Use python for reliable JSON update, fall back to overwrite
|
|
875
1063
|
python3 -c "
|
|
876
1064
|
import json, sys, os
|
|
877
1065
|
try:
|
|
@@ -1162,6 +1350,29 @@ cmd_status() {
|
|
|
1162
1350
|
echo -e "${DIM} Switch with: loki provider set <claude|codex|gemini>${NC}"
|
|
1163
1351
|
echo ""
|
|
1164
1352
|
|
|
1353
|
+
# Show running sessions (v6.4.0 - concurrent session support)
|
|
1354
|
+
local running_sessions=()
|
|
1355
|
+
while IFS= read -r line; do
|
|
1356
|
+
[ -n "$line" ] && running_sessions+=("$line")
|
|
1357
|
+
done < <(list_running_sessions 2>/dev/null)
|
|
1358
|
+
|
|
1359
|
+
if [ ${#running_sessions[@]} -gt 0 ]; then
|
|
1360
|
+
echo -e "${GREEN}Active Sessions: ${#running_sessions[@]}${NC}"
|
|
1361
|
+
for entry in "${running_sessions[@]}"; do
|
|
1362
|
+
local sid="${entry%%:*}"
|
|
1363
|
+
local spid="${entry#*:}"
|
|
1364
|
+
if [ "$sid" = "global" ]; then
|
|
1365
|
+
echo -e " ${CYAN}[global]${NC} PID $spid"
|
|
1366
|
+
else
|
|
1367
|
+
echo -e " ${CYAN}[#$sid]${NC} PID $spid"
|
|
1368
|
+
fi
|
|
1369
|
+
done
|
|
1370
|
+
echo ""
|
|
1371
|
+
echo -e "${DIM} Stop specific: loki stop <session-id>${NC}"
|
|
1372
|
+
echo -e "${DIM} Stop all: loki stop${NC}"
|
|
1373
|
+
echo ""
|
|
1374
|
+
fi
|
|
1375
|
+
|
|
1165
1376
|
# Check for signals first (more prominent)
|
|
1166
1377
|
if [ -f "$LOKI_DIR/PAUSE" ]; then
|
|
1167
1378
|
echo -e "${YELLOW}Status: PAUSED${NC}"
|
|
@@ -1881,7 +2092,7 @@ cmd_dashboard_stop() {
|
|
|
1881
2092
|
local wait_count=0
|
|
1882
2093
|
while kill -0 "$pid" 2>/dev/null && [ $wait_count -lt 10 ]; do
|
|
1883
2094
|
sleep 0.5
|
|
1884
|
-
((wait_count
|
|
2095
|
+
wait_count=$((wait_count + 1))
|
|
1885
2096
|
done
|
|
1886
2097
|
|
|
1887
2098
|
# Force kill if still running
|
|
@@ -2663,20 +2874,17 @@ cmd_run() {
|
|
|
2663
2874
|
--pr)
|
|
2664
2875
|
use_worktree=true
|
|
2665
2876
|
create_pr=true
|
|
2666
|
-
start_args+=("--parallel")
|
|
2667
2877
|
shift
|
|
2668
2878
|
;;
|
|
2669
2879
|
--ship)
|
|
2670
2880
|
use_worktree=true
|
|
2671
2881
|
create_pr=true
|
|
2672
2882
|
auto_merge=true
|
|
2673
|
-
start_args+=("--parallel")
|
|
2674
2883
|
shift
|
|
2675
2884
|
;;
|
|
2676
2885
|
--detach|-d)
|
|
2677
2886
|
use_worktree=true
|
|
2678
2887
|
run_detached=true
|
|
2679
|
-
start_args+=("--parallel")
|
|
2680
2888
|
shift
|
|
2681
2889
|
;;
|
|
2682
2890
|
-*)
|
|
@@ -2693,6 +2901,11 @@ cmd_run() {
|
|
|
2693
2901
|
esac
|
|
2694
2902
|
done
|
|
2695
2903
|
|
|
2904
|
+
# Add --parallel once if worktree mode is enabled (not per-flag)
|
|
2905
|
+
if $use_worktree; then
|
|
2906
|
+
start_args+=("--parallel")
|
|
2907
|
+
fi
|
|
2908
|
+
|
|
2696
2909
|
if [[ -z "$issue_ref" ]]; then
|
|
2697
2910
|
echo -e "${RED}Error: Issue reference required${NC}"
|
|
2698
2911
|
echo ""
|
|
@@ -2740,6 +2953,12 @@ cmd_run() {
|
|
|
2740
2953
|
fi
|
|
2741
2954
|
echo ""
|
|
2742
2955
|
|
|
2956
|
+
# Per-session locking (v6.4.0): export session ID so run.sh uses
|
|
2957
|
+
# per-session PID/lock files instead of the global lock. This allows
|
|
2958
|
+
# multiple concurrent `loki run` sessions (e.g., loki run 52 -d && loki run 54 -d).
|
|
2959
|
+
local session_id="${number:-$(date +%s)}"
|
|
2960
|
+
export LOKI_SESSION_ID="$session_id"
|
|
2961
|
+
|
|
2743
2962
|
# Progressive isolation: set up worktree branch naming
|
|
2744
2963
|
if $use_worktree; then
|
|
2745
2964
|
local branch_name="issue/${issue_provider}-${number:-$(date +%s)}"
|
|
@@ -2754,6 +2973,13 @@ cmd_run() {
|
|
|
2754
2973
|
|
|
2755
2974
|
# Detached mode: fork to background
|
|
2756
2975
|
if $run_detached; then
|
|
2976
|
+
# Guard: prevent launching duplicate session
|
|
2977
|
+
if is_session_running "$session_id"; then
|
|
2978
|
+
echo -e "${RED}Error: Session '$session_id' is already running.${NC}"
|
|
2979
|
+
echo -e "Stop it first with: ${CYAN}loki stop $session_id${NC}"
|
|
2980
|
+
exit 1
|
|
2981
|
+
fi
|
|
2982
|
+
|
|
2757
2983
|
local log_file="$LOKI_DIR/logs/run-${number:-$(date +%s)}.log"
|
|
2758
2984
|
mkdir -p "$(dirname "$log_file")"
|
|
2759
2985
|
echo -e "${GREEN}Running detached. Logs: $log_file${NC}"
|
|
@@ -2768,31 +2994,51 @@ cmd_run() {
|
|
|
2768
2994
|
branch_name="issue/detach-$(date +%s)"
|
|
2769
2995
|
fi
|
|
2770
2996
|
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2997
|
+
# Write a temp script to avoid shell injection via variable interpolation
|
|
2998
|
+
local run_script="$LOKI_DIR/scripts/run-${number:-detached}.sh"
|
|
2999
|
+
mkdir -p "$LOKI_DIR/scripts"
|
|
3000
|
+
local loki_cmd
|
|
3001
|
+
loki_cmd="$(command -v loki || echo "$0")"
|
|
3002
|
+
cat > "$run_script" << 'INNER_SCRIPT_EOF'
|
|
3003
|
+
#!/usr/bin/env bash
|
|
3004
|
+
set -euo pipefail
|
|
3005
|
+
cd "$LOKI_RUN_DIR"
|
|
3006
|
+
export LOKI_DETACHED=true
|
|
3007
|
+
export LOKI_PARALLEL_MODE=true
|
|
3008
|
+
"$LOKI_CMD" start "$LOKI_PRD_PATH" ${LOKI_START_ARGS:-}
|
|
3009
|
+
|
|
3010
|
+
# Post-completion: create PR if requested
|
|
3011
|
+
if [[ "$LOKI_CREATE_PR" == "true" ]]; then
|
|
3012
|
+
branch_current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
3013
|
+
if [[ -n "$branch_current" && "$branch_current" != "main" && "$branch_current" != "master" ]]; then
|
|
3014
|
+
git push origin "$branch_current" 2>/dev/null || true
|
|
3015
|
+
gh pr create --title "$LOKI_PR_TITLE" --body "Implemented by Loki Mode" --head "$branch_current" 2>/dev/null || true
|
|
3016
|
+
fi
|
|
3017
|
+
fi
|
|
3018
|
+
# Post-completion: auto-merge if requested
|
|
3019
|
+
if [[ "$LOKI_AUTO_MERGE" == "true" ]]; then
|
|
3020
|
+
branch_current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
3021
|
+
if gh pr merge "$branch_current" --squash --delete-branch 2>/dev/null; then
|
|
3022
|
+
if [[ -n "${LOKI_ISSUE_NUMBER:-}" ]]; then
|
|
3023
|
+
gh issue close "$LOKI_ISSUE_NUMBER" --comment "Resolved by Loki Mode" 2>/dev/null || true
|
|
3024
|
+
fi
|
|
3025
|
+
fi
|
|
3026
|
+
fi
|
|
3027
|
+
INNER_SCRIPT_EOF
|
|
3028
|
+
chmod +x "$run_script"
|
|
3029
|
+
|
|
3030
|
+
# Pass all variables safely via environment
|
|
3031
|
+
LOKI_RUN_DIR="$(pwd)" \
|
|
3032
|
+
LOKI_CMD="$loki_cmd" \
|
|
3033
|
+
LOKI_SESSION_ID="$session_id" \
|
|
3034
|
+
LOKI_WORKTREE_BRANCH="$branch_name" \
|
|
3035
|
+
LOKI_PRD_PATH="$detach_prd" \
|
|
3036
|
+
LOKI_START_ARGS="${start_args[*]+"${start_args[*]}"}" \
|
|
3037
|
+
LOKI_CREATE_PR="$create_pr" \
|
|
3038
|
+
LOKI_AUTO_MERGE="$auto_merge" \
|
|
3039
|
+
LOKI_PR_TITLE="${title:-Implementation for issue ${issue_ref}}" \
|
|
3040
|
+
LOKI_ISSUE_NUMBER="${number:-}" \
|
|
3041
|
+
nohup bash "$run_script" > "$log_file" 2>&1 &
|
|
2796
3042
|
|
|
2797
3043
|
local bg_pid=$!
|
|
2798
3044
|
echo "$bg_pid" > "$LOKI_DIR/run-${number:-detached}.pid"
|
|
@@ -4728,7 +4974,7 @@ cmd_notify_test() {
|
|
|
4728
4974
|
echo -n " Slack... "
|
|
4729
4975
|
if send_slack_notification "$message" "Test"; then
|
|
4730
4976
|
echo -e "${GREEN}OK${NC}"
|
|
4731
|
-
((channels_notified
|
|
4977
|
+
channels_notified=$((channels_notified + 1))
|
|
4732
4978
|
else
|
|
4733
4979
|
echo -e "${RED}FAILED${NC}"
|
|
4734
4980
|
fi
|
|
@@ -4741,7 +4987,7 @@ cmd_notify_test() {
|
|
|
4741
4987
|
echo -n " Discord... "
|
|
4742
4988
|
if send_discord_notification "$message" "Test"; then
|
|
4743
4989
|
echo -e "${GREEN}OK${NC}"
|
|
4744
|
-
((channels_notified
|
|
4990
|
+
channels_notified=$((channels_notified + 1))
|
|
4745
4991
|
else
|
|
4746
4992
|
echo -e "${RED}FAILED${NC}"
|
|
4747
4993
|
fi
|
|
@@ -4754,7 +5000,7 @@ cmd_notify_test() {
|
|
|
4754
5000
|
echo -n " Webhook... "
|
|
4755
5001
|
if send_webhook_notification "$message" "Test"; then
|
|
4756
5002
|
echo -e "${GREEN}OK${NC}"
|
|
4757
|
-
((channels_notified
|
|
5003
|
+
channels_notified=$((channels_notified + 1))
|
|
4758
5004
|
else
|
|
4759
5005
|
echo -e "${RED}FAILED${NC}"
|
|
4760
5006
|
fi
|
|
@@ -6473,7 +6719,7 @@ Tasks:
|
|
|
6473
6719
|
local provider_name="${LOKI_PROVIDER:-claude}"
|
|
6474
6720
|
case "$provider_name" in
|
|
6475
6721
|
claude)
|
|
6476
|
-
(cd "$codebase_path" && claude --dangerously-skip-permissions -p "$phase_prompt" --output-format stream-json --verbose 2>&1) | \
|
|
6722
|
+
{ (cd "$codebase_path" && claude --dangerously-skip-permissions -p "$phase_prompt" --output-format stream-json --verbose 2>&1) | \
|
|
6477
6723
|
while IFS= read -r line; do
|
|
6478
6724
|
# Extract text from stream-json
|
|
6479
6725
|
if echo "$line" | python3 -c "
|
|
@@ -6488,8 +6734,7 @@ except Exception: pass
|
|
|
6488
6734
|
" 2>/dev/null; then
|
|
6489
6735
|
true
|
|
6490
6736
|
fi
|
|
6491
|
-
done
|
|
6492
|
-
phase_exit=${PIPESTATUS[0]}
|
|
6737
|
+
done; } && phase_exit=0 || phase_exit=$?
|
|
6493
6738
|
;;
|
|
6494
6739
|
codex)
|
|
6495
6740
|
(cd "$codebase_path" && codex exec --full-auto "$phase_prompt" 2>&1) || phase_exit=$?
|
|
@@ -6636,7 +6881,7 @@ IMPORTANT RULES:
|
|
|
6636
6881
|
local provider_name="${LOKI_PROVIDER:-claude}"
|
|
6637
6882
|
case "$provider_name" in
|
|
6638
6883
|
claude)
|
|
6639
|
-
(cd "$codebase_path" && claude --dangerously-skip-permissions -p "$doc_prompt" --output-format stream-json --verbose 2>&1) | \
|
|
6884
|
+
{ (cd "$codebase_path" && claude --dangerously-skip-permissions -p "$doc_prompt" --output-format stream-json --verbose 2>&1) | \
|
|
6640
6885
|
while IFS= read -r line; do
|
|
6641
6886
|
if echo "$line" | python3 -c "
|
|
6642
6887
|
import sys, json
|
|
@@ -6650,8 +6895,7 @@ except Exception: pass
|
|
|
6650
6895
|
" 2>/dev/null; then
|
|
6651
6896
|
true
|
|
6652
6897
|
fi
|
|
6653
|
-
done
|
|
6654
|
-
doc_exit=${PIPESTATUS[0]}
|
|
6898
|
+
done; } && doc_exit=0 || doc_exit=$?
|
|
6655
6899
|
;;
|
|
6656
6900
|
codex)
|
|
6657
6901
|
(cd "$codebase_path" && codex exec --full-auto "$doc_prompt" 2>&1) || doc_exit=$?
|
|
@@ -7181,7 +7425,7 @@ main() {
|
|
|
7181
7425
|
cmd_init "$@"
|
|
7182
7426
|
;;
|
|
7183
7427
|
stop)
|
|
7184
|
-
cmd_stop
|
|
7428
|
+
cmd_stop "$@"
|
|
7185
7429
|
;;
|
|
7186
7430
|
cleanup)
|
|
7187
7431
|
cmd_cleanup "$@"
|