loki-mode 7.83.1 → 7.85.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/lib/proof-generator.py +407 -13
- package/autonomy/lib/proof-template.html +309 -1
- package/autonomy/lib/proof-verify.py +483 -0
- package/autonomy/loki +32 -1
- package/autonomy/run.sh +26 -2
- package/dashboard/__init__.py +1 -1
- package/dashboard/registry.py +54 -0
- package/dashboard/server.py +55 -9
- package/dashboard/static/index.html +953 -376
- package/docs/INSTALLATION.md +2 -2
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
package/dashboard/server.py
CHANGED
|
@@ -2900,6 +2900,17 @@ async def list_running_projects():
|
|
|
2900
2900
|
Live-vs-stale is derived from pid liveness, which is robust even when a
|
|
2901
2901
|
session is hard-killed (no exit hook fires). Never raises: registry
|
|
2902
2902
|
problems degrade to an empty list.
|
|
2903
|
+
|
|
2904
|
+
Registry hygiene (project switcher): the registry records every cwd ever
|
|
2905
|
+
seen by `loki start` and never garbage-collects, so the switcher list grows
|
|
2906
|
+
without bound and shows paths that no longer exist on disk. This endpoint:
|
|
2907
|
+
- opportunistically prunes registry entries whose path is gone AND which
|
|
2908
|
+
are not running (registry.prune_missing_projects), keeping the on-disk
|
|
2909
|
+
store small. Running projects are never pruned.
|
|
2910
|
+
- returns a CLEAN list: a project is included only if its path still
|
|
2911
|
+
exists on disk OR it is currently running (or is the active project).
|
|
2912
|
+
- sorts by last_accessed desc (most relevant first) and caps the list
|
|
2913
|
+
defensively, but never drops a running or the active project.
|
|
2903
2914
|
"""
|
|
2904
2915
|
out = []
|
|
2905
2916
|
try:
|
|
@@ -2907,6 +2918,19 @@ async def list_running_projects():
|
|
|
2907
2918
|
except Exception:
|
|
2908
2919
|
projects = []
|
|
2909
2920
|
active = _active_project_dir
|
|
2921
|
+
|
|
2922
|
+
def _is_active(path: str) -> bool:
|
|
2923
|
+
# Compare via realpath: /api/focus resolves symlinks (e.g. macOS
|
|
2924
|
+
# /tmp -> /private/tmp) while the registry stores abspath, so a plain
|
|
2925
|
+
# abspath compare would never match a focused symlinked project.
|
|
2926
|
+
if not (active and path):
|
|
2927
|
+
return False
|
|
2928
|
+
try:
|
|
2929
|
+
return os.path.realpath(active) == os.path.realpath(path)
|
|
2930
|
+
except OSError:
|
|
2931
|
+
return os.path.abspath(active) == os.path.abspath(path)
|
|
2932
|
+
|
|
2933
|
+
running_ids = set()
|
|
2910
2934
|
for p in projects:
|
|
2911
2935
|
path = p.get("path", "")
|
|
2912
2936
|
pid = p.get("pid")
|
|
@@ -2936,15 +2960,15 @@ async def list_running_projects():
|
|
|
2936
2960
|
running = s.get("status") == "running"
|
|
2937
2961
|
except Exception:
|
|
2938
2962
|
pass
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2963
|
+
path_exists = bool(path) and os.path.isdir(path)
|
|
2964
|
+
is_active = _is_active(path)
|
|
2965
|
+
if running:
|
|
2966
|
+
running_ids.add(p.get("id"))
|
|
2967
|
+
# CLEAN list: include only projects whose path still exists, or which
|
|
2968
|
+
# are running, or which are the active project. A dead path that is not
|
|
2969
|
+
# running is excluded (and pruned from the store below).
|
|
2970
|
+
if not (path_exists or running or is_active):
|
|
2971
|
+
continue
|
|
2948
2972
|
out.append({
|
|
2949
2973
|
"id": p.get("id"),
|
|
2950
2974
|
"name": p.get("name") or (os.path.basename(path) if path else "project"),
|
|
@@ -2953,7 +2977,29 @@ async def list_running_projects():
|
|
|
2953
2977
|
"status": p.get("status"),
|
|
2954
2978
|
"running": running,
|
|
2955
2979
|
"is_active": is_active,
|
|
2980
|
+
"_last_accessed": p.get("last_accessed") or p.get("registered_at") or "",
|
|
2956
2981
|
})
|
|
2982
|
+
|
|
2983
|
+
# Opportunistically garbage-collect dead entries from the on-disk registry
|
|
2984
|
+
# so it never grows without bound. Running projects (by id, plus any with a
|
|
2985
|
+
# live recorded pid inside the helper) are retained even if the disk check
|
|
2986
|
+
# is racy. Best-effort: a prune failure must not break the listing.
|
|
2987
|
+
try:
|
|
2988
|
+
registry.prune_missing_projects(running_ids=running_ids)
|
|
2989
|
+
except Exception:
|
|
2990
|
+
pass
|
|
2991
|
+
|
|
2992
|
+
# Most relevant first: last_accessed desc. Cap defensively, but never hide a
|
|
2993
|
+
# running or active project (partition them out before the cap).
|
|
2994
|
+
_SWITCHER_CAP = 50
|
|
2995
|
+
out.sort(key=lambda r: r.get("_last_accessed", ""), reverse=True)
|
|
2996
|
+
if len(out) > _SWITCHER_CAP:
|
|
2997
|
+
pinned = [r for r in out if r["running"] or r["is_active"]]
|
|
2998
|
+
rest = [r for r in out if not (r["running"] or r["is_active"])]
|
|
2999
|
+
out = pinned + rest[: max(0, _SWITCHER_CAP - len(pinned))]
|
|
3000
|
+
# Drop the internal sort key from the response shape.
|
|
3001
|
+
for r in out:
|
|
3002
|
+
r.pop("_last_accessed", None)
|
|
2957
3003
|
return {"projects": out, "active_project_dir": active}
|
|
2958
3004
|
|
|
2959
3005
|
|