loki-mode 7.5.9 → 7.5.10
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 +2 -2
- package/SKILL.md +16 -3
- package/VERSION +1 -1
- package/autonomy/run.sh +33 -3
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/events/emit.sh +76 -0
- package/loki-ts/dist/loki.js +2 -2
- package/loki-ts/package.json +2 -1
- package/mcp/__init__.py +1 -1
- package/memory/retrieval.py +52 -7
- package/memory/storage.py +9 -0
- package/package.json +4 -4
- package/skills/healing.md +12 -0
- package/skills/quality-gates.md +10 -0
- package/web-app/requirements.txt +3 -0
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ loki quick "build a landing page with a signup form"
|
|
|
64
64
|
|--------|---------|-------|
|
|
65
65
|
| **Bun (recommended)** | `bun install -g loki-mode` | Fastest. v8 will be Bun-only. |
|
|
66
66
|
| **Homebrew** | `brew tap asklokesh/tap && brew install loki-mode` | Auto-installs Bun as a dep |
|
|
67
|
-
| **Docker** | `docker pull asklokesh/loki-mode:7.5.
|
|
67
|
+
| **Docker** | `docker pull asklokesh/loki-mode:7.5.9 && docker run --rm asklokesh/loki-mode:7.5.9 start prd.md` | Bun pre-installed in image |
|
|
68
68
|
| **npm (compat)** | `npm install -g loki-mode` | Works without Bun (bash fallback). Migrate any time with `loki self-update --to bun`. |
|
|
69
69
|
|
|
70
70
|
**Upgrading:**
|
|
@@ -124,7 +124,7 @@ The next major release sunsets the Bash runtime entirely. There is no firm calen
|
|
|
124
124
|
| Method | Command |
|
|
125
125
|
|--------|---------|
|
|
126
126
|
| **Homebrew** | `brew tap asklokesh/tap && brew install loki-mode` |
|
|
127
|
-
| **Docker** | `docker pull asklokesh/loki-mode:7.5.
|
|
127
|
+
| **Docker** | `docker pull asklokesh/loki-mode:7.5.9` |
|
|
128
128
|
| **Inside Claude Code** | `claude --dangerously-skip-permissions` then type "Loki Mode" |
|
|
129
129
|
| **Git clone** | `git clone https://github.com/asklokesh/loki-mode.git` |
|
|
130
130
|
|
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes PRD to deployed product with minimal human intervention. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v7.5.
|
|
6
|
+
# Loki Mode v7.5.10
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -89,7 +89,7 @@ These rules guide autonomous operation. Test results and code quality always tak
|
|
|
89
89
|
|
|
90
90
|
## Model Selection
|
|
91
91
|
|
|
92
|
-
**Default since v5.3.0 (reaffirmed in v7.5.
|
|
92
|
+
**Default since v5.3.0 (reaffirmed in v7.5.10):** Haiku disabled for quality. Use `--allow-haiku` or `LOKI_ALLOW_HAIKU=true` to enable.
|
|
93
93
|
|
|
94
94
|
| Task Type | Tier | Claude (default) | Claude (--allow-haiku) | Codex (GPT-5.3) | Gemini |
|
|
95
95
|
|-----------|------|------------------|------------------------|------------------|--------|
|
|
@@ -330,6 +330,19 @@ See `references/core-workflow.md` for the full RARV-C contract.
|
|
|
330
330
|
|
|
331
331
|
---
|
|
332
332
|
|
|
333
|
+
## Concurrency and Security Hardening (v7.5.7 - v7.5.10)
|
|
334
|
+
|
|
335
|
+
Three back-to-back patches closed cross-process and security gaps. No user-facing behavior change on the default flow; verify via the cited paths.
|
|
336
|
+
|
|
337
|
+
- **Cross-process file locks** on append-or-rewrite state, so parallel runs / dashboard / MCP do not corrupt shared files: gate counter (`autonomy/run.sh` gate-counter writes), task queues (`autonomy/run.sh` queue read-modify-write), checkpoint index (`autonomy/run.sh` checkpoint index updates), `events.jsonl` append (event emission paths in `events/emit.sh` and `autonomy/run.sh`), human intervention signal files (`autonomy/run.sh:check_human_intervention()` at line ~8059 / 7897 per state-machine doc).
|
|
338
|
+
- **MCP path validation** -- file/path arguments to `mcp/server.py` tools are normalized and rejected if they escape the project root (path-traversal fix from v7.5.8).
|
|
339
|
+
- **Dashboard auth** now required on `/api/memory/*`, `/api/learning/*`, and `/api/status` in `dashboard/server.py` (previously unauthenticated read paths).
|
|
340
|
+
- **Bash quoting hardening** across `autonomy/run.sh` and `autonomy/loki` -- variable expansions inside command substitution and `[ ]` tests quoted to prevent word-splitting on paths with spaces.
|
|
341
|
+
|
|
342
|
+
See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.10] for the per-fix list and reviewer sign-off.
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
333
346
|
## Implemented Features
|
|
334
347
|
|
|
335
348
|
| Feature | Added | Notes |
|
|
@@ -365,4 +378,4 @@ See `references/core-workflow.md` for the full RARV-C contract.
|
|
|
365
378
|
|
|
366
379
|
---
|
|
367
380
|
|
|
368
|
-
**v7.5.
|
|
381
|
+
**v7.5.10 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.5.
|
|
1
|
+
7.5.10
|
package/autonomy/run.sh
CHANGED
|
@@ -477,6 +477,18 @@ parse_yaml_with_yq() {
|
|
|
477
477
|
load_config_file
|
|
478
478
|
|
|
479
479
|
# Load JSON settings from loki config set (v6.0.0)
|
|
480
|
+
#
|
|
481
|
+
# SECURITY NOTE (v7.5.10, L12#2 audit): The eval below is intentional and safe.
|
|
482
|
+
# The Python script's output is constrained to a fixed template:
|
|
483
|
+
# [ -z "${VAR:-}" ] && export VAR=<value>
|
|
484
|
+
# where:
|
|
485
|
+
# - VAR is a hardcoded env var name from the `mapping` dict (NOT user-controlled).
|
|
486
|
+
# - <value> is produced by shlex.quote(), which emits POSIX-shell-safe single-
|
|
487
|
+
# quoted strings even for adversarial input (e.g. quotes, semicolons, $()).
|
|
488
|
+
# - Non-string values from settings.json are skipped (isinstance check).
|
|
489
|
+
# Therefore no user-controlled bytes can break out of the quoted value or alter
|
|
490
|
+
# the surrounding shell syntax. Do NOT remove the shlex.quote() call or relax
|
|
491
|
+
# the isinstance(val, str) guard without re-auditing this eval.
|
|
480
492
|
_load_json_settings() {
|
|
481
493
|
local settings_file="${TARGET_DIR:-.}/.loki/config/settings.json"
|
|
482
494
|
[ -f "$settings_file" ] || return 0
|
|
@@ -7100,13 +7112,31 @@ rollback_to_checkpoint() {
|
|
|
7100
7112
|
done
|
|
7101
7113
|
|
|
7102
7114
|
# Log the rollback (use python3 for safe JSON serialization)
|
|
7103
|
-
|
|
7115
|
+
# v7.5.10: route through safe_append_event_jsonl() so parallel-worktree
|
|
7116
|
+
# rollbacks cannot interleave partial JSONL lines (POSIX append is
|
|
7117
|
+
# only atomic for <PIPE_BUF and not all platforms honor it).
|
|
7118
|
+
local timestamp rb_event
|
|
7104
7119
|
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
7105
|
-
_RB_CPID="$checkpoint_id" _RB_SHA="$git_sha" _RB_TS="$timestamp" \
|
|
7120
|
+
rb_event=$(_RB_CPID="$checkpoint_id" _RB_SHA="$git_sha" _RB_TS="$timestamp" \
|
|
7106
7121
|
python3 -c "
|
|
7107
7122
|
import json,os
|
|
7108
7123
|
print(json.dumps({'event':'rollback','checkpoint':os.environ['_RB_CPID'],'git_sha':os.environ['_RB_SHA'],'timestamp':os.environ['_RB_TS']}))
|
|
7109
|
-
"
|
|
7124
|
+
" 2>/dev/null) || rb_event=""
|
|
7125
|
+
if [ -n "$rb_event" ]; then
|
|
7126
|
+
# Source the emit lib once per call to get safe_append_event_jsonl.
|
|
7127
|
+
# Lib-only mode skips the emit script's normal CLI execution.
|
|
7128
|
+
# shellcheck disable=SC1091
|
|
7129
|
+
if [ -z "${_LOKI_EMIT_LIB_LOADED:-}" ]; then
|
|
7130
|
+
LOKI_EMIT_LIB_ONLY=1 . "$(dirname "${BASH_SOURCE[0]}")/../events/emit.sh" 2>/dev/null \
|
|
7131
|
+
&& _LOKI_EMIT_LIB_LOADED=1
|
|
7132
|
+
fi
|
|
7133
|
+
if declare -f safe_append_event_jsonl >/dev/null 2>&1; then
|
|
7134
|
+
safe_append_event_jsonl ".loki/events.jsonl" "$rb_event" 2>/dev/null || true
|
|
7135
|
+
else
|
|
7136
|
+
# Last-resort fallback: bare append (preserves prior behavior).
|
|
7137
|
+
printf '%s\n' "$rb_event" >> ".loki/events.jsonl" 2>/dev/null || true
|
|
7138
|
+
fi
|
|
7139
|
+
fi
|
|
7110
7140
|
|
|
7111
7141
|
log_info "State files restored from checkpoint: ${checkpoint_id}"
|
|
7112
7142
|
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -740,7 +740,7 @@ async def agent_card() -> dict:
|
|
|
740
740
|
|
|
741
741
|
|
|
742
742
|
# Status endpoint - reads from .loki/ flat files (primary) + DB (fallback)
|
|
743
|
-
@app.get("/api/status", response_model=StatusResponse)
|
|
743
|
+
@app.get("/api/status", response_model=StatusResponse, dependencies=[Depends(auth.require_scope("read"))])
|
|
744
744
|
async def get_status() -> StatusResponse:
|
|
745
745
|
"""Get system status from .loki/ session files."""
|
|
746
746
|
loki_dir = _get_loki_dir()
|
package/docs/INSTALLATION.md
CHANGED
package/events/emit.sh
CHANGED
|
@@ -11,6 +11,82 @@
|
|
|
11
11
|
#
|
|
12
12
|
# Environment:
|
|
13
13
|
# LOKI_DIR - Path to .loki directory (default: .loki)
|
|
14
|
+
#
|
|
15
|
+
# Sourcing:
|
|
16
|
+
# This script can also be sourced (LOKI_EMIT_LIB_ONLY=1) to expose the
|
|
17
|
+
# safe_append_event_jsonl() helper without performing an emit.
|
|
18
|
+
|
|
19
|
+
# safe_append_event_jsonl <events_jsonl_path> <line>
|
|
20
|
+
#
|
|
21
|
+
# Cross-process serialized append to .loki/events.jsonl. POSIX append is
|
|
22
|
+
# atomic only for writes <PIPE_BUF (typically 4KB) and not all filesystems
|
|
23
|
+
# honor it; under parallel-worktree contention bare `>>` can interleave
|
|
24
|
+
# partial JSONL lines. This helper serializes appends across processes.
|
|
25
|
+
#
|
|
26
|
+
# Strategy (v7.5.10):
|
|
27
|
+
# - Prefer flock(1) when available (Linux, util-linux). Uses an
|
|
28
|
+
# exclusive lock on a sentinel FD bound to <events>.lock so the lock
|
|
29
|
+
# is released automatically when the subshell exits.
|
|
30
|
+
# - Fall back to a mkdir() mutex on macOS / BSDs where flock is not
|
|
31
|
+
# installed by default. mkdir is atomic on POSIX -- exactly one
|
|
32
|
+
# concurrent caller wins the create. We retry with backoff up to
|
|
33
|
+
# ~5s, and treat a stale lockdir (>30s old) as abandonable.
|
|
34
|
+
# - The newline is appended by the helper -- callers pass the JSON
|
|
35
|
+
# payload only.
|
|
36
|
+
safe_append_event_jsonl() {
|
|
37
|
+
local events_path="$1"
|
|
38
|
+
local line="$2"
|
|
39
|
+
local lock_target="${events_path}.lock"
|
|
40
|
+
local events_dir
|
|
41
|
+
events_dir="$(dirname "$events_path")"
|
|
42
|
+
mkdir -p "$events_dir" 2>/dev/null || true
|
|
43
|
+
|
|
44
|
+
if command -v flock >/dev/null 2>&1; then
|
|
45
|
+
# flock path: bind FD 9 to the sentinel file (created if absent),
|
|
46
|
+
# take an exclusive lock, append, release on subshell exit.
|
|
47
|
+
(
|
|
48
|
+
flock -x 9
|
|
49
|
+
printf '%s\n' "$line" >> "$events_path"
|
|
50
|
+
) 9>"$lock_target"
|
|
51
|
+
return $?
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# Fallback: mkdir-based mutex. mkdir is atomic on POSIX.
|
|
55
|
+
local lock_dir="${events_path}.lockdir"
|
|
56
|
+
local attempts=0
|
|
57
|
+
local max_attempts=500 # ~5s at 10ms sleep
|
|
58
|
+
while ! mkdir "$lock_dir" 2>/dev/null; do
|
|
59
|
+
attempts=$((attempts + 1))
|
|
60
|
+
if [ "$attempts" -ge "$max_attempts" ]; then
|
|
61
|
+
# Stale lock: if the dir is older than 30s, force-remove it.
|
|
62
|
+
local age
|
|
63
|
+
age=$(( $(date +%s) - $(stat -f%m "$lock_dir" 2>/dev/null \
|
|
64
|
+
|| stat -c%Y "$lock_dir" 2>/dev/null \
|
|
65
|
+
|| echo 0) ))
|
|
66
|
+
if [ "$age" -gt 30 ]; then
|
|
67
|
+
rmdir "$lock_dir" 2>/dev/null || rm -rf "$lock_dir" 2>/dev/null || true
|
|
68
|
+
attempts=0
|
|
69
|
+
continue
|
|
70
|
+
fi
|
|
71
|
+
# Give up -- best-effort write so observability never blocks.
|
|
72
|
+
printf '%s\n' "$line" >> "$events_path" 2>/dev/null || true
|
|
73
|
+
return 1
|
|
74
|
+
fi
|
|
75
|
+
# Sleep ~10ms (perl avoids `sleep 0.01` portability issues).
|
|
76
|
+
perl -e 'select(undef,undef,undef,0.01)' 2>/dev/null || sleep 1
|
|
77
|
+
done
|
|
78
|
+
# Critical section.
|
|
79
|
+
printf '%s\n' "$line" >> "$events_path"
|
|
80
|
+
local rc=$?
|
|
81
|
+
rmdir "$lock_dir" 2>/dev/null || true
|
|
82
|
+
return $rc
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# Library-only mode: source this file to get safe_append_event_jsonl
|
|
86
|
+
# without executing the emit logic below.
|
|
87
|
+
if [ "${LOKI_EMIT_LIB_ONLY:-0}" = "1" ]; then
|
|
88
|
+
return 0 2>/dev/null || exit 0
|
|
89
|
+
fi
|
|
14
90
|
|
|
15
91
|
set -euo pipefail
|
|
16
92
|
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -423,7 +423,7 @@ Subcommands:
|
|
|
423
423
|
|
|
424
424
|
This command is invoked by autonomy/run.sh between iterations. Users
|
|
425
425
|
should not run it directly -- run \`loki start\` instead.
|
|
426
|
-
`,T5;var $6=w(()=>{m();T1();T5=c1});m();import{readFileSync as U6}from"fs";import{resolve as q6,dirname as H6}from"path";import{fileURLToPath as G6}from"url";var o=null;function r1(){if(o!==null)return o;let $="7.5.
|
|
426
|
+
`,T5;var $6=w(()=>{m();T1();T5=c1});m();import{readFileSync as U6}from"fs";import{resolve as q6,dirname as H6}from"path";import{fileURLToPath as G6}from"url";var o=null;function r1(){if(o!==null)return o;let $="7.5.10";if(typeof $==="string"&&$.length>0)return o=$,o;try{let Q=H6(G6(import.meta.url)),z=E1(Q);o=U6(q6(z,"VERSION"),"utf-8").trim()}catch{o="unknown"}return o}function s1(){return process.stdout.write(`Loki Mode v${r1()}
|
|
427
427
|
`),0}p();n();m();import{readFileSync as A6,existsSync as T6}from"fs";import{resolve as O6}from"path";var j6=["claude","codex","gemini","cline","aider"];function i1(){let $=O6(P(),"state","provider");if(!T6($))return"";try{return A6($,"utf-8").trim()}catch{return""}}function _6($,Q){return $||Q||process.env.LOKI_PROVIDER||"claude"}function I6($){let Q=i1(),z=_6($,Q);switch(process.stdout.write(`${D}Current Provider${W}
|
|
428
428
|
`),process.stdout.write(`
|
|
429
429
|
`),process.stdout.write(`${A}Provider:${W} ${z}
|
|
@@ -506,4 +506,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
506
506
|
`),2}default:return process.stderr.write(`Unknown command: ${Q}
|
|
507
507
|
`),process.stderr.write(z6),2}}process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var _5=await j5(Bun.argv.slice(2));process.exit(_5);
|
|
508
508
|
|
|
509
|
-
//# debugId=
|
|
509
|
+
//# debugId=70EC2B659E43E8E764756E2164756E21
|
package/loki-ts/package.json
CHANGED
package/mcp/__init__.py
CHANGED
package/memory/retrieval.py
CHANGED
|
@@ -295,6 +295,42 @@ class MemoryRetrieval:
|
|
|
295
295
|
"""Get the current namespace."""
|
|
296
296
|
return self._namespace
|
|
297
297
|
|
|
298
|
+
# Track which legacy entries we've already warned about to avoid log spam.
|
|
299
|
+
_LEGACY_WARN_LIMIT = 5
|
|
300
|
+
_legacy_warned_count: int = 0
|
|
301
|
+
|
|
302
|
+
def _belongs_to_namespace(self, result: Dict[str, Any]) -> bool:
|
|
303
|
+
"""
|
|
304
|
+
Check if a memory result belongs to the current namespace.
|
|
305
|
+
|
|
306
|
+
Cross-namespace leak defense (v7.5.10). Storage layer should isolate
|
|
307
|
+
files by directory, but this filter provides defense-in-depth and
|
|
308
|
+
handles cases where vector indices span namespaces.
|
|
309
|
+
|
|
310
|
+
Behavior:
|
|
311
|
+
- If self._namespace is None, accept all (backward compat for unscoped retrieval).
|
|
312
|
+
- If result has "_namespace" matching, accept.
|
|
313
|
+
- If result lacks "_namespace" (legacy entry written before stamping),
|
|
314
|
+
log a deprecation warning (rate-limited) and ACCEPT for backward compat.
|
|
315
|
+
- Otherwise, reject.
|
|
316
|
+
"""
|
|
317
|
+
if self._namespace is None:
|
|
318
|
+
return True
|
|
319
|
+
result_ns = result.get("_namespace")
|
|
320
|
+
if result_ns is None:
|
|
321
|
+
# Legacy entry without namespace stamp; accept for backward compat
|
|
322
|
+
# but warn so operators can re-save to add stamps.
|
|
323
|
+
if MemoryRetrieval._legacy_warned_count < MemoryRetrieval._LEGACY_WARN_LIMIT:
|
|
324
|
+
logger.warning(
|
|
325
|
+
"Memory entry id=%s lacks '_namespace' stamp (legacy "
|
|
326
|
+
"entry). Including in results for backward compatibility. "
|
|
327
|
+
"Re-save this entry to enable namespace isolation.",
|
|
328
|
+
result.get("id", "<unknown>"),
|
|
329
|
+
)
|
|
330
|
+
MemoryRetrieval._legacy_warned_count += 1
|
|
331
|
+
return True
|
|
332
|
+
return result_ns == self._namespace
|
|
333
|
+
|
|
298
334
|
def with_namespace(self, namespace: str) -> "MemoryRetrieval":
|
|
299
335
|
"""
|
|
300
336
|
Create a new MemoryRetrieval instance with a different namespace.
|
|
@@ -761,7 +797,10 @@ class MemoryRetrieval:
|
|
|
761
797
|
item["id"] = item_id
|
|
762
798
|
item["_score"] = float(score)
|
|
763
799
|
item["_source"] = collection
|
|
764
|
-
|
|
800
|
+
# Cross-namespace leak defense (v7.5.10): vector indices may be
|
|
801
|
+
# shared across namespaces, so filter here too.
|
|
802
|
+
if self._belongs_to_namespace(item):
|
|
803
|
+
items.append(item)
|
|
765
804
|
|
|
766
805
|
return items
|
|
767
806
|
|
|
@@ -810,7 +849,8 @@ class MemoryRetrieval:
|
|
|
810
849
|
)
|
|
811
850
|
if data:
|
|
812
851
|
data["_source"] = "episodic"
|
|
813
|
-
|
|
852
|
+
if self._belongs_to_namespace(data):
|
|
853
|
+
results.append(data)
|
|
814
854
|
|
|
815
855
|
# Filter semantic patterns by last_used
|
|
816
856
|
patterns_data = self.storage.read_json("semantic/patterns.json") or {}
|
|
@@ -831,7 +871,8 @@ class MemoryRetrieval:
|
|
|
831
871
|
|
|
832
872
|
if since <= last_used_dt <= until:
|
|
833
873
|
pattern["_source"] = "semantic"
|
|
834
|
-
|
|
874
|
+
if self._belongs_to_namespace(pattern):
|
|
875
|
+
results.append(pattern)
|
|
835
876
|
except (ValueError, TypeError):
|
|
836
877
|
continue
|
|
837
878
|
|
|
@@ -1428,7 +1469,8 @@ class MemoryRetrieval:
|
|
|
1428
1469
|
data_copy = dict(data)
|
|
1429
1470
|
data_copy["_score"] = score
|
|
1430
1471
|
data_copy["_source"] = "episodic"
|
|
1431
|
-
|
|
1472
|
+
if self._belongs_to_namespace(data_copy):
|
|
1473
|
+
results.append(data_copy)
|
|
1432
1474
|
|
|
1433
1475
|
return results
|
|
1434
1476
|
|
|
@@ -1457,7 +1499,8 @@ class MemoryRetrieval:
|
|
|
1457
1499
|
pattern_copy = dict(pattern)
|
|
1458
1500
|
pattern_copy["_score"] = score
|
|
1459
1501
|
pattern_copy["_source"] = "semantic"
|
|
1460
|
-
|
|
1502
|
+
if self._belongs_to_namespace(pattern_copy):
|
|
1503
|
+
results.append(pattern_copy)
|
|
1461
1504
|
|
|
1462
1505
|
return results
|
|
1463
1506
|
|
|
@@ -1486,7 +1529,8 @@ class MemoryRetrieval:
|
|
|
1486
1529
|
data_copy = dict(data)
|
|
1487
1530
|
data_copy["_score"] = score
|
|
1488
1531
|
data_copy["_source"] = "skills"
|
|
1489
|
-
|
|
1532
|
+
if self._belongs_to_namespace(data_copy):
|
|
1533
|
+
results.append(data_copy)
|
|
1490
1534
|
|
|
1491
1535
|
return results
|
|
1492
1536
|
|
|
@@ -1511,7 +1555,8 @@ class MemoryRetrieval:
|
|
|
1511
1555
|
anti_copy = dict(anti)
|
|
1512
1556
|
anti_copy["_score"] = score
|
|
1513
1557
|
anti_copy["_source"] = "anti_patterns"
|
|
1514
|
-
|
|
1558
|
+
if self._belongs_to_namespace(anti_copy):
|
|
1559
|
+
results.append(anti_copy)
|
|
1515
1560
|
|
|
1516
1561
|
return results
|
|
1517
1562
|
|
package/memory/storage.py
CHANGED
|
@@ -391,6 +391,11 @@ class MemoryStorage:
|
|
|
391
391
|
episode_id = episode_data.get("id") or self._generate_id("episode")
|
|
392
392
|
episode_data["id"] = episode_id
|
|
393
393
|
|
|
394
|
+
# Stamp namespace so retrieval can verify isolation (cross-namespace
|
|
395
|
+
# leak defense, v7.5.10). Defaults to DEFAULT_NAMESPACE for unscoped
|
|
396
|
+
# storage instances.
|
|
397
|
+
episode_data["_namespace"] = self._namespace or DEFAULT_NAMESPACE
|
|
398
|
+
|
|
394
399
|
# Determine storage path based on date
|
|
395
400
|
timestamp = episode_data.get("timestamp", datetime.now(timezone.utc).isoformat())
|
|
396
401
|
if isinstance(timestamp, str):
|
|
@@ -557,6 +562,8 @@ class MemoryStorage:
|
|
|
557
562
|
"created_at",
|
|
558
563
|
datetime.now(timezone.utc).isoformat()
|
|
559
564
|
)
|
|
565
|
+
# Stamp namespace for cross-namespace leak defense (v7.5.10).
|
|
566
|
+
pattern_data["_namespace"] = self._namespace or DEFAULT_NAMESPACE
|
|
560
567
|
|
|
561
568
|
patterns_path = self.base_path / "semantic" / "patterns.json"
|
|
562
569
|
|
|
@@ -750,6 +757,8 @@ class MemoryStorage:
|
|
|
750
757
|
"created_at",
|
|
751
758
|
datetime.now(timezone.utc).isoformat()
|
|
752
759
|
)
|
|
760
|
+
# Stamp namespace for cross-namespace leak defense (v7.5.10).
|
|
761
|
+
skill_data["_namespace"] = self._namespace or DEFAULT_NAMESPACE
|
|
753
762
|
|
|
754
763
|
# Use skill name for filename if available, otherwise use ID
|
|
755
764
|
skill_name = skill_data.get("name", skill_id)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
|
-
"version": "7.5.
|
|
3
|
+
"version": "7.5.10",
|
|
4
4
|
"description": "Loki Mode by Autonomi - Multi-agent autonomous startup system for Claude Code, Codex CLI, and Gemini CLI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
],
|
|
103
103
|
"scripts": {
|
|
104
104
|
"prepack": "find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null; find . -name '*.pyc' -delete 2>/dev/null; if command -v bun >/dev/null 2>&1; then (cd loki-ts && bun install --production && bun run build) || echo 'WARN: loki-ts build failed, using existing dist if present'; else echo 'WARN: bun not on PATH, skipping loki-ts build (using committed dist if present)'; fi; true",
|
|
105
|
-
"prepublishOnly": "cd dashboard-ui && npm ci && npm run build:all",
|
|
105
|
+
"prepublishOnly": "cd dashboard-ui && npm ci && npm run build:all && test -f ../dashboard/static/index.html",
|
|
106
106
|
"test": "bash -n autonomy/run.sh && bash -n autonomy/loki && bash -n autonomy/completion-council.sh && bash -n autonomy/app-runner.sh && bash -n autonomy/prd-checklist.sh && bash -n autonomy/playwright-verify.sh && node --test tests/protocols/*.test.js && node --test tests/protocols/a2a/*.test.js && node --test tests/observability/*.test.js && node --test tests/policies/*.test.js && node --test tests/audit/*.test.js && node --test tests/integrations/*.test.js && node --test tests/integrations/jira/*.test.js && node --test tests/integrations/github/*.test.js && node --test tests/integrations/slack/*.test.js && bash tests/managed_memory/test_flag_matrix.sh && bash tests/managed_memory/test_sdk_isolation.sh && bash tests/managed_memory/test_kill_switch.sh && python3 -m unittest tests.managed_memory.test_shadow_write_mock tests.managed_memory.test_retrieve_mock && echo 'All checks passed'",
|
|
107
107
|
"test:visual": "node --experimental-vm-modules node_modules/jest/bin/jest.js dashboard-ui/tests/visual-regression.test.js",
|
|
108
108
|
"test:parity": "node --experimental-vm-modules dashboard-ui/scripts/check-parity.js",
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
"test:integration": "bash tests/integration/run_integration_suite.sh"
|
|
112
112
|
},
|
|
113
113
|
"engines": {
|
|
114
|
-
"node": ">=
|
|
114
|
+
"node": ">=20.0.0"
|
|
115
115
|
},
|
|
116
116
|
"os": [
|
|
117
117
|
"darwin",
|
|
@@ -124,7 +124,7 @@
|
|
|
124
124
|
"@opentelemetry/exporter-trace-otlp-http": "^0.57.0"
|
|
125
125
|
},
|
|
126
126
|
"overrides": {
|
|
127
|
-
"protobufjs": ">=7.5.
|
|
127
|
+
"protobufjs": ">=7.5.6"
|
|
128
128
|
},
|
|
129
129
|
"devDependencies": {
|
|
130
130
|
"@types/node": "^25.2.0",
|
package/skills/healing.md
CHANGED
|
@@ -505,3 +505,15 @@ loki heal --report
|
|
|
505
505
|
# Strict mode: block any behavioral change without approval
|
|
506
506
|
LOKI_HEAL_STRICT=true loki heal ./legacy-app
|
|
507
507
|
```
|
|
508
|
+
|
|
509
|
+
## Checkpoint metadata hardening (v7.5.8)
|
|
510
|
+
|
|
511
|
+
As defense-in-depth for the healing checkpoint store, the checkpoint
|
|
512
|
+
writer now rejects ASCII control characters (U+0000-U+001F except TAB,
|
|
513
|
+
LF, CR, plus U+007F) anywhere in checkpoint metadata keys or values
|
|
514
|
+
before persisting `.loki/healing/checkpoints/<phase>.json`. This blocks
|
|
515
|
+
log-injection and JSON-poisoning vectors where adapter output, friction
|
|
516
|
+
notes, or institutional-knowledge excerpts could smuggle terminal
|
|
517
|
+
escapes or NUL bytes into the resume path. Rejected writes raise a
|
|
518
|
+
typed error and never partially overwrite the prior checkpoint, so
|
|
519
|
+
`loki heal --resume` always restarts from a clean, validated state.
|
package/skills/quality-gates.md
CHANGED
|
@@ -160,6 +160,16 @@ are silently dropped at load time. The override council uses a stub
|
|
|
160
160
|
judge in v7.5.x that approves any of those six trusted proofTypes;
|
|
161
161
|
real provider-backed judges land in Phase 2 of Part B.
|
|
162
162
|
|
|
163
|
+
**Cross-process gate counter (v7.5.5+)**: the per-iteration gate counter
|
|
164
|
+
at `.loki/state/gate-counter-<iter>.json` is now incremented under a
|
|
165
|
+
cross-process file lock via `withFileLockSync` in
|
|
166
|
+
`loki-ts/src/util/atomic.ts`. Concurrent gate runs (parallel worktrees,
|
|
167
|
+
overlapping `runQualityGates` invocations) no longer race the
|
|
168
|
+
read-modify-write, so override-council quotas and per-finding counters
|
|
169
|
+
remain consistent across processes. The lock file lives at
|
|
170
|
+
`.loki/state/gate-counter-<iter>.json.lock` and is released even on
|
|
171
|
+
crash via the primitive's `finally` cleanup.
|
|
172
|
+
|
|
163
173
|
---
|
|
164
174
|
|
|
165
175
|
## Guardrails Execution Modes
|
package/web-app/requirements.txt
CHANGED