loki-mode 6.67.2 → 6.67.3
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 +78 -2
- package/autonomy/run.sh +5 -0
- package/completions/_loki +4 -0
- package/completions/loki.bash +6 -1
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/web-app/server.py +73 -8
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.67.
|
|
6
|
+
# Loki Mode v6.67.3
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -268,4 +268,4 @@ The following features are documented in skill modules but not yet fully automat
|
|
|
268
268
|
| Quality gates 3-reviewer system | Implemented (v5.35.0) | 5 specialist reviewers in `skills/quality-gates.md`; execution in run.sh |
|
|
269
269
|
| Benchmarks (HumanEval, SWE-bench) | Infrastructure only | Runner scripts and datasets exist in `benchmarks/`; no published results |
|
|
270
270
|
|
|
271
|
-
**v6.67.
|
|
271
|
+
**v6.67.3 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
6.67.
|
|
1
|
+
6.67.3
|
package/autonomy/loki
CHANGED
|
@@ -398,6 +398,7 @@ show_help() {
|
|
|
398
398
|
echo " run <issue> Issue-driven engineering (v6.0.0) - GitHub/GitLab/Jira/Azure DevOps"
|
|
399
399
|
echo " start [PRD] Start Loki Mode (optionally with PRD file)"
|
|
400
400
|
echo " quick \"task\" Quick single-task mode (lightweight, 3 iterations max)"
|
|
401
|
+
echo " monitor [path] Monitor Docker Compose services with auto-fix (v6.67.0)"
|
|
401
402
|
echo " demo Run interactive demo (~60s simulated session)"
|
|
402
403
|
echo " init [name] Project scaffolding with 22 PRD templates"
|
|
403
404
|
echo " issue <url|num> [DEPRECATED] Use 'loki run' instead"
|
|
@@ -6987,11 +6988,12 @@ cmd_quick() {
|
|
|
6987
6988
|
|
|
6988
6989
|
local task_desc="$*"
|
|
6989
6990
|
local version=$(get_version)
|
|
6991
|
+
local max_iter="${LOKI_MAX_ITERATIONS:-3}"
|
|
6990
6992
|
|
|
6991
6993
|
echo -e "${BOLD}Loki Mode v$version - Quick Mode${NC}"
|
|
6992
6994
|
echo ""
|
|
6993
6995
|
echo -e "${CYAN}Task:${NC} $task_desc"
|
|
6994
|
-
echo -e "${DIM}Running lightweight execution (
|
|
6996
|
+
echo -e "${DIM}Running lightweight execution ($max_iter iterations max, no quality council)${NC}"
|
|
6995
6997
|
echo ""
|
|
6996
6998
|
|
|
6997
6999
|
# Create quick PRD from task description
|
|
@@ -7028,7 +7030,7 @@ QPRDEOF
|
|
|
7028
7030
|
echo ""
|
|
7029
7031
|
|
|
7030
7032
|
# Set lightweight execution environment
|
|
7031
|
-
export LOKI_MAX_ITERATIONS=
|
|
7033
|
+
export LOKI_MAX_ITERATIONS="$max_iter"
|
|
7032
7034
|
export LOKI_COMPLEXITY=simple
|
|
7033
7035
|
export LOKI_COUNCIL_ENABLED=false
|
|
7034
7036
|
export LOKI_PHASE_CODE_REVIEW=false
|
|
@@ -7048,6 +7050,77 @@ QPRDEOF
|
|
|
7048
7050
|
exec "$RUN_SH" "$quick_prd"
|
|
7049
7051
|
}
|
|
7050
7052
|
|
|
7053
|
+
# Docker Compose monitoring with auto-fix (v6.67.0)
|
|
7054
|
+
cmd_monitor() {
|
|
7055
|
+
local project_dir="${1:-.}"
|
|
7056
|
+
|
|
7057
|
+
if [[ ! -f "$project_dir/docker-compose.yml" ]] && [[ ! -f "$project_dir/docker-compose.yaml" ]]; then
|
|
7058
|
+
echo -e "${RED}No docker-compose.yml found in $project_dir${NC}"
|
|
7059
|
+
return 1
|
|
7060
|
+
fi
|
|
7061
|
+
|
|
7062
|
+
echo -e "${CYAN}Monitoring Docker Compose services in $project_dir...${NC}"
|
|
7063
|
+
echo "Press Ctrl+C to stop."
|
|
7064
|
+
echo ""
|
|
7065
|
+
|
|
7066
|
+
local fix_count=0
|
|
7067
|
+
local max_fixes=10
|
|
7068
|
+
|
|
7069
|
+
while true; do
|
|
7070
|
+
# Check service status
|
|
7071
|
+
local status
|
|
7072
|
+
status=$(cd "$project_dir" && docker compose ps -a --format json 2>/dev/null)
|
|
7073
|
+
|
|
7074
|
+
if [[ -z "$status" ]]; then
|
|
7075
|
+
echo -e "${YELLOW}No services found. Waiting...${NC}"
|
|
7076
|
+
sleep 10
|
|
7077
|
+
continue
|
|
7078
|
+
fi
|
|
7079
|
+
|
|
7080
|
+
# Parse each service
|
|
7081
|
+
local has_failure=false
|
|
7082
|
+
while IFS= read -r line; do
|
|
7083
|
+
[[ -z "$line" ]] && continue
|
|
7084
|
+
local svc_name svc_state
|
|
7085
|
+
svc_name=$(echo "$line" | python3 -c "import json,sys; d=json.loads(sys.stdin.read()); print(d.get('Service',d.get('Name','')))" 2>/dev/null)
|
|
7086
|
+
svc_state=$(echo "$line" | python3 -c "import json,sys; d=json.loads(sys.stdin.read()); print(d.get('State',''))" 2>/dev/null)
|
|
7087
|
+
|
|
7088
|
+
if [[ "$svc_state" == "exited" ]] || [[ "$svc_state" == "dead" ]]; then
|
|
7089
|
+
has_failure=true
|
|
7090
|
+
echo -e "${RED}Service '$svc_name' is $svc_state${NC}"
|
|
7091
|
+
|
|
7092
|
+
if [[ $fix_count -lt $max_fixes ]]; then
|
|
7093
|
+
# Get logs for the failed service
|
|
7094
|
+
local logs
|
|
7095
|
+
logs=$(cd "$project_dir" && docker compose logs --tail 30 "$svc_name" 2>/dev/null)
|
|
7096
|
+
|
|
7097
|
+
echo -e "${CYAN}Auto-fixing $svc_name...${NC}"
|
|
7098
|
+
|
|
7099
|
+
# Run loki quick with targeted fix
|
|
7100
|
+
LOKI_MAX_ITERATIONS=5 LOKI_AUTO_FIX=true \
|
|
7101
|
+
loki quick "Fix the '$svc_name' Docker service. It exited with an error. Logs: $logs" \
|
|
7102
|
+
2>&1 | while IFS= read -r fline; do echo " $fline"; done
|
|
7103
|
+
|
|
7104
|
+
# Rebuild and restart
|
|
7105
|
+
echo -e "${CYAN}Rebuilding $svc_name...${NC}"
|
|
7106
|
+
(cd "$project_dir" && docker compose up -d --build --no-deps "$svc_name" 2>&1) | while IFS= read -r fline; do echo " $fline"; done
|
|
7107
|
+
|
|
7108
|
+
fix_count=$((fix_count + 1))
|
|
7109
|
+
echo -e "${GREEN}Fix attempt $fix_count/$max_fixes for $svc_name${NC}"
|
|
7110
|
+
else
|
|
7111
|
+
echo -e "${YELLOW}Max fix attempts ($max_fixes) reached. Manual intervention needed.${NC}"
|
|
7112
|
+
fi
|
|
7113
|
+
fi
|
|
7114
|
+
done <<< "$status"
|
|
7115
|
+
|
|
7116
|
+
if [[ "$has_failure" == "false" ]]; then
|
|
7117
|
+
echo -e "\r${GREEN}All services healthy${NC} $(date +%H:%M:%S) \c"
|
|
7118
|
+
fi
|
|
7119
|
+
|
|
7120
|
+
sleep 10
|
|
7121
|
+
done
|
|
7122
|
+
}
|
|
7123
|
+
|
|
7051
7124
|
# Project scaffolding (v6.28.0)
|
|
7052
7125
|
cmd_init() {
|
|
7053
7126
|
# Guard: check if .loki/ already exists to avoid overwriting active session
|
|
@@ -10487,6 +10560,9 @@ main() {
|
|
|
10487
10560
|
quick)
|
|
10488
10561
|
cmd_quick "$@"
|
|
10489
10562
|
;;
|
|
10563
|
+
monitor)
|
|
10564
|
+
cmd_monitor "$@"
|
|
10565
|
+
;;
|
|
10490
10566
|
demo)
|
|
10491
10567
|
cmd_demo "$@"
|
|
10492
10568
|
;;
|
package/autonomy/run.sh
CHANGED
|
@@ -580,6 +580,11 @@ PHASE_UAT=${LOKI_PHASE_UAT:-true}
|
|
|
580
580
|
COMPLETION_PROMISE=${LOKI_COMPLETION_PROMISE:-""}
|
|
581
581
|
MAX_ITERATIONS=${LOKI_MAX_ITERATIONS:-1000}
|
|
582
582
|
ITERATION_COUNT=0
|
|
583
|
+
|
|
584
|
+
# If this is an auto-fix task, allow more iterations
|
|
585
|
+
if [[ "${LOKI_AUTO_FIX:-}" == "true" ]]; then
|
|
586
|
+
MAX_ITERATIONS="${LOKI_MAX_ITERATIONS:-5}"
|
|
587
|
+
fi
|
|
583
588
|
# Perpetual mode: never stop unless max iterations (ignores all completion signals)
|
|
584
589
|
PERPETUAL_MODE=${LOKI_PERPETUAL_MODE:-false}
|
|
585
590
|
|
package/completions/_loki
CHANGED
|
@@ -91,6 +91,9 @@ function _loki {
|
|
|
91
91
|
completions)
|
|
92
92
|
_arguments '1:shell:(bash zsh)'
|
|
93
93
|
;;
|
|
94
|
+
monitor)
|
|
95
|
+
_directories
|
|
96
|
+
;;
|
|
94
97
|
context|ctx)
|
|
95
98
|
_loki_context
|
|
96
99
|
;;
|
|
@@ -107,6 +110,7 @@ function _loki_commands {
|
|
|
107
110
|
commands=(
|
|
108
111
|
'start:Start Loki Mode'
|
|
109
112
|
'quick:Quick single-task mode'
|
|
113
|
+
'monitor:Monitor Docker Compose services with auto-fix'
|
|
110
114
|
'demo:Interactive 60-second demo'
|
|
111
115
|
'init:Interactive PRD builder'
|
|
112
116
|
'stop:Stop execution'
|
package/completions/loki.bash
CHANGED
|
@@ -5,7 +5,7 @@ _loki_completion() {
|
|
|
5
5
|
_init_completion || return
|
|
6
6
|
|
|
7
7
|
# Main subcommands (must match autonomy/loki main case statement)
|
|
8
|
-
local main_commands="start quick demo init stop pause resume status dashboard logs serve api sandbox notify import github issue config provider reset memory compound checkpoint council dogfood projects enterprise secrets doctor watchdog audit metrics syslog onboard share explain plan report test ci watch telemetry agent context code run export review optimize heal migrate cluster worktree trigger failover remote version completions help"
|
|
8
|
+
local main_commands="start quick monitor demo init stop pause resume status dashboard logs serve api sandbox notify import github issue config provider reset memory compound checkpoint council dogfood projects enterprise secrets doctor watchdog audit metrics syslog onboard share explain plan report test ci watch telemetry agent context code run export review optimize heal migrate cluster worktree trigger failover remote version completions help"
|
|
9
9
|
|
|
10
10
|
# 1. If we are on the first argument (subcommand)
|
|
11
11
|
if [[ $cword -eq 1 ]]; then
|
|
@@ -178,6 +178,11 @@ _loki_completion() {
|
|
|
178
178
|
fi
|
|
179
179
|
;;
|
|
180
180
|
|
|
181
|
+
monitor)
|
|
182
|
+
# Complete with directories
|
|
183
|
+
_filedir -d
|
|
184
|
+
;;
|
|
185
|
+
|
|
181
186
|
completions)
|
|
182
187
|
COMPREPLY=( $(compgen -W "bash zsh" -- "$cur") )
|
|
183
188
|
;;
|
package/dashboard/__init__.py
CHANGED
package/docs/INSTALLATION.md
CHANGED
package/mcp/__init__.py
CHANGED
package/package.json
CHANGED
package/web-app/server.py
CHANGED
|
@@ -1454,9 +1454,10 @@ class DevServerManager:
|
|
|
1454
1454
|
except Exception:
|
|
1455
1455
|
logger.debug("Docker context gathering for auto-fix failed", exc_info=True)
|
|
1456
1456
|
|
|
1457
|
-
# Save original command and multi_service flag before stop() removes the info dict
|
|
1457
|
+
# Save original command, framework, and multi_service flag before stop() removes the info dict
|
|
1458
1458
|
cmd = info.get("original_command")
|
|
1459
1459
|
is_multi_service = info.get("multi_service", False)
|
|
1460
|
+
framework = info.get("framework")
|
|
1460
1461
|
|
|
1461
1462
|
try:
|
|
1462
1463
|
auto_fix_env = {**os.environ}
|
|
@@ -1475,6 +1476,14 @@ class DevServerManager:
|
|
|
1475
1476
|
)
|
|
1476
1477
|
if result.returncode == 0:
|
|
1477
1478
|
logger.info("Auto-fix succeeded for session %s, restarting dev server", session_id)
|
|
1479
|
+
# For Docker projects, rebuild images before restarting
|
|
1480
|
+
# (fix may have changed Dockerfile, package.json, requirements.txt, etc.)
|
|
1481
|
+
if framework == "docker":
|
|
1482
|
+
await asyncio.to_thread(
|
|
1483
|
+
subprocess.run,
|
|
1484
|
+
["docker", "compose", "up", "-d", "--build"],
|
|
1485
|
+
capture_output=True, cwd=str(target), timeout=120
|
|
1486
|
+
)
|
|
1478
1487
|
# Restart the dev server
|
|
1479
1488
|
await self.stop(session_id)
|
|
1480
1489
|
await asyncio.sleep(1)
|
|
@@ -1723,7 +1732,17 @@ class DevServerManager:
|
|
|
1723
1732
|
was_running = prev.get("status") in ("running", None)
|
|
1724
1733
|
now_failed = svc["state"] in ("exited", "dead")
|
|
1725
1734
|
|
|
1726
|
-
|
|
1735
|
+
# Also detect services that never started (failed on initial docker compose up).
|
|
1736
|
+
# On first poll, prev is empty ({}) so prev.get("status") is None, which counts
|
|
1737
|
+
# as "was_running" above. But if a service was already "exited" before we ever
|
|
1738
|
+
# saw it as "running", the next poll would see prev.status="exited" and skip it.
|
|
1739
|
+
# Detect this case: first time we see a service in a failed state with no prior fix.
|
|
1740
|
+
first_seen_failed = (
|
|
1741
|
+
not prev # No previous health record for this service
|
|
1742
|
+
and now_failed
|
|
1743
|
+
)
|
|
1744
|
+
|
|
1745
|
+
if (was_running and now_failed) or first_seen_failed:
|
|
1727
1746
|
svc_health["restarts"] = prev.get("restarts", 0) + 1
|
|
1728
1747
|
logger.warning("Docker service '%s' failed (exit %s)", name, svc.get("exit_code"))
|
|
1729
1748
|
|
|
@@ -1794,24 +1813,70 @@ class DevServerManager:
|
|
|
1794
1813
|
return
|
|
1795
1814
|
|
|
1796
1815
|
try:
|
|
1816
|
+
fix_env = {**os.environ, **_load_secrets()}
|
|
1817
|
+
fix_env["LOKI_MAX_ITERATIONS"] = "5" # More iterations for complex Docker fixes
|
|
1818
|
+
|
|
1797
1819
|
proc = await asyncio.to_thread(
|
|
1798
1820
|
subprocess.run,
|
|
1799
1821
|
[loki, "quick", fix_prompt],
|
|
1800
1822
|
capture_output=True, text=True, cwd=project_dir, timeout=300,
|
|
1801
|
-
env=
|
|
1823
|
+
env=fix_env
|
|
1802
1824
|
)
|
|
1803
1825
|
|
|
1804
|
-
#
|
|
1826
|
+
# Rebuild the image (fix may have changed Dockerfile or package.json)
|
|
1827
|
+
# --no-deps prevents restarting healthy services, --build rebuilds with the fix
|
|
1805
1828
|
await asyncio.to_thread(
|
|
1806
1829
|
subprocess.run,
|
|
1807
|
-
["docker", "compose", "
|
|
1808
|
-
capture_output=True, cwd=project_dir, timeout=
|
|
1830
|
+
["docker", "compose", "up", "-d", "--build", "--no-deps", service_name],
|
|
1831
|
+
capture_output=True, cwd=project_dir, timeout=120
|
|
1809
1832
|
)
|
|
1810
1833
|
|
|
1834
|
+
# Wait for service to stabilize after rebuild
|
|
1835
|
+
await asyncio.sleep(10)
|
|
1836
|
+
|
|
1837
|
+
# Verify the fix actually worked
|
|
1838
|
+
fix_worked = False
|
|
1839
|
+
try:
|
|
1840
|
+
verify_proc = await asyncio.to_thread(
|
|
1841
|
+
subprocess.run,
|
|
1842
|
+
["docker", "compose", "ps", "-a", "--format", "json"],
|
|
1843
|
+
capture_output=True, text=True, cwd=project_dir, timeout=10
|
|
1844
|
+
)
|
|
1845
|
+
if verify_proc.returncode == 0 and verify_proc.stdout.strip():
|
|
1846
|
+
raw = verify_proc.stdout.strip()
|
|
1847
|
+
try:
|
|
1848
|
+
parsed = json.loads(raw)
|
|
1849
|
+
if not isinstance(parsed, list):
|
|
1850
|
+
parsed = [parsed]
|
|
1851
|
+
except json.JSONDecodeError:
|
|
1852
|
+
parsed = []
|
|
1853
|
+
for line in raw.split("\n"):
|
|
1854
|
+
if line.strip():
|
|
1855
|
+
try:
|
|
1856
|
+
parsed.append(json.loads(line))
|
|
1857
|
+
except json.JSONDecodeError:
|
|
1858
|
+
pass
|
|
1859
|
+
for svc in parsed:
|
|
1860
|
+
if isinstance(svc, dict) and svc.get("Name", svc.get("name", "")) == service_name:
|
|
1861
|
+
state = svc.get("State", svc.get("state", ""))
|
|
1862
|
+
fix_worked = state == "running"
|
|
1863
|
+
break
|
|
1864
|
+
except Exception:
|
|
1865
|
+
logger.debug("Post-fix verification failed for service '%s'", service_name, exc_info=True)
|
|
1866
|
+
|
|
1867
|
+
# Determine final fix status
|
|
1868
|
+
if proc.returncode == 0 and fix_worked:
|
|
1869
|
+
final_status = "fixed"
|
|
1870
|
+
elif proc.returncode == 0 and not fix_worked:
|
|
1871
|
+
final_status = "fix_failed"
|
|
1872
|
+
logger.warning("loki quick succeeded but service '%s' still not running", service_name)
|
|
1873
|
+
else:
|
|
1874
|
+
final_status = "fix_failed"
|
|
1875
|
+
|
|
1811
1876
|
# BUG-V64-005: Re-fetch from live info dict to avoid writing to detached dict
|
|
1812
1877
|
info = self.servers.get(session_id)
|
|
1813
1878
|
if info and "docker_service_health" in info and service_name in info["docker_service_health"]:
|
|
1814
|
-
info["docker_service_health"][service_name]["fix_status"] =
|
|
1879
|
+
info["docker_service_health"][service_name]["fix_status"] = final_status
|
|
1815
1880
|
except Exception as exc:
|
|
1816
1881
|
logger.error("Auto-fix for service '%s' failed: %s", service_name, exc)
|
|
1817
1882
|
# BUG-V64-005: Re-fetch from live info dict
|
|
@@ -1856,7 +1921,7 @@ async def _gather_docker_context(project_dir: Path) -> dict:
|
|
|
1856
1921
|
# Get service status via docker compose ps
|
|
1857
1922
|
try:
|
|
1858
1923
|
ps_proc = await loop.run_in_executor(None, lambda: subprocess.run(
|
|
1859
|
-
["docker", "compose", "ps", "--format", "json"],
|
|
1924
|
+
["docker", "compose", "ps", "-a", "--format", "json"],
|
|
1860
1925
|
capture_output=True, text=True, cwd=str(project_dir), timeout=10
|
|
1861
1926
|
))
|
|
1862
1927
|
if ps_proc.returncode == 0 and ps_proc.stdout.strip():
|