loki-mode 7.7.32 → 7.7.33

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 CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: Autonomous spec-to-product system. Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product via the RARV-C closure loop, with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v7.7.32
6
+ # Loki Mode v7.7.33
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -381,4 +381,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
381
381
 
382
382
  ---
383
383
 
384
- **v7.7.32 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
384
+ **v7.7.33 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.7.32
1
+ 7.7.33
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.7.32"
10
+ __version__ = "7.7.33"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -10,6 +10,7 @@ import asyncio
10
10
  import json
11
11
  import logging
12
12
  import os
13
+ import subprocess
13
14
  import time
14
15
  from collections import defaultdict
15
16
  from dataclasses import asdict
@@ -2322,6 +2323,19 @@ async def stop_running_project(request: Request, body: RunningProjectStopRequest
2322
2323
  # pid already dead or unsignalable -- treat as stopped.
2323
2324
  stopped = True
2324
2325
 
2326
+ # v7.7.33: the registry pid can be stale (a crashed/restarted session leaves
2327
+ # an orphaned loki-run-*.sh under a new pid). Reap any orchestrator whose CWD
2328
+ # is this project's dir so a stale pid cannot yield a false "stopped". Scoped
2329
+ # by cwd to this project only.
2330
+ if loki_dir is not None:
2331
+ proj_dir = loki_dir.parent
2332
+ found_any, all_gone = await asyncio.to_thread(
2333
+ _reap_orchestrators_until_clear, proj_dir, str(proj_dir))
2334
+ if found_any:
2335
+ stopped = all_gone
2336
+ elif not all_gone:
2337
+ stopped = False
2338
+
2325
2339
  # Mark session.json stopped in that project's .loki.
2326
2340
  if loki_dir is not None:
2327
2341
  session_file = loki_dir / "session.json"
@@ -2581,6 +2595,209 @@ def _get_loki_dir() -> _Path:
2581
2595
  return _Path(".loki")
2582
2596
 
2583
2597
 
2598
+ def _find_orchestrator_pids_for_dir(project_dir: _Path) -> list[int]:
2599
+ """Find live Loki orchestrator PIDs whose working directory IS project_dir.
2600
+
2601
+ v7.7.33: the dashboard Stop button used to signal only loki.pid. When that
2602
+ pid file was stale (e.g. a crashed/restarted session left an orphaned
2603
+ `bash /tmp/loki-run-XXXXXX.sh` reparented to init under a NEW pid), Stop
2604
+ killed nothing live yet reported "stopped". The orchestrator temp-script
2605
+ name carries no project identity, so we map orchestrator -> project by the
2606
+ process CWD, which is reliably the project directory.
2607
+
2608
+ Strictly scoped: returns ONLY pids whose cwd resolves to project_dir, so a
2609
+ stop on one project never reaps another folder's runner. Best-effort: on any
2610
+ enumeration failure returns an empty list (callers still signal loki.pid).
2611
+ """
2612
+ pids: list[int] = []
2613
+ try:
2614
+ target = os.path.realpath(str(project_dir))
2615
+ except OSError:
2616
+ return pids
2617
+
2618
+ # Enumerate candidate orchestrator processes (the loki-run-*.sh temp script).
2619
+ # Anchor the pattern to the real temp-dir path prefix (mktemp writes the
2620
+ # runner to $TMPDIR or /tmp), so an unrelated process that merely mentions a
2621
+ # "loki-run-*.sh" string in its argv is far less likely to match. The cwd
2622
+ # equality check below is the authoritative scope guard regardless.
2623
+ # pgrep enumeration can transiently miss a live process (kernel proc-list
2624
+ # timing under load), which would let a still-running orphan slip past the
2625
+ # post-kill survivor check and yield a false "stopped". Union a few quick
2626
+ # passes to make the enumeration resilient. The cwd filter below still
2627
+ # guarantees scope correctness for whatever is enumerated.
2628
+ import time as _time
2629
+ candidate_set: set[int] = set()
2630
+ enumerated = False
2631
+ for _attempt in range(3):
2632
+ try:
2633
+ # Match the runner temp script by its path segment. Anchor on the
2634
+ # "/loki-run-" path prefix (mktemp writes it under $TMPDIR or /tmp)
2635
+ # for scope, but do NOT constrain the random suffix charset: mktemp
2636
+ # names can contain any of [A-Za-z0-9_] depending on platform, and
2637
+ # an over-tight charset (e.g. excluding "_") silently misses live
2638
+ # orchestrators -- the cwd check below is the real scope guard.
2639
+ out = subprocess.run(
2640
+ ["pgrep", "-f", r"/loki-run-[^/ ]*\.sh"],
2641
+ capture_output=True, text=True, timeout=5,
2642
+ )
2643
+ enumerated = True
2644
+ for line in out.stdout.split():
2645
+ try:
2646
+ candidate_set.add(int(line))
2647
+ except ValueError:
2648
+ pass
2649
+ except (OSError, subprocess.SubprocessError):
2650
+ pass
2651
+ if _attempt < 2:
2652
+ _time.sleep(0.15)
2653
+ if not enumerated:
2654
+ return pids
2655
+ candidates = list(candidate_set)
2656
+
2657
+ for pid in candidates:
2658
+ if _pid_is_gone(pid):
2659
+ continue # skip zombies / already-reaped
2660
+ cwd = _pid_cwd(pid)
2661
+ if cwd and os.path.realpath(cwd) == target:
2662
+ pids.append(pid)
2663
+ return pids
2664
+
2665
+
2666
+ def _pid_cwd(pid: int) -> Optional[str]:
2667
+ """Return a process's current working directory, or None.
2668
+
2669
+ Linux: read /proc/<pid>/cwd. macOS/BSD: fall back to `lsof -a -p <pid> -d cwd`.
2670
+ Best-effort and exception-safe; never raises.
2671
+ """
2672
+ # Linux fast path
2673
+ proc_cwd = f"/proc/{pid}/cwd"
2674
+ try:
2675
+ if os.path.isdir(f"/proc/{pid}"):
2676
+ return os.readlink(proc_cwd)
2677
+ except OSError:
2678
+ pass
2679
+ # macOS / BSD: lsof
2680
+ try:
2681
+ out = subprocess.run(
2682
+ ["lsof", "-a", "-p", str(pid), "-d", "cwd", "-Fn"],
2683
+ capture_output=True, text=True, timeout=5,
2684
+ )
2685
+ for line in out.stdout.splitlines():
2686
+ if line.startswith("n"):
2687
+ return line[1:]
2688
+ except (OSError, subprocess.SubprocessError):
2689
+ pass
2690
+ return None
2691
+
2692
+
2693
+ def _reap_orchestrators_until_clear(project_dir: _Path, expected_cwd: str,
2694
+ rounds: int = 6) -> tuple[bool, bool]:
2695
+ """Find and terminate orchestrators for project_dir, looping until the
2696
+ project is clear across a confirming re-scan. Returns (found_any, all_gone).
2697
+
2698
+ Robust against transient pgrep enumeration misses: a single find-then-scan
2699
+ could miss a live orphan and falsely report it gone. We repeat find+kill and
2700
+ require TWO consecutive empty scans before declaring all_gone, so a one-off
2701
+ enumeration miss cannot yield a false "stopped". Runs in a worker thread (the
2702
+ caller wraps it in asyncio.to_thread) so the blocking kills do not stall the
2703
+ event loop. Strictly cwd-scoped via _find_orchestrator_pids_for_dir.
2704
+ """
2705
+ import time as _time
2706
+ found_any = False
2707
+ consecutive_empty = 0
2708
+ for _round in range(rounds):
2709
+ found = _find_orchestrator_pids_for_dir(project_dir)
2710
+ if found:
2711
+ found_any = True
2712
+ consecutive_empty = 0
2713
+ for opid in found:
2714
+ _terminate_pid(opid, expected_cwd=expected_cwd)
2715
+ else:
2716
+ consecutive_empty += 1
2717
+ if consecutive_empty >= 2:
2718
+ return (found_any, True) # clear, confirmed twice
2719
+ _time.sleep(0.2) # brief pause before the confirming re-scan
2720
+ # Exhausted rounds: report gone only if the final scan is empty.
2721
+ return (found_any, not _find_orchestrator_pids_for_dir(project_dir))
2722
+
2723
+
2724
+ def _pid_is_gone(pid: int) -> bool:
2725
+ """True if pid no longer exists OR is a zombie/defunct (effectively dead,
2726
+ just not yet reaped by its parent). os.kill(pid,0) succeeds on a zombie, so
2727
+ we additionally consult `ps` for the process state. Never raises."""
2728
+ try:
2729
+ os.kill(pid, 0)
2730
+ except OSError:
2731
+ return True # no such process
2732
+ # alive per signal-0; check for zombie state (Z / defunct). Note: os.kill
2733
+ # above proved the pid exists, so EMPTY ps output is a transient race, NOT a
2734
+ # reap -- do not treat it as gone (that would falsely report a live
2735
+ # orchestrator stopped). Only an explicit Z/zombie state counts as gone.
2736
+ try:
2737
+ out = subprocess.run(["ps", "-o", "state=", "-p", str(pid)],
2738
+ capture_output=True, text=True, timeout=5)
2739
+ st = out.stdout.strip()
2740
+ if st.startswith("Z"):
2741
+ return True # zombie / defunct -- effectively dead
2742
+ except (OSError, subprocess.SubprocessError):
2743
+ pass
2744
+ return False
2745
+
2746
+
2747
+ def _terminate_pid(pid: int, timeout_s: float = 5.0,
2748
+ expected_cwd: Optional[str] = None) -> bool:
2749
+ """SIGTERM a pid, wait up to timeout_s, then SIGKILL. Return True if it is
2750
+ gone afterward. Reaps direct children first (pkill -P) so the provider/app
2751
+ child does not outlive the orchestrator. A zombie counts as gone.
2752
+ Best-effort, never raises.
2753
+
2754
+ If expected_cwd is given, re-verify the pid's cwd still matches it right
2755
+ before signaling (TOCTOU guard against pid reuse between enumeration and
2756
+ kill). If it no longer matches, do nothing and report the pid gone."""
2757
+ import time as _time
2758
+ if expected_cwd is not None:
2759
+ cwd = _pid_cwd(pid)
2760
+ # Only skip the kill when the cwd POSITIVELY differs (true pid reuse).
2761
+ # A failed/transient cwd lookup (cwd is None) must NOT cancel the kill:
2762
+ # the pid came from a cwd-matched enumeration moments ago, and treating a
2763
+ # transient lookup miss as "recycled" would skip killing a live
2764
+ # orchestrator and falsely report it stopped.
2765
+ if cwd:
2766
+ try:
2767
+ if os.path.realpath(cwd) != os.path.realpath(expected_cwd):
2768
+ return True # pid recycled to a different cwd -- do not kill
2769
+ except OSError:
2770
+ pass # fall through and kill the originally-matched pid
2771
+ try:
2772
+ # reap children first so a wedged child cannot keep the tree alive
2773
+ subprocess.run(["pkill", "-TERM", "-P", str(pid)],
2774
+ capture_output=True, timeout=5)
2775
+ except (OSError, subprocess.SubprocessError):
2776
+ pass
2777
+ try:
2778
+ os.kill(pid, 15)
2779
+ except (ProcessLookupError, OSError):
2780
+ return True # already gone
2781
+ deadline = timeout_s
2782
+ while deadline > 0:
2783
+ _time.sleep(0.5)
2784
+ deadline -= 0.5
2785
+ if _pid_is_gone(pid):
2786
+ return True
2787
+ # still alive -> SIGKILL the tree
2788
+ try:
2789
+ subprocess.run(["pkill", "-9", "-P", str(pid)],
2790
+ capture_output=True, timeout=5)
2791
+ except (OSError, subprocess.SubprocessError):
2792
+ pass
2793
+ try:
2794
+ os.kill(pid, 9)
2795
+ except (ProcessLookupError, OSError):
2796
+ return True
2797
+ _time.sleep(0.3)
2798
+ return _pid_is_gone(pid)
2799
+
2800
+
2584
2801
  _SAFE_ID_RE = re.compile(r'^[a-zA-Z0-9_-]+$')
2585
2802
 
2586
2803
 
@@ -3914,6 +4131,26 @@ async def stop_session(request: Request):
3914
4131
  except (ValueError, OSError, ProcessLookupError):
3915
4132
  process_stopped = True
3916
4133
 
4134
+ # v7.7.33: loki.pid alone is not authoritative. If it was stale (a crashed
4135
+ # or restarted session can leave an orphaned `loki-run-*.sh` under a new pid
4136
+ # reparented to init), the kill above is a no-op yet reports "stopped" while
4137
+ # the real orchestrator keeps running. Reap any orchestrator process whose
4138
+ # CWD is THIS project's directory. Strictly scoped by cwd, so a stop on one
4139
+ # project never touches another folder's runner.
4140
+ project_dir = _get_loki_dir().parent
4141
+ _proj = str(project_dir)
4142
+ found_any, all_gone = await asyncio.to_thread(
4143
+ _reap_orchestrators_until_clear, project_dir, _proj)
4144
+ # The orchestrator-survivor scan is authoritative over loki.pid. If any
4145
+ # orchestrator for this project was ever found, report stopped only when none
4146
+ # survive. If we found one (stale-pid case), the real outcome wins over the
4147
+ # loki.pid false positive. If the scan kept finding survivors, report not
4148
+ # stopped even if loki.pid claimed success.
4149
+ if found_any:
4150
+ process_stopped = all_gone
4151
+ elif not all_gone:
4152
+ process_stopped = False
4153
+
3917
4154
  # Mark session.json as stopped
3918
4155
  session_file = _get_loki_dir() / "session.json"
3919
4156
  if session_file.exists():
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v7.7.32
5
+ **Version:** v7.7.33
6
6
 
7
7
  ---
8
8
 
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var _7=Object.defineProperty;var I7=(K)=>K;function P7(K,$){this[K]=I7.bind(null,$)}var v=(K,$)=>{for(var Q in $)_7(K,Q,{get:$[Q],enumerable:!0,configurable:!0,set:P7.bind($,Q)})};var w=(K,$)=>()=>(K&&($=K(K=0)),$);var t=import.meta.require;var e1={};v(e1,{lokiDir:()=>L,homeLokiDir:()=>k1,findRepoRootForVersion:()=>S1,REPO_ROOT:()=>p});import{resolve as u,dirname as N1}from"path";import{fileURLToPath as L7}from"url";import{existsSync as J1}from"fs";import{homedir as R7}from"os";function E7(){let K=i1;for(let $=0;$<6;$++){if(J1(u(K,"VERSION"))&&J1(u(K,"autonomy/run.sh")))return K;let Q=N1(K);if(Q===K)break;K=Q}return u(i1,"..","..","..")}function S1(K){let $=K;for(let Q=0;Q<6;Q++){if(J1(u($,"VERSION"))&&J1(u($,"autonomy/run.sh")))return $;let X=N1($);if(X===$)break;$=X}return u(K,"..","..","..")}function L(){return process.env.LOKI_DIR??u(process.cwd(),".loki")}function k1(){return u(R7(),".loki")}var i1,p;var g=w(()=>{i1=N1(L7(import.meta.url));p=E7()});import{readFileSync as w7}from"fs";import{resolve as x7,dirname as F7}from"path";import{fileURLToPath as N7}from"url";function G1(){if(o!==null)return o;let K="7.7.32";if(typeof K==="string"&&K.length>0)return o=K,o;try{let $=F7(N7(import.meta.url)),Q=S1($);o=w7(x7(Q,"VERSION"),"utf-8").trim()}catch{o="unknown"}return o}var o=null;var C1=w(()=>{g()});var $0={};v($0,{runOrThrow:()=>S7,run:()=>C,commandVersion:()=>C7,commandExists:()=>b,ShellError:()=>D1});async function C(K,$={}){let Q=Bun.spawn({cmd:[...K],stdout:"pipe",stderr:"pipe",env:$.env?{...process.env,...$.env}:process.env,cwd:$.cwd}),X,Z;if($.timeoutMs&&$.timeoutMs>0)X=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}Z=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},$.timeoutMs);try{let[W,z,q]=await Promise.all([new Response(Q.stdout).text(),new Response(Q.stderr).text(),Q.exited]);return{stdout:W,stderr:z,exitCode:q}}finally{if(X)clearTimeout(X);if(Z)clearTimeout(Z)}}async function S7(K,$={}){let Q=await C(K,$);if(Q.exitCode!==0)throw new D1(`command failed (${Q.exitCode}): ${K.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function b(K){let $=k7(K),Q=await C(["sh","-c",`command -v ${$}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function k7(K){if(!/^[A-Za-z0-9._/-]+$/.test(K))throw Error(`refused to shell-escape suspect token: ${K}`);return K}async function C7(K,$="--version"){if(!await b(K))return null;let X=await C([K,$],{timeoutMs:5000});if(X.exitCode!==0)return null;return((X.stdout||X.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var D1;var n=w(()=>{D1=class D1 extends Error{message;exitCode;stdout;stderr;constructor(K,$,Q,X){super(K);this.message=K;this.exitCode=$;this.stdout=Q;this.stderr=X;this.name="ShellError"}}});function c(K){return D7?"":K}var D7,F,y,N,A6,A,D,S,H;var a=w(()=>{D7=(process.env.NO_COLOR??"").length>0;F=c("\x1B[0;31m"),y=c("\x1B[0;32m"),N=c("\x1B[1;33m"),A6=c("\x1B[0;34m"),A=c("\x1B[0;36m"),D=c("\x1B[1m"),S=c("\x1B[2m"),H=c("\x1B[0m")});import{existsSync as c7}from"fs";async function i(){if(Z1!==void 0)return Z1;let K="/opt/homebrew/bin/python3.12";if(c7(K))return Z1=K,K;let $=await b("python3.12");if($)return Z1=$,$;let Q=await b("python3");return Z1=Q,Q}async function s(K,$={}){let Q=await i();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return C([Q,"-c",K],$)}var Z1;var z1=w(()=>{n()});var G0={};v(G0,{runStatus:()=>X5});import{existsSync as k,readFileSync as K1,readdirSync as W0,statSync as H0}from"fs";import{resolve as R,basename as a7}from"path";import{homedir as s7}from"os";async function t7(){if(await b("jq"))return!0;return process.stdout.write(`${F}Error: jq is required but not installed.${H}
2
+ var _7=Object.defineProperty;var I7=(K)=>K;function P7(K,$){this[K]=I7.bind(null,$)}var v=(K,$)=>{for(var Q in $)_7(K,Q,{get:$[Q],enumerable:!0,configurable:!0,set:P7.bind($,Q)})};var w=(K,$)=>()=>(K&&($=K(K=0)),$);var t=import.meta.require;var e1={};v(e1,{lokiDir:()=>L,homeLokiDir:()=>k1,findRepoRootForVersion:()=>S1,REPO_ROOT:()=>p});import{resolve as u,dirname as N1}from"path";import{fileURLToPath as L7}from"url";import{existsSync as J1}from"fs";import{homedir as R7}from"os";function E7(){let K=i1;for(let $=0;$<6;$++){if(J1(u(K,"VERSION"))&&J1(u(K,"autonomy/run.sh")))return K;let Q=N1(K);if(Q===K)break;K=Q}return u(i1,"..","..","..")}function S1(K){let $=K;for(let Q=0;Q<6;Q++){if(J1(u($,"VERSION"))&&J1(u($,"autonomy/run.sh")))return $;let X=N1($);if(X===$)break;$=X}return u(K,"..","..","..")}function L(){return process.env.LOKI_DIR??u(process.cwd(),".loki")}function k1(){return u(R7(),".loki")}var i1,p;var g=w(()=>{i1=N1(L7(import.meta.url));p=E7()});import{readFileSync as w7}from"fs";import{resolve as x7,dirname as F7}from"path";import{fileURLToPath as N7}from"url";function G1(){if(o!==null)return o;let K="7.7.33";if(typeof K==="string"&&K.length>0)return o=K,o;try{let $=F7(N7(import.meta.url)),Q=S1($);o=w7(x7(Q,"VERSION"),"utf-8").trim()}catch{o="unknown"}return o}var o=null;var C1=w(()=>{g()});var $0={};v($0,{runOrThrow:()=>S7,run:()=>C,commandVersion:()=>C7,commandExists:()=>b,ShellError:()=>D1});async function C(K,$={}){let Q=Bun.spawn({cmd:[...K],stdout:"pipe",stderr:"pipe",env:$.env?{...process.env,...$.env}:process.env,cwd:$.cwd}),X,Z;if($.timeoutMs&&$.timeoutMs>0)X=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}Z=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},$.timeoutMs);try{let[W,z,q]=await Promise.all([new Response(Q.stdout).text(),new Response(Q.stderr).text(),Q.exited]);return{stdout:W,stderr:z,exitCode:q}}finally{if(X)clearTimeout(X);if(Z)clearTimeout(Z)}}async function S7(K,$={}){let Q=await C(K,$);if(Q.exitCode!==0)throw new D1(`command failed (${Q.exitCode}): ${K.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function b(K){let $=k7(K),Q=await C(["sh","-c",`command -v ${$}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function k7(K){if(!/^[A-Za-z0-9._/-]+$/.test(K))throw Error(`refused to shell-escape suspect token: ${K}`);return K}async function C7(K,$="--version"){if(!await b(K))return null;let X=await C([K,$],{timeoutMs:5000});if(X.exitCode!==0)return null;return((X.stdout||X.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var D1;var n=w(()=>{D1=class D1 extends Error{message;exitCode;stdout;stderr;constructor(K,$,Q,X){super(K);this.message=K;this.exitCode=$;this.stdout=Q;this.stderr=X;this.name="ShellError"}}});function c(K){return D7?"":K}var D7,F,y,N,A6,A,D,S,H;var a=w(()=>{D7=(process.env.NO_COLOR??"").length>0;F=c("\x1B[0;31m"),y=c("\x1B[0;32m"),N=c("\x1B[1;33m"),A6=c("\x1B[0;34m"),A=c("\x1B[0;36m"),D=c("\x1B[1m"),S=c("\x1B[2m"),H=c("\x1B[0m")});import{existsSync as c7}from"fs";async function i(){if(Z1!==void 0)return Z1;let K="/opt/homebrew/bin/python3.12";if(c7(K))return Z1=K,K;let $=await b("python3.12");if($)return Z1=$,$;let Q=await b("python3");return Z1=Q,Q}async function s(K,$={}){let Q=await i();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return C([Q,"-c",K],$)}var Z1;var z1=w(()=>{n()});var G0={};v(G0,{runStatus:()=>X5});import{existsSync as k,readFileSync as K1,readdirSync as W0,statSync as H0}from"fs";import{resolve as R,basename as a7}from"path";import{homedir as s7}from"os";async function t7(){if(await b("jq"))return!0;return process.stdout.write(`${F}Error: jq is required but not installed.${H}
3
3
  `),process.stdout.write(`Install with:
4
4
  `),process.stdout.write(` brew install jq (macOS)
5
5
  `),process.stdout.write(` apt install jq (Debian/Ubuntu)
@@ -604,4 +604,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
604
604
  `),2}default:return process.stderr.write(`Unknown command: ${$}
605
605
  `),process.stderr.write(j7),2}}process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var Z6=await X6(Bun.argv.slice(2));process.exit(Z6);
606
606
 
607
- //# debugId=CE5371E5853C122164756E2164756E21
607
+ //# debugId=916AFC3C81F3859F64756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.7.32'
60
+ __version__ = '7.7.33'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "7.7.32",
3
+ "version": "7.7.33",
4
4
  "description": "Loki Mode by Autonomi. Autonomous spec-to-product system: takes a PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief to a deployed app via the RARV-C closure loop with 11 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
5
5
  "keywords": [
6
6
  "agent",