loki-mode 7.23.1 → 7.24.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 CHANGED
@@ -26,6 +26,7 @@
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).
29
30
  - **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
31
  - **Self-hosted and private** -- Your keys, your infrastructure, no data leaves your network
31
32
  - **Legacy system healing** -- `loki heal` archaeology/stabilize/isolate/modernize/validate phases (v6.67.0, see `skills/healing.md`)
@@ -251,7 +252,7 @@ Blind review, anti-sycophancy, severity blocking, mock/mutation detection, backw
251
252
  <td width="33%" valign="top">
252
253
 
253
254
  ### Dashboard
254
- Real-time monitoring, agent status, task queue, WebSocket streaming. Auto-starts at `localhost:57374`.
255
+ 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
256
 
256
257
  [Dashboard Guide](docs/dashboard-guide.md)
257
258
 
@@ -352,6 +353,7 @@ Claude gets full features (subagents, parallelization, MCP, Task tool). Other ac
352
353
  | `loki pause` / `resume` | Pause/resume after current session |
353
354
  | `loki status` | Show current status |
354
355
  | `loki dashboard` | Open web dashboard |
356
+ | `loki preview` / `loki open` | Print running app URL and open in browser (Live App Preview, v7.24.0) |
355
357
  | `loki web` | Launch Purple Lab web UI |
356
358
  | `loki doctor` | Check environment and dependencies |
357
359
  | `loki plan [PRD]` | Pre-execution analysis: complexity, cost, iterations |
@@ -421,7 +423,7 @@ See [benchmarks/](benchmarks/) for methodology.
421
423
 
422
424
  ![Loki Mode Presentation](docs/loki-mode-presentation.gif)
423
425
 
424
- *9 slides: Problem, Solution, 41 Agents, RARV Cycle, Benchmarks, Multi-Provider, Full Lifecycle*
426
+ *11 slides: Problem, Solution, 41 Agents, RARV Cycle, 9 Quality Gates (HumanEval 98.78%), Multi-Provider, Enterprise Hardening (Live App Preview), Full Lifecycle*
425
427
 
426
428
  **[Download PPTX](docs/loki-mode-presentation.pptx)**
427
429
 
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.23.1
6
+ # Loki Mode v7.24.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.23.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
386
+ **v7.24.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.23.1
1
+ 7.24.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
  ;;
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.23.1"
10
+ __version__ = "7.24.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -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."""