loki-mode 7.90.0 → 7.90.1
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/run.sh +7 -0
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +137 -2
- package/dashboard/static/index.html +243 -383
- 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/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Autonomous spec-driven build system with a built-in trust layer. It does not call work done until it is verified (RARV-C closure loop, 8 quality gates, completion council, verified-completion evidence gate). Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v7.90.
|
|
6
|
+
# Loki Mode v7.90.1
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -408,4 +408,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
408
408
|
|
|
409
409
|
---
|
|
410
410
|
|
|
411
|
-
**v7.90.
|
|
411
|
+
**v7.90.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.90.
|
|
1
|
+
7.90.1
|
package/autonomy/run.sh
CHANGED
|
@@ -10650,7 +10650,14 @@ start_dashboard() {
|
|
|
10650
10650
|
# Start the FastAPI dashboard server
|
|
10651
10651
|
# Dashboard module is at project root (parent of autonomy/)
|
|
10652
10652
|
# LOKI_SKILL_DIR tells server.py where to find static files
|
|
10653
|
+
# LOKI_DASHBOARD_AUTOSTARTED=1 marks this as a run-launched (auto-started)
|
|
10654
|
+
# dashboard, distinct from an explicit `loki dashboard start`. The server
|
|
10655
|
+
# uses this so that, after the dashboard Stop button stops the last active
|
|
10656
|
+
# run, it may shut ITSELF down -- but ONLY when it was auto-started, never
|
|
10657
|
+
# when the user started it deliberately to keep monitoring. The explicit
|
|
10658
|
+
# cmd_dashboard_start path never sets this var (M4 fix).
|
|
10653
10659
|
LOKI_TLS_CERT="${LOKI_TLS_CERT:-}" LOKI_TLS_KEY="${LOKI_TLS_KEY:-}" \
|
|
10660
|
+
LOKI_DASHBOARD_AUTOSTARTED=1 \
|
|
10654
10661
|
LOKI_SKILL_DIR="${skill_dir}" PYTHONPATH="${skill_dir}" nohup "$python_cmd" -m dashboard.server > "$log_file" 2>&1 &
|
|
10655
10662
|
DASHBOARD_PID=$!
|
|
10656
10663
|
register_pid "$DASHBOARD_PID" "dashboard" "port=${DASHBOARD_PORT:-57374}"
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -3325,12 +3325,14 @@ async def stop_running_project(request: Request, body: RunningProjectStopRequest
|
|
|
3325
3325
|
except OSError:
|
|
3326
3326
|
pass
|
|
3327
3327
|
registry.mark_project_stopped(project_id)
|
|
3328
|
-
|
|
3328
|
+
_resp = {
|
|
3329
3329
|
"success": True,
|
|
3330
3330
|
"project_id": project_id,
|
|
3331
3331
|
"stopped": stop_signaled,
|
|
3332
3332
|
"already_stopped": not stop_signaled,
|
|
3333
3333
|
}
|
|
3334
|
+
_resp.update(_dashboard_teardown_after_project_stop(path))
|
|
3335
|
+
return _resp
|
|
3334
3336
|
|
|
3335
3337
|
# Write the STOP file so the runner's own cleanup STOP-branch fires for a
|
|
3336
3338
|
# clean teardown. Only into the registry-resolved .loki dir.
|
|
@@ -3392,12 +3394,14 @@ async def stop_running_project(request: Request, body: RunningProjectStopRequest
|
|
|
3392
3394
|
|
|
3393
3395
|
registry.mark_project_stopped(project_id)
|
|
3394
3396
|
|
|
3395
|
-
|
|
3397
|
+
_resp = {
|
|
3396
3398
|
"success": True,
|
|
3397
3399
|
"project_id": project_id,
|
|
3398
3400
|
"stopped": stopped,
|
|
3399
3401
|
"already_stopped": False,
|
|
3400
3402
|
}
|
|
3403
|
+
_resp.update(_dashboard_teardown_after_project_stop(path))
|
|
3404
|
+
return _resp
|
|
3401
3405
|
|
|
3402
3406
|
|
|
3403
3407
|
def _recover_spec_source(path: str) -> Optional[_Path]:
|
|
@@ -4057,6 +4061,17 @@ def get_compliance_status(request: Request, report_type: str = Query("soc2", ali
|
|
|
4057
4061
|
# When set, _get_loki_dir() resolves .loki/ relative to this path instead of CWD.
|
|
4058
4062
|
_active_project_dir: Optional[str] = None
|
|
4059
4063
|
|
|
4064
|
+
# M4 (v7.90.1): was this dashboard auto-started by a run (`loki start` ->
|
|
4065
|
+
# run.sh start_dashboard), or started deliberately by the user
|
|
4066
|
+
# (`loki dashboard start`)? Captured ONCE at import from the environment marker
|
|
4067
|
+
# that ONLY the auto-start path sets. Used to decide whether the dashboard may
|
|
4068
|
+
# shut ITSELF down after its Stop button stops the last active run: an
|
|
4069
|
+
# auto-started dashboard self-exits (it came up with the run, so it should go
|
|
4070
|
+
# down with the last run), while a user-started dashboard is left up and only
|
|
4071
|
+
# surfaces a non-destructive notice. Frozen at startup so a later child process
|
|
4072
|
+
# inheriting/clearing the var cannot flip the decision.
|
|
4073
|
+
_DASHBOARD_AUTOSTARTED: bool = os.environ.get("LOKI_DASHBOARD_AUTOSTARTED") == "1"
|
|
4074
|
+
|
|
4060
4075
|
|
|
4061
4076
|
def _get_loki_dir() -> _Path:
|
|
4062
4077
|
"""Get LOKI_DIR, refreshing from env on each call for consistency.
|
|
@@ -4364,6 +4379,126 @@ def _reap_orchestrators_until_clear(project_dir: _Path, expected_cwd: str,
|
|
|
4364
4379
|
return (found_any, not _find_orchestrator_pids_for_dir(project_dir))
|
|
4365
4380
|
|
|
4366
4381
|
|
|
4382
|
+
def _other_runs_alive(exclude_path: Optional[str] = None) -> bool:
|
|
4383
|
+
"""True if ANY registered project OTHER than exclude_path still has a live
|
|
4384
|
+
orchestrator pid. Mirrors run.sh's CLEAR/KEEP check (and cmd_stop's): a
|
|
4385
|
+
project counts as alive only when its recorded pid is still running. The
|
|
4386
|
+
just-stopped project must already be marked stopped in the registry (its pid
|
|
4387
|
+
set to None by mark_project_stopped) before this is called, so it is not
|
|
4388
|
+
self-counted; exclude_path is an extra belt-and-suspenders guard by realpath.
|
|
4389
|
+
Best-effort: any registry/probe error degrades to True (KEEP) so we NEVER
|
|
4390
|
+
tear the dashboard down on uncertain information.
|
|
4391
|
+
"""
|
|
4392
|
+
try:
|
|
4393
|
+
exclude_real = None
|
|
4394
|
+
if exclude_path:
|
|
4395
|
+
try:
|
|
4396
|
+
exclude_real = os.path.realpath(exclude_path)
|
|
4397
|
+
except OSError:
|
|
4398
|
+
exclude_real = os.path.abspath(exclude_path)
|
|
4399
|
+
for p in registry.list_projects(include_inactive=True):
|
|
4400
|
+
path = p.get("path", "")
|
|
4401
|
+
if exclude_real and path:
|
|
4402
|
+
try:
|
|
4403
|
+
if os.path.realpath(path) == exclude_real:
|
|
4404
|
+
continue
|
|
4405
|
+
except OSError:
|
|
4406
|
+
if os.path.abspath(path) == exclude_real:
|
|
4407
|
+
continue
|
|
4408
|
+
pid = p.get("pid")
|
|
4409
|
+
if isinstance(pid, int) and pid > 0 and not _pid_is_gone(pid):
|
|
4410
|
+
return True
|
|
4411
|
+
return False
|
|
4412
|
+
except Exception:
|
|
4413
|
+
# Unknown -> assume something is still running so we never wrongly kill
|
|
4414
|
+
# the dashboard.
|
|
4415
|
+
return True
|
|
4416
|
+
|
|
4417
|
+
|
|
4418
|
+
def _self_shutdown_after_response(delay_s: float = 1.0) -> None:
|
|
4419
|
+
"""Gracefully shut THIS dashboard server down shortly after the current HTTP
|
|
4420
|
+
response has been sent. Used only after the dashboard's own Stop button stops
|
|
4421
|
+
the LAST active run and only when this dashboard was auto-started (M4).
|
|
4422
|
+
|
|
4423
|
+
Why a delayed background thread (not an inline exit): the Stop request is
|
|
4424
|
+
still in flight when this is decided. We must let FastAPI/uvicorn finish
|
|
4425
|
+
writing the response (so the UI gets its JSON) before the process goes away.
|
|
4426
|
+
A short-delay daemon thread sends SIGTERM to our OWN pid, which uvicorn
|
|
4427
|
+
handles as a graceful shutdown (runs the lifespan teardown). This sidesteps
|
|
4428
|
+
the fragile path where the dashboard relied on the orchestrator's cleanup
|
|
4429
|
+
trap to kill it -- a path that a SIGKILL of the orchestrator (after the 5s
|
|
4430
|
+
window) or a uvicorn graceful-shutdown deadlock could bypass, leaving the
|
|
4431
|
+
dashboard lingering (the reported M4 bug). Best-effort; never raises into
|
|
4432
|
+
the request.
|
|
4433
|
+
"""
|
|
4434
|
+
import signal as _signal
|
|
4435
|
+
|
|
4436
|
+
def _kill_self() -> None:
|
|
4437
|
+
try:
|
|
4438
|
+
time.sleep(max(0.0, delay_s))
|
|
4439
|
+
except Exception:
|
|
4440
|
+
pass
|
|
4441
|
+
try:
|
|
4442
|
+
logger.info(
|
|
4443
|
+
"Auto-started dashboard: last active run stopped and no other "
|
|
4444
|
+
"run is alive; shutting self down (pid=%s).", os.getpid())
|
|
4445
|
+
except Exception:
|
|
4446
|
+
pass
|
|
4447
|
+
try:
|
|
4448
|
+
os.kill(os.getpid(), _signal.SIGTERM)
|
|
4449
|
+
except OSError:
|
|
4450
|
+
# Last resort: hard-exit this process only (never touches others).
|
|
4451
|
+
os._exit(0)
|
|
4452
|
+
|
|
4453
|
+
try:
|
|
4454
|
+
threading.Thread(target=_kill_self, name="loki-dash-selfstop",
|
|
4455
|
+
daemon=True).start()
|
|
4456
|
+
except Exception:
|
|
4457
|
+
# If we cannot even spawn the thread, do nothing: a lingering dashboard
|
|
4458
|
+
# is strictly safer than any broader action.
|
|
4459
|
+
pass
|
|
4460
|
+
|
|
4461
|
+
|
|
4462
|
+
def _dashboard_teardown_after_project_stop(stopped_path: Optional[str]) -> dict:
|
|
4463
|
+
"""Decide what happens to THIS dashboard after a per-project Stop, and return
|
|
4464
|
+
fields to merge into the Stop response (M4).
|
|
4465
|
+
|
|
4466
|
+
Rules (conservative; never touches any process but this server's own):
|
|
4467
|
+
- If another registered run is still alive -> KEEP: do nothing, the
|
|
4468
|
+
dashboard stays up because other projects still need it.
|
|
4469
|
+
- If no other run is alive (CLEAR) and this dashboard was AUTO-STARTED by a
|
|
4470
|
+
run -> schedule a graceful self-shutdown after the response is sent. It
|
|
4471
|
+
came up with a run, so it goes down with the last run.
|
|
4472
|
+
- If CLEAR but the dashboard was started DELIBERATELY by the user
|
|
4473
|
+
(`loki dashboard start`) -> do NOT self-kill; return a non-destructive
|
|
4474
|
+
notice so the UI can tell the user how to stop it (loki dashboard stop).
|
|
4475
|
+
|
|
4476
|
+
Returns one of:
|
|
4477
|
+
{"dashboard": "kept"} # other runs alive
|
|
4478
|
+
{"dashboard": "stopping", ...} # auto-started + CLEAR
|
|
4479
|
+
{"dashboard": "idle", "notice": "...", ...} # user-started + CLEAR
|
|
4480
|
+
"""
|
|
4481
|
+
try:
|
|
4482
|
+
if _other_runs_alive(exclude_path=stopped_path):
|
|
4483
|
+
return {"dashboard": "kept"}
|
|
4484
|
+
if _DASHBOARD_AUTOSTARTED:
|
|
4485
|
+
_self_shutdown_after_response()
|
|
4486
|
+
return {
|
|
4487
|
+
"dashboard": "stopping",
|
|
4488
|
+
"dashboard_autostarted": True,
|
|
4489
|
+
}
|
|
4490
|
+
return {
|
|
4491
|
+
"dashboard": "idle",
|
|
4492
|
+
"dashboard_autostarted": False,
|
|
4493
|
+
"notice": ("No active runs. This dashboard was started manually; "
|
|
4494
|
+
"stop it with: loki dashboard stop"),
|
|
4495
|
+
}
|
|
4496
|
+
except Exception:
|
|
4497
|
+
# Any failure in the decision must never break the Stop response, and
|
|
4498
|
+
# must never kill the dashboard on uncertain info.
|
|
4499
|
+
return {"dashboard": "kept"}
|
|
4500
|
+
|
|
4501
|
+
|
|
4367
4502
|
def _pid_is_gone(pid: int) -> bool:
|
|
4368
4503
|
"""True if pid no longer exists OR is a zombie/defunct (effectively dead,
|
|
4369
4504
|
just not yet reaped by its parent). os.kill(pid,0) succeeds on a zombie, so
|