loki-mode 10.5.10 → 10.5.13
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 +30 -962
- package/SKILL.md +3 -3
- package/VERSION +1 -1
- package/autonomy/app-runner.sh +2 -1
- package/autonomy/completion-council.sh +40 -3
- package/autonomy/lib/doctor-fix.py +1 -1
- package/autonomy/lib/proof-generator.py +40 -84
- package/autonomy/lib/proof-template.html +7 -11
- package/autonomy/loki +118 -69
- package/autonomy/receipt_jwt.py +59 -14
- package/autonomy/run.sh +91 -124
- package/autonomy/trigger-server.py +2 -2
- package/autonomy/verify.sh +37 -2
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +2 -2
- package/docs/COMPETITIVE-NEXT-10.md +3 -4
- package/docs/INSTALLATION.md +1 -1
- package/docs/README-FULL.md +977 -0
- package/docs/SIGNED-RECEIPTS.md +40 -82
- package/docs/TOOLS.md +0 -23
- package/docs/VERIFICATION-COST.md +2 -2
- package/docs/dev/release-checklist.md +2 -0
- package/docs/v10/ADOPTION-PLAN.md +375 -0
- package/docs/v10/BOARD.md +65 -13
- package/docs/v10/DECISIONS.md +11 -0
- package/docs/v10/DEPS.md +10 -4
- package/docs/v10/ENGINE.md +4 -4
- package/docs/v10/FIRST-RUN-GATE.md +39 -0
- package/docs/v10/FOUNDER-QUEUE.md +7 -0
- package/docs/v10/GUIDE.md +0 -4
- package/docs/v10/PROGRESS.md +75 -0
- package/loki-ts/dist/loki.js +590 -561
- package/mcp/__init__.py +1 -1
- package/package.json +2 -2
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/tools/gate-status.py +3 -46
- package/tools/receipt-attest.py +1 -1
- package/web-app/dist/assets/{AdminPage-B67oykTq.js → AdminPage-C8zraQ0u.js} +1 -1
- package/web-app/dist/assets/{Avatar-oGATGRH9.js → Avatar-DnjIA4Ap.js} +1 -1
- package/web-app/dist/assets/{Badge-BL6VnQ5l.js → Badge-C487hf0b.js} +1 -1
- package/web-app/dist/assets/{Button-QGZm-VP0.js → Button-5AWAEksl.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-B42XvLAM.js → CockpitPage-B6_1gTFO.js} +1 -1
- package/web-app/dist/assets/{ComparePage-CvXeI65J.js → ComparePage-DDbC8-yi.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-C7BGRqtK.js → ErrorBoundary-CWHKwjEp.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-CWtQJFvz.js → EvidenceReceiptPanel-MfJq92Ty.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-CaNG20H6.js → GitHubIssuesPanel-BBdijzdg.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-CipAJzPg.js → GitHubPRsPanel-DjBHyymT.js} +1 -1
- package/web-app/dist/assets/{HomePage-_QeUkY15.js → HomePage-B_6m7ITK.js} +2 -2
- package/web-app/dist/assets/{LoginPage-DWBRdcLN.js → LoginPage-OGgMvfjT.js} +1 -1
- package/web-app/dist/assets/{MagicPage-DWjFPG7D.js → MagicPage-m4MSfxWf.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-C4d6da1F.js → MetricsPage-D8Ea1ALe.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-DeDsW3_m.js → NotFoundPage-ttvXL-7K.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-Dm8ok-xS.js → ProjectPage-CDX8tSPV.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-CTzhekl2.js → ProjectsPage-BQ73nYWf.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-CiLJNswp.js → SettingsPage-B6k71q2g.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-f_1nS83o.js → ShowcasePage-CNbwtr7D.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-DYrUvPNT.js → SystemSettingsPage-CD2qys43.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-H4RiPWAZ.js → TeamsPage-DAmsRIVD.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-CPuxRvnt.js → TemplatesPage-CcRyyUhL.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-CWrePFkI.js → TerminalOutput-DRquxtQ1.js} +1 -1
- package/web-app/dist/assets/{activity-P02y0O8o.js → activity-DUDUi3Qd.js} +1 -1
- package/web-app/dist/assets/{bell-HQXzg-Hl.js → bell-tDpCOcHx.js} +1 -1
- package/web-app/dist/assets/{bot-Dk5s9GSN.js → bot-87IR4aTF.js} +1 -1
- package/web-app/dist/assets/{check-CwDjf4PE.js → check-C2Y2z-Ge.js} +1 -1
- package/web-app/dist/assets/{chevron-left-C7TwN2mS.js → chevron-left-CsPFT88x.js} +1 -1
- package/web-app/dist/assets/{circle-alert-DM4oNAW-.js → circle-alert-Cp2UJikd.js} +1 -1
- package/web-app/dist/assets/{clock-ds_JQQTA.js → clock-DZ1cwNyf.js} +1 -1
- package/web-app/dist/assets/{cloud-DH1iftlI.js → cloud-q5TrIOV-.js} +1 -1
- package/web-app/dist/assets/{code-xml-DVxWm1OZ.js → code-xml-0TNGOS3c.js} +1 -1
- package/web-app/dist/assets/{database-DncR8cxO.js → database-g73vgDVx.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-BgaDf1Xc.js → dollar-sign-DAKkR_MO.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-qXSN84-m.js → file-code-corner-DTAvdxjl.js} +1 -1
- package/web-app/dist/assets/{file-plus-C_G98fmf.js → file-plus-D5SB-1Uk.js} +1 -1
- package/web-app/dist/assets/{globe-riusJlUK.js → globe-j5YaNmx9.js} +1 -1
- package/web-app/dist/assets/{hammer-8rTeIjNL.js → hammer-C9yoXOC6.js} +1 -1
- package/web-app/dist/assets/{index-Bm8x_X7_.js → index--EayJsJ-.js} +3 -3
- package/web-app/dist/assets/{layers-lthGyhBX.js → layers-B4mxkWdH.js} +1 -1
- package/web-app/dist/assets/{loader-circle-Ct6LWreQ.js → loader-circle-CP-qD1W2.js} +1 -1
- package/web-app/dist/assets/{lock-ui-BaDIH.js → lock-DwHNUSTU.js} +1 -1
- package/web-app/dist/assets/{package-BKKKJduw.js → package-BxLWioQ8.js} +1 -1
- package/web-app/dist/assets/{plus-B4cHwuUk.js → plus-B-43Byzl.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-BJZsXiQT.js → refresh-cw-5s47zAFV.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-DazXGjet.js → rotate-ccw-BewZ8_Rx.js} +1 -1
- package/web-app/dist/assets/{scroll-text-CoXDGbkm.js → scroll-text-DPNpHL-Z.js} +1 -1
- package/web-app/dist/assets/{server-BepN1rgV.js → server-hL8_-vRq.js} +1 -1
- package/web-app/dist/assets/{shield-alert-DLnd3crY.js → shield-alert-DAGc2aLO.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-oUBuPBUu.js → thumbs-up-DRGzcWEH.js} +1 -1
- package/web-app/dist/assets/{trash-2-BY9HtMUs.js → trash-2-Bo_YPIqS.js} +1 -1
- package/web-app/dist/assets/{trending-down-DZMdGaq1.js → trending-down-BQ1cK9F3.js} +1 -1
- package/web-app/dist/assets/{trending-up-Cq5n8lwY.js → trending-up-BpnkTO1T.js} +1 -1
- package/web-app/dist/assets/{upload-sdNXkoXm.js → upload-DDdQNxmW.js} +1 -1
- package/web-app/dist/assets/{usePolling-CFw3EoGh.js → usePolling-z_FXsGz7.js} +1 -1
- package/web-app/dist/assets/{user-bN0b7P81.js → user-obPH42HX.js} +1 -1
- package/web-app/dist/index.html +1 -1
- package/tools/signing-status.py +0 -292
package/autonomy/run.sh
CHANGED
|
@@ -238,6 +238,31 @@ fi
|
|
|
238
238
|
SCRIPT_DIR="${LOKI_ORIGINAL_SCRIPT_DIR:-$SCRIPT_DIR}"
|
|
239
239
|
PROJECT_DIR="${LOKI_ORIGINAL_PROJECT_DIR:-$PROJECT_DIR}"
|
|
240
240
|
|
|
241
|
+
# A-134: `loki quick` is quiet unless LOKI_VERBOSE=1. The run re-executes itself with
|
|
242
|
+
# stdout in .loki/quick-run.log; this shell prints the inner run's compact receipt and
|
|
243
|
+
# a warning count. Errors go to stderr.
|
|
244
|
+
if [ -z "${LOKI_QUICK_INNER:-}" ] && [ "${LOKI_VERBOSE:-0}" != "1" ] && [ "${BASH_SOURCE[0]}" = "${0}" ]; then
|
|
245
|
+
case "${1:-}" in */quick-prd-*.md | quick-prd-*.md)
|
|
246
|
+
_qd="$(cd "$(dirname "$1")" && pwd -P)"; rm -f "$_qd/quick-receipt.txt"
|
|
247
|
+
# Async with default INT/QUIT (a bare `&` child ignores them), signals forwarded.
|
|
248
|
+
( trap - INT QUIT; LOKI_QUICK_DIR="$_qd" LOKI_QUICK_OUTER_PID=$$ LOKI_QUICK_INNER=1 exec "$BASH" "$0" "$@" >"$_qd/quick-run.log" ) &
|
|
249
|
+
_qpid=$!
|
|
250
|
+
trap 'kill -INT "$_qpid" 2>/dev/null' INT
|
|
251
|
+
trap 'kill -TERM "$_qpid" 2>/dev/null' TERM HUP
|
|
252
|
+
wait "$_qpid"; _qrc=$?
|
|
253
|
+
kill -0 "$_qpid" 2>/dev/null && { wait "$_qpid"; _qrc=$?; }
|
|
254
|
+
trap - INT TERM HUP
|
|
255
|
+
_qw=$(grep -cF '[WARN]' "$_qd/quick-run.log" 2>/dev/null); _qw="${_qw:-0}"
|
|
256
|
+
if [ "$_qrc" -ne 0 ] || [ ! -s "$_qd/quick-receipt.txt" ]; then
|
|
257
|
+
_qr=$(grep -E '\[(ERROR|WARN)\]|[Ss]topped|[Ff]ailed' "$_qd/quick-run.log" 2>/dev/null | tail -n 3)
|
|
258
|
+
echo "${_qr:-$(tail -n 3 "$_qd/quick-run.log" 2>/dev/null)}"
|
|
259
|
+
fi
|
|
260
|
+
[ "$_qw" -gt 0 ] && echo "Warnings: $_qw (see $_qd/quick-run.log)"
|
|
261
|
+
cat "$_qd/quick-receipt.txt" 2>/dev/null
|
|
262
|
+
exit "$_qrc" ;;
|
|
263
|
+
esac
|
|
264
|
+
fi
|
|
265
|
+
|
|
241
266
|
# Clean up ONLY the recorded temp copy, and ONLY if it is a real temp file.
|
|
242
267
|
# Deleting BASH_SOURCE[0] here was the bug: an inherited LOKI_RUNNING_FROM_TEMP
|
|
243
268
|
# made BASH_SOURCE[0] the canonical source, so run.sh deleted itself on spawned
|
|
@@ -1722,7 +1747,7 @@ if [ -f "$PROVIDERS_DIR/loader.sh" ]; then
|
|
|
1722
1747
|
_detected="$(auto_detect_provider 2>/dev/null || true)"
|
|
1723
1748
|
if [ -n "$_detected" ]; then
|
|
1724
1749
|
LOKI_PROVIDER="$_detected"
|
|
1725
|
-
echo "[loki] provider: $LOKI_PROVIDER (auto-detected)" >&2
|
|
1750
|
+
[ -n "${LOKI_QUICK_INNER:-}" ] || echo "[loki] provider: $LOKI_PROVIDER (auto-detected)" >&2
|
|
1726
1751
|
else
|
|
1727
1752
|
# Nothing installed. Keep the historical default so the existing
|
|
1728
1753
|
# "not installed" error path reports claude, which is the actionable
|
|
@@ -1833,7 +1858,7 @@ _loki_log_enabled() { [ "$1" -ge "$(_loki_log_threshold)" ]; }
|
|
|
1833
1858
|
log_info() { _loki_log_enabled 1 && echo -e "${GREEN}[INFO]${NC} $*" || true; }
|
|
1834
1859
|
log_warn() { _loki_log_enabled 2 && echo -e "${YELLOW}[WARN]${NC} $*" || true; }
|
|
1835
1860
|
log_warning() { log_warn "$@"; } # Alias for backwards compatibility
|
|
1836
|
-
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
|
|
1861
|
+
log_error() { local _fd=1; [ -z "${LOKI_QUICK_INNER:-}" ] || _fd=2; echo -e "${RED}[ERROR]${NC} $*" >&"$_fd"; }
|
|
1837
1862
|
log_step() { _loki_log_enabled 1 && echo -e "${CYAN}[STEP]${NC} $*" || true; }
|
|
1838
1863
|
log_debug() { [[ "${LOKI_DEBUG:-}" == "true" ]] && echo -e "${CYAN}[DEBUG]${NC} $*" >&2 || true; }
|
|
1839
1864
|
|
|
@@ -7716,68 +7741,6 @@ EOF
|
|
|
7716
7741
|
# Track last known phase to detect changes
|
|
7717
7742
|
LAST_KNOWN_PHASE=""
|
|
7718
7743
|
|
|
7719
|
-
# Set the current phase and emit event if changed
|
|
7720
|
-
# v7.5.12: append a log entry to the iteration-N task in in-progress.json.
|
|
7721
|
-
# Args: iteration, phase, level, message. All silent on failure -- this
|
|
7722
|
-
# must NEVER kill the run.
|
|
7723
|
-
append_iteration_task_log() {
|
|
7724
|
-
local iteration="${1:-0}"
|
|
7725
|
-
local phase="${2:-}"
|
|
7726
|
-
local level="${3:-info}"
|
|
7727
|
-
local message="${4:-}"
|
|
7728
|
-
local in_progress_file=".loki/queue/in-progress.json"
|
|
7729
|
-
|
|
7730
|
-
[ -z "$iteration" ] && return 0
|
|
7731
|
-
[ "$iteration" = "0" ] && return 0
|
|
7732
|
-
[ ! -f "$in_progress_file" ] && return 0
|
|
7733
|
-
|
|
7734
|
-
local timestamp
|
|
7735
|
-
timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
7736
|
-
|
|
7737
|
-
ITER="$iteration" PHASE="$phase" LEVEL="$level" \
|
|
7738
|
-
MESSAGE="$message" TIMESTAMP="$timestamp" \
|
|
7739
|
-
python3 - "$in_progress_file" <<'PY' 2>/dev/null || true
|
|
7740
|
-
import json, os, sys, tempfile
|
|
7741
|
-
path = sys.argv[1]
|
|
7742
|
-
target_id = f"iteration-{os.environ['ITER']}"
|
|
7743
|
-
entry = {
|
|
7744
|
-
"timestamp": os.environ["TIMESTAMP"],
|
|
7745
|
-
"iteration": int(os.environ["ITER"]),
|
|
7746
|
-
"level": os.environ.get("LEVEL", "info"),
|
|
7747
|
-
"phase": os.environ.get("PHASE", ""),
|
|
7748
|
-
"message": os.environ.get("MESSAGE", ""),
|
|
7749
|
-
}
|
|
7750
|
-
try:
|
|
7751
|
-
with open(path) as f:
|
|
7752
|
-
data = json.load(f)
|
|
7753
|
-
except Exception:
|
|
7754
|
-
sys.exit(0)
|
|
7755
|
-
# Support both [...] and {tasks: [...]} shapes (matches load_queue_tasks).
|
|
7756
|
-
tasks = data["tasks"] if isinstance(data, dict) and isinstance(data.get("tasks"), list) else (data if isinstance(data, list) else None)
|
|
7757
|
-
if tasks is None:
|
|
7758
|
-
sys.exit(0)
|
|
7759
|
-
mutated = False
|
|
7760
|
-
for t in tasks:
|
|
7761
|
-
if not isinstance(t, dict):
|
|
7762
|
-
continue
|
|
7763
|
-
if t.get("id") == target_id:
|
|
7764
|
-
logs = t.get("logs")
|
|
7765
|
-
if not isinstance(logs, list):
|
|
7766
|
-
logs = []
|
|
7767
|
-
logs.append(entry)
|
|
7768
|
-
t["logs"] = logs
|
|
7769
|
-
mutated = True
|
|
7770
|
-
break
|
|
7771
|
-
if not mutated:
|
|
7772
|
-
sys.exit(0)
|
|
7773
|
-
out_dir = os.path.dirname(path) or "."
|
|
7774
|
-
fd, tmp = tempfile.mkstemp(dir=out_dir, suffix=".json")
|
|
7775
|
-
with os.fdopen(fd, "w") as f:
|
|
7776
|
-
json.dump(data, f, indent=2)
|
|
7777
|
-
os.replace(tmp, path)
|
|
7778
|
-
PY
|
|
7779
|
-
}
|
|
7780
|
-
|
|
7781
7744
|
#===============================================================================
|
|
7782
7745
|
# Dashboard State Writer (Real-time sync with web dashboard)
|
|
7783
7746
|
#===============================================================================
|
|
@@ -11062,6 +11025,12 @@ _loki_untrack_agent_committed_user_files() {
|
|
|
11062
11025
|
return 0
|
|
11063
11026
|
}
|
|
11064
11027
|
|
|
11028
|
+
# A-132: true for a `loki quick` run. Only cmd_quick writes .loki/quick-prd-<pid>.md.
|
|
11029
|
+
_loki_is_quick_prd() {
|
|
11030
|
+
case "${1:-}" in */quick-prd-*.md | quick-prd-*.md) return 0 ;; esac
|
|
11031
|
+
return 1
|
|
11032
|
+
}
|
|
11033
|
+
|
|
11065
11034
|
commit_session_changes() {
|
|
11066
11035
|
# Squash the session's work into one honest session-end commit on the agent
|
|
11067
11036
|
# branch (LOCK A3/A4/A8). Commit-always (incl. failed runs) so the user is
|
|
@@ -11256,6 +11225,29 @@ _loki_proof_json_for_pr() {
|
|
|
11256
11225
|
return 0
|
|
11257
11226
|
}
|
|
11258
11227
|
|
|
11228
|
+
# A-134: compact quiet-mode receipt for the outer shell (see top of file) to print.
|
|
11229
|
+
_loki_quick_receipt_write() {
|
|
11230
|
+
local ld="${TARGET_DIR:-.}/.loki" rid pj
|
|
11231
|
+
rid="$(cat "$ld/state/last-proof-id.txt" 2>/dev/null || true)"
|
|
11232
|
+
case "$rid" in '' | *[!A-Za-z0-9._-]*) return 0 ;; esac
|
|
11233
|
+
pj="$ld/proofs/$rid/proof.json"
|
|
11234
|
+
[ -f "$pj" ] || return 0
|
|
11235
|
+
python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
11236
|
+
import json
|
|
11237
|
+
d = json.load(open(sys.argv[1]))
|
|
11238
|
+
g = (d.get('facts') or {}).get('git') or {}
|
|
11239
|
+
v = d.get('verification') or {}
|
|
11240
|
+
if sys.argv[3] and g.get('head_sha') != sys.argv[3]:
|
|
11241
|
+
print('Evidence Receipt: unavailable (final proof generation failed; newest proof predates the session commit)')
|
|
11242
|
+
sys.exit(0)
|
|
11243
|
+
deg = [str(x.get('item') or '').split(':')[-1] for x in ((d.get('honesty') or {}).get('degraded') or []) if isinstance(x, dict)]
|
|
11244
|
+
notes = ([] if (v.get('gpg_signature') or v.get('attestation')) else ['unsigned']) + (['%d not proven: %s' % (len(deg), ', '.join(deg))] if deg else [])
|
|
11245
|
+
print('Evidence Receipt: ' + str((d.get('honesty') or {}).get('headline') or 'unavailable') + (' (' + '; '.join(notes) + ')' if notes else ''))
|
|
11246
|
+
print('receipt_sha256: ' + str(v.get('hash') or ''))
|
|
11247
|
+
print('Head sha: ' + str(g.get('head_sha') or '') + ' Diff sha256: ' + str(g.get('diff_sha256') or ''))
|
|
11248
|
+
print('Check it: loki verify (or loki proof verify ' + sys.argv[2] + ')')" "$pj" "$rid" "$(git -C "${TARGET_DIR:-.}" rev-parse HEAD 2>/dev/null || true)" > "$LOKI_QUICK_DIR/quick-receipt.txt" 2>/dev/null || rm -f "$LOKI_QUICK_DIR/quick-receipt.txt"
|
|
11249
|
+
}
|
|
11250
|
+
|
|
11259
11251
|
create_session_pr() {
|
|
11260
11252
|
# Advise the user how to open a PR for the agent branch. PRINT-ONLY by
|
|
11261
11253
|
# default (no push, no PR). LOKI_AUTO_PR=1 restores the legacy auto behavior.
|
|
@@ -11321,11 +11313,9 @@ create_session_pr() {
|
|
|
11321
11313
|
# Proven PR (Loop 6): print the Evidence Receipt block AFTER the push/PR
|
|
11322
11314
|
# advice so a user opening a manual PR can paste it into the body. This is
|
|
11323
11315
|
# the print-PR-body fallback. Default-on; LOKI_PROVEN_PR=0 -> not invoked
|
|
11324
|
-
# (advisory output byte-identical to before).
|
|
11325
|
-
#
|
|
11326
|
-
#
|
|
11327
|
-
# head and feeding it would false-degrade every legitimate receipt; the
|
|
11328
|
-
# anti-stale guarantee is the run_id pointer (R-DET-1), not a head match.
|
|
11316
|
+
# (advisory output byte-identical to before). The final proof now precedes this
|
|
11317
|
+
# call, so its head is the branch head; expected_head_sha stays empty and the
|
|
11318
|
+
# anti-stale guarantee is the run_id pointer (R-DET-1).
|
|
11329
11319
|
if [ "${LOKI_PROVEN_PR:-1}" != "0" ] && declare -f render_evidence_receipt_md >/dev/null 2>&1; then
|
|
11330
11320
|
local _pr_proof=""
|
|
11331
11321
|
_pr_proof="$(_loki_proof_json_for_pr 2>/dev/null || true)"
|
|
@@ -20989,6 +20979,7 @@ PYEOF
|
|
|
20989
20979
|
_intelligent_usage_regen() {
|
|
20990
20980
|
local target_dir="${TARGET_DIR:-.}"
|
|
20991
20981
|
local usage_path="$target_dir/USAGE.md"
|
|
20982
|
+
_loki_is_quick_prd "${PRD_PATH:-}" && return 0 # A-132: no USAGE regen on a quick fix
|
|
20992
20983
|
# Only the provider the operator chose may see a prompt (V10 P5): on any
|
|
20993
20984
|
# other provider keep the agent-written USAGE.md. Policy (and the
|
|
20994
20985
|
# LOKI_ALLOW_CLAUDE_SIDECALLS=1 opt-in) lives in providers/loader.sh,
|
|
@@ -22426,6 +22417,9 @@ build_prompt() {
|
|
|
22426
22417
|
# and to the dashboard/Purple Lab UI.
|
|
22427
22418
|
local usage_doc_instruction="USAGE_DOC_REQUIRED: Before invoking loki_complete_task (or touching .loki/signals/COMPLETION_REQUESTED), write USAGE.md at the project root. Detect the stack from package.json/requirements.txt/Cargo.toml/go.mod/etc. and include these sections: (1) Prerequisites (runtimes, ports, env vars), (2) Install (exact command, e.g. 'npm install' or 'pip install -r requirements.txt'), (3) Start (exact command, e.g. 'npm start' or 'python server.py'), (4) Verify -- 2 to 3 copy-paste commands the user can run to confirm it works (curl examples for APIs with expected output, browser URL for web UIs, command invocation for CLIs), (5) Stop (Ctrl+C or 'lsof -ti:PORT | xargs kill -9' for backgrounded servers). Keep it under 100 lines, plain Markdown, no emojis. If USAGE.md already exists and is accurate, leave it; otherwise create or update it."
|
|
22428
22419
|
|
|
22420
|
+
# A-132: a quick fix does not ask for USAGE.md (persist_user_prd repoints $prd, so key on PRD_PATH).
|
|
22421
|
+
_loki_is_quick_prd "${PRD_PATH:-}" && usage_doc_instruction=""
|
|
22422
|
+
|
|
22429
22423
|
# DOC_SCOPE instruction (F52): scale generated documentation to the detected
|
|
22430
22424
|
# project complexity. A trivial one-file app does not warrant a nine-file
|
|
22431
22425
|
# architecture suite (ARCHITECTURE/COMPONENTS/DECISIONS/API/SETUP/TESTING) --
|
|
@@ -23625,7 +23619,7 @@ populate_prd_queue() {
|
|
|
23625
23619
|
# Prefer the original project PRD over generated quick-prd.md
|
|
23626
23620
|
# quick-prd.md contains boilerplate that produces garbage tasks
|
|
23627
23621
|
local effective_prd="$prd_file"
|
|
23628
|
-
if [[ "$prd_file" == *"quick-prd.md" ]] || [[ "$prd_file" == *"chat-prd.md" ]]; then
|
|
23622
|
+
if [[ "$prd_file" == *"quick-prd.md" ]] || _loki_is_quick_prd "$prd_file" || [[ "$prd_file" == *"chat-prd.md" ]]; then
|
|
23629
23623
|
# Look for the real PRD in the project root
|
|
23630
23624
|
for candidate in "PRD.md" "prd.md" "requirements.md" "REQUIREMENTS.md" "spec.md" "SPEC.md"; do
|
|
23631
23625
|
if [[ -f "$candidate" ]]; then
|
|
@@ -23999,7 +23993,8 @@ else:
|
|
|
23999
23993
|
with open(pending_path, "w") as f:
|
|
24000
23994
|
json.dump(output, f, indent=2)
|
|
24001
23995
|
|
|
24002
|
-
|
|
23996
|
+
if not os.environ.get("LOKI_QUICK_INNER"):
|
|
23997
|
+
print(f"Extracted {added} tasks from PRD ({len(features)} features found)", file=sys.stderr)
|
|
24003
23998
|
PRD_PARSE_EOF
|
|
24004
23999
|
|
|
24005
24000
|
if [[ $? -ne 0 ]]; then
|
|
@@ -27753,7 +27748,7 @@ reap_own_process_group() {
|
|
|
27753
27748
|
# Collect protected pids (dashboard, app-runner, registered children) so the
|
|
27754
27749
|
# reap never takes down the shared dashboard if it happens to share our
|
|
27755
27750
|
# group. Mirrors the `loki stop` / dashboard reaper protection set.
|
|
27756
|
-
local _protected=" $$ "
|
|
27751
|
+
local _protected=" $$ ${LOKI_QUICK_OUTER_PID:-} " # A-134: the quiet-mode outer shell shares our group
|
|
27757
27752
|
local _pf _p
|
|
27758
27753
|
if [ -d "$loki_dir/pids" ]; then
|
|
27759
27754
|
for _pf in "$loki_dir/pids"/*.json; do
|
|
@@ -29200,27 +29195,13 @@ main() {
|
|
|
29200
29195
|
# learnings writers, proof generation, metrics aggregation -- runs here
|
|
29201
29196
|
# and was never timed. Opened immediately so nothing below is missed.
|
|
29202
29197
|
_teardown_t0=$(date +%s 2>/dev/null)
|
|
29203
|
-
# PRE-EDIT SNAPSHOT: freeze the agent's raw diff HERE, the first
|
|
29204
|
-
#
|
|
29205
|
-
#
|
|
29206
|
-
#
|
|
29207
|
-
# files before that. The snapshot is write-once, so capturing it late
|
|
29208
|
-
# would permanently record someone else's edits as the agent's. Runs in
|
|
29209
|
-
# the FOREGROUND on purpose: the entire value of this position is that
|
|
29210
|
-
# the capture COMPLETES before any mutation, and backgrounding it would
|
|
29211
|
-
# reintroduce exactly the race the placement exists to remove (the
|
|
29212
|
-
# module bounds each git call at 60s, so the cost is bounded).
|
|
29198
|
+
# PRE-EDIT SNAPSHOT: freeze the agent's raw diff HERE, the first instruction
|
|
29199
|
+
# after the loop: commit, HANDOFF and learnings writers below all change the
|
|
29200
|
+
# tree, and the snapshot is write-once. Foreground on purpose (each git call
|
|
29201
|
+
# is bounded at 60s) so the capture completes before any mutation.
|
|
29213
29202
|
capture_preedit_snapshot || true
|
|
29214
|
-
# ZOMBIE-RECEIPT GUARD: proof
|
|
29215
|
-
#
|
|
29216
|
-
# reap) between here and there, a genuinely finished build (real code, exit
|
|
29217
|
-
# 0) leaves NO proof.json and STATUS stuck "BUILDING" -- the build "worked"
|
|
29218
|
-
# but produced no Evidence Receipt (observed on run-20260716194328). Emit
|
|
29219
|
-
# the proof HERE too, right after the loop returns, so the receipt survives
|
|
29220
|
-
# a late teardown death. Idempotent + fire-and-forget: the teardown's own
|
|
29221
|
-
# generate_proof_of_run re-runs harmlessly (same run_id -> same proof dir),
|
|
29222
|
-
# and LOKI_PROOF=0 still opts out. This closes the "nothing gets verified"
|
|
29223
|
-
# gap at its highest-value point without touching run_autonomous itself.
|
|
29203
|
+
# ZOMBIE-RECEIPT GUARD: emit the proof here too, so a teardown killed below
|
|
29204
|
+
# (Docker restart, OOM) still leaves a receipt. Idempotent; LOKI_PROOF=0 opts out.
|
|
29224
29205
|
if [ "${LOKI_PROOF:-1}" != "0" ] && type generate_proof_of_run &>/dev/null; then
|
|
29225
29206
|
generate_proof_of_run "$result" || true
|
|
29226
29207
|
fi
|
|
@@ -29344,6 +29325,11 @@ main() {
|
|
|
29344
29325
|
# move, so a partial write never leaves a truncated HANDOFF.md.
|
|
29345
29326
|
local _handoff_dir _handoff_md _handoff_tmp
|
|
29346
29327
|
_handoff_dir="${TARGET_DIR:-.}"
|
|
29328
|
+
# A-132: a quick fix keeps HANDOFF.md under .loki/, out of the repo root.
|
|
29329
|
+
if _loki_is_quick_prd "${PRD_PATH:-}"; then
|
|
29330
|
+
_handoff_dir="${LOKI_DIR:-${TARGET_DIR:-.}/.loki}"
|
|
29331
|
+
mkdir -p "$_handoff_dir" 2>/dev/null || true
|
|
29332
|
+
fi
|
|
29347
29333
|
_handoff_md="$_handoff_dir/HANDOFF.md"
|
|
29348
29334
|
_handoff_tmp="$_handoff_dir/.HANDOFF.md.tmp"
|
|
29349
29335
|
if python3 "$_own_render" --loki-dir "${LOKI_DIR:-${TARGET_DIR:-.}/.loki}" --md > "$_handoff_tmp" 2>/dev/null; then
|
|
@@ -29367,13 +29353,6 @@ main() {
|
|
|
29367
29353
|
# then advise the user how to open a PR. Both are no-ops when no agent branch
|
|
29368
29354
|
# was set up (LOKI_BRANCH_PROTECTION=false) or nothing changed.
|
|
29369
29355
|
commit_session_changes
|
|
29370
|
-
# Trusted post-session step. NOT wrapped in _loki_with_github_tokens as a
|
|
29371
|
-
# whole (round 5): that ran every git call inside it (rev-list, merge-base,
|
|
29372
|
-
# push) with the real credentials AND the agent's repo config loaded. Its
|
|
29373
|
-
# push (_loki_trusted_push) and gh calls (gh wrapper) each re-grant the
|
|
29374
|
-
# credentials themselves, outside the agent's repo.
|
|
29375
|
-
create_session_pr
|
|
29376
|
-
audit_agent_action "session_stop" "Session ended" "result=$result,iterations=$ITERATION_COUNT"
|
|
29377
29356
|
|
|
29378
29357
|
# The first terminal summary is written before session changes are committed.
|
|
29379
29358
|
# Refresh its durable files against the final HEAD without notifying twice.
|
|
@@ -29391,35 +29370,23 @@ except Exception:
|
|
|
29391
29370
|
build_completion_summary "$_completion_outcome" || true
|
|
29392
29371
|
fi
|
|
29393
29372
|
|
|
29394
|
-
# Final source-tree binding
|
|
29395
|
-
#
|
|
29396
|
-
#
|
|
29397
|
-
# so proof.tree_sha256 describes the exact tree the runner returns.
|
|
29373
|
+
# Final source-tree binding, AFTER HANDOFF.md and commit_session_changes (A-134),
|
|
29374
|
+
# so Head and diff sha256 describe the tree the runner returns. Every receipt
|
|
29375
|
+
# print follows; create_session_pr never changes HEAD or the tree.
|
|
29398
29376
|
if [ "${LOKI_PROOF:-1}" != "0" ]; then
|
|
29399
29377
|
generate_proof_of_run "$result" || true
|
|
29400
29378
|
fi
|
|
29379
|
+
[ -z "${LOKI_QUICK_INNER:-}" ] || _loki_quick_receipt_write
|
|
29380
|
+
# Trusted post-session step. NOT wrapped in _loki_with_github_tokens as a
|
|
29381
|
+
# whole (round 5): its push (_loki_trusted_push) and gh calls each re-grant
|
|
29382
|
+
# the credentials themselves, outside the agent's repo.
|
|
29383
|
+
create_session_pr
|
|
29384
|
+
audit_agent_action "session_stop" "Session ended" "result=$result,iterations=$ITERATION_COUNT"
|
|
29401
29385
|
|
|
29402
|
-
# Evidence Receipt (#209): tell the user
|
|
29403
|
-
#
|
|
29404
|
-
#
|
|
29405
|
-
#
|
|
29406
|
-
# generate_proof_of_run, which is the only point where the receipt for THIS
|
|
29407
|
-
# run is guaranteed written and .loki/state/last-proof-id.txt is guaranteed
|
|
29408
|
-
# to point at it. Every earlier surface is either too early (the completion
|
|
29409
|
-
# card renders from inside run_autonomous, long before any proof exists) or
|
|
29410
|
-
# unreachable to a foreground user (COMPLETION.txt self-heals here, but only
|
|
29411
|
-
# a --bg launch is ever told to read it). Before #211 the pointer was never
|
|
29412
|
-
# cleared at run start, so announcing from an earlier site printed the
|
|
29413
|
-
# PREVIOUS run's receipt and verdict on a second run in the same directory.
|
|
29414
|
-
# Run init now clears it (search "Same reasoning for the proof pointer"), so
|
|
29415
|
-
# a stale pointer no longer survives into a new run; the position still
|
|
29416
|
-
# matters because an earlier site is simply too early for THIS run's proof.
|
|
29417
|
-
#
|
|
29418
|
-
# TTY-gated the same way print_ttfv_next_steps is above: machine output and
|
|
29419
|
-
# --bg stay byte-identical, and those readers already get the same facts
|
|
29420
|
-
# from COMPLETION.txt. Fail-silent and best-effort: prints nothing at all
|
|
29421
|
-
# when no receipt was written (LOKI_PROOF=0, or generation failed), and
|
|
29422
|
-
# never fails the run.
|
|
29386
|
+
# Evidence Receipt (#209): tell the user a checkable receipt exists. POSITION IS
|
|
29387
|
+
# LOAD-BEARING: only after the final proof above does last-proof-id.txt point at
|
|
29388
|
+
# THIS run's receipt (earlier sites print the previous run's). TTY-only and
|
|
29389
|
+
# fail-silent; --bg and machine output read the same facts from COMPLETION.txt.
|
|
29423
29390
|
if [ -t 1 ] && [ "${BACKGROUND_MODE:-false}" != "true" ]; then
|
|
29424
29391
|
_rcpt="$(_loki_receipt_facts "${TARGET_DIR:-.}/.loki" 2>/dev/null || true)"
|
|
29425
29392
|
if [ -n "${_rcpt:-}" ]; then
|
|
@@ -71,7 +71,7 @@ except ImportError: # pragma: no cover - only on a stripped install
|
|
|
71
71
|
def build_jwks(private_key=None, retired_public_keys=None):
|
|
72
72
|
return {"keys": []}
|
|
73
73
|
|
|
74
|
-
def load_signing_key():
|
|
74
|
+
def load_signing_key(auto_generate=True):
|
|
75
75
|
return None, ""
|
|
76
76
|
|
|
77
77
|
def load_retired_public_keys():
|
|
@@ -1360,7 +1360,7 @@ def main():
|
|
|
1360
1360
|
WebhookHandler.api_token = api_token
|
|
1361
1361
|
WebhookHandler.dispatcher = dispatcher
|
|
1362
1362
|
|
|
1363
|
-
signing_key, signing_kid = load_signing_key()
|
|
1363
|
+
signing_key, signing_kid = load_signing_key(auto_generate=False)
|
|
1364
1364
|
WebhookHandler.signing_key = signing_key
|
|
1365
1365
|
WebhookHandler.signing_kid = signing_kid
|
|
1366
1366
|
WebhookHandler.retired_pubkeys = tuple(load_retired_public_keys())
|
package/autonomy/verify.sh
CHANGED
|
@@ -3048,6 +3048,36 @@ PYEOF
|
|
|
3048
3048
|
# ---------------------------------------------------------------------------
|
|
3049
3049
|
# Entry point
|
|
3050
3050
|
# ---------------------------------------------------------------------------
|
|
3051
|
+
# A-134: when this tree holds a legacy Evidence Receipt, report its integrity digest
|
|
3052
|
+
# (proof-verify.py's own canonical re-hash, no second canonicalizer). Silent otherwise.
|
|
3053
|
+
_verify_receipt_digest() {
|
|
3054
|
+
local rid pj lib rc=0
|
|
3055
|
+
rid="$(cat .loki/state/last-proof-id.txt 2>/dev/null || true)"
|
|
3056
|
+
case "$rid" in '' | *[!A-Za-z0-9._-]*) return 0 ;; esac
|
|
3057
|
+
pj=".loki/proofs/$rid/proof.json"
|
|
3058
|
+
[ -f "$pj" ] || return 0
|
|
3059
|
+
lib="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib"
|
|
3060
|
+
python3 -E -c "import sys; sys.path[:] = [p for p in sys.path if p not in ('', '.')]
|
|
3061
|
+
import importlib.util, json
|
|
3062
|
+
sys.path.insert(0, sys.argv[1])
|
|
3063
|
+
sp = importlib.util.spec_from_file_location('pv', sys.argv[1] + '/proof-verify.py')
|
|
3064
|
+
m = importlib.util.module_from_spec(sp); sp.loader.exec_module(m)
|
|
3065
|
+
p = json.load(open(sys.argv[2]))
|
|
3066
|
+
ok = m.verify_integrity(p)['hash_ok']
|
|
3067
|
+
h = (p.get('verification') or {}).get('hash') or ''
|
|
3068
|
+
print('receipt_sha256: ' + h if ok else 'receipt: TAMPERED (integrity hash does not match proof.json)' if h else 'receipt: NOT CHECKABLE (no hash recorded)')
|
|
3069
|
+
sys.exit(0 if ok else 3)" "$lib" "$pj" 2>/dev/null || rc=$?
|
|
3070
|
+
[ "$rc" -ne 3 ] || return 1
|
|
3071
|
+
# Provenance: the deploy gate's own verdict (Ed25519 attestation against the local
|
|
3072
|
+
# key plus LOKI_RECEIPT_RETIRED_PUBKEYS; unknown kid or no key is UNCHECKED).
|
|
3073
|
+
# Defined in autonomy/loki, so it is absent when verify.sh runs standalone.
|
|
3074
|
+
declare -f _deploy_receipt_verdict >/dev/null 2>&1 || return 0
|
|
3075
|
+
local att
|
|
3076
|
+
att="$(_deploy_receipt_verdict "$pj")"
|
|
3077
|
+
printf 'attestation: %s\n' "$att"
|
|
3078
|
+
[ "$att" != "TAMPERED" ]
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3051
3081
|
verify_main() {
|
|
3052
3082
|
local base_ref=""
|
|
3053
3083
|
local out_dir=".loki/verify"
|
|
@@ -3247,6 +3277,13 @@ verify_main() {
|
|
|
3247
3277
|
|
|
3248
3278
|
verify_compute_verdict "$block_on"
|
|
3249
3279
|
|
|
3280
|
+
# A-134: a tampered receipt turns the verdict BLOCKED BEFORE evidence.json is
|
|
3281
|
+
# written, so no artifact ever records VERIFIED for it. Under --json the human
|
|
3282
|
+
# lines go to stderr.
|
|
3283
|
+
_v_banner_fd=1
|
|
3284
|
+
[ "${VERIFY_JSON:-0}" = "1" ] && _v_banner_fd=2
|
|
3285
|
+
_verify_receipt_digest >&$_v_banner_fd || { VERIFY_VERDICT=BLOCKED; VERIFY_EXIT=$VERIFY_EXIT_BLOCKED; }
|
|
3286
|
+
|
|
3250
3287
|
completed_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
3251
3288
|
|
|
3252
3289
|
# stdout is discarded (the emitter's own chatter is not wanted on the human
|
|
@@ -3284,8 +3321,6 @@ verify_main() {
|
|
|
3284
3321
|
# Under --json stdout carries the evidence document alone, so the human
|
|
3285
3322
|
# banner is redirected to stderr rather than dropped: an operator watching a
|
|
3286
3323
|
# terminal still sees the verdict, and `| jq` still parses.
|
|
3287
|
-
_v_banner_fd=1
|
|
3288
|
-
[ "${VERIFY_JSON:-0}" = "1" ] && _v_banner_fd=2
|
|
3289
3324
|
printf 'VERDICT: %s\n' "$VERIFY_VERDICT" >&$_v_banner_fd
|
|
3290
3325
|
printf 'Evidence: %s/evidence.json\n' "$out_dir" >&$_v_banner_fd
|
|
3291
3326
|
printf 'Report: %s/report.md\n' "$out_dir" >&$_v_banner_fd
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -12574,8 +12574,8 @@ async def proofs_summary():
|
|
|
12574
12574
|
But on the UNSIGNED path the generator is TRUSTED -- a forger who rewrites
|
|
12575
12575
|
both the facts and the headline to a mutually consistent lie and recomputes
|
|
12576
12576
|
the hash still buckets as verified here. Neutral, adversarial non-forgeability
|
|
12577
|
-
(the generator is not trusted) requires the SIGNED record (
|
|
12578
|
-
|
|
12577
|
+
(the generator is not trusted) requires the SIGNED record (an Ed25519
|
|
12578
|
+
attestation checked against a key set). This endpoint reports what the generator recorded;
|
|
12579
12579
|
it does not itself re-verify. Buckets:
|
|
12580
12580
|
|
|
12581
12581
|
verified -> honesty.headline == "VERIFIED"
|
|
@@ -264,10 +264,9 @@ fixed at the same time.
|
|
|
264
264
|
already states signature status explicitly in both directions
|
|
265
265
|
(`proof-generator.py:1808-1820`): SIGNED, or UNSIGNED with the honest line that
|
|
266
266
|
the integrity hash "does NOT prove who produced them, so this receipt trusts
|
|
267
|
-
its generator." That is the shippable version.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
project exists to prevent. Key distribution is a founder decision.
|
|
267
|
+
its generator." That is the shippable version. Signing is now default-on through an
|
|
268
|
+
auto-generated local Ed25519 key (A-120); the old gpg switch was deleted.
|
|
269
|
+
Key distribution remains a founder decision.
|
|
271
270
|
- **OTEL to a customer-owned collector.** Already implemented -- `otel_endpoint`
|
|
272
271
|
is persisted and read (`autonomy/loki:26755`, `:26828-26837`).
|
|
273
272
|
- **A headless exec contract with documented exit codes.** Already exists via
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
|
|
4
4
|
|
|
5
|
-
**Version:** v10.5.
|
|
5
|
+
**Version:** v10.5.13
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|