loki-mode 7.23.1 → 7.25.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 +6 -3
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/loki +85 -0
- package/autonomy/run.sh +92 -3
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +79 -2
- package/dashboard/static/index.html +224 -121
- package/docs/INSTALLATION.md +1 -1
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
|
|
27
27
|
- **Spec-driven, autonomous, with a built-in trust layer** -- Hand Loki a spec, walk away, come back to working code with tests. The full RARV-C closure loop (Reason - Act - Reflect - Verify - Close) runs until the work is actually done, not just attempted. The verified-completion evidence gate (`skills/quality-gates.md`) refuses any "done" claim on an empty git diff against the run-start commit, and blocks completion when tests run red, so "complete" means proven, not promised.
|
|
28
28
|
- **Production quality built in** -- 11 quality gates (`skills/quality-gates.md`), blind 3-reviewer code review (`run.sh:run_code_review()`), anti-sycophancy checks
|
|
29
|
+
- **Live App Preview** -- The dashboard embeds the locally-running app in an iframe so you can interact with it immediately during a build. Use `loki preview` (alias `loki open`) to print the URL and open it in your browser. Local-first: no hosted service, no vendor lock (v7.24.0).
|
|
30
|
+
- **Intelligent `loki start`** -- For interactive foreground runs the dashboard auto-opens in the browser (cross-platform; skipped in CI, SSH-without-TTY, and piped runs; opt out with `LOKI_NO_AUTO_OPEN=1`). The completion summary shows "Your app is live at <url>" so you know exactly where to try what Loki just built. The autonomous loop passes Claude Code's `--effort`, `--max-budget-usd`, and `--fallback-model` on every iteration (each gated on CLI support and individual opt-out env vars) for better long-run unattended execution (v7.25.0).
|
|
29
31
|
- **Cross-project memory** -- Episodic/semantic/procedural memory with vector search; knowledge learned on one project surfaces on the next (v5.15.0+, see `memory/engine.py`)
|
|
30
32
|
- **Self-hosted and private** -- Your keys, your infrastructure, no data leaves your network
|
|
31
33
|
- **Legacy system healing** -- `loki heal` archaeology/stabilize/isolate/modernize/validate phases (v6.67.0, see `skills/healing.md`)
|
|
@@ -251,7 +253,7 @@ Blind review, anti-sycophancy, severity blocking, mock/mutation detection, backw
|
|
|
251
253
|
<td width="33%" valign="top">
|
|
252
254
|
|
|
253
255
|
### Dashboard
|
|
254
|
-
Real-time monitoring, agent status, task queue, WebSocket streaming. Auto-starts at `localhost:57374`.
|
|
256
|
+
Real-time monitoring, agent status, task queue, WebSocket streaming, and Live App Preview (embedded iframe of the running app with Refresh/Open/Restart toolbar). Auto-starts at `localhost:57374`.
|
|
255
257
|
|
|
256
258
|
[Dashboard Guide](docs/dashboard-guide.md)
|
|
257
259
|
|
|
@@ -346,12 +348,13 @@ Claude gets full features (subagents, parallelization, MCP, Task tool). Other ac
|
|
|
346
348
|
|
|
347
349
|
| Command | Description |
|
|
348
350
|
|---------|-------------|
|
|
349
|
-
| `loki start [PRD]` | Start with optional PRD file (also accepts an issue ref; replaces deprecated `loki run`) |
|
|
351
|
+
| `loki start [PRD]` | Start with optional PRD file (also accepts an issue ref; replaces deprecated `loki run`). Auto-opens the dashboard in the browser for interactive runs and passes native `--effort`/`--max-budget-usd`/`--fallback-model` for resilience (v7.25.0) |
|
|
350
352
|
| `loki stop` | Stop execution |
|
|
351
353
|
| `loki heal <path>` | Legacy system healing (archaeology, stabilize, isolate, modernize, validate -- v6.67.0) |
|
|
352
354
|
| `loki pause` / `resume` | Pause/resume after current session |
|
|
353
355
|
| `loki status` | Show current status |
|
|
354
356
|
| `loki dashboard` | Open web dashboard |
|
|
357
|
+
| `loki preview` / `loki open` | Print running app URL and open in browser (Live App Preview, v7.24.0) |
|
|
355
358
|
| `loki web` | Launch Purple Lab web UI |
|
|
356
359
|
| `loki doctor` | Check environment and dependencies |
|
|
357
360
|
| `loki plan [PRD]` | Pre-execution analysis: complexity, cost, iterations |
|
|
@@ -421,7 +424,7 @@ See [benchmarks/](benchmarks/) for methodology.
|
|
|
421
424
|
|
|
422
425
|

|
|
423
426
|
|
|
424
|
-
*
|
|
427
|
+
*11 slides: Problem, Solution, 41 Agents, RARV Cycle, 9 Quality Gates (HumanEval 98.78%), Multi-Provider, Enterprise Hardening (Live App Preview), Full Lifecycle*
|
|
425
428
|
|
|
426
429
|
**[Download PPTX](docs/loki-mode-presentation.pptx)**
|
|
427
430
|
|
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, 11 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.
|
|
6
|
+
# Loki Mode v7.25.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -383,4 +383,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
383
383
|
|
|
384
384
|
---
|
|
385
385
|
|
|
386
|
-
**v7.
|
|
386
|
+
**v7.25.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.
|
|
1
|
+
7.25.0
|
package/autonomy/loki
CHANGED
|
@@ -536,6 +536,7 @@ show_help() {
|
|
|
536
536
|
echo " logs Show recent log output"
|
|
537
537
|
echo " dashboard [cmd] Dashboard server (start|stop|status|url|open)"
|
|
538
538
|
echo " web [cmd] Web app UI (start|stop|status) -- serves web-app/dist/"
|
|
539
|
+
echo " preview Open the running app Loki built (alias: open)"
|
|
539
540
|
echo " provider [cmd] Manage AI provider (show|set|list|info)"
|
|
540
541
|
echo " serve Start dashboard/API server (alias for api start)"
|
|
541
542
|
echo " api [cmd] Dashboard/API server (start|stop|status)"
|
|
@@ -4751,6 +4752,87 @@ cmd_web_status() {
|
|
|
4751
4752
|
echo "Purple Lab is not running."
|
|
4752
4753
|
}
|
|
4753
4754
|
|
|
4755
|
+
# Open the running app preview (the app Loki built and started locally).
|
|
4756
|
+
# Surfaces the existing app-runner state; does not start or change the app.
|
|
4757
|
+
cmd_preview() {
|
|
4758
|
+
local open_browser=true
|
|
4759
|
+
case "${1:-}" in
|
|
4760
|
+
--help|-h|help)
|
|
4761
|
+
echo -e "${BOLD}Loki Mode -- open the running app preview${NC}"
|
|
4762
|
+
echo ""
|
|
4763
|
+
echo "Usage: loki preview [--no-open]"
|
|
4764
|
+
echo " loki open (alias)"
|
|
4765
|
+
echo ""
|
|
4766
|
+
echo "Prints the URL of the app Loki built and started locally, then"
|
|
4767
|
+
echo "opens it in your browser. The app runner starts the app after the"
|
|
4768
|
+
echo "first successful build iteration. This serves a real local build"
|
|
4769
|
+
echo "from localhost on your machine; it is not hosted."
|
|
4770
|
+
echo ""
|
|
4771
|
+
echo "Options:"
|
|
4772
|
+
echo " --no-open Print the URL and status only; do not open a browser"
|
|
4773
|
+
echo " --help, -h Show this help and exit"
|
|
4774
|
+
return 0
|
|
4775
|
+
;;
|
|
4776
|
+
--no-open)
|
|
4777
|
+
open_browser=false
|
|
4778
|
+
;;
|
|
4779
|
+
esac
|
|
4780
|
+
|
|
4781
|
+
local state_file="${LOKI_DIR}/app-runner/state.json"
|
|
4782
|
+
if [ ! -f "$state_file" ]; then
|
|
4783
|
+
echo "No app running. The app runner starts after the first successful build iteration."
|
|
4784
|
+
echo "Run 'loki status' to check the current run."
|
|
4785
|
+
return 0
|
|
4786
|
+
fi
|
|
4787
|
+
|
|
4788
|
+
# Parse url/status/port. Prefer python3 (used throughout); fall back to grep.
|
|
4789
|
+
# Pass the path as argv (not inline interpolation) so a path with quotes
|
|
4790
|
+
# cannot break the script, and parse all three fields in one invocation.
|
|
4791
|
+
local url status port parsed
|
|
4792
|
+
if command -v python3 &> /dev/null; then
|
|
4793
|
+
parsed=$(python3 -c "import json,sys
|
|
4794
|
+
try:
|
|
4795
|
+
d=json.load(open(sys.argv[1]))
|
|
4796
|
+
print(d.get('url',''))
|
|
4797
|
+
print(d.get('status',''))
|
|
4798
|
+
print(d.get('port',''))
|
|
4799
|
+
except Exception:
|
|
4800
|
+
print('');print('');print('')" "$state_file" 2>/dev/null)
|
|
4801
|
+
url=$(printf '%s\n' "$parsed" | sed -n '1p')
|
|
4802
|
+
status=$(printf '%s\n' "$parsed" | sed -n '2p')
|
|
4803
|
+
port=$(printf '%s\n' "$parsed" | sed -n '3p')
|
|
4804
|
+
else
|
|
4805
|
+
url=$(grep -oE '"url"[[:space:]]*:[[:space:]]*"[^"]*"' "$state_file" 2>/dev/null | head -1 | sed 's/.*"url"[[:space:]]*:[[:space:]]*"//;s/"$//')
|
|
4806
|
+
status=$(grep -oE '"status"[[:space:]]*:[[:space:]]*"[^"]*"' "$state_file" 2>/dev/null | head -1 | sed 's/.*"status"[[:space:]]*:[[:space:]]*"//;s/"$//')
|
|
4807
|
+
port=$(grep -oE '"port"[[:space:]]*:[[:space:]]*[0-9]+' "$state_file" 2>/dev/null | head -1 | grep -oE '[0-9]+$')
|
|
4808
|
+
fi
|
|
4809
|
+
|
|
4810
|
+
if [ "$status" != "running" ]; then
|
|
4811
|
+
echo "App is not running (status: ${status:-unknown})."
|
|
4812
|
+
echo "The app runner starts the app after a successful build iteration."
|
|
4813
|
+
return 0
|
|
4814
|
+
fi
|
|
4815
|
+
|
|
4816
|
+
if [ -z "$url" ]; then
|
|
4817
|
+
url="http://localhost:${port:-3000}"
|
|
4818
|
+
fi
|
|
4819
|
+
|
|
4820
|
+
echo -e "${GREEN}Live app:${NC} $url [running, port ${port:-?}]"
|
|
4821
|
+
echo "Served from localhost on this machine."
|
|
4822
|
+
|
|
4823
|
+
if [ "$open_browser" = true ]; then
|
|
4824
|
+
if command -v open &> /dev/null; then
|
|
4825
|
+
open "$url"
|
|
4826
|
+
elif command -v xdg-open &> /dev/null; then
|
|
4827
|
+
xdg-open "$url"
|
|
4828
|
+
elif command -v start &> /dev/null; then
|
|
4829
|
+
start "$url"
|
|
4830
|
+
else
|
|
4831
|
+
echo "Please open in browser: $url"
|
|
4832
|
+
fi
|
|
4833
|
+
fi
|
|
4834
|
+
}
|
|
4835
|
+
|
|
4754
4836
|
# Import GitHub issues
|
|
4755
4837
|
cmd_import() {
|
|
4756
4838
|
# v7.6.2 B-13 fix: --help must print help, not start an import.
|
|
@@ -13296,6 +13378,9 @@ main() {
|
|
|
13296
13378
|
web)
|
|
13297
13379
|
cmd_web "$@"
|
|
13298
13380
|
;;
|
|
13381
|
+
preview|open)
|
|
13382
|
+
cmd_preview "$@"
|
|
13383
|
+
;;
|
|
13299
13384
|
logs)
|
|
13300
13385
|
cmd_logs "$@"
|
|
13301
13386
|
;;
|
package/autonomy/run.sh
CHANGED
|
@@ -2423,6 +2423,20 @@ build_completion_summary() {
|
|
|
2423
2423
|
*) outcome_label="$outcome"; notify_title="Run finished" ;;
|
|
2424
2424
|
esac
|
|
2425
2425
|
|
|
2426
|
+
# Live app URL (best-effort): if the app runner has a running app, surface
|
|
2427
|
+
# where the user can try it. Reads .loki/app-runner/state.json written by
|
|
2428
|
+
# app-runner.sh. Empty when no app is running.
|
|
2429
|
+
local live_app_url=""
|
|
2430
|
+
local _app_state_file="$loki_dir/app-runner/state.json"
|
|
2431
|
+
if [ -f "$_app_state_file" ]; then
|
|
2432
|
+
live_app_url="$(python3 -c "import json,sys
|
|
2433
|
+
try:
|
|
2434
|
+
d=json.load(open(sys.argv[1]))
|
|
2435
|
+
print(d.get('url','') if d.get('status')=='running' else '')
|
|
2436
|
+
except Exception:
|
|
2437
|
+
print('')" "$_app_state_file" 2>/dev/null)"
|
|
2438
|
+
fi
|
|
2439
|
+
|
|
2426
2440
|
# Branch + diff stats vs the run-start SHA (best-effort; non-git or empty
|
|
2427
2441
|
# baseline yields empty values, which we render as "unknown"/"0").
|
|
2428
2442
|
local start_sha="${_LOKI_RUN_START_SHA:-}"
|
|
@@ -2483,6 +2497,15 @@ build_completion_summary() {
|
|
|
2483
2497
|
echo "Pull request: not opened (set LOKI_DELEGATE_PR=1 to open one)"
|
|
2484
2498
|
fi
|
|
2485
2499
|
echo ""
|
|
2500
|
+
if [ -n "$live_app_url" ]; then
|
|
2501
|
+
# Compute the dashboard scheme the same way start_dashboard does
|
|
2502
|
+
# (url_scheme is local to that function, not visible here).
|
|
2503
|
+
local _dash_scheme="http"
|
|
2504
|
+
[ -n "${LOKI_TLS_CERT:-}" ] && [ -n "${LOKI_TLS_KEY:-}" ] && _dash_scheme="https"
|
|
2505
|
+
echo "Your app is live at: $live_app_url (served locally on this machine)"
|
|
2506
|
+
echo " Dashboard: ${_dash_scheme}://127.0.0.1:${DASHBOARD_PORT:-57374}/ (App Runner -> Live App)"
|
|
2507
|
+
echo ""
|
|
2508
|
+
fi
|
|
2486
2509
|
echo "Tasks: pending=$pending in_progress=$in_progress completed=$completed failed=$failed"
|
|
2487
2510
|
echo ""
|
|
2488
2511
|
echo "Review the work:"
|
|
@@ -8216,9 +8239,22 @@ start_dashboard() {
|
|
|
8216
8239
|
log_info "Dashboard started (PID: $DASHBOARD_PID)"
|
|
8217
8240
|
log_info "Dashboard: ${CYAN}${url_scheme}://127.0.0.1:$DASHBOARD_PORT/${NC}"
|
|
8218
8241
|
|
|
8219
|
-
#
|
|
8220
|
-
|
|
8221
|
-
|
|
8242
|
+
# Auto-open the dashboard in the browser, but ONLY for an interactive
|
|
8243
|
+
# foreground session. Gated on: a TTY on stdout ([ -t 1 ]), not
|
|
8244
|
+
# background/detached mode, and not explicitly opted out via
|
|
8245
|
+
# LOKI_NO_AUTO_OPEN=1. This keeps CI, --detach, SSH-no-TTY, and piped
|
|
8246
|
+
# runs from spawning a browser. Cross-platform: open / xdg-open / start.
|
|
8247
|
+
if [ -t 1 ] && [ "${BACKGROUND_MODE:-false}" != "true" ] && [ "${LOKI_NO_AUTO_OPEN:-0}" != "1" ]; then
|
|
8248
|
+
local _dash_url="${url_scheme}://127.0.0.1:$DASHBOARD_PORT/"
|
|
8249
|
+
if command -v open >/dev/null 2>&1; then
|
|
8250
|
+
open "$_dash_url" 2>/dev/null || true
|
|
8251
|
+
elif command -v xdg-open >/dev/null 2>&1; then
|
|
8252
|
+
xdg-open "$_dash_url" 2>/dev/null || true
|
|
8253
|
+
elif command -v cmd.exe >/dev/null 2>&1; then
|
|
8254
|
+
# Windows (Git Bash/WSL): `start` is a cmd builtin, not on PATH,
|
|
8255
|
+
# so invoke it via cmd.exe. The empty "" is start's title arg.
|
|
8256
|
+
cmd.exe /c start "" "$_dash_url" 2>/dev/null || true
|
|
8257
|
+
fi
|
|
8222
8258
|
fi
|
|
8223
8259
|
return 0
|
|
8224
8260
|
else
|
|
@@ -12141,6 +12177,59 @@ except Exception as exc:
|
|
|
12141
12177
|
&& loki_claude_flag_supported "--include-partial-messages"; then
|
|
12142
12178
|
_loki_claude_argv+=("--include-partial-messages")
|
|
12143
12179
|
fi
|
|
12180
|
+
# ---- Bash<->Bun invocation-flag convergence ledger (v7.25.0) ----------
|
|
12181
|
+
# The fixture corpus covers build_prompt/stats output, NOT this claude
|
|
12182
|
+
# argv, so drift here is invisible to parity tests. Keep this ledger
|
|
12183
|
+
# current. Live route today is BASH (bin/loki routes `start` -> bash).
|
|
12184
|
+
# The claude provider in loki-ts/src/runner/providers.ts is implemented
|
|
12185
|
+
# but is NOT reached for `start` (start is not ported to the Bun router;
|
|
12186
|
+
# the shim falls through to bash), so its flag set has zero live impact
|
|
12187
|
+
# today.
|
|
12188
|
+
# Bash argv (canonical, live): --dangerously-skip-permissions --model M
|
|
12189
|
+
# [--append-system-prompt] [--setting-sources] [--include-partial-messages]
|
|
12190
|
+
# [--effort] [--max-budget-usd] [--fallback-model] -p PROMPT
|
|
12191
|
+
# --output-format stream-json --verbose
|
|
12192
|
+
# Bun buildAutoFlags also emits: --exclude-dynamic-system-prompt-sections
|
|
12193
|
+
# (cost-only), --mcp-config (bash gets MCP via --setting-sources +
|
|
12194
|
+
# .mcp.json discovery; a how-difference, likely behavior-equivalent),
|
|
12195
|
+
# --include-hook-events (bash handles hook events in its embedded
|
|
12196
|
+
# stream parser; likely moot). These three are Bun-only and MUST be
|
|
12197
|
+
# reconciled to a deliberately chosen canonical set BEFORE `start`
|
|
12198
|
+
# flips to the Bun runner. They have zero live impact today.
|
|
12199
|
+
# v7.25.0: long-run resilience + cost flags, appended individually here
|
|
12200
|
+
# (NOT via _loki_build_claude_auto_flags, which would double the three
|
|
12201
|
+
# flags above). Each is gated on CLI support + an opt-out env var, same
|
|
12202
|
+
# pattern as above. These improve unattended/long-run execution:
|
|
12203
|
+
# --effort adaptive reasoning depth per RARV tier
|
|
12204
|
+
# --max-budget-usd per-call hard backstop (complements the
|
|
12205
|
+
# cumulative check_budget_limit PAUSE gate)
|
|
12206
|
+
# --fallback-model resilience to model overload/unavailability
|
|
12207
|
+
# The trust/verification gates stay deterministic; these only tune how
|
|
12208
|
+
# the provider is invoked, never whether work is judged complete.
|
|
12209
|
+
if [ "${LOKI_AUTO_EFFORT:-on}" != "off" ] \
|
|
12210
|
+
&& type loki_effort_for_tier >/dev/null 2>&1 \
|
|
12211
|
+
&& type loki_claude_flag_supported >/dev/null 2>&1 \
|
|
12212
|
+
&& loki_claude_flag_supported "--effort"; then
|
|
12213
|
+
local _loki_effort
|
|
12214
|
+
_loki_effort="$(loki_effort_for_tier "$CURRENT_TIER" "${DETECTED_COMPLEXITY:-${LOKI_COMPLEXITY:-standard}}")"
|
|
12215
|
+
[ -n "$_loki_effort" ] && _loki_claude_argv+=("--effort" "$_loki_effort")
|
|
12216
|
+
fi
|
|
12217
|
+
if [ "${LOKI_AUTO_BUDGET:-on}" != "off" ] \
|
|
12218
|
+
&& type loki_remaining_budget >/dev/null 2>&1 \
|
|
12219
|
+
&& type loki_claude_flag_supported >/dev/null 2>&1 \
|
|
12220
|
+
&& loki_claude_flag_supported "--max-budget-usd"; then
|
|
12221
|
+
local _loki_rem_budget
|
|
12222
|
+
_loki_rem_budget="$(loki_remaining_budget)"
|
|
12223
|
+
[ -n "$_loki_rem_budget" ] && _loki_claude_argv+=("--max-budget-usd" "$_loki_rem_budget")
|
|
12224
|
+
fi
|
|
12225
|
+
if [ "${LOKI_AUTO_FALLBACK:-on}" != "off" ] \
|
|
12226
|
+
&& type loki_fallback_for_primary >/dev/null 2>&1 \
|
|
12227
|
+
&& type loki_claude_flag_supported >/dev/null 2>&1 \
|
|
12228
|
+
&& loki_claude_flag_supported "--fallback-model"; then
|
|
12229
|
+
local _loki_fallback
|
|
12230
|
+
_loki_fallback="$(loki_fallback_for_primary "$tier_param")"
|
|
12231
|
+
[ -n "$_loki_fallback" ] && _loki_claude_argv+=("--fallback-model" "$_loki_fallback")
|
|
12232
|
+
fi
|
|
12144
12233
|
case "${PROVIDER_NAME:-claude}" in
|
|
12145
12234
|
claude)
|
|
12146
12235
|
# Claude: Full features with stream-json output and agent tracking
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -6628,20 +6628,97 @@ async def get_app_runner_status():
|
|
|
6628
6628
|
return {"status": "error"}
|
|
6629
6629
|
|
|
6630
6630
|
|
|
6631
|
+
def _get_log_redactor():
|
|
6632
|
+
"""Lazily load autonomy/lib/proof_redact.redact_value.
|
|
6633
|
+
|
|
6634
|
+
Lives under autonomy/lib (not on the dashboard import path), so import it
|
|
6635
|
+
by path with a graceful fallback. Returns a callable str -> str. On any
|
|
6636
|
+
import failure returns a redactor that withholds the line rather than
|
|
6637
|
+
leaking raw runtime output (which can contain secrets in stack traces).
|
|
6638
|
+
"""
|
|
6639
|
+
cached = getattr(_get_log_redactor, "_cached", None)
|
|
6640
|
+
if cached is not None:
|
|
6641
|
+
return cached
|
|
6642
|
+
try:
|
|
6643
|
+
import importlib.util
|
|
6644
|
+
|
|
6645
|
+
lib_path = _Path(__file__).resolve().parent.parent / "autonomy" / "lib" / "proof_redact.py"
|
|
6646
|
+
spec = importlib.util.spec_from_file_location("loki_proof_redact", str(lib_path))
|
|
6647
|
+
if spec is None or spec.loader is None:
|
|
6648
|
+
raise ImportError("proof_redact spec unavailable")
|
|
6649
|
+
mod = importlib.util.module_from_spec(spec)
|
|
6650
|
+
spec.loader.exec_module(mod)
|
|
6651
|
+
try:
|
|
6652
|
+
mod.set_context(home=os.path.expanduser("~"), repo_root=str(_project_root()))
|
|
6653
|
+
except Exception:
|
|
6654
|
+
pass
|
|
6655
|
+
redactor = mod.redact_value
|
|
6656
|
+
except Exception:
|
|
6657
|
+
# Fail closed: withhold rather than leak raw log content.
|
|
6658
|
+
def redactor(_s):
|
|
6659
|
+
return "[log withheld: redactor unavailable]"
|
|
6660
|
+
_get_log_redactor._cached = redactor
|
|
6661
|
+
return redactor
|
|
6662
|
+
|
|
6663
|
+
|
|
6631
6664
|
@app.get("/api/app-runner/logs")
|
|
6632
6665
|
async def get_app_runner_logs(lines: int = Query(default=100, ge=1, le=1000)):
|
|
6633
|
-
"""Get last N lines of app runner logs."""
|
|
6666
|
+
"""Get last N lines of app runner logs (redacted)."""
|
|
6634
6667
|
loki_dir = _get_loki_dir()
|
|
6635
6668
|
log_file = loki_dir / "app-runner" / "app.log"
|
|
6636
6669
|
if not log_file.exists():
|
|
6637
6670
|
return {"lines": []}
|
|
6638
6671
|
try:
|
|
6672
|
+
redact = _get_log_redactor()
|
|
6639
6673
|
all_lines = _safe_read_text(log_file).splitlines()
|
|
6640
|
-
return {"lines": all_lines[-lines:]}
|
|
6674
|
+
return {"lines": [redact(ln) for ln in all_lines[-lines:]], "redacted": True}
|
|
6641
6675
|
except OSError:
|
|
6642
6676
|
return {"lines": []}
|
|
6643
6677
|
|
|
6644
6678
|
|
|
6679
|
+
@app.get("/api/app-runner/errors")
|
|
6680
|
+
async def get_app_runner_errors(lines: int = Query(default=50, ge=1, le=500)):
|
|
6681
|
+
"""Get the last N lines of app runner output, redacted, plus crash state.
|
|
6682
|
+
|
|
6683
|
+
Powers the dashboard error banner. Reads .loki/app-runner/app.log (the same
|
|
6684
|
+
log the app writes) and the crash/status fields from state.json so the UI
|
|
6685
|
+
can decide whether to surface the banner without a second round-trip.
|
|
6686
|
+
The error banner is fed exclusively by this server-side endpoint: the
|
|
6687
|
+
running app is cross-origin to the dashboard, so the browser cannot read
|
|
6688
|
+
runtime errors out of the preview iframe.
|
|
6689
|
+
"""
|
|
6690
|
+
loki_dir = _get_loki_dir()
|
|
6691
|
+
app_dir = loki_dir / "app-runner"
|
|
6692
|
+
log_file = app_dir / "app.log"
|
|
6693
|
+
state_file = app_dir / "state.json"
|
|
6694
|
+
|
|
6695
|
+
status = "not_initialized"
|
|
6696
|
+
crash_count = 0
|
|
6697
|
+
if state_file.exists():
|
|
6698
|
+
try:
|
|
6699
|
+
state = json.loads(state_file.read_text())
|
|
6700
|
+
status = state.get("status", "unknown")
|
|
6701
|
+
crash_count = int(state.get("crash_count", 0) or 0)
|
|
6702
|
+
except (json.JSONDecodeError, OSError, ValueError, TypeError):
|
|
6703
|
+
status = "error"
|
|
6704
|
+
|
|
6705
|
+
out_lines = []
|
|
6706
|
+
if log_file.exists():
|
|
6707
|
+
try:
|
|
6708
|
+
redact = _get_log_redactor()
|
|
6709
|
+
all_lines = _safe_read_text(log_file).splitlines()
|
|
6710
|
+
out_lines = [redact(ln) for ln in all_lines[-lines:]]
|
|
6711
|
+
except OSError:
|
|
6712
|
+
out_lines = []
|
|
6713
|
+
|
|
6714
|
+
return {
|
|
6715
|
+
"lines": out_lines,
|
|
6716
|
+
"redacted": True,
|
|
6717
|
+
"status": status,
|
|
6718
|
+
"crash_count": crash_count,
|
|
6719
|
+
}
|
|
6720
|
+
|
|
6721
|
+
|
|
6645
6722
|
@app.post("/api/control/app-restart", dependencies=[Depends(auth.require_scope("control"))])
|
|
6646
6723
|
async def control_app_restart(request: Request):
|
|
6647
6724
|
"""Signal app runner to restart the application."""
|