loki-mode 5.34.0 → 5.36.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 +4 -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 +84 -54
- package/autonomy/run.sh +597 -36
- package/autonomy/sandbox.sh +4 -10
- package/autonomy/serve.sh +10 -10
- package/dashboard/__init__.py +1 -1
- package/dashboard/auth.py +18 -5
- package/dashboard/requirements.txt +7 -7
- package/dashboard/server.py +67 -30
- package/dashboard/static/index.html +359 -58
- package/docs/INSTALLATION.md +4 -4
- package/docs/SYNERGY-ROADMAP.md +1 -1
- package/docs/architecture/DASHBOARD_V2_ARCHITECTURE.md +4 -3
- package/docs/dashboard-guide.md +1 -1
- 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.36.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -255,9 +255,9 @@ The following features are documented in skill modules but not yet fully automat
|
|
|
255
255
|
| Feature | Status | Notes |
|
|
256
256
|
|---------|--------|-------|
|
|
257
257
|
| PRE-ACT goal drift detection | Planned | Agent-level attention check before each action; no automated enforcement yet |
|
|
258
|
-
| CONTINUITY.md working memory |
|
|
258
|
+
| CONTINUITY.md working memory | Implemented (v5.35.0) | Auto-managed by run.sh, updated each iteration |
|
|
259
259
|
| GitHub issue import | Planned | Config flags exist (`LOKI_GITHUB_IMPORT`); `gh` CLI integration partial |
|
|
260
|
-
| 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 |
|
|
261
261
|
| Benchmarks (HumanEval, SWE-bench) | Infrastructure only | Runner scripts and datasets exist in `benchmarks/`; no published results |
|
|
262
262
|
|
|
263
|
-
**v5.
|
|
263
|
+
**v5.36.0 | security hardening: auth wiring, salted hashing, non-root Docker, shell injection fix | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.
|
|
1
|
+
5.36.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
|
}
|
|
@@ -5360,9 +5359,9 @@ cmd_checkpoint() {
|
|
|
5360
5359
|
for (( i=start; i<total; i++ )); do
|
|
5361
5360
|
local entry="${lines[$i]}"
|
|
5362
5361
|
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)
|
|
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)
|
|
5366
5365
|
|
|
5367
5366
|
if [ -n "$cp_id" ]; then
|
|
5368
5367
|
printf " ${GREEN}%-14s${NC} %-20s ${CYAN}%-10s${NC} %s\n" "$cp_id" "$cp_ts" "$cp_sha" "$cp_msg"
|
|
@@ -5379,7 +5378,10 @@ cmd_checkpoint() {
|
|
|
5379
5378
|
;;
|
|
5380
5379
|
|
|
5381
5380
|
create)
|
|
5382
|
-
local
|
|
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)
|
|
5383
5385
|
|
|
5384
5386
|
echo -e "${BOLD}Creating checkpoint...${NC}"
|
|
5385
5387
|
echo ""
|
|
@@ -5417,23 +5419,39 @@ cmd_checkpoint() {
|
|
|
5417
5419
|
fi
|
|
5418
5420
|
done
|
|
5419
5421
|
|
|
5420
|
-
# Write metadata
|
|
5422
|
+
# Write metadata (use python3 json.dumps for safe serialization)
|
|
5421
5423
|
local iso_ts=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
|
5422
|
-
|
|
5423
|
-
{
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
"
|
|
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"]),
|
|
5430
5437
|
"created_by": "loki checkpoint create"
|
|
5431
5438
|
}
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
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
|
|
5437
5455
|
|
|
5438
5456
|
echo -e " Checkpoint: ${GREEN}$cp_id${NC}"
|
|
5439
5457
|
echo -e " Git SHA: ${CYAN}${git_sha:0:8}${NC} ($git_branch)"
|
|
@@ -5453,6 +5471,12 @@ METADATA_EOF
|
|
|
5453
5471
|
return 1
|
|
5454
5472
|
fi
|
|
5455
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
|
+
|
|
5456
5480
|
local cp_dir="$checkpoints_dir/$cp_id"
|
|
5457
5481
|
local metadata="$cp_dir/metadata.json"
|
|
5458
5482
|
|
|
@@ -5465,9 +5489,9 @@ METADATA_EOF
|
|
|
5465
5489
|
echo -e "${BOLD}Checkpoint: $cp_id${NC}"
|
|
5466
5490
|
echo ""
|
|
5467
5491
|
|
|
5468
|
-
python3 << SHOW_EOF
|
|
5492
|
+
_CP_METADATA="$metadata" python3 << 'SHOW_EOF'
|
|
5469
5493
|
import json, os
|
|
5470
|
-
with open("
|
|
5494
|
+
with open(os.environ["_CP_METADATA"], "r") as f:
|
|
5471
5495
|
d = json.load(f)
|
|
5472
5496
|
print(f" ID: {d.get('id', 'unknown')}")
|
|
5473
5497
|
print(f" Timestamp: {d.get('timestamp', 'unknown')}")
|
|
@@ -5503,6 +5527,12 @@ SHOW_EOF
|
|
|
5503
5527
|
return 1
|
|
5504
5528
|
fi
|
|
5505
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
|
+
|
|
5506
5536
|
local cp_dir="$checkpoints_dir/$cp_id"
|
|
5507
5537
|
local metadata="$cp_dir/metadata.json"
|
|
5508
5538
|
|
|
@@ -5533,7 +5563,7 @@ SHOW_EOF
|
|
|
5533
5563
|
echo -e " Restored: ${GREEN}$restored${NC} state items from $cp_id"
|
|
5534
5564
|
|
|
5535
5565
|
# Show git info for manual code rollback
|
|
5536
|
-
local cp_sha=$(python3 -c "import json; d=json.load(open('
|
|
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)
|
|
5537
5567
|
if [ -n "$cp_sha" ] && [ "$cp_sha" != "unknown" ] && [ "$cp_sha" != "not-a-git-repo" ]; then
|
|
5538
5568
|
echo ""
|
|
5539
5569
|
echo -e " ${YELLOW}Note:${NC} Session state has been restored, but code is unchanged."
|