loki-mode 7.42.0 → 7.44.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 +4 -31
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/app-runner.sh +174 -8
- package/autonomy/completion-council.sh +16 -3
- package/autonomy/loki +54 -9
- package/autonomy/run.sh +73 -2
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +226 -1
- package/dashboard/static/index.html +87 -30
- package/docs/INSTALLATION.md +1 -1
- package/events/bus.py +9 -6
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/mcp/server.py +26 -2
- package/memory/vector_index.py +6 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/providers/codex.sh +21 -1
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var n6=Object.defineProperty;var a6=($)=>$;function s6($,Q){this[$]=a6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)n6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:s6.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var K$=import.meta.require;var S1={};h(S1,{lokiDir:()=>P,homeLokiDir:()=>o$,findRepoRootForVersion:()=>d$,REPO_ROOT:()=>m});import{resolve as n,dirname as l$}from"path";import{fileURLToPath as t6}from"url";import{existsSync as P$}from"fs";import{homedir as r6}from"os";function i6(){let $=N1;for(let Q=0;Q<6;Q++){if(P$(n($,"VERSION"))&&P$(n($,"autonomy/run.sh")))return $;let Z=l$($);if(Z===$)break;$=Z}return n(N1,"..","..","..")}function d$($){let Q=$;for(let Z=0;Z<6;Z++){if(P$(n(Q,"VERSION"))&&P$(n(Q,"autonomy/run.sh")))return Q;let z=l$(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function o$(){return n(r6(),".loki")}var N1,m;var C=L(()=>{N1=l$(t6(import.meta.url));m=i6()});import{readFileSync as e6}from"fs";import{resolve as $Q,dirname as QQ}from"path";import{fileURLToPath as ZQ}from"url";function F$(){if($$!==null)return $$;let $="7.
|
|
2
|
+
var n6=Object.defineProperty;var a6=($)=>$;function s6($,Q){this[$]=a6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)n6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:s6.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var K$=import.meta.require;var S1={};h(S1,{lokiDir:()=>P,homeLokiDir:()=>o$,findRepoRootForVersion:()=>d$,REPO_ROOT:()=>m});import{resolve as n,dirname as l$}from"path";import{fileURLToPath as t6}from"url";import{existsSync as P$}from"fs";import{homedir as r6}from"os";function i6(){let $=N1;for(let Q=0;Q<6;Q++){if(P$(n($,"VERSION"))&&P$(n($,"autonomy/run.sh")))return $;let Z=l$($);if(Z===$)break;$=Z}return n(N1,"..","..","..")}function d$($){let Q=$;for(let Z=0;Z<6;Z++){if(P$(n(Q,"VERSION"))&&P$(n(Q,"autonomy/run.sh")))return Q;let z=l$(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function o$(){return n(r6(),".loki")}var N1,m;var C=L(()=>{N1=l$(t6(import.meta.url));m=i6()});import{readFileSync as e6}from"fs";import{resolve as $Q,dirname as QQ}from"path";import{fileURLToPath as ZQ}from"url";function F$(){if($$!==null)return $$;let $="7.44.0";if(typeof $==="string"&&$.length>0)return $$=$,$$;try{let Q=QQ(ZQ(import.meta.url)),Z=d$(Q);$$=e6($Q(Z,"VERSION"),"utf-8").trim()}catch{$$="unknown"}return $$}var $$=null;var n$=L(()=>{C()});var C1={};h(C1,{runOrThrow:()=>zQ,run:()=>j,commandVersion:()=>KQ,commandExists:()=>f,ShellError:()=>a$});async function j($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,X;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[W,K,U]=await Promise.all([new Response(Z.stdout).text(),new Response(Z.stderr).text(),Z.exited]);return{stdout:W,stderr:K,exitCode:U}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function zQ($,Q={}){let Z=await j($,Q);if(Z.exitCode!==0)throw new a$(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=XQ($),Z=await j(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function XQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function KQ($,Q="--version"){if(!await f($))return null;let z=await j([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var a$;var d=L(()=>{a$=class a$ extends Error{message;exitCode;stdout;stderr;constructor($,Q,Z,z){super($);this.message=$;this.exitCode=Q;this.stdout=Z;this.stderr=z;this.name="ShellError"}}});function a($){return WQ?"":$}var WQ,T,S,I,TZ,w,R,y,q;var c=L(()=>{WQ=(process.env.NO_COLOR??"").length>0;T=a("\x1B[0;31m"),S=a("\x1B[0;32m"),I=a("\x1B[1;33m"),TZ=a("\x1B[0;34m"),w=a("\x1B[0;36m"),R=a("\x1B[1m"),y=a("\x1B[2m"),q=a("\x1B[0m")});import{existsSync as TQ}from"fs";async function Q$(){if(B$!==void 0)return B$;let $="/opt/homebrew/bin/python3.12";if(TQ($))return B$=$,$;let Q=await f("python3.12");if(Q)return B$=Q,Q;let Z=await f("python3");return B$=Z,Z}async function Z$($,Q={}){let Z=await Q$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return j([Z,"-c",$],Q)}var B$;var W$=L(()=>{d()});var t1={};h(t1,{runStatus:()=>gQ});import{existsSync as v,readFileSync as U$,readdirSync as l1,statSync as d1}from"fs";import{resolve as D,basename as xQ}from"path";import{homedir as NQ}from"os";async function DQ(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${q}
|
|
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)
|
|
@@ -789,4 +789,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
789
789
|
`),2}default:return process.stderr.write(`Unknown command: ${Q}
|
|
790
790
|
`),process.stderr.write(o6),2}}p1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var ZZ=await QZ(Bun.argv.slice(2));process.exit(ZZ);
|
|
791
791
|
|
|
792
|
-
//# debugId=
|
|
792
|
+
//# debugId=EAB4A25E0B3FC92864756E2164756E21
|
package/mcp/__init__.py
CHANGED
package/mcp/server.py
CHANGED
|
@@ -1257,6 +1257,12 @@ async def get_continuity() -> str:
|
|
|
1257
1257
|
return "# CONTINUITY.md not found"
|
|
1258
1258
|
except PathTraversalError:
|
|
1259
1259
|
return "# Access denied"
|
|
1260
|
+
except Exception as e:
|
|
1261
|
+
# Match the tool-handler error-envelope pattern so a corrupt or
|
|
1262
|
+
# unreadable state file (e.g. IsADirectoryError, OSError) returns an
|
|
1263
|
+
# honest error string instead of raising uncaught into the MCP runtime.
|
|
1264
|
+
logger.error(f"get_continuity failed: {e}")
|
|
1265
|
+
return f"# Error reading CONTINUITY.md: {e}"
|
|
1260
1266
|
|
|
1261
1267
|
|
|
1262
1268
|
@mcp.resource("loki://memory/index")
|
|
@@ -1271,14 +1277,26 @@ async def get_memory_index() -> str:
|
|
|
1271
1277
|
return json.dumps(index_data)
|
|
1272
1278
|
return json.dumps({"topics": [], "message": "Index not initialized"})
|
|
1273
1279
|
|
|
1274
|
-
# Fallback to direct file read
|
|
1280
|
+
# Fallback to direct file read. Parse-and-reserialize so a corrupt
|
|
1281
|
+
# index.json yields a clean error envelope instead of serving corrupt
|
|
1282
|
+
# bytes as a successful response (or raising on a downstream consumer).
|
|
1275
1283
|
index_path = safe_path_join('.loki', 'memory', 'index.json')
|
|
1276
1284
|
if os.path.exists(index_path):
|
|
1277
1285
|
with safe_open(index_path, 'r') as f:
|
|
1278
|
-
|
|
1286
|
+
raw = f.read()
|
|
1287
|
+
try:
|
|
1288
|
+
return json.dumps(json.loads(raw))
|
|
1289
|
+
except (json.JSONDecodeError, ValueError) as e:
|
|
1290
|
+
logger.error(f"get_memory_index: corrupt index.json: {e}")
|
|
1291
|
+
return json.dumps({"error": f"corrupt index.json: {e}", "topics": []})
|
|
1279
1292
|
return json.dumps({"topics": [], "message": "Index not initialized"})
|
|
1280
1293
|
except PathTraversalError:
|
|
1281
1294
|
return json.dumps({"error": "Access denied", "topics": []})
|
|
1295
|
+
except Exception as e:
|
|
1296
|
+
# Generic envelope so any other state-file failure (OSError,
|
|
1297
|
+
# IsADirectoryError) returns honestly rather than raising uncaught.
|
|
1298
|
+
logger.error(f"get_memory_index failed: {e}")
|
|
1299
|
+
return json.dumps({"error": str(e), "topics": []})
|
|
1282
1300
|
|
|
1283
1301
|
|
|
1284
1302
|
@mcp.resource("loki://queue/pending")
|
|
@@ -1304,6 +1322,12 @@ async def get_pending_tasks() -> str:
|
|
|
1304
1322
|
return json.dumps({"pending_tasks": [], "count": 0})
|
|
1305
1323
|
except PathTraversalError:
|
|
1306
1324
|
return json.dumps({"error": "Access denied", "pending_tasks": [], "count": 0})
|
|
1325
|
+
except Exception as e:
|
|
1326
|
+
# Generic envelope: a degraded install (STATE_MANAGER_AVAILABLE=False)
|
|
1327
|
+
# does a bare json.load on .loki/state/task-queue.json; a corrupt file
|
|
1328
|
+
# raises JSONDecodeError that must return an error, not crash the runtime.
|
|
1329
|
+
logger.error(f"get_pending_tasks failed: {e}")
|
|
1330
|
+
return json.dumps({"error": str(e), "pending_tasks": [], "count": 0})
|
|
1307
1331
|
|
|
1308
1332
|
|
|
1309
1333
|
# ============================================================
|
package/memory/vector_index.py
CHANGED
|
@@ -281,7 +281,12 @@ class VectorIndex:
|
|
|
281
281
|
import tempfile
|
|
282
282
|
npz_path = f"{path}.npz"
|
|
283
283
|
npz_dir = os.path.dirname(npz_path) or "."
|
|
284
|
-
|
|
284
|
+
# The temp file MUST end in ".npz". np.savez appends ".npz" to any
|
|
285
|
+
# target whose name does not already end in ".npz", so a ".npz.tmp"
|
|
286
|
+
# suffix would make numpy write the real archive to <tmp>.npz and leave
|
|
287
|
+
# the original temp file 0 bytes. os.replace would then move the empty
|
|
288
|
+
# file into place and orphan the real data (corrupting every index).
|
|
289
|
+
tmp_fd, tmp_path = tempfile.mkstemp(dir=npz_dir, suffix=".npz")
|
|
285
290
|
os.close(tmp_fd)
|
|
286
291
|
try:
|
|
287
292
|
np.savez(
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
3
|
"mcpName": "io.github.asklokesh/loki-mode",
|
|
4
|
-
"version": "7.
|
|
4
|
+
"version": "7.44.0",
|
|
5
5
|
"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).",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agent",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "loki-mode",
|
|
4
4
|
"displayName": "Loki Mode",
|
|
5
|
-
"version": "7.
|
|
5
|
+
"version": "7.44.0",
|
|
6
6
|
"description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 11 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Autonomi",
|
package/providers/codex.sh
CHANGED
|
@@ -116,10 +116,17 @@ provider_version() {
|
|
|
116
116
|
# Invocation function
|
|
117
117
|
# Note: Codex uses positional prompt, not -p flag
|
|
118
118
|
# Note: Reasoning effort is configured via environment or config, not CLI flag
|
|
119
|
+
# v7.x: pin the resolved model explicitly via -m/--model. Without it, codex
|
|
120
|
+
# falls back to the installed CLI's built-in default (e.g. gpt-5.5 on codex
|
|
121
|
+
# 0.132.0), which silently ignores _codex_validate_model and makes the run.sh
|
|
122
|
+
# cost table (priced for gpt-5.3-codex) wrong. --model is the documented model
|
|
123
|
+
# selector and is readable in process listings.
|
|
119
124
|
provider_invoke() {
|
|
120
125
|
local prompt="$1"
|
|
121
126
|
shift
|
|
122
|
-
codex exec --full-auto --skip-git-repo-check
|
|
127
|
+
codex exec --full-auto --skip-git-repo-check \
|
|
128
|
+
--model "$PROVIDER_MODEL_DEVELOPMENT" \
|
|
129
|
+
"$prompt" "$@"
|
|
123
130
|
}
|
|
124
131
|
|
|
125
132
|
# Model tier to effort level parameter (Codex uses effort, not separate models)
|
|
@@ -197,6 +204,18 @@ provider_invoke_with_tier() {
|
|
|
197
204
|
local effort
|
|
198
205
|
effort=$(resolve_model_for_tier "$tier")
|
|
199
206
|
|
|
207
|
+
# Resolve the model name by tier. These three vars can diverge via the
|
|
208
|
+
# generic LOKI_MODEL_* env (each validated by _codex_validate_model), so
|
|
209
|
+
# honor the tier rather than hardcoding development. Capability aliases
|
|
210
|
+
# (best/balanced/cheap) mirror resolve_model_for_tier's mapping.
|
|
211
|
+
local model
|
|
212
|
+
case "$tier" in
|
|
213
|
+
planning|best) model="$PROVIDER_MODEL_PLANNING" ;;
|
|
214
|
+
development|balanced) model="$PROVIDER_MODEL_DEVELOPMENT" ;;
|
|
215
|
+
fast|cheap) model="$PROVIDER_MODEL_FAST" ;;
|
|
216
|
+
*) model="$PROVIDER_MODEL_DEVELOPMENT" ;;
|
|
217
|
+
esac
|
|
218
|
+
|
|
200
219
|
local extra_flags=()
|
|
201
220
|
if [ "${LOKI_CODEX_WEB_SEARCH:-false}" = "true" ]; then
|
|
202
221
|
extra_flags+=(--search)
|
|
@@ -211,6 +230,7 @@ provider_invoke_with_tier() {
|
|
|
211
230
|
--ask-for-approval never \
|
|
212
231
|
--sandbox danger-full-access \
|
|
213
232
|
--skip-git-repo-check \
|
|
233
|
+
--model "$model" \
|
|
214
234
|
"${extra_flags[@]}" \
|
|
215
235
|
"$prompt" "$@"
|
|
216
236
|
}
|