loki-mode 7.78.0 → 7.80.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/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/checkpoint_sync.py +189 -0
- package/autonomy/lib/config-map.sh +955 -0
- package/autonomy/loki +273 -8
- package/autonomy/run.sh +230 -195
- package/autonomy/sandbox.sh +98 -0
- package/autonomy/trigger-server.py +419 -43
- package/dashboard/__init__.py +1 -1
- package/dashboard/registry.py +212 -0
- package/dashboard/server.py +299 -7
- package/dashboard/static/index.html +383 -150
- package/docs/CONFIG-FILE-PLAN.md +459 -0
- package/docs/ENTERPRISE-IDENTITY-ROADMAP.md +206 -0
- package/docs/INSTALLATION.md +2 -2
- package/loki-ts/dist/loki.js +2 -2
- package/lokistore/__init__.py +65 -0
- package/lokistore/base.py +172 -0
- package/lokistore/cloud.py +305 -0
- package/lokistore/factory.py +187 -0
- package/lokistore/local.py +219 -0
- package/mcp/__init__.py +1 -1
- package/memory/retrieval.py +147 -0
- package/memory/tree_index.py +499 -0
- package/memory/tree_search.py +305 -0
- package/package.json +2 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
package/autonomy/loki
CHANGED
|
@@ -125,6 +125,16 @@ if [ -f "$_LOKI_SCRIPT_DIR/lib/git-pr-advisory.sh" ]; then
|
|
|
125
125
|
source "$_LOKI_SCRIPT_DIR/lib/git-pr-advisory.sh"
|
|
126
126
|
fi
|
|
127
127
|
|
|
128
|
+
# Unified config-file support (#691). Canonical LOKI_CONFIG_MAP + the config-file
|
|
129
|
+
# loader (loki_maybe_apply_config_file / loki_apply_config_file) and the
|
|
130
|
+
# example/schema/validate generators. Side-effect-free on source: defines the
|
|
131
|
+
# array + functions, exports nothing. The main() pre-pass and run.sh's YAML
|
|
132
|
+
# parsers both call into this single lib so the mapping cannot drift.
|
|
133
|
+
if [ -f "$_LOKI_SCRIPT_DIR/lib/config-map.sh" ]; then
|
|
134
|
+
# shellcheck source=autonomy/lib/config-map.sh
|
|
135
|
+
source "$_LOKI_SCRIPT_DIR/lib/config-map.sh"
|
|
136
|
+
fi
|
|
137
|
+
|
|
128
138
|
# Resolve the script's real path (handles symlinks)
|
|
129
139
|
resolve_script_path() {
|
|
130
140
|
local script="$1"
|
|
@@ -1473,6 +1483,24 @@ cmd_start() {
|
|
|
1473
1483
|
issue_mode_args+=("--detach")
|
|
1474
1484
|
shift
|
|
1475
1485
|
;;
|
|
1486
|
+
# #691: unified config-file aliases. Already applied by the main()
|
|
1487
|
+
# pre-pass (loki_maybe_apply_config_file); these arms only consume
|
|
1488
|
+
# the flag+path so the loop does not misread the path as the PRD
|
|
1489
|
+
# positional, and do NOT forward it to the run.sh exec target.
|
|
1490
|
+
--config|--vars|--env-file)
|
|
1491
|
+
# Guard the shift 2: a dangling flag (path omitted, flag is the
|
|
1492
|
+
# last arg) would underflow shift 2 and, under set -euo pipefail,
|
|
1493
|
+
# abort the command SILENTLY. Give an honest actionable error.
|
|
1494
|
+
if [ "$#" -ge 2 ]; then
|
|
1495
|
+
shift 2
|
|
1496
|
+
else
|
|
1497
|
+
echo "loki: $1 requires a file path (e.g. --config .loki/config.yaml)" >&2
|
|
1498
|
+
exit 1
|
|
1499
|
+
fi
|
|
1500
|
+
;;
|
|
1501
|
+
--config=*|--vars=*|--env-file=*)
|
|
1502
|
+
shift
|
|
1503
|
+
;;
|
|
1476
1504
|
-*)
|
|
1477
1505
|
echo -e "${RED}Unknown option: $1${NC}"
|
|
1478
1506
|
exit 1
|
|
@@ -2899,6 +2927,133 @@ cmd_resume() {
|
|
|
2899
2927
|
fi
|
|
2900
2928
|
}
|
|
2901
2929
|
|
|
2930
|
+
# loki why -- actionable failure/outcome diagnosis (B5).
|
|
2931
|
+
# Reads the already-captured run artifacts (no new state): the terminal run state
|
|
2932
|
+
# (.loki/<autonomy-state>.json: status, lastExitCode, iterationCount), the durable
|
|
2933
|
+
# completion record (.loki/state/completion.json: outcome, branch, files, pr_url),
|
|
2934
|
+
# and the latest structured handoff (.loki/memory/handoffs/*.md). Produces an
|
|
2935
|
+
# honest "what happened + what to do" report. Read-only; never fabricates -- if a
|
|
2936
|
+
# field was not captured it says so. --json emits the machine-readable record.
|
|
2937
|
+
cmd_why() {
|
|
2938
|
+
local as_json=0
|
|
2939
|
+
case "${1:-}" in
|
|
2940
|
+
--json) as_json=1 ;;
|
|
2941
|
+
--help|-h) echo "Usage: loki why [--json] -- explain the last build's outcome and what to do next"; return 0 ;;
|
|
2942
|
+
"" ) : ;;
|
|
2943
|
+
*) echo -e "${RED}Unknown flag: $1${NC}"; echo "Usage: loki why [--json]"; return 1 ;;
|
|
2944
|
+
esac
|
|
2945
|
+
|
|
2946
|
+
local loki_dir="${LOKI_DIR:-.loki}"
|
|
2947
|
+
# Session-namespaced state when LOKI_SESSION_ID is set (mirrors run.sh A6).
|
|
2948
|
+
local state_file="$loki_dir/autonomy-state.json"
|
|
2949
|
+
if [ -n "${LOKI_SESSION_ID:-}" ] && [ -f "$loki_dir/sessions/${LOKI_SESSION_ID}/autonomy-state.json" ]; then
|
|
2950
|
+
state_file="$loki_dir/sessions/${LOKI_SESSION_ID}/autonomy-state.json"
|
|
2951
|
+
fi
|
|
2952
|
+
local completion_file="$loki_dir/state/completion.json"
|
|
2953
|
+
|
|
2954
|
+
if [ ! -f "$state_file" ] && [ ! -f "$completion_file" ]; then
|
|
2955
|
+
echo "loki why: no run found here yet (no $state_file or $completion_file)." >&2
|
|
2956
|
+
echo "Run a build first: loki start <spec>" >&2
|
|
2957
|
+
return 1
|
|
2958
|
+
fi
|
|
2959
|
+
|
|
2960
|
+
if [ "$as_json" = "1" ]; then
|
|
2961
|
+
_LOKI_WHY_STATE="$state_file" _LOKI_WHY_COMPLETION="$completion_file" python3 - <<'WHYJSON'
|
|
2962
|
+
import json, os
|
|
2963
|
+
def load(p):
|
|
2964
|
+
try:
|
|
2965
|
+
with open(p) as f: return json.load(f)
|
|
2966
|
+
except Exception: return {}
|
|
2967
|
+
state = load(os.environ.get("_LOKI_WHY_STATE", ""))
|
|
2968
|
+
comp = load(os.environ.get("_LOKI_WHY_COMPLETION", ""))
|
|
2969
|
+
print(json.dumps({"state": state, "completion": comp}, indent=2))
|
|
2970
|
+
WHYJSON
|
|
2971
|
+
return 0
|
|
2972
|
+
fi
|
|
2973
|
+
|
|
2974
|
+
# Human-readable report. The diagnosis maps the terminal status to a plain
|
|
2975
|
+
# explanation + a concrete next action; everything is sourced from the files,
|
|
2976
|
+
# nothing is invented.
|
|
2977
|
+
_LOKI_WHY_STATE="$state_file" _LOKI_WHY_COMPLETION="$completion_file" \
|
|
2978
|
+
_LOKI_WHY_HANDOFFS="$loki_dir/memory/handoffs" python3 - <<'WHYTXT'
|
|
2979
|
+
import json, os, glob
|
|
2980
|
+
def load(p):
|
|
2981
|
+
try:
|
|
2982
|
+
with open(p) as f: return json.load(f)
|
|
2983
|
+
except Exception: return {}
|
|
2984
|
+
state = load(os.environ.get("_LOKI_WHY_STATE", ""))
|
|
2985
|
+
comp = load(os.environ.get("_LOKI_WHY_COMPLETION", ""))
|
|
2986
|
+
status = state.get("status") or comp.get("outcome") or "unknown"
|
|
2987
|
+
exit_code = state.get("lastExitCode")
|
|
2988
|
+
iters = state.get("iterationCount")
|
|
2989
|
+
|
|
2990
|
+
# status -> (one-line meaning, suggested next action). Honest + specific.
|
|
2991
|
+
GUIDE = {
|
|
2992
|
+
"council_approved": ("The completion council agreed the work is done and verified.",
|
|
2993
|
+
"Review the diff and open a PR (git push + gh pr create, or LOKI_AUTO_PR=1)."),
|
|
2994
|
+
"council_force_approved": ("Completion was force-approved (council could not fully converge).",
|
|
2995
|
+
"Review the diff carefully before merging -- convergence was not unanimous."),
|
|
2996
|
+
"completion_promise_fulfilled": ("The agent declared its explicit completion promise fulfilled.",
|
|
2997
|
+
"Verify the promised outcome, then review and PR."),
|
|
2998
|
+
"max_iterations_reached": ("The build hit the iteration cap before the council approved it.",
|
|
2999
|
+
"Inspect what is left (loki status), raise LOKI_MAX_ITERATIONS or narrow the spec, and resume."),
|
|
3000
|
+
"max_retries_exceeded": ("The build exhausted its retry budget on a repeating failure.",
|
|
3001
|
+
"Read .loki/logs for the recurring error (rate limit? failing test?), fix the root cause, then re-run."),
|
|
3002
|
+
"failed": ("The build ended in a failure state.",
|
|
3003
|
+
"Read .loki/logs/ + the handoff below for the failure, fix it, then re-run."),
|
|
3004
|
+
"policy_blocked": ("A policy/trust gate blocked completion.",
|
|
3005
|
+
"Review the blocking finding; address it or use the documented override path."),
|
|
3006
|
+
"budget_exceeded": ("The cost budget breaker paused the build.",
|
|
3007
|
+
"Raise LOKI_BUDGET_LIMIT or accept the partial result, then resume."),
|
|
3008
|
+
"paused": ("The build is paused (human-intervention signal).",
|
|
3009
|
+
"Resume with: loki resume."),
|
|
3010
|
+
"interrupted": ("The build was interrupted before a terminal state.",
|
|
3011
|
+
"Resume with: loki resume."),
|
|
3012
|
+
"stopped": ("The build was stopped by the operator.",
|
|
3013
|
+
"Start a new build with loki start, or resume if you meant to continue."),
|
|
3014
|
+
"force_stopped": ("The build was force-stopped.",
|
|
3015
|
+
"Start a new build, or investigate why a force-stop was needed."),
|
|
3016
|
+
"running": ("The recorded state says a build is still running (or crashed mid-run).",
|
|
3017
|
+
"If no build is active it likely crashed; in durable mode (LOKI_DURABLE_STATE=1) a restart resumes, else loki start re-runs."),
|
|
3018
|
+
}
|
|
3019
|
+
meaning, action = GUIDE.get(status, ("No diagnosis mapping for this status; see the raw fields below.",
|
|
3020
|
+
"Check loki status and .loki/logs/ for detail."))
|
|
3021
|
+
|
|
3022
|
+
print("Loki: why")
|
|
3023
|
+
print("=" * 60)
|
|
3024
|
+
print(f" Outcome : {status}")
|
|
3025
|
+
if exit_code is not None:
|
|
3026
|
+
print(f" Exit code : {exit_code}")
|
|
3027
|
+
if iters is not None:
|
|
3028
|
+
print(f" Iterations : {iters}")
|
|
3029
|
+
if comp.get("branch"):
|
|
3030
|
+
print(f" Branch : {comp['branch']}")
|
|
3031
|
+
if comp.get("files_changed") is not None:
|
|
3032
|
+
print(f" Changes : {comp.get('files_changed',0)} files (+{comp.get('insertions',0)}/-{comp.get('deletions',0)})")
|
|
3033
|
+
if comp.get("pr_url"):
|
|
3034
|
+
print(f" PR : {comp['pr_url']}")
|
|
3035
|
+
print()
|
|
3036
|
+
print(f" What happened: {meaning}")
|
|
3037
|
+
print(f" What to do : {action}")
|
|
3038
|
+
|
|
3039
|
+
# Surface the latest structured handoff (already-captured context), honestly.
|
|
3040
|
+
hd = sorted(glob.glob(os.path.join(os.environ.get("_LOKI_WHY_HANDOFFS",""), "*.md")))
|
|
3041
|
+
if hd:
|
|
3042
|
+
print()
|
|
3043
|
+
print(f" Latest handoff: {hd[-1]}")
|
|
3044
|
+
try:
|
|
3045
|
+
with open(hd[-1]) as f:
|
|
3046
|
+
head = "".join(f.readlines()[:8]).rstrip()
|
|
3047
|
+
for line in head.splitlines():
|
|
3048
|
+
print(f" {line}")
|
|
3049
|
+
except Exception:
|
|
3050
|
+
pass
|
|
3051
|
+
print()
|
|
3052
|
+
print(" (loki why --json for the raw record; loki status for live state.)")
|
|
3053
|
+
WHYTXT
|
|
3054
|
+
return 0
|
|
3055
|
+
}
|
|
3056
|
+
|
|
2902
3057
|
# Show current status
|
|
2903
3058
|
cmd_status() {
|
|
2904
3059
|
# Check for flags
|
|
@@ -6701,6 +6856,21 @@ cmd_run() {
|
|
|
6701
6856
|
run_detached=true
|
|
6702
6857
|
shift
|
|
6703
6858
|
;;
|
|
6859
|
+
# #691: unified config-file aliases. Applied by the main() pre-pass;
|
|
6860
|
+
# consume here so the path is not misread as the issue-ref positional
|
|
6861
|
+
# and is not forwarded to cmd_start.
|
|
6862
|
+
--config|--vars|--env-file)
|
|
6863
|
+
# Guard shift 2 against a dangling flag (silent set -e abort).
|
|
6864
|
+
if [ "$#" -ge 2 ]; then
|
|
6865
|
+
shift 2
|
|
6866
|
+
else
|
|
6867
|
+
echo "loki: $1 requires a file path (e.g. --config .loki/config.yaml)" >&2
|
|
6868
|
+
exit 1
|
|
6869
|
+
fi
|
|
6870
|
+
;;
|
|
6871
|
+
--config=*|--vars=*|--env-file=*)
|
|
6872
|
+
shift
|
|
6873
|
+
;;
|
|
6704
6874
|
-*)
|
|
6705
6875
|
echo -e "${RED}Unknown option: $1${NC}"
|
|
6706
6876
|
echo "Run 'loki run --help' for usage."
|
|
@@ -8109,15 +8279,27 @@ cmd_config() {
|
|
|
8109
8279
|
get)
|
|
8110
8280
|
cmd_config_get "$@"
|
|
8111
8281
|
;;
|
|
8282
|
+
example)
|
|
8283
|
+
cmd_config_example "$@"
|
|
8284
|
+
;;
|
|
8285
|
+
schema)
|
|
8286
|
+
cmd_config_schema "$@"
|
|
8287
|
+
;;
|
|
8288
|
+
validate)
|
|
8289
|
+
cmd_config_validate "$@"
|
|
8290
|
+
;;
|
|
8112
8291
|
*)
|
|
8113
|
-
echo -e "${YELLOW}Usage: loki config [show|init|edit|path|set|get]${NC}"
|
|
8114
|
-
echo ""
|
|
8115
|
-
echo " show
|
|
8116
|
-
echo " init
|
|
8117
|
-
echo " edit
|
|
8118
|
-
echo " path
|
|
8119
|
-
echo " set KEY VALUE
|
|
8120
|
-
echo " get KEY
|
|
8292
|
+
echo -e "${YELLOW}Usage: loki config [show|init|edit|path|set|get|example|schema|validate]${NC}"
|
|
8293
|
+
echo ""
|
|
8294
|
+
echo " show Show current configuration (default)"
|
|
8295
|
+
echo " init Create a config file from template"
|
|
8296
|
+
echo " edit Open config file in editor"
|
|
8297
|
+
echo " path Show config file paths"
|
|
8298
|
+
echo " set KEY VALUE Set a configuration value"
|
|
8299
|
+
echo " get KEY Get a configuration value"
|
|
8300
|
+
echo " example Print an annotated config template (for --config)"
|
|
8301
|
+
echo " schema Print the key -> env-var mapping table"
|
|
8302
|
+
echo " validate FILE Validate a config file (--config) without applying it"
|
|
8121
8303
|
echo ""
|
|
8122
8304
|
echo "Settable keys (v6.0.0):"
|
|
8123
8305
|
echo " maxTier Cost ceiling: opus, sonnet, haiku (default: opus)"
|
|
@@ -8295,6 +8477,41 @@ SET_CONFIG
|
|
|
8295
8477
|
esac
|
|
8296
8478
|
}
|
|
8297
8479
|
|
|
8480
|
+
# #691: print an annotated config template generated from LOKI_CONFIG_MAP, so it
|
|
8481
|
+
# can never drift from the parsers. Suitable for `loki start --config <path>`.
|
|
8482
|
+
cmd_config_example() {
|
|
8483
|
+
if ! declare -f loki_config_generate_example >/dev/null 2>&1; then
|
|
8484
|
+
echo -e "${RED}config example: config-map.sh helper not loaded${NC}" >&2
|
|
8485
|
+
return 1
|
|
8486
|
+
fi
|
|
8487
|
+
loki_config_generate_example
|
|
8488
|
+
}
|
|
8489
|
+
|
|
8490
|
+
# #691: print the machine-readable key -> LOKI_ENV_VAR mapping table.
|
|
8491
|
+
cmd_config_schema() {
|
|
8492
|
+
if ! declare -f loki_config_generate_schema >/dev/null 2>&1; then
|
|
8493
|
+
echo -e "${RED}config schema: config-map.sh helper not loaded${NC}" >&2
|
|
8494
|
+
return 1
|
|
8495
|
+
fi
|
|
8496
|
+
loki_config_generate_schema
|
|
8497
|
+
}
|
|
8498
|
+
|
|
8499
|
+
# #691: validate a config file (--config) WITHOUT applying it. Reports
|
|
8500
|
+
# unresolved ${VAR} refs, raw-secret literals (error), and per-value validation
|
|
8501
|
+
# failures. Non-zero exit on any failure.
|
|
8502
|
+
cmd_config_validate() {
|
|
8503
|
+
local file="${1:-}"
|
|
8504
|
+
if [ -z "$file" ]; then
|
|
8505
|
+
echo -e "${RED}Usage: loki config validate <file>${NC}" >&2
|
|
8506
|
+
return 2
|
|
8507
|
+
fi
|
|
8508
|
+
if ! declare -f loki_config_validate_file >/dev/null 2>&1; then
|
|
8509
|
+
echo -e "${RED}config validate: config-map.sh helper not loaded${NC}" >&2
|
|
8510
|
+
return 1
|
|
8511
|
+
fi
|
|
8512
|
+
loki_config_validate_file "$file"
|
|
8513
|
+
}
|
|
8514
|
+
|
|
8298
8515
|
# v6.0.0: Get a configuration value
|
|
8299
8516
|
cmd_config_get() {
|
|
8300
8517
|
local key="${1:-}"
|
|
@@ -10820,6 +11037,25 @@ cmd_quick() {
|
|
|
10820
11037
|
fi
|
|
10821
11038
|
fi
|
|
10822
11039
|
|
|
11040
|
+
# #691: drop the unified config-file aliases from the task description. The
|
|
11041
|
+
# main() pre-pass already applied the file; without this, a trailing
|
|
11042
|
+
# `--config f.env` would be swept into task_desc (cmd_quick has no arg loop).
|
|
11043
|
+
local _quick_args=()
|
|
11044
|
+
local _skip_next=false
|
|
11045
|
+
local _qa
|
|
11046
|
+
for _qa in "$@"; do
|
|
11047
|
+
if [ "$_skip_next" = "true" ]; then
|
|
11048
|
+
_skip_next=false
|
|
11049
|
+
continue
|
|
11050
|
+
fi
|
|
11051
|
+
case "$_qa" in
|
|
11052
|
+
--config|--vars|--env-file) _skip_next=true; continue ;;
|
|
11053
|
+
--config=*|--vars=*|--env-file=*) continue ;;
|
|
11054
|
+
esac
|
|
11055
|
+
_quick_args+=("$_qa")
|
|
11056
|
+
done
|
|
11057
|
+
set -- "${_quick_args[@]+"${_quick_args[@]}"}"
|
|
11058
|
+
|
|
10823
11059
|
local task_desc="$*"
|
|
10824
11060
|
local version=$(get_version)
|
|
10825
11061
|
local max_iter="${LOKI_MAX_ITERATIONS:-3}"
|
|
@@ -15306,6 +15542,32 @@ main() {
|
|
|
15306
15542
|
# runs for the 8 ported commands). Marker file: ~/.loki-first-run.
|
|
15307
15543
|
loki_telemetry "cli_command" "command=$command" 2>/dev/null || true
|
|
15308
15544
|
|
|
15545
|
+
# Unified config-file pre-pass (#691). For session commands ONLY, honor
|
|
15546
|
+
# LOKI_CONFIG_FILE / --config|--vars|--env-file and load the file BEFORE the
|
|
15547
|
+
# per-command arg loop runs. Running here means: config exports survive the
|
|
15548
|
+
# exec into run.sh, and the subsequent CLI arg loop still overrides them
|
|
15549
|
+
# (CLI > config). The pre-pass SCANS without consuming; the per-command
|
|
15550
|
+
# loops carry consume-and-ignore arms for the three aliases. Gated to
|
|
15551
|
+
# start/run/quick so status/config/stop never trigger a config load.
|
|
15552
|
+
case "$command" in
|
|
15553
|
+
start|run|quick)
|
|
15554
|
+
if declare -f loki_maybe_apply_config_file >/dev/null 2>&1; then
|
|
15555
|
+
loki_maybe_apply_config_file "$@"
|
|
15556
|
+
fi
|
|
15557
|
+
# #691: documented observation hook. When LOKI_CONFIG_DUMP=1, print
|
|
15558
|
+
# the resolved LOKI_* environment (after the config pre-pass AND any
|
|
15559
|
+
# ambient env) and exit 0 WITHOUT running a build. Lets operators and
|
|
15560
|
+
# tests verify exactly which values a --config file resolves to,
|
|
15561
|
+
# without spend. The per-command CLI arg loops do not run on this
|
|
15562
|
+
# path, so CLI-flag overrides are NOT reflected by the dump; it shows
|
|
15563
|
+
# the config+env resolution only.
|
|
15564
|
+
if [ "${LOKI_CONFIG_DUMP:-}" = "1" ]; then
|
|
15565
|
+
env | grep -E '^LOKI_' | LC_ALL=C sort
|
|
15566
|
+
exit 0
|
|
15567
|
+
fi
|
|
15568
|
+
;;
|
|
15569
|
+
esac
|
|
15570
|
+
|
|
15309
15571
|
case "$command" in
|
|
15310
15572
|
run)
|
|
15311
15573
|
cmd_run "$@"
|
|
@@ -15346,6 +15608,9 @@ main() {
|
|
|
15346
15608
|
status)
|
|
15347
15609
|
cmd_status "$@"
|
|
15348
15610
|
;;
|
|
15611
|
+
why)
|
|
15612
|
+
cmd_why "$@"
|
|
15613
|
+
;;
|
|
15349
15614
|
stats)
|
|
15350
15615
|
# CLI consolidation (Phase A): 'stats' is a deprecated alias of
|
|
15351
15616
|
# 'report session'. On the Bun route this arm is never reached
|