loki-mode 7.13.0 → 7.15.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 +4 -2
- package/VERSION +1 -1
- package/autonomy/loki +364 -16
- package/autonomy/run.sh +62 -12
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +94 -0
- package/dashboard/static/index.html +12 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/OPEN-CORE-BOUNDARY.md +58 -0
- package/docs/R6-ROLLBACK-CHECKPOINT-PLAN.md +107 -0
- package/docs/R9-OPEN-CORE-HOOKS-PLAN.md +113 -0
- package/loki-ts/dist/loki.js +244 -211
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Autonomous spec-to-product system. Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product via the RARV-C closure loop, with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v7.
|
|
6
|
+
# Loki Mode v7.15.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -160,6 +160,8 @@ GROWTH ──[continuous improvement loop]──> GROWTH
|
|
|
160
160
|
| `.loki/signals/HUMAN_REVIEW_NEEDED` | Never | When human decision required |
|
|
161
161
|
| `.loki/state/checkpoints/` | After task completion | Automatic + manual via `loki checkpoint` |
|
|
162
162
|
|
|
163
|
+
One-command rollback (v7.5.2+): `loki rollback latest` or `loki rollback to <id>` restores `.loki/` state from a checkpoint. It first captures a forced pre-rollback snapshot of the current state and prints its id, so a rollback is itself undoable (`loki rollback to <that-id>`). Use `loki rollback list` to see checkpoints.
|
|
164
|
+
|
|
163
165
|
---
|
|
164
166
|
|
|
165
167
|
## Module Loading Protocol (Skills)
|
|
@@ -381,4 +383,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
381
383
|
|
|
382
384
|
---
|
|
383
385
|
|
|
384
|
-
**v7.
|
|
386
|
+
**v7.15.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.
|
|
1
|
+
7.15.0
|
package/autonomy/loki
CHANGED
|
@@ -13111,6 +13111,9 @@ main() {
|
|
|
13111
13111
|
checkpoint|cp)
|
|
13112
13112
|
cmd_checkpoint "$@"
|
|
13113
13113
|
;;
|
|
13114
|
+
rollback)
|
|
13115
|
+
cmd_rollback "$@"
|
|
13116
|
+
;;
|
|
13114
13117
|
council)
|
|
13115
13118
|
cmd_council "$@"
|
|
13116
13119
|
;;
|
|
@@ -16438,12 +16441,19 @@ SHOW_EOF
|
|
|
16438
16441
|
local restored=0
|
|
16439
16442
|
for item in "$cp_dir"/*; do
|
|
16440
16443
|
[ -e "$item" ] || continue
|
|
16441
|
-
local name
|
|
16444
|
+
local name
|
|
16445
|
+
name=$(basename "$item")
|
|
16442
16446
|
[ "$name" = "metadata.json" ] && continue
|
|
16447
|
+
# R6: do not restore the worktree-snapshot sidecar as state.
|
|
16448
|
+
[ "$name" = "worktree-snapshot.txt" ] && continue
|
|
16443
16449
|
|
|
16444
16450
|
if [ -d "$item" ]; then
|
|
16445
|
-
rm -rf ".loki/$name"
|
|
16446
|
-
|
|
16451
|
+
# R6 data-loss fix: NEVER `rm -rf ".loki/$name"` -- the
|
|
16452
|
+
# checkpoint store lives under .loki/state/checkpoints/, so
|
|
16453
|
+
# blowing away .loki/state/ would destroy every checkpoint
|
|
16454
|
+
# (including this one). Merge-copy directory contents instead.
|
|
16455
|
+
mkdir -p ".loki/$name"
|
|
16456
|
+
cp -r "$item"/. ".loki/$name"/ 2>/dev/null && restored=$((restored + 1))
|
|
16447
16457
|
else
|
|
16448
16458
|
cp "$item" ".loki/$name" 2>/dev/null && restored=$((restored + 1))
|
|
16449
16459
|
fi
|
|
@@ -16451,17 +16461,28 @@ SHOW_EOF
|
|
|
16451
16461
|
|
|
16452
16462
|
echo -e " Restored: ${GREEN}$restored${NC} state items from $cp_id"
|
|
16453
16463
|
|
|
16454
|
-
# Show
|
|
16455
|
-
|
|
16456
|
-
|
|
16464
|
+
# Show how to also restore code. R6: prefer the anchored working-tree
|
|
16465
|
+
# snapshot over `git reset --hard <git_sha>`. git_sha is HEAD (the last
|
|
16466
|
+
# commit), and Loki does not commit per iteration, so a hard reset
|
|
16467
|
+
# discards the iteration's work instead of reconstructing it -- the old
|
|
16468
|
+
# hint was misleading.
|
|
16469
|
+
if git rev-parse --verify "refs/loki/cp/${cp_id}" >/dev/null 2>&1; then
|
|
16457
16470
|
echo ""
|
|
16458
|
-
echo -e " ${YELLOW}Note:${NC} Session state
|
|
16459
|
-
echo " To also
|
|
16471
|
+
echo -e " ${YELLOW}Note:${NC} Session state restored. Code is unchanged."
|
|
16472
|
+
echo " To also restore the working tree to this checkpoint's snapshot:"
|
|
16460
16473
|
echo ""
|
|
16461
|
-
echo -e " ${DIM}git
|
|
16474
|
+
echo -e " ${DIM}git stash apply refs/loki/cp/${cp_id}${NC}"
|
|
16462
16475
|
echo ""
|
|
16463
|
-
echo -e " ${
|
|
16464
|
-
|
|
16476
|
+
echo -e " ${DIM}(restores tracked files; newly-added files are not removed)${NC}"
|
|
16477
|
+
else
|
|
16478
|
+
local cp_sha
|
|
16479
|
+
cp_sha=$(_CP_METADATA="$metadata" python3 -c "import json, os; d=json.load(open(os.environ['_CP_METADATA'])); print(d.get('git_sha','unknown'))" 2>/dev/null)
|
|
16480
|
+
if [ -n "$cp_sha" ] && [ "$cp_sha" != "unknown" ] && [ "$cp_sha" != "not-a-git-repo" ]; then
|
|
16481
|
+
echo ""
|
|
16482
|
+
echo -e " ${YELLOW}Note:${NC} Session state restored, but no working-tree snapshot"
|
|
16483
|
+
echo " was captured for this checkpoint, so code changes since the last"
|
|
16484
|
+
echo -e " commit (${cp_sha:0:8}) are not restorable from here."
|
|
16485
|
+
fi
|
|
16465
16486
|
fi
|
|
16466
16487
|
;;
|
|
16467
16488
|
|
|
@@ -16497,6 +16518,157 @@ SHOW_EOF
|
|
|
16497
16518
|
esac
|
|
16498
16519
|
}
|
|
16499
16520
|
|
|
16521
|
+
# R6: one-command rollback. Top-level, obvious entry point that mirrors the Bun
|
|
16522
|
+
# `loki rollback` subcommands (list/show/to/latest) so both routes are at parity.
|
|
16523
|
+
# Restore is destructive on .loki/ state, so it ALWAYS captures a forced
|
|
16524
|
+
# pre-rollback snapshot first (re-undoability invariant) and then glob-restores
|
|
16525
|
+
# whatever the checkpoint dir contains (works across all three checkpoint writers).
|
|
16526
|
+
cmd_rollback() {
|
|
16527
|
+
local subcommand="${1:-help}"
|
|
16528
|
+
shift 2>/dev/null || true
|
|
16529
|
+
|
|
16530
|
+
local checkpoints_dir=".loki/state/checkpoints"
|
|
16531
|
+
local index_file="$checkpoints_dir/index.jsonl"
|
|
16532
|
+
|
|
16533
|
+
# Resolve the most recent checkpoint id from on-disk cp-*/chk-* dirs, sorted
|
|
16534
|
+
# by mtime (newest last). Mirrors the Bun "latest = last entry" semantics but
|
|
16535
|
+
# is robust to the three differing id formats.
|
|
16536
|
+
_rollback_latest_id() {
|
|
16537
|
+
[ -d "$checkpoints_dir" ] || return 1
|
|
16538
|
+
ls -1dt "$checkpoints_dir"/*/ 2>/dev/null | head -1 | sed 's#/$##' | xargs -I{} basename {} 2>/dev/null
|
|
16539
|
+
}
|
|
16540
|
+
|
|
16541
|
+
# Force a pre-rollback snapshot of current state, then glob-restore the target.
|
|
16542
|
+
# Delegates the actual restore to `cmd_checkpoint rollback`, which already
|
|
16543
|
+
# glob-restores and is shared with the manual path (no duplication).
|
|
16544
|
+
_rollback_restore() {
|
|
16545
|
+
local target_id="$1"
|
|
16546
|
+
local want_code="$2"
|
|
16547
|
+
|
|
16548
|
+
# Validate id (defense in depth; cmd_checkpoint validates again).
|
|
16549
|
+
if [[ ! "$target_id" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
|
16550
|
+
echo -e "${RED}Error: Invalid checkpoint ID${NC}"
|
|
16551
|
+
return 1
|
|
16552
|
+
fi
|
|
16553
|
+
if [ ! -d "$checkpoints_dir/$target_id" ]; then
|
|
16554
|
+
echo -e "${RED}Error: Checkpoint not found: $target_id${NC}"
|
|
16555
|
+
echo "Run 'loki rollback list' to see available checkpoints."
|
|
16556
|
+
return 1
|
|
16557
|
+
fi
|
|
16558
|
+
|
|
16559
|
+
# Re-undoability: snapshot current state before overwriting it.
|
|
16560
|
+
local pre_id
|
|
16561
|
+
pre_id="rb-pre-$(date -u '+%Y%m%d-%H%M%S')"
|
|
16562
|
+
local pre_dir="$checkpoints_dir/$pre_id"
|
|
16563
|
+
mkdir -p "$pre_dir"
|
|
16564
|
+
local saved=0
|
|
16565
|
+
for item in .loki/session.json .loki/dashboard-state.json .loki/CONTINUITY.md .loki/autonomy-state.json .loki/state .loki/queue; do
|
|
16566
|
+
if [ -e "$item" ]; then
|
|
16567
|
+
cp -r "$item" "$pre_dir/" 2>/dev/null && saved=$((saved + 1))
|
|
16568
|
+
fi
|
|
16569
|
+
done
|
|
16570
|
+
_RB_PID="$pre_id" _RB_TS="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" _RB_DIR="$pre_dir" \
|
|
16571
|
+
_RB_INDEX="$index_file" _RB_CHKDIR="$checkpoints_dir" python3 << 'RB_PRE_EOF' 2>/dev/null || true
|
|
16572
|
+
import json, os
|
|
16573
|
+
meta = {"id": os.environ["_RB_PID"], "timestamp": os.environ["_RB_TS"],
|
|
16574
|
+
"message": "pre-rollback snapshot", "created_by": "loki rollback"}
|
|
16575
|
+
d = os.environ["_RB_DIR"]
|
|
16576
|
+
os.makedirs(d, exist_ok=True)
|
|
16577
|
+
with open(os.path.join(d, "metadata.json"), "w") as f:
|
|
16578
|
+
json.dump(meta, f, indent=2)
|
|
16579
|
+
os.makedirs(os.environ["_RB_CHKDIR"], exist_ok=True)
|
|
16580
|
+
with open(os.environ["_RB_INDEX"], "a") as f:
|
|
16581
|
+
f.write(json.dumps({"id": meta["id"], "timestamp": meta["timestamp"], "message": meta["message"]}) + "\n")
|
|
16582
|
+
RB_PRE_EOF
|
|
16583
|
+
echo -e " ${DIM}Saved prior state as ${pre_id} (undo this rollback with: loki rollback to ${pre_id})${NC}"
|
|
16584
|
+
|
|
16585
|
+
# Perform the actual state restore via the shared glob-restore path.
|
|
16586
|
+
cmd_checkpoint rollback "$target_id"
|
|
16587
|
+
|
|
16588
|
+
# Optional code restore from the anchored working-tree snapshot.
|
|
16589
|
+
if [ "$want_code" = "1" ]; then
|
|
16590
|
+
if git rev-parse --verify "refs/loki/cp/${target_id}" >/dev/null 2>&1; then
|
|
16591
|
+
echo ""
|
|
16592
|
+
echo -e " ${YELLOW}Restoring working tree from snapshot refs/loki/cp/${target_id}...${NC}"
|
|
16593
|
+
if git stash apply "refs/loki/cp/${target_id}" 2>/dev/null; then
|
|
16594
|
+
echo -e " ${GREEN}Working tree restored.${NC} (tracked files only; newly-added files were not removed)"
|
|
16595
|
+
else
|
|
16596
|
+
echo -e " ${RED}Could not apply snapshot cleanly.${NC} Resolve conflicts, or run: git stash apply refs/loki/cp/${target_id}"
|
|
16597
|
+
fi
|
|
16598
|
+
else
|
|
16599
|
+
echo ""
|
|
16600
|
+
echo -e " ${YELLOW}No working-tree snapshot anchored for ${target_id}; code not restored.${NC}"
|
|
16601
|
+
fi
|
|
16602
|
+
fi
|
|
16603
|
+
}
|
|
16604
|
+
|
|
16605
|
+
case "$subcommand" in
|
|
16606
|
+
list|ls)
|
|
16607
|
+
cmd_checkpoint list
|
|
16608
|
+
;;
|
|
16609
|
+
show)
|
|
16610
|
+
cmd_checkpoint show "$@"
|
|
16611
|
+
;;
|
|
16612
|
+
to)
|
|
16613
|
+
local target="${1:-}"
|
|
16614
|
+
local want_code=0
|
|
16615
|
+
shift 2>/dev/null || true
|
|
16616
|
+
for a in "$@"; do [ "$a" = "--code" ] && want_code=1; done
|
|
16617
|
+
if [ -z "$target" ]; then
|
|
16618
|
+
echo -e "${RED}Error: Specify a checkpoint ID${NC}"
|
|
16619
|
+
echo "Usage: loki rollback to <id> [--code]"
|
|
16620
|
+
echo "Run 'loki rollback list' to see available checkpoints."
|
|
16621
|
+
return 1
|
|
16622
|
+
fi
|
|
16623
|
+
echo -e "${BOLD}Rolling back to checkpoint: $target${NC}"
|
|
16624
|
+
echo ""
|
|
16625
|
+
_rollback_restore "$target" "$want_code"
|
|
16626
|
+
;;
|
|
16627
|
+
latest)
|
|
16628
|
+
local want_code=0
|
|
16629
|
+
for a in "$@"; do [ "$a" = "--code" ] && want_code=1; done
|
|
16630
|
+
local latest_id
|
|
16631
|
+
latest_id=$(_rollback_latest_id)
|
|
16632
|
+
if [ -z "$latest_id" ]; then
|
|
16633
|
+
echo -e "${RED}No checkpoints found to roll back to.${NC}"
|
|
16634
|
+
return 1
|
|
16635
|
+
fi
|
|
16636
|
+
echo -e "${BOLD}Rolling back to latest checkpoint: $latest_id${NC}"
|
|
16637
|
+
echo ""
|
|
16638
|
+
_rollback_restore "$latest_id" "$want_code"
|
|
16639
|
+
;;
|
|
16640
|
+
help|--help|-h)
|
|
16641
|
+
echo -e "${BOLD}loki rollback${NC} - One-command rollback to a checkpoint"
|
|
16642
|
+
echo ""
|
|
16643
|
+
echo "Restore .loki/ state and iteration/conversation context to a"
|
|
16644
|
+
echo "previous checkpoint. Every rollback first saves your current state"
|
|
16645
|
+
echo "as a pre-rollback snapshot, so you can always undo the undo."
|
|
16646
|
+
echo ""
|
|
16647
|
+
echo "Usage: loki rollback <command> [args]"
|
|
16648
|
+
echo ""
|
|
16649
|
+
echo "Commands:"
|
|
16650
|
+
echo " list List recent checkpoints"
|
|
16651
|
+
echo " show <id> Show checkpoint details"
|
|
16652
|
+
echo " to <id> [--code] Restore to checkpoint <id>"
|
|
16653
|
+
echo " latest [--code] Restore to the most recent checkpoint"
|
|
16654
|
+
echo ""
|
|
16655
|
+
echo " --code Also restore the working tree from the anchored"
|
|
16656
|
+
echo " git snapshot (tracked files only; overwrites them)."
|
|
16657
|
+
echo ""
|
|
16658
|
+
echo "Examples:"
|
|
16659
|
+
echo " loki rollback list"
|
|
16660
|
+
echo " loki rollback latest"
|
|
16661
|
+
echo " loki rollback to cp-3-1717000000"
|
|
16662
|
+
echo " loki rollback to cp-3-1717000000 --code"
|
|
16663
|
+
;;
|
|
16664
|
+
*)
|
|
16665
|
+
echo -e "${RED}Unknown rollback command: $subcommand${NC}"
|
|
16666
|
+
echo "Run 'loki rollback help' for usage."
|
|
16667
|
+
return 1
|
|
16668
|
+
;;
|
|
16669
|
+
esac
|
|
16670
|
+
}
|
|
16671
|
+
|
|
16500
16672
|
# Completion Council management
|
|
16501
16673
|
cmd_council() {
|
|
16502
16674
|
local subcommand="${1:-status}"
|
|
@@ -25103,6 +25275,173 @@ _loki_gist_upload() {
|
|
|
25103
25275
|
echo -e "${GREEN}Shared: ${gist_url}${NC}"
|
|
25104
25276
|
}
|
|
25105
25277
|
|
|
25278
|
+
# loki_tier_gate - R9 open-core tier/license seam.
|
|
25279
|
+
#
|
|
25280
|
+
# OSS-FIRST CONTRACT: this is a no-op ALLOW for OSS users. LOKI_TIER defaults
|
|
25281
|
+
# to "oss" and every existing free feature stays fully free. This function is
|
|
25282
|
+
# the single place where a future hosted/enterprise build would gate a
|
|
25283
|
+
# hosted-only capability. It is NEVER called from any existing free command
|
|
25284
|
+
# path; its only caller is the opt-in --hosted publish seam below. For OSS
|
|
25285
|
+
# (the default), it always returns 0 (allow).
|
|
25286
|
+
#
|
|
25287
|
+
# Args: $1 = capability name (informational; e.g. "hosted_publish").
|
|
25288
|
+
# Returns: 0 = allowed, 1 = gated (non-OSS tier without entitlement).
|
|
25289
|
+
# Env: LOKI_TIER (default "oss"), LOKI_LICENSE_KEY (optional, non-OSS only).
|
|
25290
|
+
loki_tier_gate() {
|
|
25291
|
+
local capability="${1:-}"
|
|
25292
|
+
local tier="${LOKI_TIER:-oss}"
|
|
25293
|
+
|
|
25294
|
+
# OSS tier: everything is allowed, always. No license, no network, no gate.
|
|
25295
|
+
if [ "$tier" = "oss" ]; then
|
|
25296
|
+
return 0
|
|
25297
|
+
fi
|
|
25298
|
+
|
|
25299
|
+
# Non-OSS tiers (hosted/enterprise) are a SEAM only. The hosted backend
|
|
25300
|
+
# and license-verification service do not exist yet, so we cannot validate
|
|
25301
|
+
# an entitlement. Be honest: do not pretend to grant a paid capability.
|
|
25302
|
+
# A real hosted build replaces this branch with a verified license check.
|
|
25303
|
+
if [ -z "${LOKI_LICENSE_KEY:-}" ]; then
|
|
25304
|
+
echo -e "${YELLOW}LOKI_TIER='${tier}' requested but no LOKI_LICENSE_KEY set.${NC}" >&2
|
|
25305
|
+
echo "Hosted/enterprise license verification is not available yet." >&2
|
|
25306
|
+
echo "OSS users: leave LOKI_TIER unset (or 'oss') -- everything stays free." >&2
|
|
25307
|
+
return 1
|
|
25308
|
+
fi
|
|
25309
|
+
|
|
25310
|
+
# A license key is present but there is no verification backend yet. We do
|
|
25311
|
+
# NOT fabricate a successful verification. The capability stays ungated for
|
|
25312
|
+
# OSS-equivalent use; the seam is documented in docs/OPEN-CORE-BOUNDARY.md.
|
|
25313
|
+
echo -e "${YELLOW}LOKI_LICENSE_KEY set but the verification backend is not available yet (R9 seam).${NC}" >&2
|
|
25314
|
+
return 0
|
|
25315
|
+
}
|
|
25316
|
+
|
|
25317
|
+
# _loki_hosted_publish_proof - R9 hosted proof-publish client stub.
|
|
25318
|
+
#
|
|
25319
|
+
# Posts an ALREADY-REDACTED proof page to a self-hosted/SaaS endpoint given by
|
|
25320
|
+
# LOKI_HOSTED_ENDPOINT. There is NO official Loki hosted backend yet; this is a
|
|
25321
|
+
# clean client seam an operator can point at their own endpoint. We never
|
|
25322
|
+
# fabricate a hosted URL: on success we print the URL the endpoint returned (or
|
|
25323
|
+
# the endpoint itself); on any failure we print an honest error and exit non-0.
|
|
25324
|
+
#
|
|
25325
|
+
# Args: $1 = proof id, $2 = redacted index.html path, $3 = proof.json path.
|
|
25326
|
+
# Returns: 0 on success, non-zero on missing endpoint / transport / non-2xx.
|
|
25327
|
+
_loki_hosted_publish_proof() {
|
|
25328
|
+
local id="$1"
|
|
25329
|
+
local html="$2"
|
|
25330
|
+
local pj="$3"
|
|
25331
|
+
|
|
25332
|
+
# Tier seam (no-op allow for OSS). Hosted publish is opt-in regardless.
|
|
25333
|
+
loki_tier_gate "hosted_publish" || true
|
|
25334
|
+
|
|
25335
|
+
local endpoint="${LOKI_HOSTED_ENDPOINT:-}"
|
|
25336
|
+
if [ -z "$endpoint" ]; then
|
|
25337
|
+
echo -e "${YELLOW}Hosted publishing backend not available.${NC}" >&2
|
|
25338
|
+
echo "There is no official Loki hosted service yet (R9 ships the seam, not a live backend)." >&2
|
|
25339
|
+
echo "To publish to your own hosted endpoint, set LOKI_HOSTED_ENDPOINT to its URL." >&2
|
|
25340
|
+
echo "Or publish to a GitHub Gist instead: loki proof share ${id}" >&2
|
|
25341
|
+
return 1
|
|
25342
|
+
fi
|
|
25343
|
+
|
|
25344
|
+
if ! command -v curl &>/dev/null; then
|
|
25345
|
+
echo -e "${RED}curl not found${NC}" >&2
|
|
25346
|
+
echo "Hosted publishing requires curl. Install curl or use: loki proof share ${id}" >&2
|
|
25347
|
+
return 1
|
|
25348
|
+
fi
|
|
25349
|
+
|
|
25350
|
+
# CREDIBILITY: we upload the file the generator already redacted (the same
|
|
25351
|
+
# bytes 'loki proof share' would put on a gist). We do not build a fresh
|
|
25352
|
+
# body that could bypass redaction. If proof.json reports redaction was not
|
|
25353
|
+
# applied, refuse -- never publish an unredacted artifact.
|
|
25354
|
+
if [ -f "$pj" ]; then
|
|
25355
|
+
local redaction_ok
|
|
25356
|
+
redaction_ok=$(LOKI_PROOF_JSON="$pj" python3 - <<'PYEOF' 2>/dev/null || echo "unknown"
|
|
25357
|
+
import json, os
|
|
25358
|
+
try:
|
|
25359
|
+
d = json.load(open(os.environ["LOKI_PROOF_JSON"]))
|
|
25360
|
+
except Exception:
|
|
25361
|
+
print("unknown")
|
|
25362
|
+
else:
|
|
25363
|
+
print("yes" if (d.get("redaction") or {}).get("applied") else "no")
|
|
25364
|
+
PYEOF
|
|
25365
|
+
)
|
|
25366
|
+
if [ "$redaction_ok" = "no" ]; then
|
|
25367
|
+
echo -e "${RED}Refusing to publish: proof redaction was not applied.${NC}" >&2
|
|
25368
|
+
echo "Regenerate the proof (LOKI_PROOF=1) so the redactor runs, then retry." >&2
|
|
25369
|
+
return 1
|
|
25370
|
+
fi
|
|
25371
|
+
fi
|
|
25372
|
+
|
|
25373
|
+
echo -e "${BOLD}Publishing proof '${id}' to hosted endpoint${NC}"
|
|
25374
|
+
echo " endpoint: ${endpoint}"
|
|
25375
|
+
echo " payload: ${html} (already redacted by the generator)"
|
|
25376
|
+
echo ""
|
|
25377
|
+
|
|
25378
|
+
# POST the redacted HTML. Auth header is sent only if a license key exists;
|
|
25379
|
+
# OSS users with their own endpoint need no key.
|
|
25380
|
+
local tmp_body tmp_code
|
|
25381
|
+
tmp_body=$(mktemp "/tmp/loki-hosted-XXXXXX.out")
|
|
25382
|
+
local -a curl_args=(-sS -o "$tmp_body" -w '%{http_code}' -X POST
|
|
25383
|
+
-H "Content-Type: text/html"
|
|
25384
|
+
-H "X-Loki-Proof-Id: ${id}"
|
|
25385
|
+
--data-binary "@${html}")
|
|
25386
|
+
if [ -n "${LOKI_LICENSE_KEY:-}" ]; then
|
|
25387
|
+
curl_args+=(-H "Authorization: Bearer ${LOKI_LICENSE_KEY}")
|
|
25388
|
+
fi
|
|
25389
|
+
tmp_code=$(curl "${curl_args[@]}" "$endpoint" 2>/dev/null)
|
|
25390
|
+
local curl_exit=$?
|
|
25391
|
+
|
|
25392
|
+
if [ "$curl_exit" -ne 0 ]; then
|
|
25393
|
+
echo -e "${RED}Failed to reach hosted endpoint (curl exit ${curl_exit}).${NC}" >&2
|
|
25394
|
+
echo "Check LOKI_HOSTED_ENDPOINT or publish to a gist: loki proof share ${id}" >&2
|
|
25395
|
+
rm -f "$tmp_body"
|
|
25396
|
+
return 1
|
|
25397
|
+
fi
|
|
25398
|
+
|
|
25399
|
+
# Accept any 2xx. The published URL comes from the endpoint response if it
|
|
25400
|
+
# returns one (we look for a "url" field), else we report the endpoint. We
|
|
25401
|
+
# NEVER print a fabricated URL.
|
|
25402
|
+
case "$tmp_code" in
|
|
25403
|
+
2*)
|
|
25404
|
+
local published_url
|
|
25405
|
+
published_url=$(LOKI_HOSTED_BODY="$tmp_body" LOKI_HOSTED_EP="$endpoint" python3 - <<'PYEOF' 2>/dev/null || true
|
|
25406
|
+
import json, os
|
|
25407
|
+
body_path = os.environ["LOKI_HOSTED_BODY"]
|
|
25408
|
+
try:
|
|
25409
|
+
txt = open(body_path).read().strip()
|
|
25410
|
+
except Exception:
|
|
25411
|
+
txt = ""
|
|
25412
|
+
url = ""
|
|
25413
|
+
try:
|
|
25414
|
+
d = json.loads(txt)
|
|
25415
|
+
if isinstance(d, dict):
|
|
25416
|
+
url = d.get("url") or d.get("public_url") or ""
|
|
25417
|
+
except Exception:
|
|
25418
|
+
url = ""
|
|
25419
|
+
print(url)
|
|
25420
|
+
PYEOF
|
|
25421
|
+
)
|
|
25422
|
+
rm -f "$tmp_body"
|
|
25423
|
+
if [ -n "$published_url" ]; then
|
|
25424
|
+
echo -e "${GREEN}Published: ${published_url}${NC}"
|
|
25425
|
+
else
|
|
25426
|
+
echo -e "${GREEN}Published to ${endpoint} (HTTP ${tmp_code}).${NC}"
|
|
25427
|
+
echo "The endpoint did not return a 'url' field; check your endpoint's response."
|
|
25428
|
+
fi
|
|
25429
|
+
return 0
|
|
25430
|
+
;;
|
|
25431
|
+
*)
|
|
25432
|
+
echo -e "${RED}Hosted endpoint returned HTTP ${tmp_code}.${NC}" >&2
|
|
25433
|
+
if [ -s "$tmp_body" ]; then
|
|
25434
|
+
echo "Response:" >&2
|
|
25435
|
+
head -c 500 "$tmp_body" >&2
|
|
25436
|
+
echo "" >&2
|
|
25437
|
+
fi
|
|
25438
|
+
echo "Nothing was published. Or publish to a gist: loki proof share ${id}" >&2
|
|
25439
|
+
rm -f "$tmp_body"
|
|
25440
|
+
return 1
|
|
25441
|
+
;;
|
|
25442
|
+
esac
|
|
25443
|
+
}
|
|
25444
|
+
|
|
25106
25445
|
# loki bench - head-to-head benchmark harness (R2).
|
|
25107
25446
|
# Subcommands: run <task> | vs <task> | list | verify <result.json>.
|
|
25108
25447
|
# Thin pass-through to benchmarks/bench/run.sh (shared python core runner.py).
|
|
@@ -25170,7 +25509,7 @@ cmd_proof() {
|
|
|
25170
25509
|
echo "Options for 'share':"
|
|
25171
25510
|
echo " --yes Skip the redaction-preview confirmation prompt"
|
|
25172
25511
|
echo " --private Create a secret gist (default: public)"
|
|
25173
|
-
echo " --hosted
|
|
25512
|
+
echo " --hosted Publish to LOKI_HOSTED_ENDPOINT (open-core seam; no official backend yet)"
|
|
25174
25513
|
echo ""
|
|
25175
25514
|
echo "Proofs are generated automatically at run completion (LOKI_PROOF=0 to opt out)."
|
|
25176
25515
|
[ "$sub" = "" ] && exit 1
|
|
@@ -25269,15 +25608,13 @@ PYEOF
|
|
|
25269
25608
|
local id=""
|
|
25270
25609
|
local skip_confirm=0
|
|
25271
25610
|
local visibility="--public"
|
|
25611
|
+
local hosted=0
|
|
25272
25612
|
while [[ $# -gt 0 ]]; do
|
|
25273
25613
|
case "$1" in
|
|
25274
25614
|
--yes|-y) skip_confirm=1; shift ;;
|
|
25275
25615
|
--private) visibility=""; shift ;;
|
|
25276
25616
|
--public) visibility="--public"; shift ;;
|
|
25277
|
-
--hosted)
|
|
25278
|
-
echo -e "${RED}Hosted publishing is not available yet (coming in R9).${NC}"
|
|
25279
|
-
exit 1
|
|
25280
|
-
;;
|
|
25617
|
+
--hosted) hosted=1; shift ;;
|
|
25281
25618
|
-*) echo -e "${RED}Unknown option: $1${NC}"; exit 1 ;;
|
|
25282
25619
|
*) id="$1"; shift ;;
|
|
25283
25620
|
esac
|
|
@@ -25292,6 +25629,17 @@ PYEOF
|
|
|
25292
25629
|
echo "Use 'loki proof list' to see available proofs."
|
|
25293
25630
|
exit 1
|
|
25294
25631
|
fi
|
|
25632
|
+
# R9 open-core hosted-publish seam. Only taken when the user
|
|
25633
|
+
# explicitly passes --hosted. The default gist path below stays
|
|
25634
|
+
# byte-for-byte unchanged for OSS users (zero hosted backend
|
|
25635
|
+
# required). We never silent-fall-back to gist here: the user asked
|
|
25636
|
+
# for hosted, so we POST to a configured LOKI_HOSTED_ENDPOINT or
|
|
25637
|
+
# print an honest "no endpoint configured" message and exit non-zero.
|
|
25638
|
+
# We never fabricate a hosted URL.
|
|
25639
|
+
if [ "$hosted" -eq 1 ]; then
|
|
25640
|
+
_loki_hosted_publish_proof "$id" "$html" "${proofs_dir}/${id}/proof.json"
|
|
25641
|
+
exit $?
|
|
25642
|
+
fi
|
|
25295
25643
|
if ! command -v gh &>/dev/null; then
|
|
25296
25644
|
echo -e "${RED}gh CLI not found${NC}"
|
|
25297
25645
|
echo "Install the GitHub CLI to publish a proof:"
|
package/autonomy/run.sh
CHANGED
|
@@ -7443,10 +7443,18 @@ create_checkpoint() {
|
|
|
7443
7443
|
|
|
7444
7444
|
mkdir -p "$checkpoint_dir"
|
|
7445
7445
|
|
|
7446
|
-
# Only checkpoint if there are uncommitted changes
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7446
|
+
# Only checkpoint if there are uncommitted changes.
|
|
7447
|
+
# R6: _LOKI_CP_FORCE=1 bypasses this guard. Used by rollback to guarantee a
|
|
7448
|
+
# pre-rollback snapshot of .loki/ state even when the git tree is clean (the
|
|
7449
|
+
# .loki/ state files about to be overwritten are not git-tracked, so the
|
|
7450
|
+
# clean-tree guard would otherwise skip the safety snapshot). Mirrors the
|
|
7451
|
+
# Bun `forceCreate` seam in checkpoint.ts.
|
|
7452
|
+
if [ "${_LOKI_CP_FORCE:-0}" != "1" ]; then
|
|
7453
|
+
if git diff --quiet 2>/dev/null && git diff --cached --quiet 2>/dev/null; then
|
|
7454
|
+
log_info "No uncommitted changes to checkpoint"
|
|
7455
|
+
_LAST_CHECKPOINT_ID=""
|
|
7456
|
+
return 0
|
|
7457
|
+
fi
|
|
7450
7458
|
fi
|
|
7451
7459
|
|
|
7452
7460
|
# Capture git state
|
|
@@ -7465,7 +7473,9 @@ create_checkpoint() {
|
|
|
7465
7473
|
|
|
7466
7474
|
# Copy critical state files (lightweight -- not full .loki/)
|
|
7467
7475
|
# BUG-ST-009: Include autonomy-state.json in checkpoint backup
|
|
7468
|
-
|
|
7476
|
+
# R6: Include CONTINUITY.md so a rollback also restores iteration/conversation
|
|
7477
|
+
# handoff context, not just machine state. Mirrors Bun COPIED_FILES.
|
|
7478
|
+
for f in state/orchestrator.json autonomy-state.json queue/pending.json queue/completed.json queue/in-progress.json queue/current-task.json CONTINUITY.md; do
|
|
7469
7479
|
if [ -f ".loki/$f" ]; then
|
|
7470
7480
|
local target_dir="$cp_dir/$(dirname "$f")"
|
|
7471
7481
|
mkdir -p "$target_dir"
|
|
@@ -7473,6 +7483,23 @@ create_checkpoint() {
|
|
|
7473
7483
|
fi
|
|
7474
7484
|
done
|
|
7475
7485
|
|
|
7486
|
+
# R6: capture a real working-tree snapshot so code can be truly undone later.
|
|
7487
|
+
# Loki does not commit per iteration, so git_sha (HEAD) cannot reconstruct
|
|
7488
|
+
# this iteration's working tree. `git stash create` builds a commit object
|
|
7489
|
+
# capturing tracked changes WITHOUT disturbing the tree; we then anchor it
|
|
7490
|
+
# under refs/loki/cp/<id> so `git gc` cannot prune the dangling commit. The
|
|
7491
|
+
# snapshot sha goes in a sidecar (worktree-snapshot.txt), NOT metadata.json,
|
|
7492
|
+
# to preserve byte-for-byte parity with the Bun port.
|
|
7493
|
+
# Honest limit: captures tracked changes only (not untracked/ignored files).
|
|
7494
|
+
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
7495
|
+
local snap_sha
|
|
7496
|
+
snap_sha=$(git stash create "loki checkpoint ${checkpoint_id}" 2>/dev/null || echo "")
|
|
7497
|
+
if [ -n "$snap_sha" ]; then
|
|
7498
|
+
git update-ref "refs/loki/cp/${checkpoint_id}" "$snap_sha" 2>/dev/null \
|
|
7499
|
+
&& printf '%s\n' "$snap_sha" > "$cp_dir/worktree-snapshot.txt" 2>/dev/null || true
|
|
7500
|
+
fi
|
|
7501
|
+
fi
|
|
7502
|
+
|
|
7476
7503
|
# Write checkpoint metadata (use python3 json.dumps for safe serialization)
|
|
7477
7504
|
local phase_val
|
|
7478
7505
|
phase_val=$(cat .loki/state/orchestrator.json 2>/dev/null | python3 -c 'import sys,json; print(json.load(sys.stdin).get("currentPhase","unknown"))' 2>/dev/null || echo 'unknown')
|
|
@@ -7531,6 +7558,10 @@ print(json.dumps({'id':m['id'],'ts':m['timestamp'],'iter':m['iteration'],'task':
|
|
|
7531
7558
|
fi
|
|
7532
7559
|
|
|
7533
7560
|
log_info "Checkpoint created: ${checkpoint_id} (git: ${git_sha:0:8})"
|
|
7561
|
+
# R6: expose the id via a global so callers (rollback, run loop) can reference
|
|
7562
|
+
# it without parsing stdout (log_info writes to stdout, so command-substitution
|
|
7563
|
+
# capture would include log lines).
|
|
7564
|
+
_LAST_CHECKPOINT_ID="$checkpoint_id"
|
|
7534
7565
|
}
|
|
7535
7566
|
|
|
7536
7567
|
rollback_to_checkpoint() {
|
|
@@ -7558,11 +7589,18 @@ rollback_to_checkpoint() {
|
|
|
7558
7589
|
|
|
7559
7590
|
log_warn "Rolling back to checkpoint: ${checkpoint_id}"
|
|
7560
7591
|
|
|
7561
|
-
#
|
|
7562
|
-
|
|
7592
|
+
# R6 re-undoability invariant: force a pre-rollback snapshot of CURRENT state
|
|
7593
|
+
# before overwriting, even if the git tree is clean (the .loki/ state we are
|
|
7594
|
+
# about to clobber is not git-tracked). _LOKI_CP_FORCE bypasses the clean-tree
|
|
7595
|
+
# guard. Capture the snapshot id so we can tell the user how to undo the undo.
|
|
7596
|
+
_LOKI_CP_FORCE=1 create_checkpoint "pre-rollback snapshot (before restoring ${checkpoint_id})" "rollback"
|
|
7597
|
+
local pre_rollback_id="${_LAST_CHECKPOINT_ID:-}"
|
|
7598
|
+
if [ -n "$pre_rollback_id" ]; then
|
|
7599
|
+
log_info "Saved prior state as ${pre_rollback_id} (undo this rollback with: loki rollback to ${pre_rollback_id})"
|
|
7600
|
+
fi
|
|
7563
7601
|
|
|
7564
|
-
# Restore state files
|
|
7565
|
-
for f in state/orchestrator.json queue/pending.json queue/completed.json queue/in-progress.json queue/current-task.json; do
|
|
7602
|
+
# Restore state files (R6: CONTINUITY.md restores iteration/conversation context)
|
|
7603
|
+
for f in state/orchestrator.json queue/pending.json queue/completed.json queue/in-progress.json queue/current-task.json CONTINUITY.md; do
|
|
7566
7604
|
if [ -f "${cp_dir}/${f}" ]; then
|
|
7567
7605
|
local target_dir=".loki/$(dirname "$f")"
|
|
7568
7606
|
mkdir -p "$target_dir"
|
|
@@ -7599,9 +7637,17 @@ print(json.dumps({'event':'rollback','checkpoint':os.environ['_RB_CPID'],'git_sh
|
|
|
7599
7637
|
|
|
7600
7638
|
log_info "State files restored from checkpoint: ${checkpoint_id}"
|
|
7601
7639
|
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7640
|
+
# R6: the prior hint `git reset --hard ${git_sha}` was MISLEADING. git_sha is
|
|
7641
|
+
# HEAD (the last commit), and Loki does not commit per iteration, so a hard
|
|
7642
|
+
# reset would discard the iteration's work rather than reconstruct it. The
|
|
7643
|
+
# correct, durable recovery is the anchored working-tree snapshot, if present.
|
|
7644
|
+
if [ -f "${cp_dir}/worktree-snapshot.txt" ]; then
|
|
7645
|
+
log_info "To also restore the working tree to this checkpoint:"
|
|
7646
|
+
log_info " git stash apply refs/loki/cp/${checkpoint_id}"
|
|
7647
|
+
elif [ -n "$git_sha" ] && [ "$git_sha" != "no-git" ]; then
|
|
7648
|
+
log_info "Git SHA at checkpoint (last commit): ${git_sha}"
|
|
7649
|
+
log_info "Note: no working-tree snapshot was captured for this checkpoint;"
|
|
7650
|
+
log_info "code changes since the last commit are not restorable from here."
|
|
7605
7651
|
fi
|
|
7606
7652
|
}
|
|
7607
7653
|
|
|
@@ -12001,6 +12047,10 @@ if __name__ == "__main__":
|
|
|
12001
12047
|
|
|
12002
12048
|
# Checkpoint after each iteration (v5.57.0)
|
|
12003
12049
|
create_checkpoint "iteration-${ITERATION_COUNT} complete" "iteration-${ITERATION_COUNT}"
|
|
12050
|
+
# R6: prominent "you can safely undo this" signal so users run boldly.
|
|
12051
|
+
if [ -n "${_LAST_CHECKPOINT_ID:-}" ]; then
|
|
12052
|
+
log_info "Safety net: checkpoint ${_LAST_CHECKPOINT_ID} saved. Undo this iteration with: loki rollback to ${_LAST_CHECKPOINT_ID}"
|
|
12053
|
+
fi
|
|
12004
12054
|
|
|
12005
12055
|
# Quality gates (v6.10.0 - escalation ladder)
|
|
12006
12056
|
log_step "Post-iteration: running quality gates..."
|