loki-mode 5.33.0 → 5.34.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 +3 -2
- package/VERSION +1 -1
- package/autonomy/loki +260 -0
- package/autonomy/run.sh +164 -0
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +143 -0
- package/docs/INSTALLATION.md +10 -11
- package/docs/TOOL-INTEGRATION.md +1 -1
- package/docs/dashboard-guide.md +1 -1
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes PRD to deployed product with zero human intervention. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v5.
|
|
6
|
+
# Loki Mode v5.34.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -149,6 +149,7 @@ GROWTH ──[continuous improvement loop]──> GROWTH
|
|
|
149
149
|
| `.loki/queue/dead-letter.json` | Session start | On task failure (5+ attempts) |
|
|
150
150
|
| `.loki/signals/CONTEXT_CLEAR_REQUESTED` | Never | When context heavy |
|
|
151
151
|
| `.loki/signals/HUMAN_REVIEW_NEEDED` | Never | When human decision required |
|
|
152
|
+
| `.loki/state/checkpoints/` | After task completion | Automatic + manual via `loki checkpoint` |
|
|
152
153
|
|
|
153
154
|
---
|
|
154
155
|
|
|
@@ -259,4 +260,4 @@ The following features are documented in skill modules but not yet fully automat
|
|
|
259
260
|
| Quality gates 3-reviewer system | Planned | Instructions in `skills/quality-gates.md`; not automated |
|
|
260
261
|
| Benchmarks (HumanEval, SWE-bench) | Infrastructure only | Runner scripts and datasets exist in `benchmarks/`; no published results |
|
|
261
262
|
|
|
262
|
-
**v5.
|
|
263
|
+
**v5.34.0 | checkpoint/restore, GitHub Action provider-agnostic | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.
|
|
1
|
+
5.34.0
|
package/autonomy/loki
CHANGED
|
@@ -4032,6 +4032,9 @@ main() {
|
|
|
4032
4032
|
compound)
|
|
4033
4033
|
cmd_compound "$@"
|
|
4034
4034
|
;;
|
|
4035
|
+
checkpoint|cp)
|
|
4036
|
+
cmd_checkpoint "$@"
|
|
4037
|
+
;;
|
|
4035
4038
|
council)
|
|
4036
4039
|
cmd_council "$@"
|
|
4037
4040
|
;;
|
|
@@ -5318,6 +5321,263 @@ COMPOUND_RUN_SCRIPT
|
|
|
5318
5321
|
esac
|
|
5319
5322
|
}
|
|
5320
5323
|
|
|
5324
|
+
# Checkpoint management - save and restore session state (v5.34.0)
|
|
5325
|
+
cmd_checkpoint() {
|
|
5326
|
+
local subcommand="${1:-list}"
|
|
5327
|
+
shift 2>/dev/null || true
|
|
5328
|
+
|
|
5329
|
+
local checkpoints_dir=".loki/state/checkpoints"
|
|
5330
|
+
local index_file="$checkpoints_dir/index.jsonl"
|
|
5331
|
+
|
|
5332
|
+
case "$subcommand" in
|
|
5333
|
+
list|ls)
|
|
5334
|
+
echo -e "${BOLD}Session Checkpoints${NC}"
|
|
5335
|
+
echo ""
|
|
5336
|
+
|
|
5337
|
+
if [ ! -f "$index_file" ]; then
|
|
5338
|
+
echo " No checkpoints yet."
|
|
5339
|
+
echo ""
|
|
5340
|
+
echo " Create one with: loki checkpoint create [message]"
|
|
5341
|
+
return 0
|
|
5342
|
+
fi
|
|
5343
|
+
|
|
5344
|
+
local count=0
|
|
5345
|
+
local lines=()
|
|
5346
|
+
while IFS= read -r line; do
|
|
5347
|
+
lines+=("$line")
|
|
5348
|
+
done < "$index_file"
|
|
5349
|
+
|
|
5350
|
+
# Show last 10 entries (most recent last)
|
|
5351
|
+
local total=${#lines[@]}
|
|
5352
|
+
local start=0
|
|
5353
|
+
if [ "$total" -gt 10 ]; then
|
|
5354
|
+
start=$((total - 10))
|
|
5355
|
+
fi
|
|
5356
|
+
|
|
5357
|
+
printf " ${DIM}%-14s %-20s %-10s %s${NC}\n" "ID" "TIMESTAMP" "GIT SHA" "MESSAGE"
|
|
5358
|
+
|
|
5359
|
+
local i
|
|
5360
|
+
for (( i=start; i<total; i++ )); do
|
|
5361
|
+
local entry="${lines[$i]}"
|
|
5362
|
+
local cp_id=$(echo "$entry" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null)
|
|
5363
|
+
local cp_ts=$(echo "$entry" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('timestamp',''))" 2>/dev/null)
|
|
5364
|
+
local cp_sha=$(echo "$entry" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('git_sha','')[:8])" 2>/dev/null)
|
|
5365
|
+
local cp_msg=$(echo "$entry" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('message','')[:50])" 2>/dev/null)
|
|
5366
|
+
|
|
5367
|
+
if [ -n "$cp_id" ]; then
|
|
5368
|
+
printf " ${GREEN}%-14s${NC} %-20s ${CYAN}%-10s${NC} %s\n" "$cp_id" "$cp_ts" "$cp_sha" "$cp_msg"
|
|
5369
|
+
count=$((count + 1))
|
|
5370
|
+
fi
|
|
5371
|
+
done
|
|
5372
|
+
|
|
5373
|
+
if [ "$count" -eq 0 ]; then
|
|
5374
|
+
echo " No valid checkpoints found."
|
|
5375
|
+
else
|
|
5376
|
+
echo ""
|
|
5377
|
+
echo " Showing ${count} of ${total} checkpoints"
|
|
5378
|
+
fi
|
|
5379
|
+
;;
|
|
5380
|
+
|
|
5381
|
+
create)
|
|
5382
|
+
local message="${*:-manual checkpoint}"
|
|
5383
|
+
|
|
5384
|
+
echo -e "${BOLD}Creating checkpoint...${NC}"
|
|
5385
|
+
echo ""
|
|
5386
|
+
|
|
5387
|
+
# Ensure .loki exists
|
|
5388
|
+
if [ ! -d ".loki" ]; then
|
|
5389
|
+
echo -e "${RED}Error: No .loki directory found. Are you in a Loki project?${NC}"
|
|
5390
|
+
return 1
|
|
5391
|
+
fi
|
|
5392
|
+
|
|
5393
|
+
# Capture git info
|
|
5394
|
+
local git_sha=""
|
|
5395
|
+
local git_branch=""
|
|
5396
|
+
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
5397
|
+
git_sha=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
|
|
5398
|
+
git_branch=$(git branch --show-current 2>/dev/null || echo "unknown")
|
|
5399
|
+
else
|
|
5400
|
+
git_sha="not-a-git-repo"
|
|
5401
|
+
git_branch="none"
|
|
5402
|
+
fi
|
|
5403
|
+
|
|
5404
|
+
# Generate checkpoint ID with timestamp
|
|
5405
|
+
local ts=$(date -u '+%Y%m%d-%H%M%S')
|
|
5406
|
+
local cp_id="cp-${ts}"
|
|
5407
|
+
local cp_dir="$checkpoints_dir/$cp_id"
|
|
5408
|
+
|
|
5409
|
+
# Create checkpoint directory
|
|
5410
|
+
mkdir -p "$cp_dir"
|
|
5411
|
+
|
|
5412
|
+
# Copy state files
|
|
5413
|
+
local copied=0
|
|
5414
|
+
for item in .loki/session.json .loki/dashboard-state.json .loki/queue .loki/memory .loki/metrics .loki/council; do
|
|
5415
|
+
if [ -e "$item" ]; then
|
|
5416
|
+
cp -r "$item" "$cp_dir/" 2>/dev/null && copied=$((copied + 1))
|
|
5417
|
+
fi
|
|
5418
|
+
done
|
|
5419
|
+
|
|
5420
|
+
# Write metadata
|
|
5421
|
+
local iso_ts=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
|
5422
|
+
cat > "$cp_dir/metadata.json" << METADATA_EOF
|
|
5423
|
+
{
|
|
5424
|
+
"id": "$cp_id",
|
|
5425
|
+
"timestamp": "$iso_ts",
|
|
5426
|
+
"git_sha": "$git_sha",
|
|
5427
|
+
"git_branch": "$git_branch",
|
|
5428
|
+
"message": "$message",
|
|
5429
|
+
"files_copied": $copied,
|
|
5430
|
+
"created_by": "loki checkpoint create"
|
|
5431
|
+
}
|
|
5432
|
+
METADATA_EOF
|
|
5433
|
+
|
|
5434
|
+
# Append to index
|
|
5435
|
+
mkdir -p "$checkpoints_dir"
|
|
5436
|
+
echo "{\"id\":\"$cp_id\",\"timestamp\":\"$iso_ts\",\"git_sha\":\"$git_sha\",\"git_branch\":\"$git_branch\",\"message\":\"$message\"}" >> "$index_file"
|
|
5437
|
+
|
|
5438
|
+
echo -e " Checkpoint: ${GREEN}$cp_id${NC}"
|
|
5439
|
+
echo -e " Git SHA: ${CYAN}${git_sha:0:8}${NC} ($git_branch)"
|
|
5440
|
+
echo -e " Message: $message"
|
|
5441
|
+
echo -e " Files: $copied state items copied"
|
|
5442
|
+
echo -e " Location: $cp_dir"
|
|
5443
|
+
echo ""
|
|
5444
|
+
echo " Restore with: loki checkpoint rollback $cp_id"
|
|
5445
|
+
;;
|
|
5446
|
+
|
|
5447
|
+
show)
|
|
5448
|
+
local cp_id="${1:-}"
|
|
5449
|
+
if [ -z "$cp_id" ]; then
|
|
5450
|
+
echo -e "${RED}Error: Specify a checkpoint ID${NC}"
|
|
5451
|
+
echo "Usage: loki checkpoint show <id>"
|
|
5452
|
+
echo "Run 'loki checkpoint list' to see available checkpoints."
|
|
5453
|
+
return 1
|
|
5454
|
+
fi
|
|
5455
|
+
|
|
5456
|
+
local cp_dir="$checkpoints_dir/$cp_id"
|
|
5457
|
+
local metadata="$cp_dir/metadata.json"
|
|
5458
|
+
|
|
5459
|
+
if [ ! -f "$metadata" ]; then
|
|
5460
|
+
echo -e "${RED}Error: Checkpoint not found: $cp_id${NC}"
|
|
5461
|
+
echo "Run 'loki checkpoint list' to see available checkpoints."
|
|
5462
|
+
return 1
|
|
5463
|
+
fi
|
|
5464
|
+
|
|
5465
|
+
echo -e "${BOLD}Checkpoint: $cp_id${NC}"
|
|
5466
|
+
echo ""
|
|
5467
|
+
|
|
5468
|
+
python3 << SHOW_EOF
|
|
5469
|
+
import json, os
|
|
5470
|
+
with open("$metadata", "r") as f:
|
|
5471
|
+
d = json.load(f)
|
|
5472
|
+
print(f" ID: {d.get('id', 'unknown')}")
|
|
5473
|
+
print(f" Timestamp: {d.get('timestamp', 'unknown')}")
|
|
5474
|
+
print(f" Git SHA: {d.get('git_sha', 'unknown')}")
|
|
5475
|
+
print(f" Git Branch: {d.get('git_branch', 'unknown')}")
|
|
5476
|
+
print(f" Message: {d.get('message', 'none')}")
|
|
5477
|
+
print(f" Files: {d.get('files_copied', 0)} state items")
|
|
5478
|
+
print(f" Created By: {d.get('created_by', 'unknown')}")
|
|
5479
|
+
SHOW_EOF
|
|
5480
|
+
|
|
5481
|
+
echo ""
|
|
5482
|
+
echo " Contents:"
|
|
5483
|
+
for item in "$cp_dir"/*; do
|
|
5484
|
+
[ -e "$item" ] || continue
|
|
5485
|
+
local name=$(basename "$item")
|
|
5486
|
+
[ "$name" = "metadata.json" ] && continue
|
|
5487
|
+
if [ -d "$item" ]; then
|
|
5488
|
+
local fcount=$(find "$item" -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
5489
|
+
echo -e " ${DIM}[dir]${NC} $name/ ($fcount files)"
|
|
5490
|
+
else
|
|
5491
|
+
local fsize=$(wc -c < "$item" 2>/dev/null | tr -d ' ')
|
|
5492
|
+
echo -e " ${DIM}[file]${NC} $name (${fsize} bytes)"
|
|
5493
|
+
fi
|
|
5494
|
+
done
|
|
5495
|
+
;;
|
|
5496
|
+
|
|
5497
|
+
rollback)
|
|
5498
|
+
local cp_id="${1:-}"
|
|
5499
|
+
if [ -z "$cp_id" ]; then
|
|
5500
|
+
echo -e "${RED}Error: Specify a checkpoint ID${NC}"
|
|
5501
|
+
echo "Usage: loki checkpoint rollback <id>"
|
|
5502
|
+
echo "Run 'loki checkpoint list' to see available checkpoints."
|
|
5503
|
+
return 1
|
|
5504
|
+
fi
|
|
5505
|
+
|
|
5506
|
+
local cp_dir="$checkpoints_dir/$cp_id"
|
|
5507
|
+
local metadata="$cp_dir/metadata.json"
|
|
5508
|
+
|
|
5509
|
+
if [ ! -f "$metadata" ]; then
|
|
5510
|
+
echo -e "${RED}Error: Checkpoint not found: $cp_id${NC}"
|
|
5511
|
+
echo "Run 'loki checkpoint list' to see available checkpoints."
|
|
5512
|
+
return 1
|
|
5513
|
+
fi
|
|
5514
|
+
|
|
5515
|
+
echo -e "${BOLD}Rolling back to checkpoint: $cp_id${NC}"
|
|
5516
|
+
echo ""
|
|
5517
|
+
|
|
5518
|
+
# Restore state files
|
|
5519
|
+
local restored=0
|
|
5520
|
+
for item in "$cp_dir"/*; do
|
|
5521
|
+
[ -e "$item" ] || continue
|
|
5522
|
+
local name=$(basename "$item")
|
|
5523
|
+
[ "$name" = "metadata.json" ] && continue
|
|
5524
|
+
|
|
5525
|
+
if [ -d "$item" ]; then
|
|
5526
|
+
rm -rf ".loki/$name"
|
|
5527
|
+
cp -r "$item" ".loki/$name" 2>/dev/null && restored=$((restored + 1))
|
|
5528
|
+
else
|
|
5529
|
+
cp "$item" ".loki/$name" 2>/dev/null && restored=$((restored + 1))
|
|
5530
|
+
fi
|
|
5531
|
+
done
|
|
5532
|
+
|
|
5533
|
+
echo -e " Restored: ${GREEN}$restored${NC} state items from $cp_id"
|
|
5534
|
+
|
|
5535
|
+
# Show git info for manual code rollback
|
|
5536
|
+
local cp_sha=$(python3 -c "import json; d=json.load(open('$metadata')); print(d.get('git_sha','unknown'))" 2>/dev/null)
|
|
5537
|
+
if [ -n "$cp_sha" ] && [ "$cp_sha" != "unknown" ] && [ "$cp_sha" != "not-a-git-repo" ]; then
|
|
5538
|
+
echo ""
|
|
5539
|
+
echo -e " ${YELLOW}Note:${NC} Session state has been restored, but code is unchanged."
|
|
5540
|
+
echo " To also roll back code to the checkpoint's git state:"
|
|
5541
|
+
echo ""
|
|
5542
|
+
echo -e " ${DIM}git reset --hard ${cp_sha:0:8}${NC}"
|
|
5543
|
+
echo ""
|
|
5544
|
+
echo -e " ${RED}Warning:${NC} git reset --hard will discard uncommitted changes."
|
|
5545
|
+
echo " Consider 'git stash' first to preserve current work."
|
|
5546
|
+
fi
|
|
5547
|
+
;;
|
|
5548
|
+
|
|
5549
|
+
help|--help|-h)
|
|
5550
|
+
echo -e "${BOLD}loki checkpoint${NC} - Session state checkpoints"
|
|
5551
|
+
echo ""
|
|
5552
|
+
echo "Save and restore session state snapshots during autonomous runs."
|
|
5553
|
+
echo "Checkpoints capture .loki/ state files and record the git SHA"
|
|
5554
|
+
echo "at the time of creation."
|
|
5555
|
+
echo ""
|
|
5556
|
+
echo "Usage: loki checkpoint <command> [args]"
|
|
5557
|
+
echo " loki cp <command> [args]"
|
|
5558
|
+
echo ""
|
|
5559
|
+
echo "Commands:"
|
|
5560
|
+
echo " list List recent checkpoints (default)"
|
|
5561
|
+
echo " create [message] Create a new checkpoint"
|
|
5562
|
+
echo " show <id> Show checkpoint details"
|
|
5563
|
+
echo " rollback <id> Restore state from a checkpoint"
|
|
5564
|
+
echo " help Show this help"
|
|
5565
|
+
echo ""
|
|
5566
|
+
echo "Examples:"
|
|
5567
|
+
echo " loki checkpoint create 'before refactor'"
|
|
5568
|
+
echo " loki cp list"
|
|
5569
|
+
echo " loki cp show cp-20260212-143022"
|
|
5570
|
+
echo " loki cp rollback cp-20260212-143022"
|
|
5571
|
+
;;
|
|
5572
|
+
|
|
5573
|
+
*)
|
|
5574
|
+
echo -e "${RED}Unknown checkpoint command: $subcommand${NC}"
|
|
5575
|
+
echo "Run 'loki checkpoint help' for usage."
|
|
5576
|
+
return 1
|
|
5577
|
+
;;
|
|
5578
|
+
esac
|
|
5579
|
+
}
|
|
5580
|
+
|
|
5321
5581
|
# Completion Council management
|
|
5322
5582
|
cmd_council() {
|
|
5323
5583
|
local subcommand="${1:-status}"
|
package/autonomy/run.sh
CHANGED
|
@@ -3879,6 +3879,167 @@ if top:
|
|
|
3879
3879
|
SOLUTIONS_SCRIPT
|
|
3880
3880
|
}
|
|
3881
3881
|
|
|
3882
|
+
# ============================================================================
|
|
3883
|
+
# Checkpoint/Snapshot System (v5.34.0)
|
|
3884
|
+
# Git-based checkpoints after task completion with state snapshots
|
|
3885
|
+
# Inspired by Cursor Self-Driving Codebases + Entire.io provenance tracking
|
|
3886
|
+
# ============================================================================
|
|
3887
|
+
|
|
3888
|
+
create_checkpoint() {
|
|
3889
|
+
# Create a git checkpoint after task completion
|
|
3890
|
+
# Args: $1 = task description, $2 = task_id (optional)
|
|
3891
|
+
local task_desc="${1:-task completed}"
|
|
3892
|
+
local task_id="${2:-unknown}"
|
|
3893
|
+
local checkpoint_dir=".loki/state/checkpoints"
|
|
3894
|
+
local iteration="${ITERATION_COUNT:-0}"
|
|
3895
|
+
|
|
3896
|
+
mkdir -p "$checkpoint_dir"
|
|
3897
|
+
|
|
3898
|
+
# Only checkpoint if there are uncommitted changes
|
|
3899
|
+
if ! git diff --quiet 2>/dev/null && ! git diff --cached --quiet 2>/dev/null; then
|
|
3900
|
+
log_info "No uncommitted changes to checkpoint"
|
|
3901
|
+
return 0
|
|
3902
|
+
fi
|
|
3903
|
+
|
|
3904
|
+
# Capture git state
|
|
3905
|
+
local git_sha
|
|
3906
|
+
git_sha=$(git rev-parse HEAD 2>/dev/null || echo "no-git")
|
|
3907
|
+
local git_branch
|
|
3908
|
+
git_branch=$(git branch --show-current 2>/dev/null || echo "unknown")
|
|
3909
|
+
|
|
3910
|
+
# Snapshot .loki state files
|
|
3911
|
+
local timestamp
|
|
3912
|
+
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
3913
|
+
local checkpoint_id="cp-${iteration}-$(date +%s)"
|
|
3914
|
+
local cp_dir="${checkpoint_dir}/${checkpoint_id}"
|
|
3915
|
+
|
|
3916
|
+
mkdir -p "$cp_dir"
|
|
3917
|
+
|
|
3918
|
+
# Copy critical state files (lightweight -- not full .loki/)
|
|
3919
|
+
for f in state/orchestrator.json queue/pending.json queue/completed.json queue/in-progress.json queue/current-task.json; do
|
|
3920
|
+
if [ -f ".loki/$f" ]; then
|
|
3921
|
+
local target_dir="$cp_dir/$(dirname "$f")"
|
|
3922
|
+
mkdir -p "$target_dir"
|
|
3923
|
+
cp ".loki/$f" "$cp_dir/$f" 2>/dev/null || true
|
|
3924
|
+
fi
|
|
3925
|
+
done
|
|
3926
|
+
|
|
3927
|
+
# Write checkpoint metadata
|
|
3928
|
+
local safe_desc
|
|
3929
|
+
safe_desc=$(printf '%s' "$task_desc" | sed 's/\\/\\\\/g; s/"/\\"/g' | head -c 200)
|
|
3930
|
+
cat > "$cp_dir/metadata.json" << CPEOF
|
|
3931
|
+
{
|
|
3932
|
+
"id": "${checkpoint_id}",
|
|
3933
|
+
"timestamp": "${timestamp}",
|
|
3934
|
+
"iteration": ${iteration},
|
|
3935
|
+
"task_id": "${task_id}",
|
|
3936
|
+
"task_description": "${safe_desc}",
|
|
3937
|
+
"git_sha": "${git_sha}",
|
|
3938
|
+
"git_branch": "${git_branch}",
|
|
3939
|
+
"provider": "${PROVIDER_NAME:-claude}",
|
|
3940
|
+
"phase": "$(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')"
|
|
3941
|
+
}
|
|
3942
|
+
CPEOF
|
|
3943
|
+
|
|
3944
|
+
# Maintain checkpoint index for fast listing
|
|
3945
|
+
local index_file="${checkpoint_dir}/index.jsonl"
|
|
3946
|
+
printf '{"id":"%s","ts":"%s","iter":%d,"task":"%s","sha":"%s"}\n' \
|
|
3947
|
+
"$checkpoint_id" "$timestamp" "$iteration" "$safe_desc" "$git_sha" \
|
|
3948
|
+
>> "$index_file"
|
|
3949
|
+
|
|
3950
|
+
# Retention: keep last 50 checkpoints, prune older
|
|
3951
|
+
local cp_count
|
|
3952
|
+
cp_count=$(find "$checkpoint_dir" -maxdepth 1 -type d -name "cp-*" 2>/dev/null | wc -l | tr -d ' ')
|
|
3953
|
+
if [ "$cp_count" -gt 50 ]; then
|
|
3954
|
+
local to_remove=$((cp_count - 50))
|
|
3955
|
+
find "$checkpoint_dir" -maxdepth 1 -type d -name "cp-*" 2>/dev/null | sort | head -n "$to_remove" | while read -r old_cp; do
|
|
3956
|
+
rm -r "$old_cp" 2>/dev/null || true
|
|
3957
|
+
done
|
|
3958
|
+
# Rebuild index from remaining checkpoints
|
|
3959
|
+
: > "$index_file"
|
|
3960
|
+
for remaining in "$checkpoint_dir"/cp-*/metadata.json; do
|
|
3961
|
+
[ -f "$remaining" ] || continue
|
|
3962
|
+
python3 -c "
|
|
3963
|
+
import json,sys
|
|
3964
|
+
m=json.load(open('$remaining'))
|
|
3965
|
+
print(json.dumps({'id':m['id'],'ts':m['timestamp'],'iter':m['iteration'],'task':m.get('task_description',''),'sha':m['git_sha']}))
|
|
3966
|
+
" >> "$index_file" 2>/dev/null || true
|
|
3967
|
+
done
|
|
3968
|
+
fi
|
|
3969
|
+
|
|
3970
|
+
log_info "Checkpoint created: ${checkpoint_id} (git: ${git_sha:0:8})"
|
|
3971
|
+
}
|
|
3972
|
+
|
|
3973
|
+
rollback_to_checkpoint() {
|
|
3974
|
+
# Rollback state files to a specific checkpoint
|
|
3975
|
+
# Args: $1 = checkpoint_id
|
|
3976
|
+
local checkpoint_id="$1"
|
|
3977
|
+
local checkpoint_dir=".loki/state/checkpoints"
|
|
3978
|
+
local cp_dir="${checkpoint_dir}/${checkpoint_id}"
|
|
3979
|
+
|
|
3980
|
+
if [ ! -d "$cp_dir" ]; then
|
|
3981
|
+
log_error "Checkpoint not found: ${checkpoint_id}"
|
|
3982
|
+
return 1
|
|
3983
|
+
fi
|
|
3984
|
+
|
|
3985
|
+
# Read checkpoint metadata
|
|
3986
|
+
local git_sha
|
|
3987
|
+
git_sha=$(python3 -c "import json; print(json.load(open('${cp_dir}/metadata.json'))['git_sha'])" 2>/dev/null || echo "")
|
|
3988
|
+
|
|
3989
|
+
log_warn "Rolling back to checkpoint: ${checkpoint_id}"
|
|
3990
|
+
|
|
3991
|
+
# Create a pre-rollback checkpoint first
|
|
3992
|
+
create_checkpoint "pre-rollback snapshot" "rollback"
|
|
3993
|
+
|
|
3994
|
+
# Restore state files
|
|
3995
|
+
for f in state/orchestrator.json queue/pending.json queue/completed.json queue/in-progress.json queue/current-task.json; do
|
|
3996
|
+
if [ -f "${cp_dir}/${f}" ]; then
|
|
3997
|
+
local target_dir=".loki/$(dirname "$f")"
|
|
3998
|
+
mkdir -p "$target_dir"
|
|
3999
|
+
cp "${cp_dir}/${f}" ".loki/${f}" 2>/dev/null || true
|
|
4000
|
+
fi
|
|
4001
|
+
done
|
|
4002
|
+
|
|
4003
|
+
# Log the rollback
|
|
4004
|
+
local timestamp
|
|
4005
|
+
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
4006
|
+
printf '{"event":"rollback","checkpoint":"%s","git_sha":"%s","timestamp":"%s"}\n' \
|
|
4007
|
+
"$checkpoint_id" "$git_sha" "$timestamp" \
|
|
4008
|
+
>> ".loki/events.jsonl" 2>/dev/null || true
|
|
4009
|
+
|
|
4010
|
+
log_info "State files restored from checkpoint: ${checkpoint_id}"
|
|
4011
|
+
|
|
4012
|
+
if [ -n "$git_sha" ] && [ "$git_sha" != "no-git" ]; then
|
|
4013
|
+
log_info "Git SHA at checkpoint: ${git_sha}"
|
|
4014
|
+
log_info "To rollback code: git reset --hard ${git_sha}"
|
|
4015
|
+
fi
|
|
4016
|
+
}
|
|
4017
|
+
|
|
4018
|
+
list_checkpoints() {
|
|
4019
|
+
# List recent checkpoints
|
|
4020
|
+
local checkpoint_dir=".loki/state/checkpoints"
|
|
4021
|
+
local index_file="${checkpoint_dir}/index.jsonl"
|
|
4022
|
+
local limit="${1:-10}"
|
|
4023
|
+
|
|
4024
|
+
if [ ! -f "$index_file" ]; then
|
|
4025
|
+
echo "No checkpoints found."
|
|
4026
|
+
return
|
|
4027
|
+
fi
|
|
4028
|
+
|
|
4029
|
+
tail -n "$limit" "$index_file" | python3 -c "
|
|
4030
|
+
import sys, json
|
|
4031
|
+
lines = sys.stdin.readlines()
|
|
4032
|
+
for line in reversed(lines):
|
|
4033
|
+
try:
|
|
4034
|
+
cp = json.loads(line)
|
|
4035
|
+
sha = cp.get('sha','')[:8]
|
|
4036
|
+
task = cp.get('task','')[:60]
|
|
4037
|
+
print(f\" {cp['id']} {cp['ts']} [{sha}] {task}\")
|
|
4038
|
+
except:
|
|
4039
|
+
continue
|
|
4040
|
+
"
|
|
4041
|
+
}
|
|
4042
|
+
|
|
3882
4043
|
start_dashboard() {
|
|
3883
4044
|
log_header "Starting Loki Dashboard"
|
|
3884
4045
|
|
|
@@ -5795,6 +5956,9 @@ main() {
|
|
|
5795
5956
|
# Compound learnings into structured solution files (v5.30.0)
|
|
5796
5957
|
compound_session_to_solutions
|
|
5797
5958
|
|
|
5959
|
+
# Create session-end checkpoint (v5.34.0)
|
|
5960
|
+
create_checkpoint "session end (iterations=$ITERATION_COUNT)" "session-end"
|
|
5961
|
+
|
|
5798
5962
|
# Log session end for audit
|
|
5799
5963
|
audit_log "SESSION_END" "result=$result,prd=$PRD_PATH"
|
|
5800
5964
|
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -1984,6 +1984,149 @@ async def force_council_review():
|
|
|
1984
1984
|
return {"success": True, "message": "Council review requested"}
|
|
1985
1985
|
|
|
1986
1986
|
|
|
1987
|
+
# =============================================================================
|
|
1988
|
+
# Checkpoint API (v5.34.0)
|
|
1989
|
+
# =============================================================================
|
|
1990
|
+
|
|
1991
|
+
class CheckpointCreate(BaseModel):
|
|
1992
|
+
"""Schema for creating a checkpoint."""
|
|
1993
|
+
message: Optional[str] = Field(None, description="Optional description for the checkpoint")
|
|
1994
|
+
|
|
1995
|
+
|
|
1996
|
+
def _sanitize_checkpoint_id(checkpoint_id: str) -> str:
|
|
1997
|
+
"""Validate checkpoint_id contains only safe characters for file paths."""
|
|
1998
|
+
if not checkpoint_id or ".." in checkpoint_id or not _SAFE_ID_RE.match(checkpoint_id):
|
|
1999
|
+
raise HTTPException(
|
|
2000
|
+
status_code=400,
|
|
2001
|
+
detail="Invalid checkpoint_id: must contain only alphanumeric characters, hyphens, and underscores",
|
|
2002
|
+
)
|
|
2003
|
+
return checkpoint_id
|
|
2004
|
+
|
|
2005
|
+
|
|
2006
|
+
@app.get("/api/checkpoints")
|
|
2007
|
+
async def list_checkpoints(limit: int = Query(default=20, ge=1, le=200)):
|
|
2008
|
+
"""List recent checkpoints from index.jsonl."""
|
|
2009
|
+
loki_dir = _get_loki_dir()
|
|
2010
|
+
index_file = loki_dir / "state" / "checkpoints" / "index.jsonl"
|
|
2011
|
+
checkpoints = []
|
|
2012
|
+
|
|
2013
|
+
if index_file.exists():
|
|
2014
|
+
try:
|
|
2015
|
+
for line in index_file.read_text().strip().split("\n"):
|
|
2016
|
+
if line.strip():
|
|
2017
|
+
try:
|
|
2018
|
+
checkpoints.append(json.loads(line))
|
|
2019
|
+
except json.JSONDecodeError:
|
|
2020
|
+
pass
|
|
2021
|
+
except Exception:
|
|
2022
|
+
pass
|
|
2023
|
+
|
|
2024
|
+
# Return most recent first, limited
|
|
2025
|
+
checkpoints.reverse()
|
|
2026
|
+
return checkpoints[:limit]
|
|
2027
|
+
|
|
2028
|
+
|
|
2029
|
+
@app.get("/api/checkpoints/{checkpoint_id}")
|
|
2030
|
+
async def get_checkpoint(checkpoint_id: str):
|
|
2031
|
+
"""Get checkpoint details by ID."""
|
|
2032
|
+
checkpoint_id = _sanitize_checkpoint_id(checkpoint_id)
|
|
2033
|
+
loki_dir = _get_loki_dir()
|
|
2034
|
+
metadata_file = loki_dir / "state" / "checkpoints" / checkpoint_id / "metadata.json"
|
|
2035
|
+
|
|
2036
|
+
if not metadata_file.exists():
|
|
2037
|
+
raise HTTPException(status_code=404, detail="Checkpoint not found")
|
|
2038
|
+
|
|
2039
|
+
try:
|
|
2040
|
+
return json.loads(metadata_file.read_text())
|
|
2041
|
+
except (json.JSONDecodeError, IOError) as e:
|
|
2042
|
+
raise HTTPException(status_code=500, detail=f"Failed to read checkpoint: {e}")
|
|
2043
|
+
|
|
2044
|
+
|
|
2045
|
+
@app.post("/api/checkpoints", status_code=201)
|
|
2046
|
+
async def create_checkpoint(body: CheckpointCreate = None):
|
|
2047
|
+
"""Create a new checkpoint capturing current state."""
|
|
2048
|
+
import subprocess
|
|
2049
|
+
import shutil
|
|
2050
|
+
|
|
2051
|
+
loki_dir = _get_loki_dir()
|
|
2052
|
+
checkpoints_dir = loki_dir / "state" / "checkpoints"
|
|
2053
|
+
checkpoints_dir.mkdir(parents=True, exist_ok=True)
|
|
2054
|
+
|
|
2055
|
+
# Generate checkpoint ID from timestamp
|
|
2056
|
+
now = datetime.now(timezone.utc)
|
|
2057
|
+
checkpoint_id = now.strftime("chk-%Y%m%d-%H%M%S")
|
|
2058
|
+
|
|
2059
|
+
# Create checkpoint directory
|
|
2060
|
+
checkpoint_dir = checkpoints_dir / checkpoint_id
|
|
2061
|
+
checkpoint_dir.mkdir(parents=True, exist_ok=True)
|
|
2062
|
+
|
|
2063
|
+
# Capture git SHA
|
|
2064
|
+
git_sha = ""
|
|
2065
|
+
try:
|
|
2066
|
+
result = subprocess.run(
|
|
2067
|
+
["git", "rev-parse", "HEAD"],
|
|
2068
|
+
capture_output=True, text=True, timeout=5,
|
|
2069
|
+
)
|
|
2070
|
+
if result.returncode == 0:
|
|
2071
|
+
git_sha = result.stdout.strip()
|
|
2072
|
+
except Exception:
|
|
2073
|
+
pass
|
|
2074
|
+
|
|
2075
|
+
# Copy key state files into checkpoint
|
|
2076
|
+
state_files = [
|
|
2077
|
+
"dashboard-state.json",
|
|
2078
|
+
"session.json",
|
|
2079
|
+
]
|
|
2080
|
+
for fname in state_files:
|
|
2081
|
+
src = loki_dir / fname
|
|
2082
|
+
if src.exists():
|
|
2083
|
+
try:
|
|
2084
|
+
shutil.copy2(str(src), str(checkpoint_dir / fname))
|
|
2085
|
+
except Exception:
|
|
2086
|
+
pass
|
|
2087
|
+
|
|
2088
|
+
# Copy queue directory if present
|
|
2089
|
+
queue_src = loki_dir / "queue"
|
|
2090
|
+
if queue_src.exists():
|
|
2091
|
+
try:
|
|
2092
|
+
shutil.copytree(str(queue_src), str(checkpoint_dir / "queue"), dirs_exist_ok=True)
|
|
2093
|
+
except Exception:
|
|
2094
|
+
pass
|
|
2095
|
+
|
|
2096
|
+
# Build metadata
|
|
2097
|
+
message = ""
|
|
2098
|
+
if body and body.message:
|
|
2099
|
+
message = body.message
|
|
2100
|
+
|
|
2101
|
+
metadata = {
|
|
2102
|
+
"id": checkpoint_id,
|
|
2103
|
+
"created_at": now.isoformat(),
|
|
2104
|
+
"git_sha": git_sha,
|
|
2105
|
+
"message": message,
|
|
2106
|
+
"files": [f.name for f in checkpoint_dir.iterdir() if f.is_file()],
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
# Write metadata.json
|
|
2110
|
+
(checkpoint_dir / "metadata.json").write_text(json.dumps(metadata, indent=2))
|
|
2111
|
+
|
|
2112
|
+
# Append to index.jsonl
|
|
2113
|
+
index_file = checkpoints_dir / "index.jsonl"
|
|
2114
|
+
with open(str(index_file), "a") as f:
|
|
2115
|
+
f.write(json.dumps(metadata) + "\n")
|
|
2116
|
+
|
|
2117
|
+
# Retention policy: keep last 50 checkpoints
|
|
2118
|
+
MAX_CHECKPOINTS = 50
|
|
2119
|
+
all_dirs = sorted(
|
|
2120
|
+
[d for d in checkpoints_dir.iterdir() if d.is_dir()],
|
|
2121
|
+
key=lambda d: d.name,
|
|
2122
|
+
)
|
|
2123
|
+
while len(all_dirs) > MAX_CHECKPOINTS:
|
|
2124
|
+
oldest = all_dirs.pop(0)
|
|
2125
|
+
shutil.rmtree(str(oldest), ignore_errors=True)
|
|
2126
|
+
|
|
2127
|
+
return metadata
|
|
2128
|
+
|
|
2129
|
+
|
|
1987
2130
|
# =============================================================================
|
|
1988
2131
|
# Agent Management API (v5.25.0)
|
|
1989
2132
|
# =============================================================================
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Complete installation instructions for all platforms and use cases.
|
|
4
4
|
|
|
5
|
-
**Version:** v5.
|
|
5
|
+
**Version:** v5.34.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -90,7 +90,7 @@ Open VS Code Settings and search for "loki":
|
|
|
90
90
|
| Setting | Default | Description |
|
|
91
91
|
|---------|---------|-------------|
|
|
92
92
|
| `loki.provider` | `claude` | Default AI provider |
|
|
93
|
-
| `loki.apiPort` | `
|
|
93
|
+
| `loki.apiPort` | `57374` | API server port |
|
|
94
94
|
| `loki.apiHost` | `localhost` | API server host |
|
|
95
95
|
| `loki.autoConnect` | `true` | Auto-connect on activation |
|
|
96
96
|
| `loki.showStatusBar` | `true` | Show status bar item |
|
|
@@ -110,10 +110,10 @@ loki start
|
|
|
110
110
|
./autonomy/run.sh
|
|
111
111
|
|
|
112
112
|
# Option C: Direct API server start
|
|
113
|
-
|
|
113
|
+
loki serve
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
The extension will automatically connect when it detects the server is running at `localhost:
|
|
116
|
+
The extension will automatically connect when it detects the server is running at `localhost:57374`.
|
|
117
117
|
|
|
118
118
|
**Troubleshooting:** If you see "API server is not running" errors, make sure you started the server first using one of the commands above.
|
|
119
119
|
|
|
@@ -579,15 +579,14 @@ Loki Mode uses two network ports for different services:
|
|
|
579
579
|
|
|
580
580
|
| Port | Service | Description |
|
|
581
581
|
|------|---------|-------------|
|
|
582
|
-
| **57374** | Dashboard (FastAPI) |
|
|
583
|
-
| **9898** | REST API Server | JSON API used by the VS Code extension, CLI tools, and programmatic access. Serves endpoints like `/api/status`, `/api/tasks`, `/api/memory`, etc. |
|
|
582
|
+
| **57374** | Dashboard + API (FastAPI) | Unified server serving both the web dashboard UI (real-time monitoring, task board, Completion Council, memory browser, log streaming) and the REST API (used by VS Code extension, CLI tools, programmatic access). Served by `dashboard/server.py`. |
|
|
584
583
|
|
|
585
584
|
### When to Use Which Port
|
|
586
585
|
|
|
587
586
|
- **Browser access** (dashboard, monitoring): Use port **57374** -- `http://localhost:57374`
|
|
588
|
-
- **API calls** (REST, programmatic): Use port **
|
|
589
|
-
- **VS Code extension**: Connects to API on port **
|
|
590
|
-
-
|
|
587
|
+
- **API calls** (REST, programmatic): Use port **57374** -- `http://localhost:57374`
|
|
588
|
+
- **VS Code extension**: Connects to API on port **57374** automatically (configurable via `loki.apiPort` setting)
|
|
589
|
+
- The server is started automatically when you run `loki start` or `./autonomy/run.sh`. No manual configuration is needed.
|
|
591
590
|
|
|
592
591
|
### Port Configuration
|
|
593
592
|
|
|
@@ -595,8 +594,8 @@ Loki Mode uses two network ports for different services:
|
|
|
595
594
|
# Dashboard port (default: 57374)
|
|
596
595
|
LOKI_DASHBOARD_PORT=57374 loki dashboard start
|
|
597
596
|
|
|
598
|
-
# API port (default:
|
|
599
|
-
loki serve --port
|
|
597
|
+
# API port (default: 57374)
|
|
598
|
+
loki serve --port 57374
|
|
600
599
|
```
|
|
601
600
|
|
|
602
601
|
### CORS Configuration
|
package/docs/TOOL-INTEGRATION.md
CHANGED
package/docs/dashboard-guide.md
CHANGED
|
@@ -24,7 +24,7 @@ open http://localhost:57374
|
|
|
24
24
|
|
|
25
25
|
The dashboard automatically syncs with Loki Mode when it's running, polling `dashboard-state.json` every 2 seconds.
|
|
26
26
|
|
|
27
|
-
**Ports:** The dashboard
|
|
27
|
+
**Ports:** The dashboard and API run on unified port **57374** (FastAPI serves both). See [INSTALLATION.md](INSTALLATION.md#ports) for details.
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|