loki-mode 5.33.0 → 5.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/SKILL.md +5 -4
- package/VERSION +1 -1
- package/api/README.md +1 -1
- package/api/server.js +1 -1
- package/api/server.ts +5 -5
- package/api/test.js +1 -1
- package/autonomy/api-server.js +6 -4
- package/autonomy/completion-council.sh +4 -2
- package/autonomy/hooks/store-episode.sh +2 -2
- package/autonomy/loki +323 -33
- package/autonomy/run.sh +708 -0
- package/autonomy/sandbox.sh +3 -9
- package/autonomy/serve.sh +10 -10
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +156 -13
- package/dashboard/static/index.html +359 -58
- package/docs/INSTALLATION.md +13 -14
- package/docs/SYNERGY-ROADMAP.md +1 -1
- package/docs/TOOL-INTEGRATION.md +1 -1
- package/docs/architecture/DASHBOARD_V2_ARCHITECTURE.md +4 -3
- package/docs/dashboard-guide.md +2 -2
- package/memory/layers/index_layer.py +4 -4
- package/memory/layers/timeline_layer.py +5 -5
- package/memory/retrieval.py +10 -2
- package/memory/storage.py +1 -1
- package/memory/token_economics.py +12 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -304,14 +304,14 @@ Last Updated: 2026-01-04 20:45:32
|
|
|
304
304
|
|
|
305
305
|
**Access the dashboard:**
|
|
306
306
|
```bash
|
|
307
|
-
# Automatically
|
|
307
|
+
# Automatically starts when running autonomously
|
|
308
308
|
./autonomy/run.sh ./docs/requirements.md
|
|
309
309
|
|
|
310
310
|
# Or open manually
|
|
311
|
-
open
|
|
311
|
+
open http://localhost:57374
|
|
312
312
|
```
|
|
313
313
|
|
|
314
|
-
|
|
314
|
+
The dashboard at `http://localhost:57374` auto-refreshes via WebSocket. Works with any modern browser.
|
|
315
315
|
|
|
316
316
|
---
|
|
317
317
|
|
|
@@ -488,7 +488,7 @@ graph TB
|
|
|
488
488
|
|
|
489
489
|
---
|
|
490
490
|
|
|
491
|
-
## CLI Commands
|
|
491
|
+
## CLI Commands
|
|
492
492
|
|
|
493
493
|
The `loki` CLI provides easy access to all Loki Mode features:
|
|
494
494
|
|
|
@@ -663,7 +663,7 @@ loki memory retrieve "query" # Test task-aware retrieval
|
|
|
663
663
|
```
|
|
664
664
|
|
|
665
665
|
**API Endpoints:**
|
|
666
|
-
- `GET /api/memory` - Memory summary
|
|
666
|
+
- `GET /api/memory/summary` - Memory summary
|
|
667
667
|
- `POST /api/memory/retrieve` - Query memories
|
|
668
668
|
- `POST /api/memory/consolidate` - Trigger consolidation
|
|
669
669
|
- `GET /api/memory/economics` - Token economics
|
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.35.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
|
|
|
@@ -254,9 +255,9 @@ The following features are documented in skill modules but not yet fully automat
|
|
|
254
255
|
| Feature | Status | Notes |
|
|
255
256
|
|---------|--------|-------|
|
|
256
257
|
| PRE-ACT goal drift detection | Planned | Agent-level attention check before each action; no automated enforcement yet |
|
|
257
|
-
| CONTINUITY.md working memory |
|
|
258
|
+
| CONTINUITY.md working memory | Implemented (v5.35.0) | Auto-managed by run.sh, updated each iteration |
|
|
258
259
|
| GitHub issue import | Planned | Config flags exist (`LOKI_GITHUB_IMPORT`); `gh` CLI integration partial |
|
|
259
|
-
| Quality gates 3-reviewer system |
|
|
260
|
+
| Quality gates 3-reviewer system | Implemented (v5.35.0) | 5 specialist reviewers in `skills/quality-gates.md`; execution in run.sh |
|
|
260
261
|
| Benchmarks (HumanEval, SWE-bench) | Infrastructure only | Runner scripts and datasets exist in `benchmarks/`; no published results |
|
|
261
262
|
|
|
262
|
-
**v5.
|
|
263
|
+
**v5.35.0 | checkpoint/restore, GitHub Action provider-agnostic | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.
|
|
1
|
+
5.35.0
|
package/api/README.md
CHANGED
|
@@ -33,7 +33,7 @@ brew install deno
|
|
|
33
33
|
|
|
34
34
|
| Variable | Default | Description |
|
|
35
35
|
|----------|---------|-------------|
|
|
36
|
-
| `
|
|
36
|
+
| `LOKI_DASHBOARD_PORT` | `57374` | Server port |
|
|
37
37
|
| `LOKI_API_HOST` | `localhost` | Server host |
|
|
38
38
|
| `LOKI_API_TOKEN` | none | API token for remote access |
|
|
39
39
|
| `LOKI_DIR` | auto | Loki installation directory |
|
package/api/server.js
CHANGED
|
@@ -33,7 +33,7 @@ const { EventEmitter } = require('events');
|
|
|
33
33
|
// Configuration
|
|
34
34
|
//=============================================================================
|
|
35
35
|
|
|
36
|
-
const DEFAULT_PORT =
|
|
36
|
+
const DEFAULT_PORT = 57374;
|
|
37
37
|
const DEFAULT_HOST = '127.0.0.1';
|
|
38
38
|
const PROJECT_DIR = process.env.LOKI_PROJECT_DIR || process.cwd();
|
|
39
39
|
|
package/api/server.ts
CHANGED
|
@@ -84,8 +84,8 @@ interface ServerConfig {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
const defaultConfig: ServerConfig = {
|
|
87
|
-
port: parseInt(Deno.env.get("
|
|
88
|
-
host: Deno.env.get("
|
|
87
|
+
port: parseInt(Deno.env.get("LOKI_DASHBOARD_PORT") || "57374", 10),
|
|
88
|
+
host: Deno.env.get("LOKI_DASHBOARD_HOST") || "localhost",
|
|
89
89
|
cors: true,
|
|
90
90
|
auth: true,
|
|
91
91
|
};
|
|
@@ -365,15 +365,15 @@ Usage:
|
|
|
365
365
|
deno run --allow-all api/server.ts [options]
|
|
366
366
|
|
|
367
367
|
Options:
|
|
368
|
-
--port, -p <port> Port to listen on (default:
|
|
368
|
+
--port, -p <port> Port to listen on (default: 57374)
|
|
369
369
|
--host, -h <host> Host to bind to (default: localhost)
|
|
370
370
|
--no-cors Disable CORS
|
|
371
371
|
--no-auth Disable authentication
|
|
372
372
|
--help Show this help message
|
|
373
373
|
|
|
374
374
|
Environment Variables:
|
|
375
|
-
|
|
376
|
-
|
|
375
|
+
LOKI_DASHBOARD_PORT Port (overridden by --port)
|
|
376
|
+
LOKI_DASHBOARD_HOST Host (overridden by --host)
|
|
377
377
|
LOKI_API_TOKEN API token for remote access
|
|
378
378
|
LOKI_DIR Loki installation directory
|
|
379
379
|
LOKI_VERSION Version string
|
package/api/test.js
CHANGED
|
@@ -34,7 +34,7 @@ const fs = require('fs');
|
|
|
34
34
|
// Test Configuration
|
|
35
35
|
//=============================================================================
|
|
36
36
|
|
|
37
|
-
const PORT =
|
|
37
|
+
const PORT = 67374; // Use a different port for testing (57374 + 10000)
|
|
38
38
|
const HOST = '127.0.0.1';
|
|
39
39
|
const BASE_URL = `http://${HOST}:${PORT}`;
|
|
40
40
|
const SERVER_PATH = path.join(__dirname, 'server.js');
|
package/autonomy/api-server.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Loki Mode HTTP API Server (v1.2.0)
|
|
3
|
+
* Loki Mode HTTP API Server (v1.2.0) - LEGACY
|
|
4
|
+
* NOTE: The production API is now the unified FastAPI server at dashboard/server.py (port 57374)
|
|
5
|
+
* This file is kept for backward compatibility only.
|
|
4
6
|
* Zero npm dependencies - uses only Node.js built-ins
|
|
5
7
|
*
|
|
6
8
|
* Usage:
|
|
7
|
-
* node autonomy/api-server.js [--port
|
|
8
|
-
*
|
|
9
|
+
* node autonomy/api-server.js [--port 57374]
|
|
10
|
+
* LOKI_DASHBOARD_PORT=57374 node autonomy/api-server.js
|
|
9
11
|
* loki api start
|
|
10
12
|
*
|
|
11
13
|
* Endpoints:
|
|
@@ -69,7 +71,7 @@ function parseArgs() {
|
|
|
69
71
|
const cliArgs = parseArgs();
|
|
70
72
|
|
|
71
73
|
// Configuration
|
|
72
|
-
const PORT = cliArgs.port || parseInt(process.env.
|
|
74
|
+
const PORT = cliArgs.port || parseInt(process.env.LOKI_DASHBOARD_PORT || '57374');
|
|
73
75
|
const MAX_BODY_SIZE = parseInt(process.env.LOKI_API_MAX_BODY || '1048576'); // 1MB default
|
|
74
76
|
const LOKI_DIR = process.env.LOKI_DIR || path.join(process.cwd(), '.loki');
|
|
75
77
|
const STATE_DIR = path.join(LOKI_DIR, 'state');
|
|
@@ -823,6 +823,7 @@ council_aggregate_votes() {
|
|
|
823
823
|
_THRESHOLD="$threshold" \
|
|
824
824
|
_VERDICT="$verdict" \
|
|
825
825
|
_VOTES="$votes_json" \
|
|
826
|
+
_ROUND_FILE="$round_file" \
|
|
826
827
|
python3 -c "
|
|
827
828
|
import json, os
|
|
828
829
|
from datetime import datetime, timezone
|
|
@@ -836,7 +837,7 @@ round_data = {
|
|
|
836
837
|
'verdict': os.environ['_VERDICT'],
|
|
837
838
|
'votes': json.loads(os.environ['_VOTES'])
|
|
838
839
|
}
|
|
839
|
-
with open('
|
|
840
|
+
with open(os.environ['_ROUND_FILE'], 'w') as f:
|
|
840
841
|
json.dump(round_data, f, indent=2)
|
|
841
842
|
" || log_warn "Failed to write round vote file"
|
|
842
843
|
|
|
@@ -926,6 +927,7 @@ council_devils_advocate_review() {
|
|
|
926
927
|
_ROUND="$round" \
|
|
927
928
|
_ISSUES="$issues_found" \
|
|
928
929
|
_DETAILS="${issue_details:-none}" \
|
|
930
|
+
_DA_FILE="$da_file" \
|
|
929
931
|
python3 -c "
|
|
930
932
|
import json, os
|
|
931
933
|
from datetime import datetime, timezone
|
|
@@ -936,7 +938,7 @@ da_result = {
|
|
|
936
938
|
'details': os.environ['_DETAILS'],
|
|
937
939
|
'override': int(os.environ['_ISSUES']) > 0
|
|
938
940
|
}
|
|
939
|
-
with open('
|
|
941
|
+
with open(os.environ['_DA_FILE'], 'w') as f:
|
|
940
942
|
json.dump(da_result, f, indent=2)
|
|
941
943
|
" || log_warn "Failed to write devil's advocate result"
|
|
942
944
|
|
|
@@ -21,7 +21,7 @@ sys.path.insert(0, cwd)
|
|
|
21
21
|
try:
|
|
22
22
|
from memory.engine import MemoryEngine
|
|
23
23
|
from memory.schemas import EpisodeTrace
|
|
24
|
-
from datetime import datetime
|
|
24
|
+
from datetime import datetime, timezone
|
|
25
25
|
import json
|
|
26
26
|
|
|
27
27
|
engine = MemoryEngine(os.path.join(cwd, '.loki/memory'))
|
|
@@ -30,7 +30,7 @@ try:
|
|
|
30
30
|
episode = EpisodeTrace(
|
|
31
31
|
id=session_id,
|
|
32
32
|
task_id=f'session-{session_id}',
|
|
33
|
-
timestamp=datetime.
|
|
33
|
+
timestamp=datetime.now(timezone.utc),
|
|
34
34
|
duration_seconds=0,
|
|
35
35
|
agent='loki-mode',
|
|
36
36
|
phase='session',
|
package/autonomy/loki
CHANGED
|
@@ -310,8 +310,8 @@ show_help() {
|
|
|
310
310
|
echo " logs Show recent log output"
|
|
311
311
|
echo " dashboard [cmd] Dashboard server (start|stop|status|url|open)"
|
|
312
312
|
echo " provider [cmd] Manage AI provider (show|set|list|info)"
|
|
313
|
-
echo " serve Start
|
|
314
|
-
echo " api [cmd]
|
|
313
|
+
echo " serve Start dashboard/API server (alias for api start)"
|
|
314
|
+
echo " api [cmd] Dashboard/API server (start|stop|status)"
|
|
315
315
|
echo " sandbox [cmd] Docker sandbox (start|stop|status|logs|shell|build)"
|
|
316
316
|
echo " notify [cmd] Send notifications (test|slack|discord|webhook|status)"
|
|
317
317
|
echo " voice [cmd] Voice input for PRD creation (status|listen|dictate|speak|start)"
|
|
@@ -2762,18 +2762,17 @@ cmd_logs() {
|
|
|
2762
2762
|
tail -n "$lines" "$log_file"
|
|
2763
2763
|
}
|
|
2764
2764
|
|
|
2765
|
-
# API server management
|
|
2765
|
+
# API server management (delegates to unified FastAPI dashboard server)
|
|
2766
2766
|
cmd_api() {
|
|
2767
2767
|
local subcommand="${1:-help}"
|
|
2768
|
-
local port="${
|
|
2769
|
-
local pid_file="$LOKI_DIR/
|
|
2770
|
-
local api_server="$SKILL_DIR/api/server.js"
|
|
2768
|
+
local port="${LOKI_DASHBOARD_PORT:-57374}"
|
|
2769
|
+
local pid_file="$LOKI_DIR/dashboard/dashboard.pid"
|
|
2771
2770
|
|
|
2772
2771
|
case "$subcommand" in
|
|
2773
2772
|
start)
|
|
2774
|
-
# Check if
|
|
2775
|
-
if ! command -v
|
|
2776
|
-
echo -e "${RED}Error:
|
|
2773
|
+
# Check if Python is available
|
|
2774
|
+
if ! command -v python3 &> /dev/null; then
|
|
2775
|
+
echo -e "${RED}Error: Python 3 not found. Install with: brew install python3${NC}"
|
|
2777
2776
|
exit 1
|
|
2778
2777
|
fi
|
|
2779
2778
|
|
|
@@ -2781,19 +2780,19 @@ cmd_api() {
|
|
|
2781
2780
|
if [ -f "$pid_file" ]; then
|
|
2782
2781
|
local existing_pid=$(cat "$pid_file")
|
|
2783
2782
|
if kill -0 "$existing_pid" 2>/dev/null; then
|
|
2784
|
-
echo -e "${YELLOW}
|
|
2783
|
+
echo -e "${YELLOW}Dashboard server already running (PID: $existing_pid)${NC}"
|
|
2785
2784
|
echo "URL: http://localhost:$port"
|
|
2786
2785
|
exit 0
|
|
2787
2786
|
fi
|
|
2788
2787
|
fi
|
|
2789
2788
|
|
|
2790
2789
|
# Start server
|
|
2791
|
-
mkdir -p "$LOKI_DIR/logs"
|
|
2792
|
-
|
|
2790
|
+
mkdir -p "$LOKI_DIR/logs" "$LOKI_DIR/dashboard"
|
|
2791
|
+
LOKI_DIR="$LOKI_DIR" nohup python3 -m uvicorn dashboard.server:app --host 0.0.0.0 --port "$port" > "$LOKI_DIR/logs/api.log" 2>&1 &
|
|
2793
2792
|
local new_pid=$!
|
|
2794
2793
|
echo "$new_pid" > "$pid_file"
|
|
2795
2794
|
|
|
2796
|
-
echo -e "${GREEN}
|
|
2795
|
+
echo -e "${GREEN}Dashboard server started${NC}"
|
|
2797
2796
|
echo " PID: $new_pid"
|
|
2798
2797
|
echo " URL: http://localhost:$port"
|
|
2799
2798
|
echo " Logs: $LOKI_DIR/logs/api.log"
|
|
@@ -2805,13 +2804,13 @@ cmd_api() {
|
|
|
2805
2804
|
if kill -0 "$pid" 2>/dev/null; then
|
|
2806
2805
|
kill "$pid"
|
|
2807
2806
|
rm -f "$pid_file"
|
|
2808
|
-
echo -e "${GREEN}
|
|
2807
|
+
echo -e "${GREEN}Dashboard server stopped${NC}"
|
|
2809
2808
|
else
|
|
2810
2809
|
rm -f "$pid_file"
|
|
2811
|
-
echo -e "${YELLOW}
|
|
2810
|
+
echo -e "${YELLOW}Dashboard server was not running${NC}"
|
|
2812
2811
|
fi
|
|
2813
2812
|
else
|
|
2814
|
-
echo -e "${YELLOW}No
|
|
2813
|
+
echo -e "${YELLOW}No dashboard server PID file found${NC}"
|
|
2815
2814
|
fi
|
|
2816
2815
|
;;
|
|
2817
2816
|
|
|
@@ -2819,7 +2818,7 @@ cmd_api() {
|
|
|
2819
2818
|
if [ -f "$pid_file" ]; then
|
|
2820
2819
|
local pid=$(cat "$pid_file")
|
|
2821
2820
|
if kill -0 "$pid" 2>/dev/null; then
|
|
2822
|
-
echo -e "${GREEN}
|
|
2821
|
+
echo -e "${GREEN}Dashboard server running${NC}"
|
|
2823
2822
|
echo " PID: $pid"
|
|
2824
2823
|
echo " URL: http://localhost:$port"
|
|
2825
2824
|
|
|
@@ -2827,39 +2826,39 @@ cmd_api() {
|
|
|
2827
2826
|
if command -v curl &> /dev/null; then
|
|
2828
2827
|
echo ""
|
|
2829
2828
|
echo -e "${CYAN}Status:${NC}"
|
|
2830
|
-
curl -s "http://localhost:$port/status" 2>/dev/null | jq . 2>/dev/null || true
|
|
2829
|
+
curl -s "http://localhost:$port/api/status" 2>/dev/null | jq . 2>/dev/null || true
|
|
2831
2830
|
fi
|
|
2832
2831
|
else
|
|
2833
|
-
echo -e "${YELLOW}
|
|
2832
|
+
echo -e "${YELLOW}Dashboard server not running (stale PID file)${NC}"
|
|
2834
2833
|
rm -f "$pid_file"
|
|
2835
2834
|
fi
|
|
2836
2835
|
else
|
|
2837
|
-
echo -e "${YELLOW}
|
|
2836
|
+
echo -e "${YELLOW}Dashboard server not running${NC}"
|
|
2838
2837
|
fi
|
|
2839
2838
|
;;
|
|
2840
2839
|
|
|
2841
2840
|
*)
|
|
2842
|
-
echo -e "${BOLD}Loki Mode API Server${NC}"
|
|
2841
|
+
echo -e "${BOLD}Loki Mode Dashboard/API Server${NC}"
|
|
2843
2842
|
echo ""
|
|
2844
2843
|
echo "Usage: loki api <command>"
|
|
2845
2844
|
echo ""
|
|
2846
2845
|
echo "Commands:"
|
|
2847
|
-
echo " start Start the
|
|
2848
|
-
echo " stop Stop the
|
|
2849
|
-
echo " status Check if
|
|
2846
|
+
echo " start Start the unified FastAPI dashboard server"
|
|
2847
|
+
echo " stop Stop the dashboard server"
|
|
2848
|
+
echo " status Check if dashboard server is running"
|
|
2850
2849
|
echo ""
|
|
2851
2850
|
echo "Environment:"
|
|
2852
|
-
echo "
|
|
2851
|
+
echo " LOKI_DASHBOARD_PORT Port to listen on (default: 57374)"
|
|
2853
2852
|
echo ""
|
|
2854
2853
|
echo "Endpoints:"
|
|
2855
|
-
echo " GET /health - Health check"
|
|
2856
|
-
echo " GET /status - Session status"
|
|
2857
|
-
echo " GET /events - SSE stream"
|
|
2858
|
-
echo " GET /logs - Recent logs"
|
|
2859
|
-
echo " POST /start - Start session"
|
|
2860
|
-
echo " POST /stop - Stop session"
|
|
2861
|
-
echo " POST /pause - Pause session"
|
|
2862
|
-
echo " POST /resume - Resume session"
|
|
2854
|
+
echo " GET /api/health - Health check"
|
|
2855
|
+
echo " GET /api/status - Session status"
|
|
2856
|
+
echo " GET /api/events - SSE stream"
|
|
2857
|
+
echo " GET /api/logs - Recent logs"
|
|
2858
|
+
echo " POST /api/start - Start session"
|
|
2859
|
+
echo " POST /api/stop - Stop session"
|
|
2860
|
+
echo " POST /api/pause - Pause session"
|
|
2861
|
+
echo " POST /api/resume - Resume session"
|
|
2863
2862
|
;;
|
|
2864
2863
|
esac
|
|
2865
2864
|
}
|
|
@@ -4032,6 +4031,9 @@ main() {
|
|
|
4032
4031
|
compound)
|
|
4033
4032
|
cmd_compound "$@"
|
|
4034
4033
|
;;
|
|
4034
|
+
checkpoint|cp)
|
|
4035
|
+
cmd_checkpoint "$@"
|
|
4036
|
+
;;
|
|
4035
4037
|
council)
|
|
4036
4038
|
cmd_council "$@"
|
|
4037
4039
|
;;
|
|
@@ -5318,6 +5320,294 @@ COMPOUND_RUN_SCRIPT
|
|
|
5318
5320
|
esac
|
|
5319
5321
|
}
|
|
5320
5322
|
|
|
5323
|
+
# Checkpoint management - save and restore session state (v5.34.0)
|
|
5324
|
+
cmd_checkpoint() {
|
|
5325
|
+
local subcommand="${1:-list}"
|
|
5326
|
+
shift 2>/dev/null || true
|
|
5327
|
+
|
|
5328
|
+
local checkpoints_dir=".loki/state/checkpoints"
|
|
5329
|
+
local index_file="$checkpoints_dir/index.jsonl"
|
|
5330
|
+
|
|
5331
|
+
case "$subcommand" in
|
|
5332
|
+
list|ls)
|
|
5333
|
+
echo -e "${BOLD}Session Checkpoints${NC}"
|
|
5334
|
+
echo ""
|
|
5335
|
+
|
|
5336
|
+
if [ ! -f "$index_file" ]; then
|
|
5337
|
+
echo " No checkpoints yet."
|
|
5338
|
+
echo ""
|
|
5339
|
+
echo " Create one with: loki checkpoint create [message]"
|
|
5340
|
+
return 0
|
|
5341
|
+
fi
|
|
5342
|
+
|
|
5343
|
+
local count=0
|
|
5344
|
+
local lines=()
|
|
5345
|
+
while IFS= read -r line; do
|
|
5346
|
+
lines+=("$line")
|
|
5347
|
+
done < "$index_file"
|
|
5348
|
+
|
|
5349
|
+
# Show last 10 entries (most recent last)
|
|
5350
|
+
local total=${#lines[@]}
|
|
5351
|
+
local start=0
|
|
5352
|
+
if [ "$total" -gt 10 ]; then
|
|
5353
|
+
start=$((total - 10))
|
|
5354
|
+
fi
|
|
5355
|
+
|
|
5356
|
+
printf " ${DIM}%-14s %-20s %-10s %s${NC}\n" "ID" "TIMESTAMP" "GIT SHA" "MESSAGE"
|
|
5357
|
+
|
|
5358
|
+
local i
|
|
5359
|
+
for (( i=start; i<total; i++ )); do
|
|
5360
|
+
local entry="${lines[$i]}"
|
|
5361
|
+
local cp_id=$(echo "$entry" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null)
|
|
5362
|
+
local cp_ts=$(echo "$entry" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('timestamp','') or d.get('ts',''))" 2>/dev/null)
|
|
5363
|
+
local cp_sha=$(echo "$entry" | python3 -c "import sys,json; d=json.load(sys.stdin); print((d.get('git_sha','') or d.get('sha',''))[:8])" 2>/dev/null)
|
|
5364
|
+
local cp_msg=$(echo "$entry" | python3 -c "import sys,json; d=json.load(sys.stdin); print((d.get('message','') or d.get('task',''))[:50])" 2>/dev/null)
|
|
5365
|
+
|
|
5366
|
+
if [ -n "$cp_id" ]; then
|
|
5367
|
+
printf " ${GREEN}%-14s${NC} %-20s ${CYAN}%-10s${NC} %s\n" "$cp_id" "$cp_ts" "$cp_sha" "$cp_msg"
|
|
5368
|
+
count=$((count + 1))
|
|
5369
|
+
fi
|
|
5370
|
+
done
|
|
5371
|
+
|
|
5372
|
+
if [ "$count" -eq 0 ]; then
|
|
5373
|
+
echo " No valid checkpoints found."
|
|
5374
|
+
else
|
|
5375
|
+
echo ""
|
|
5376
|
+
echo " Showing ${count} of ${total} checkpoints"
|
|
5377
|
+
fi
|
|
5378
|
+
;;
|
|
5379
|
+
|
|
5380
|
+
create)
|
|
5381
|
+
local raw_message="${*:-manual checkpoint}"
|
|
5382
|
+
# Escape backslashes and double quotes for JSON safety
|
|
5383
|
+
local message
|
|
5384
|
+
message=$(printf '%s' "$raw_message" | sed 's/\\/\\\\/g; s/"/\\"/g' | head -c 200)
|
|
5385
|
+
|
|
5386
|
+
echo -e "${BOLD}Creating checkpoint...${NC}"
|
|
5387
|
+
echo ""
|
|
5388
|
+
|
|
5389
|
+
# Ensure .loki exists
|
|
5390
|
+
if [ ! -d ".loki" ]; then
|
|
5391
|
+
echo -e "${RED}Error: No .loki directory found. Are you in a Loki project?${NC}"
|
|
5392
|
+
return 1
|
|
5393
|
+
fi
|
|
5394
|
+
|
|
5395
|
+
# Capture git info
|
|
5396
|
+
local git_sha=""
|
|
5397
|
+
local git_branch=""
|
|
5398
|
+
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
5399
|
+
git_sha=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
|
|
5400
|
+
git_branch=$(git branch --show-current 2>/dev/null || echo "unknown")
|
|
5401
|
+
else
|
|
5402
|
+
git_sha="not-a-git-repo"
|
|
5403
|
+
git_branch="none"
|
|
5404
|
+
fi
|
|
5405
|
+
|
|
5406
|
+
# Generate checkpoint ID with timestamp
|
|
5407
|
+
local ts=$(date -u '+%Y%m%d-%H%M%S')
|
|
5408
|
+
local cp_id="cp-${ts}"
|
|
5409
|
+
local cp_dir="$checkpoints_dir/$cp_id"
|
|
5410
|
+
|
|
5411
|
+
# Create checkpoint directory
|
|
5412
|
+
mkdir -p "$cp_dir"
|
|
5413
|
+
|
|
5414
|
+
# Copy state files
|
|
5415
|
+
local copied=0
|
|
5416
|
+
for item in .loki/session.json .loki/dashboard-state.json .loki/queue .loki/memory .loki/metrics .loki/council; do
|
|
5417
|
+
if [ -e "$item" ]; then
|
|
5418
|
+
cp -r "$item" "$cp_dir/" 2>/dev/null && copied=$((copied + 1))
|
|
5419
|
+
fi
|
|
5420
|
+
done
|
|
5421
|
+
|
|
5422
|
+
# Write metadata (use python3 json.dumps for safe serialization)
|
|
5423
|
+
local iso_ts=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
|
5424
|
+
# Truncate message to 500 chars to prevent abuse
|
|
5425
|
+
local safe_message="${message:0:500}"
|
|
5426
|
+
_CP_ID="$cp_id" _CP_TS="$iso_ts" _CP_SHA="$git_sha" _CP_BRANCH="$git_branch" \
|
|
5427
|
+
_CP_MSG="$safe_message" _CP_FILES="$copied" _CP_DIR="$cp_dir" _CP_INDEX="$index_file" \
|
|
5428
|
+
_CP_CHKDIR="$checkpoints_dir" python3 << 'WRITE_META_EOF'
|
|
5429
|
+
import json, os
|
|
5430
|
+
metadata = {
|
|
5431
|
+
"id": os.environ["_CP_ID"],
|
|
5432
|
+
"timestamp": os.environ["_CP_TS"],
|
|
5433
|
+
"git_sha": os.environ["_CP_SHA"],
|
|
5434
|
+
"git_branch": os.environ["_CP_BRANCH"],
|
|
5435
|
+
"message": os.environ["_CP_MSG"],
|
|
5436
|
+
"files_copied": int(os.environ["_CP_FILES"]),
|
|
5437
|
+
"created_by": "loki checkpoint create"
|
|
5438
|
+
}
|
|
5439
|
+
cp_dir = os.environ["_CP_DIR"]
|
|
5440
|
+
os.makedirs(cp_dir, exist_ok=True)
|
|
5441
|
+
with open(os.path.join(cp_dir, "metadata.json"), "w") as f:
|
|
5442
|
+
json.dump(metadata, f, indent=4)
|
|
5443
|
+
index_file = os.environ["_CP_INDEX"]
|
|
5444
|
+
os.makedirs(os.environ["_CP_CHKDIR"], exist_ok=True)
|
|
5445
|
+
with open(index_file, "a") as f:
|
|
5446
|
+
index_entry = {
|
|
5447
|
+
"id": metadata["id"],
|
|
5448
|
+
"timestamp": metadata["timestamp"],
|
|
5449
|
+
"git_sha": metadata["git_sha"],
|
|
5450
|
+
"git_branch": metadata["git_branch"],
|
|
5451
|
+
"message": metadata["message"],
|
|
5452
|
+
}
|
|
5453
|
+
f.write(json.dumps(index_entry) + "\n")
|
|
5454
|
+
WRITE_META_EOF
|
|
5455
|
+
|
|
5456
|
+
echo -e " Checkpoint: ${GREEN}$cp_id${NC}"
|
|
5457
|
+
echo -e " Git SHA: ${CYAN}${git_sha:0:8}${NC} ($git_branch)"
|
|
5458
|
+
echo -e " Message: $message"
|
|
5459
|
+
echo -e " Files: $copied state items copied"
|
|
5460
|
+
echo -e " Location: $cp_dir"
|
|
5461
|
+
echo ""
|
|
5462
|
+
echo " Restore with: loki checkpoint rollback $cp_id"
|
|
5463
|
+
;;
|
|
5464
|
+
|
|
5465
|
+
show)
|
|
5466
|
+
local cp_id="${1:-}"
|
|
5467
|
+
if [ -z "$cp_id" ]; then
|
|
5468
|
+
echo -e "${RED}Error: Specify a checkpoint ID${NC}"
|
|
5469
|
+
echo "Usage: loki checkpoint show <id>"
|
|
5470
|
+
echo "Run 'loki checkpoint list' to see available checkpoints."
|
|
5471
|
+
return 1
|
|
5472
|
+
fi
|
|
5473
|
+
|
|
5474
|
+
# Validate checkpoint ID (prevent path traversal)
|
|
5475
|
+
if [[ ! "$cp_id" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
|
5476
|
+
echo -e "${RED}Error: Invalid checkpoint ID (must be alphanumeric, hyphens, underscores only)${NC}"
|
|
5477
|
+
return 1
|
|
5478
|
+
fi
|
|
5479
|
+
|
|
5480
|
+
local cp_dir="$checkpoints_dir/$cp_id"
|
|
5481
|
+
local metadata="$cp_dir/metadata.json"
|
|
5482
|
+
|
|
5483
|
+
if [ ! -f "$metadata" ]; then
|
|
5484
|
+
echo -e "${RED}Error: Checkpoint not found: $cp_id${NC}"
|
|
5485
|
+
echo "Run 'loki checkpoint list' to see available checkpoints."
|
|
5486
|
+
return 1
|
|
5487
|
+
fi
|
|
5488
|
+
|
|
5489
|
+
echo -e "${BOLD}Checkpoint: $cp_id${NC}"
|
|
5490
|
+
echo ""
|
|
5491
|
+
|
|
5492
|
+
_CP_METADATA="$metadata" python3 << 'SHOW_EOF'
|
|
5493
|
+
import json, os
|
|
5494
|
+
with open(os.environ["_CP_METADATA"], "r") as f:
|
|
5495
|
+
d = json.load(f)
|
|
5496
|
+
print(f" ID: {d.get('id', 'unknown')}")
|
|
5497
|
+
print(f" Timestamp: {d.get('timestamp', 'unknown')}")
|
|
5498
|
+
print(f" Git SHA: {d.get('git_sha', 'unknown')}")
|
|
5499
|
+
print(f" Git Branch: {d.get('git_branch', 'unknown')}")
|
|
5500
|
+
print(f" Message: {d.get('message', 'none')}")
|
|
5501
|
+
print(f" Files: {d.get('files_copied', 0)} state items")
|
|
5502
|
+
print(f" Created By: {d.get('created_by', 'unknown')}")
|
|
5503
|
+
SHOW_EOF
|
|
5504
|
+
|
|
5505
|
+
echo ""
|
|
5506
|
+
echo " Contents:"
|
|
5507
|
+
for item in "$cp_dir"/*; do
|
|
5508
|
+
[ -e "$item" ] || continue
|
|
5509
|
+
local name=$(basename "$item")
|
|
5510
|
+
[ "$name" = "metadata.json" ] && continue
|
|
5511
|
+
if [ -d "$item" ]; then
|
|
5512
|
+
local fcount=$(find "$item" -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
5513
|
+
echo -e " ${DIM}[dir]${NC} $name/ ($fcount files)"
|
|
5514
|
+
else
|
|
5515
|
+
local fsize=$(wc -c < "$item" 2>/dev/null | tr -d ' ')
|
|
5516
|
+
echo -e " ${DIM}[file]${NC} $name (${fsize} bytes)"
|
|
5517
|
+
fi
|
|
5518
|
+
done
|
|
5519
|
+
;;
|
|
5520
|
+
|
|
5521
|
+
rollback)
|
|
5522
|
+
local cp_id="${1:-}"
|
|
5523
|
+
if [ -z "$cp_id" ]; then
|
|
5524
|
+
echo -e "${RED}Error: Specify a checkpoint ID${NC}"
|
|
5525
|
+
echo "Usage: loki checkpoint rollback <id>"
|
|
5526
|
+
echo "Run 'loki checkpoint list' to see available checkpoints."
|
|
5527
|
+
return 1
|
|
5528
|
+
fi
|
|
5529
|
+
|
|
5530
|
+
# Validate checkpoint ID (prevent path traversal)
|
|
5531
|
+
if [[ ! "$cp_id" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
|
5532
|
+
echo -e "${RED}Error: Invalid checkpoint ID (must be alphanumeric, hyphens, underscores only)${NC}"
|
|
5533
|
+
return 1
|
|
5534
|
+
fi
|
|
5535
|
+
|
|
5536
|
+
local cp_dir="$checkpoints_dir/$cp_id"
|
|
5537
|
+
local metadata="$cp_dir/metadata.json"
|
|
5538
|
+
|
|
5539
|
+
if [ ! -f "$metadata" ]; then
|
|
5540
|
+
echo -e "${RED}Error: Checkpoint not found: $cp_id${NC}"
|
|
5541
|
+
echo "Run 'loki checkpoint list' to see available checkpoints."
|
|
5542
|
+
return 1
|
|
5543
|
+
fi
|
|
5544
|
+
|
|
5545
|
+
echo -e "${BOLD}Rolling back to checkpoint: $cp_id${NC}"
|
|
5546
|
+
echo ""
|
|
5547
|
+
|
|
5548
|
+
# Restore state files
|
|
5549
|
+
local restored=0
|
|
5550
|
+
for item in "$cp_dir"/*; do
|
|
5551
|
+
[ -e "$item" ] || continue
|
|
5552
|
+
local name=$(basename "$item")
|
|
5553
|
+
[ "$name" = "metadata.json" ] && continue
|
|
5554
|
+
|
|
5555
|
+
if [ -d "$item" ]; then
|
|
5556
|
+
rm -rf ".loki/$name"
|
|
5557
|
+
cp -r "$item" ".loki/$name" 2>/dev/null && restored=$((restored + 1))
|
|
5558
|
+
else
|
|
5559
|
+
cp "$item" ".loki/$name" 2>/dev/null && restored=$((restored + 1))
|
|
5560
|
+
fi
|
|
5561
|
+
done
|
|
5562
|
+
|
|
5563
|
+
echo -e " Restored: ${GREEN}$restored${NC} state items from $cp_id"
|
|
5564
|
+
|
|
5565
|
+
# Show git info for manual code rollback
|
|
5566
|
+
local 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)
|
|
5567
|
+
if [ -n "$cp_sha" ] && [ "$cp_sha" != "unknown" ] && [ "$cp_sha" != "not-a-git-repo" ]; then
|
|
5568
|
+
echo ""
|
|
5569
|
+
echo -e " ${YELLOW}Note:${NC} Session state has been restored, but code is unchanged."
|
|
5570
|
+
echo " To also roll back code to the checkpoint's git state:"
|
|
5571
|
+
echo ""
|
|
5572
|
+
echo -e " ${DIM}git reset --hard ${cp_sha:0:8}${NC}"
|
|
5573
|
+
echo ""
|
|
5574
|
+
echo -e " ${RED}Warning:${NC} git reset --hard will discard uncommitted changes."
|
|
5575
|
+
echo " Consider 'git stash' first to preserve current work."
|
|
5576
|
+
fi
|
|
5577
|
+
;;
|
|
5578
|
+
|
|
5579
|
+
help|--help|-h)
|
|
5580
|
+
echo -e "${BOLD}loki checkpoint${NC} - Session state checkpoints"
|
|
5581
|
+
echo ""
|
|
5582
|
+
echo "Save and restore session state snapshots during autonomous runs."
|
|
5583
|
+
echo "Checkpoints capture .loki/ state files and record the git SHA"
|
|
5584
|
+
echo "at the time of creation."
|
|
5585
|
+
echo ""
|
|
5586
|
+
echo "Usage: loki checkpoint <command> [args]"
|
|
5587
|
+
echo " loki cp <command> [args]"
|
|
5588
|
+
echo ""
|
|
5589
|
+
echo "Commands:"
|
|
5590
|
+
echo " list List recent checkpoints (default)"
|
|
5591
|
+
echo " create [message] Create a new checkpoint"
|
|
5592
|
+
echo " show <id> Show checkpoint details"
|
|
5593
|
+
echo " rollback <id> Restore state from a checkpoint"
|
|
5594
|
+
echo " help Show this help"
|
|
5595
|
+
echo ""
|
|
5596
|
+
echo "Examples:"
|
|
5597
|
+
echo " loki checkpoint create 'before refactor'"
|
|
5598
|
+
echo " loki cp list"
|
|
5599
|
+
echo " loki cp show cp-20260212-143022"
|
|
5600
|
+
echo " loki cp rollback cp-20260212-143022"
|
|
5601
|
+
;;
|
|
5602
|
+
|
|
5603
|
+
*)
|
|
5604
|
+
echo -e "${RED}Unknown checkpoint command: $subcommand${NC}"
|
|
5605
|
+
echo "Run 'loki checkpoint help' for usage."
|
|
5606
|
+
return 1
|
|
5607
|
+
;;
|
|
5608
|
+
esac
|
|
5609
|
+
}
|
|
5610
|
+
|
|
5321
5611
|
# Completion Council management
|
|
5322
5612
|
cmd_council() {
|
|
5323
5613
|
local subcommand="${1:-status}"
|