loki-mode 6.3.1 → 6.4.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/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/loki +235 -17
- package/autonomy/run.sh +98 -32
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +74 -1
- package/docs/INSTALLATION.md +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
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.4.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.4.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
6.
|
|
1
|
+
6.4.0
|
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 1 ]; 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}"
|
|
@@ -2740,6 +2951,12 @@ cmd_run() {
|
|
|
2740
2951
|
fi
|
|
2741
2952
|
echo ""
|
|
2742
2953
|
|
|
2954
|
+
# Per-session locking (v6.4.0): export session ID so run.sh uses
|
|
2955
|
+
# per-session PID/lock files instead of the global lock. This allows
|
|
2956
|
+
# multiple concurrent `loki run` sessions (e.g., loki run 52 -d && loki run 54 -d).
|
|
2957
|
+
local session_id="${number:-$(date +%s)}"
|
|
2958
|
+
export LOKI_SESSION_ID="$session_id"
|
|
2959
|
+
|
|
2743
2960
|
# Progressive isolation: set up worktree branch naming
|
|
2744
2961
|
if $use_worktree; then
|
|
2745
2962
|
local branch_name="issue/${issue_provider}-${number:-$(date +%s)}"
|
|
@@ -2772,6 +2989,7 @@ cmd_run() {
|
|
|
2772
2989
|
cd $(pwd)
|
|
2773
2990
|
export LOKI_DETACHED=true
|
|
2774
2991
|
export LOKI_PARALLEL_MODE=true
|
|
2992
|
+
export LOKI_SESSION_ID=\"$session_id\"
|
|
2775
2993
|
export LOKI_WORKTREE_BRANCH=\"$branch_name\"
|
|
2776
2994
|
$(command -v loki || echo "$0") start \"$detach_prd\" --parallel ${start_args[*]+"${start_args[*]}"}
|
|
2777
2995
|
|
|
@@ -7181,7 +7399,7 @@ main() {
|
|
|
7181
7399
|
cmd_init "$@"
|
|
7182
7400
|
;;
|
|
7183
7401
|
stop)
|
|
7184
|
-
cmd_stop
|
|
7402
|
+
cmd_stop "$@"
|
|
7185
7403
|
;;
|
|
7186
7404
|
cleanup)
|
|
7187
7405
|
cmd_cleanup "$@"
|
package/autonomy/run.sh
CHANGED
|
@@ -2642,33 +2642,52 @@ init_loki_dir() {
|
|
|
2642
2642
|
# Clean up stale control files ONLY if no other session is running
|
|
2643
2643
|
# Deleting these while another session is active would destroy its signals
|
|
2644
2644
|
# Use flock if available to avoid TOCTOU race
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2645
|
+
#
|
|
2646
|
+
# Per-session locking (v6.4.0): When LOKI_SESSION_ID is set, only clean up
|
|
2647
|
+
# that session's files. Global control files (PAUSE/STOP) are only cleaned
|
|
2648
|
+
# when NO sessions are active.
|
|
2649
|
+
local lock_file can_cleanup=false
|
|
2650
|
+
|
|
2651
|
+
if [ -n "${LOKI_SESSION_ID:-}" ]; then
|
|
2652
|
+
# Per-session: check only this session's lock
|
|
2653
|
+
lock_file=".loki/sessions/${LOKI_SESSION_ID}/session.lock"
|
|
2654
|
+
local session_pid_file=".loki/sessions/${LOKI_SESSION_ID}/loki.pid"
|
|
2655
|
+
if command -v flock >/dev/null 2>&1 && [ -f "$lock_file" ]; then
|
|
2656
|
+
{ if flock -n 201 2>/dev/null; then can_cleanup=true; fi } 201>"$lock_file"
|
|
2657
|
+
else
|
|
2658
|
+
local existing_pid=""
|
|
2659
|
+
if [ -f "$session_pid_file" ]; then
|
|
2660
|
+
existing_pid=$(cat "$session_pid_file" 2>/dev/null)
|
|
2661
|
+
fi
|
|
2662
|
+
if [ -z "$existing_pid" ] || ! kill -0 "$existing_pid" 2>/dev/null; then
|
|
2652
2663
|
can_cleanup=true
|
|
2653
2664
|
fi
|
|
2654
|
-
|
|
2665
|
+
fi
|
|
2666
|
+
if [ "$can_cleanup" = "true" ]; then
|
|
2667
|
+
rm -f "$session_pid_file" 2>/dev/null
|
|
2668
|
+
rm -f "$lock_file" 2>/dev/null
|
|
2669
|
+
fi
|
|
2655
2670
|
else
|
|
2656
|
-
#
|
|
2657
|
-
|
|
2658
|
-
if [ -f "
|
|
2659
|
-
|
|
2671
|
+
# Global: original behavior
|
|
2672
|
+
lock_file=".loki/session.lock"
|
|
2673
|
+
if command -v flock >/dev/null 2>&1 && [ -f "$lock_file" ]; then
|
|
2674
|
+
{ if flock -n 201 2>/dev/null; then can_cleanup=true; fi } 201>"$lock_file"
|
|
2675
|
+
else
|
|
2676
|
+
local existing_pid=""
|
|
2677
|
+
if [ -f ".loki/loki.pid" ]; then
|
|
2678
|
+
existing_pid=$(cat ".loki/loki.pid" 2>/dev/null)
|
|
2679
|
+
fi
|
|
2680
|
+
if [ -z "$existing_pid" ] || ! kill -0 "$existing_pid" 2>/dev/null; then
|
|
2681
|
+
can_cleanup=true
|
|
2682
|
+
fi
|
|
2660
2683
|
fi
|
|
2661
|
-
if [
|
|
2662
|
-
|
|
2684
|
+
if [ "$can_cleanup" = "true" ]; then
|
|
2685
|
+
rm -f .loki/PAUSE .loki/STOP .loki/HUMAN_INPUT.md 2>/dev/null
|
|
2686
|
+
rm -f .loki/loki.pid 2>/dev/null
|
|
2687
|
+
rm -f .loki/session.lock 2>/dev/null
|
|
2663
2688
|
fi
|
|
2664
2689
|
fi
|
|
2665
2690
|
|
|
2666
|
-
if [ "$can_cleanup" = "true" ]; then
|
|
2667
|
-
rm -f .loki/PAUSE .loki/STOP .loki/HUMAN_INPUT.md 2>/dev/null
|
|
2668
|
-
rm -f .loki/loki.pid 2>/dev/null
|
|
2669
|
-
rm -f .loki/session.lock 2>/dev/null
|
|
2670
|
-
fi
|
|
2671
|
-
|
|
2672
2691
|
mkdir -p .loki/{state,queue,messages,logs,config,prompts,artifacts,scripts}
|
|
2673
2692
|
mkdir -p .loki/queue
|
|
2674
2693
|
mkdir -p .loki/state/checkpoints
|
|
@@ -8093,6 +8112,10 @@ cleanup() {
|
|
|
8093
8112
|
stop_status_monitor
|
|
8094
8113
|
kill_all_registered
|
|
8095
8114
|
rm -f "$loki_dir/loki.pid" 2>/dev/null
|
|
8115
|
+
# Clean up per-session PID file if running with session ID
|
|
8116
|
+
if [ -n "${LOKI_SESSION_ID:-}" ]; then
|
|
8117
|
+
rm -f "$loki_dir/sessions/${LOKI_SESSION_ID}/loki.pid" 2>/dev/null
|
|
8118
|
+
fi
|
|
8096
8119
|
if [ -f "$loki_dir/session.json" ]; then
|
|
8097
8120
|
_LOKI_SESSION_FILE="$loki_dir/session.json" python3 -c "
|
|
8098
8121
|
import json, os
|
|
@@ -8121,6 +8144,10 @@ except (json.JSONDecodeError, OSError): pass
|
|
|
8121
8144
|
stop_status_monitor
|
|
8122
8145
|
kill_all_registered
|
|
8123
8146
|
rm -f .loki/loki.pid .loki/PAUSE 2>/dev/null
|
|
8147
|
+
# Clean up per-session PID file if running with session ID
|
|
8148
|
+
if [ -n "${LOKI_SESSION_ID:-}" ]; then
|
|
8149
|
+
rm -f ".loki/sessions/${LOKI_SESSION_ID}/loki.pid" 2>/dev/null
|
|
8150
|
+
fi
|
|
8124
8151
|
# Mark session.json as stopped
|
|
8125
8152
|
if [ -f ".loki/session.json" ]; then
|
|
8126
8153
|
python3 -c "
|
|
@@ -8325,7 +8352,13 @@ main() {
|
|
|
8325
8352
|
mkdir -p .loki/logs
|
|
8326
8353
|
|
|
8327
8354
|
local log_file=".loki/logs/background-$(date +%Y%m%d-%H%M%S).log"
|
|
8328
|
-
local pid_file
|
|
8355
|
+
local pid_file
|
|
8356
|
+
if [ -n "${LOKI_SESSION_ID:-}" ]; then
|
|
8357
|
+
mkdir -p ".loki/sessions/${LOKI_SESSION_ID}"
|
|
8358
|
+
pid_file=".loki/sessions/${LOKI_SESSION_ID}/loki.pid"
|
|
8359
|
+
else
|
|
8360
|
+
pid_file=".loki/loki.pid"
|
|
8361
|
+
fi
|
|
8329
8362
|
local project_path=$(pwd)
|
|
8330
8363
|
local project_name=$(basename "$project_path")
|
|
8331
8364
|
|
|
@@ -8419,12 +8452,22 @@ main() {
|
|
|
8419
8452
|
# Initialize session continuity file with empty template
|
|
8420
8453
|
update_continuity
|
|
8421
8454
|
|
|
8422
|
-
# Session lock: prevent concurrent sessions
|
|
8423
|
-
#
|
|
8424
|
-
|
|
8425
|
-
|
|
8455
|
+
# Session lock: prevent concurrent sessions
|
|
8456
|
+
# Per-session locking (v6.4.0): LOKI_SESSION_ID enables multiple concurrent
|
|
8457
|
+
# sessions (e.g., loki run 52 -d && loki run 54 -d). Each session gets its
|
|
8458
|
+
# own PID/lock files under .loki/sessions/<id>/.
|
|
8459
|
+
# Without LOKI_SESSION_ID, the global .loki/loki.pid lock is used (single session).
|
|
8460
|
+
local pid_file lock_file
|
|
8461
|
+
if [ -n "${LOKI_SESSION_ID:-}" ]; then
|
|
8462
|
+
mkdir -p ".loki/sessions/${LOKI_SESSION_ID}"
|
|
8463
|
+
pid_file=".loki/sessions/${LOKI_SESSION_ID}/loki.pid"
|
|
8464
|
+
lock_file=".loki/sessions/${LOKI_SESSION_ID}/session.lock"
|
|
8465
|
+
else
|
|
8466
|
+
pid_file=".loki/loki.pid"
|
|
8467
|
+
lock_file=".loki/session.lock"
|
|
8468
|
+
fi
|
|
8426
8469
|
|
|
8427
|
-
#
|
|
8470
|
+
# Use flock for atomic locking to prevent TOCTOU race conditions
|
|
8428
8471
|
if command -v flock >/dev/null 2>&1; then
|
|
8429
8472
|
# Create lock file
|
|
8430
8473
|
touch "$lock_file"
|
|
@@ -8435,8 +8478,13 @@ main() {
|
|
|
8435
8478
|
|
|
8436
8479
|
# Try to acquire exclusive lock (non-blocking)
|
|
8437
8480
|
if ! flock -n 200 2>/dev/null; then
|
|
8438
|
-
|
|
8439
|
-
|
|
8481
|
+
if [ -n "${LOKI_SESSION_ID:-}" ]; then
|
|
8482
|
+
log_error "Session '${LOKI_SESSION_ID}' is already running (locked)"
|
|
8483
|
+
log_error "Stop it first with: loki stop ${LOKI_SESSION_ID}"
|
|
8484
|
+
else
|
|
8485
|
+
log_error "Another Loki session is already running (locked)"
|
|
8486
|
+
log_error "Stop it first with: loki stop"
|
|
8487
|
+
fi
|
|
8440
8488
|
exit 1
|
|
8441
8489
|
fi
|
|
8442
8490
|
|
|
@@ -8446,8 +8494,13 @@ main() {
|
|
|
8446
8494
|
existing_pid=$(cat "$pid_file" 2>/dev/null)
|
|
8447
8495
|
# Skip if it's our own PID or parent PID (background mode writes PID before child starts)
|
|
8448
8496
|
if [ -n "$existing_pid" ] && [ "$existing_pid" != "$$" ] && [ "$existing_pid" != "$PPID" ] && kill -0 "$existing_pid" 2>/dev/null; then
|
|
8449
|
-
|
|
8450
|
-
|
|
8497
|
+
if [ -n "${LOKI_SESSION_ID:-}" ]; then
|
|
8498
|
+
log_error "Session '${LOKI_SESSION_ID}' is already running (PID: $existing_pid)"
|
|
8499
|
+
log_error "Stop it first with: loki stop ${LOKI_SESSION_ID}"
|
|
8500
|
+
else
|
|
8501
|
+
log_error "Another Loki session is already running (PID: $existing_pid)"
|
|
8502
|
+
log_error "Stop it first with: loki stop"
|
|
8503
|
+
fi
|
|
8451
8504
|
exit 1
|
|
8452
8505
|
fi
|
|
8453
8506
|
fi
|
|
@@ -8459,8 +8512,13 @@ main() {
|
|
|
8459
8512
|
existing_pid=$(cat "$pid_file" 2>/dev/null)
|
|
8460
8513
|
# Skip if it's our own PID or parent PID (background mode writes PID before child starts)
|
|
8461
8514
|
if [ -n "$existing_pid" ] && [ "$existing_pid" != "$$" ] && [ "$existing_pid" != "$PPID" ] && kill -0 "$existing_pid" 2>/dev/null; then
|
|
8462
|
-
|
|
8463
|
-
|
|
8515
|
+
if [ -n "${LOKI_SESSION_ID:-}" ]; then
|
|
8516
|
+
log_error "Session '${LOKI_SESSION_ID}' is already running (PID: $existing_pid)"
|
|
8517
|
+
log_error "Stop it first with: loki stop ${LOKI_SESSION_ID}"
|
|
8518
|
+
else
|
|
8519
|
+
log_error "Another Loki session is already running (PID: $existing_pid)"
|
|
8520
|
+
log_error "Stop it first with: loki stop"
|
|
8521
|
+
fi
|
|
8464
8522
|
exit 1
|
|
8465
8523
|
fi
|
|
8466
8524
|
fi
|
|
@@ -8468,6 +8526,10 @@ main() {
|
|
|
8468
8526
|
|
|
8469
8527
|
# Write PID file for ALL modes (foreground + background)
|
|
8470
8528
|
echo "$$" > "$pid_file"
|
|
8529
|
+
# Store session ID in state for dashboard/status visibility
|
|
8530
|
+
if [ -n "${LOKI_SESSION_ID:-}" ]; then
|
|
8531
|
+
echo "${LOKI_SESSION_ID}" > ".loki/sessions/${LOKI_SESSION_ID}/session_id"
|
|
8532
|
+
fi
|
|
8471
8533
|
|
|
8472
8534
|
# Initialize PID registry and clean up orphans from previous sessions
|
|
8473
8535
|
init_pid_registry
|
|
@@ -8675,6 +8737,10 @@ main() {
|
|
|
8675
8737
|
stop_dashboard
|
|
8676
8738
|
stop_status_monitor
|
|
8677
8739
|
rm -f .loki/loki.pid 2>/dev/null
|
|
8740
|
+
# Clean up per-session PID file if running with session ID
|
|
8741
|
+
if [ -n "${LOKI_SESSION_ID:-}" ]; then
|
|
8742
|
+
rm -f ".loki/sessions/${LOKI_SESSION_ID}/loki.pid" 2>/dev/null
|
|
8743
|
+
fi
|
|
8678
8744
|
# Mark session.json as stopped
|
|
8679
8745
|
if [ -f ".loki/session.json" ]; then
|
|
8680
8746
|
python3 -c "
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -203,6 +203,14 @@ class TaskResponse(BaseModel):
|
|
|
203
203
|
from_attributes = True
|
|
204
204
|
|
|
205
205
|
|
|
206
|
+
class SessionInfo(BaseModel):
|
|
207
|
+
"""Info about a single running session."""
|
|
208
|
+
session_id: str
|
|
209
|
+
pid: int
|
|
210
|
+
status: str = "running"
|
|
211
|
+
log_file: str = ""
|
|
212
|
+
|
|
213
|
+
|
|
206
214
|
class StatusResponse(BaseModel):
|
|
207
215
|
"""Schema for system status response."""
|
|
208
216
|
status: str
|
|
@@ -219,6 +227,8 @@ class StatusResponse(BaseModel):
|
|
|
219
227
|
mode: str = ""
|
|
220
228
|
provider: str = "claude"
|
|
221
229
|
current_task: str = ""
|
|
230
|
+
# Concurrent sessions (v6.4.0)
|
|
231
|
+
sessions: list[SessionInfo] = []
|
|
222
232
|
|
|
223
233
|
|
|
224
234
|
# WebSocket connection manager
|
|
@@ -521,11 +531,73 @@ async def get_status() -> StatusResponse:
|
|
|
521
531
|
except Exception:
|
|
522
532
|
pass
|
|
523
533
|
|
|
534
|
+
# Discover all running sessions (v6.4.0 - concurrent session support)
|
|
535
|
+
active_session_list: list[SessionInfo] = []
|
|
536
|
+
|
|
537
|
+
# Global session
|
|
538
|
+
if running:
|
|
539
|
+
active_session_list.append(SessionInfo(
|
|
540
|
+
session_id="global",
|
|
541
|
+
pid=int(pid_str) if pid_str else 0,
|
|
542
|
+
status=status,
|
|
543
|
+
))
|
|
544
|
+
|
|
545
|
+
# Per-session PIDs under .loki/sessions/<id>/
|
|
546
|
+
sessions_dir = loki_dir / "sessions"
|
|
547
|
+
if sessions_dir.is_dir():
|
|
548
|
+
for session_path in sessions_dir.iterdir():
|
|
549
|
+
if not session_path.is_dir():
|
|
550
|
+
continue
|
|
551
|
+
sid = session_path.name
|
|
552
|
+
spid_file = session_path / "loki.pid"
|
|
553
|
+
if spid_file.exists():
|
|
554
|
+
try:
|
|
555
|
+
spid_str = spid_file.read_text().strip()
|
|
556
|
+
spid = int(spid_str)
|
|
557
|
+
os.kill(spid, 0)
|
|
558
|
+
# Find log file if available
|
|
559
|
+
log_path = ""
|
|
560
|
+
log_candidate = loki_dir / "logs" / f"run-{sid}.log"
|
|
561
|
+
if log_candidate.exists():
|
|
562
|
+
log_path = str(log_candidate)
|
|
563
|
+
active_session_list.append(SessionInfo(
|
|
564
|
+
session_id=sid,
|
|
565
|
+
pid=spid,
|
|
566
|
+
status="running",
|
|
567
|
+
log_file=log_path,
|
|
568
|
+
))
|
|
569
|
+
except (ValueError, OSError, ProcessLookupError):
|
|
570
|
+
pass
|
|
571
|
+
|
|
572
|
+
# Legacy run-*.pid files
|
|
573
|
+
for rpf in loki_dir.glob("run-*.pid"):
|
|
574
|
+
sid = rpf.stem.removeprefix("run-")
|
|
575
|
+
# Skip if already found in sessions/
|
|
576
|
+
if any(s.session_id == sid for s in active_session_list):
|
|
577
|
+
continue
|
|
578
|
+
try:
|
|
579
|
+
rpid = int(rpf.read_text().strip())
|
|
580
|
+
os.kill(rpid, 0)
|
|
581
|
+
log_path = ""
|
|
582
|
+
log_candidate = loki_dir / "logs" / f"run-{sid}.log"
|
|
583
|
+
if log_candidate.exists():
|
|
584
|
+
log_path = str(log_candidate)
|
|
585
|
+
active_session_list.append(SessionInfo(
|
|
586
|
+
session_id=sid,
|
|
587
|
+
pid=rpid,
|
|
588
|
+
status="running",
|
|
589
|
+
log_file=log_path,
|
|
590
|
+
))
|
|
591
|
+
except (ValueError, OSError, ProcessLookupError):
|
|
592
|
+
pass
|
|
593
|
+
|
|
594
|
+
total_active = len(active_session_list)
|
|
595
|
+
|
|
524
596
|
return StatusResponse(
|
|
525
597
|
status=status,
|
|
526
598
|
version=version,
|
|
527
599
|
uptime_seconds=uptime,
|
|
528
|
-
active_sessions=
|
|
600
|
+
active_sessions=total_active,
|
|
529
601
|
running_agents=running_agents,
|
|
530
602
|
pending_tasks=pending_tasks,
|
|
531
603
|
database_connected=True,
|
|
@@ -535,6 +607,7 @@ async def get_status() -> StatusResponse:
|
|
|
535
607
|
mode=mode,
|
|
536
608
|
provider=provider,
|
|
537
609
|
current_task=current_task,
|
|
610
|
+
sessions=active_session_list,
|
|
538
611
|
)
|
|
539
612
|
|
|
540
613
|
|
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.4.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
## What's New in v6.
|
|
9
|
+
## What's New in v6.4.0
|
|
10
10
|
|
|
11
11
|
### Dual-Mode Architecture (v6.0.0)
|
|
12
12
|
- `loki run` command for direct autonomous execution
|
package/mcp/__init__.py
CHANGED