loki-mode 5.42.2 → 5.46.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 +4 -3
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/app-runner.sh +684 -0
- package/autonomy/checklist-verify.py +368 -0
- package/autonomy/completion-council.sh +49 -0
- package/autonomy/loki +83 -0
- package/autonomy/playwright-verify.sh +350 -0
- package/autonomy/prd-analyzer.py +457 -0
- package/autonomy/prd-checklist.sh +223 -0
- package/autonomy/run.sh +164 -4
- package/completions/loki.bash +6 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +134 -1
- package/dashboard/static/index.html +804 -265
- package/docs/INSTALLATION.md +1 -1
- package/docs/audit-logging.md +600 -0
- package/docs/authentication.md +374 -0
- package/docs/authorization.md +455 -0
- package/docs/git-workflow.md +446 -0
- package/docs/metrics.md +527 -0
- package/docs/network-security.md +275 -0
- package/docs/openclaw-integration.md +572 -0
- package/docs/siem-integration.md +579 -0
- package/learning/__init__.py +1 -1
- package/mcp/__init__.py +1 -1
- package/memory/__init__.py +2 -0
- package/package.json +2 -1
package/autonomy/run.sh
CHANGED
|
@@ -534,6 +534,24 @@ if [ -f "$COUNCIL_SCRIPT" ]; then
|
|
|
534
534
|
source "$COUNCIL_SCRIPT"
|
|
535
535
|
fi
|
|
536
536
|
|
|
537
|
+
# PRD Checklist module (v5.44.0)
|
|
538
|
+
if [ -f "${SCRIPT_DIR}/prd-checklist.sh" ]; then
|
|
539
|
+
# shellcheck source=prd-checklist.sh
|
|
540
|
+
source "${SCRIPT_DIR}/prd-checklist.sh"
|
|
541
|
+
fi
|
|
542
|
+
|
|
543
|
+
# App Runner module (v5.45.0)
|
|
544
|
+
if [ -f "${SCRIPT_DIR}/app-runner.sh" ]; then
|
|
545
|
+
# shellcheck source=app-runner.sh
|
|
546
|
+
source "${SCRIPT_DIR}/app-runner.sh"
|
|
547
|
+
fi
|
|
548
|
+
|
|
549
|
+
# Playwright Smoke Test module (v5.46.0)
|
|
550
|
+
if [ -f "${SCRIPT_DIR}/playwright-verify.sh" ]; then
|
|
551
|
+
# shellcheck source=playwright-verify.sh
|
|
552
|
+
source "${SCRIPT_DIR}/playwright-verify.sh"
|
|
553
|
+
fi
|
|
554
|
+
|
|
537
555
|
# Anonymous usage telemetry (opt-out: LOKI_TELEMETRY_DISABLED=true or DO_NOT_TRACK=1)
|
|
538
556
|
TELEMETRY_SCRIPT="$SCRIPT_DIR/telemetry.sh"
|
|
539
557
|
if [ -f "$TELEMETRY_SCRIPT" ]; then
|
|
@@ -2627,6 +2645,24 @@ write_dashboard_state() {
|
|
|
2627
2645
|
council_state=$(cat ".loki/council/state.json" 2>/dev/null || echo '{"enabled":false}')
|
|
2628
2646
|
fi
|
|
2629
2647
|
|
|
2648
|
+
# PRD Checklist summary (v5.44.0)
|
|
2649
|
+
local checklist_summary='null'
|
|
2650
|
+
if [ -f ".loki/checklist/verification-results.json" ]; then
|
|
2651
|
+
checklist_summary=$(cat ".loki/checklist/verification-results.json" 2>/dev/null || echo "null")
|
|
2652
|
+
fi
|
|
2653
|
+
|
|
2654
|
+
# App Runner state (v5.45.0)
|
|
2655
|
+
local app_runner_state='{"status":"not_initialized"}'
|
|
2656
|
+
if [ -f ".loki/app-runner/state.json" ]; then
|
|
2657
|
+
app_runner_state=$(cat ".loki/app-runner/state.json" 2>/dev/null || echo '{"status":"error"}')
|
|
2658
|
+
fi
|
|
2659
|
+
|
|
2660
|
+
# Playwright verification results (v5.46.0)
|
|
2661
|
+
local playwright_results='null'
|
|
2662
|
+
if [ -f ".loki/verification/playwright-results.json" ]; then
|
|
2663
|
+
playwright_results=$(cat ".loki/verification/playwright-results.json" 2>/dev/null || echo "null")
|
|
2664
|
+
fi
|
|
2665
|
+
|
|
2630
2666
|
# Get budget status (if configured)
|
|
2631
2667
|
local budget_json="null"
|
|
2632
2668
|
if [ -f ".loki/metrics/budget.json" ]; then
|
|
@@ -2703,6 +2739,9 @@ except: print('{\"total\":0,\"unacknowledged\":0}')
|
|
|
2703
2739
|
},
|
|
2704
2740
|
"qualityGates": $quality_gates,
|
|
2705
2741
|
"council": $council_state,
|
|
2742
|
+
"checklist": $checklist_summary,
|
|
2743
|
+
"appRunner": $app_runner_state,
|
|
2744
|
+
"playwright": $playwright_results,
|
|
2706
2745
|
"budget": $budget_json,
|
|
2707
2746
|
"context": $context_state,
|
|
2708
2747
|
"tokens": $(python3 -c "
|
|
@@ -6047,17 +6086,63 @@ build_prompt() {
|
|
|
6047
6086
|
memory_context_section="CONTEXT: $context_injection"
|
|
6048
6087
|
fi
|
|
6049
6088
|
|
|
6089
|
+
# PRD Checklist status injection (v5.44.0)
|
|
6090
|
+
local checklist_status=""
|
|
6091
|
+
if [ -n "$prd" ] && [ ! -f ".loki/checklist/checklist.json" ]; then
|
|
6092
|
+
# First iteration with PRD but no checklist yet: instruct AI to create it
|
|
6093
|
+
checklist_status="PRD_CHECKLIST_INIT: Create .loki/checklist/checklist.json from the PRD. Extract requirements into categories with items. Each item needs: id, title, description, priority (critical|major|minor), and verification checks (file_exists, file_contains, tests_pass, grep_codebase, command). This checklist will be auto-verified every ${CHECKLIST_INTERVAL:-5} iterations."
|
|
6094
|
+
elif type checklist_summary &>/dev/null && [ -f ".loki/checklist/verification-results.json" ]; then
|
|
6095
|
+
checklist_status=$(checklist_summary 2>/dev/null || true)
|
|
6096
|
+
if [ -n "$checklist_status" ]; then
|
|
6097
|
+
checklist_status="PRD_CHECKLIST_STATUS: ${checklist_status}. Review failing items and prioritize fixing them in this iteration."
|
|
6098
|
+
fi
|
|
6099
|
+
fi
|
|
6100
|
+
|
|
6101
|
+
# App Runner status injection (v5.45.0)
|
|
6102
|
+
local app_runner_info=""
|
|
6103
|
+
if [ -f ".loki/app-runner/state.json" ]; then
|
|
6104
|
+
app_runner_info=$(python3 -c "
|
|
6105
|
+
import json
|
|
6106
|
+
try:
|
|
6107
|
+
d = json.load(open('.loki/app-runner/state.json'))
|
|
6108
|
+
s = d.get('status', '')
|
|
6109
|
+
if s == 'running':
|
|
6110
|
+
print('APP_RUNNING_AT: ' + d.get('url', '') + ' (auto-restarts on code changes). Method: ' + d.get('method', ''))
|
|
6111
|
+
elif s == 'crashed':
|
|
6112
|
+
print('APP_CRASHED: Application has crashed ' + str(d.get('crash_count', 0)) + ' times. Check .loki/app-runner/app.log for errors.')
|
|
6113
|
+
except: pass
|
|
6114
|
+
" 2>/dev/null || true)
|
|
6115
|
+
fi
|
|
6116
|
+
|
|
6117
|
+
# Playwright verification status injection (v5.46.0)
|
|
6118
|
+
local playwright_info=""
|
|
6119
|
+
if [ -f ".loki/verification/playwright-results.json" ]; then
|
|
6120
|
+
playwright_info=$(python3 -c "
|
|
6121
|
+
import json
|
|
6122
|
+
try:
|
|
6123
|
+
d = json.load(open('.loki/verification/playwright-results.json'))
|
|
6124
|
+
if d.get('passed'):
|
|
6125
|
+
print('PLAYWRIGHT_SMOKE_TEST: PASSED - App loads correctly.')
|
|
6126
|
+
else:
|
|
6127
|
+
errors = d.get('errors', [])
|
|
6128
|
+
checks = d.get('checks', {})
|
|
6129
|
+
failing = [k for k, v in checks.items() if not v]
|
|
6130
|
+
print('PLAYWRIGHT_SMOKE_TEST: FAILED - ' + ', '.join(failing[:3]) + ('. Errors: ' + '; '.join(errors[:3]) if errors else ''))
|
|
6131
|
+
except: pass
|
|
6132
|
+
" 2>/dev/null || true)
|
|
6133
|
+
fi
|
|
6134
|
+
|
|
6050
6135
|
if [ $retry -eq 0 ]; then
|
|
6051
6136
|
if [ -n "$prd" ]; then
|
|
6052
|
-
echo "Loki Mode with PRD at $prd. $human_directive $queue_tasks $memory_context_section $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
6137
|
+
echo "Loki Mode with PRD at $prd. $human_directive $queue_tasks $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
6053
6138
|
else
|
|
6054
|
-
echo "Loki Mode. $human_directive $queue_tasks $memory_context_section $analysis_instruction $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
6139
|
+
echo "Loki Mode. $human_directive $queue_tasks $checklist_status $app_runner_info $playwright_info $memory_context_section $analysis_instruction $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
6055
6140
|
fi
|
|
6056
6141
|
else
|
|
6057
6142
|
if [ -n "$prd" ]; then
|
|
6058
|
-
echo "Loki Mode - Resume iteration #$iteration (retry #$retry). PRD: $prd. $human_directive $queue_tasks $memory_context_section $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
6143
|
+
echo "Loki Mode - Resume iteration #$iteration (retry #$retry). PRD: $prd. $human_directive $queue_tasks $checklist_status $app_runner_info $playwright_info $memory_context_section $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
6059
6144
|
else
|
|
6060
|
-
echo "Loki Mode - Resume iteration #$iteration (retry #$retry). $human_directive $queue_tasks $memory_context_section Use .loki/generated-prd.md if exists. $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
6145
|
+
echo "Loki Mode - Resume iteration #$iteration (retry #$retry). $human_directive $queue_tasks $checklist_status $app_runner_info $playwright_info $memory_context_section Use .loki/generated-prd.md if exists. $rarv_instruction $memory_instruction $compaction_reminder $completion_instruction $sdlc_instruction $autonomous_suffix"
|
|
6061
6146
|
fi
|
|
6062
6147
|
fi
|
|
6063
6148
|
}
|
|
@@ -6130,6 +6215,19 @@ run_autonomous() {
|
|
|
6130
6215
|
council_init "$prd_path"
|
|
6131
6216
|
fi
|
|
6132
6217
|
|
|
6218
|
+
# PRD Quality Analysis and Checklist Init (v5.44.0)
|
|
6219
|
+
if [ -n "$prd_path" ] && [ -f "$prd_path" ]; then
|
|
6220
|
+
if [ -f "${SCRIPT_DIR}/prd-analyzer.py" ]; then
|
|
6221
|
+
log_step "Analyzing PRD quality..."
|
|
6222
|
+
python3 "${SCRIPT_DIR}/prd-analyzer.py" "$prd_path" \
|
|
6223
|
+
--output ".loki/prd-observations.md" \
|
|
6224
|
+
${LOKI_INTERACTIVE_PRD:+--interactive} 2>/dev/null || true
|
|
6225
|
+
fi
|
|
6226
|
+
if type checklist_init &>/dev/null; then
|
|
6227
|
+
checklist_init "$prd_path"
|
|
6228
|
+
fi
|
|
6229
|
+
fi
|
|
6230
|
+
|
|
6133
6231
|
# Check max iterations before starting
|
|
6134
6232
|
if check_max_iterations; then
|
|
6135
6233
|
log_error "Max iterations already reached. Reset with: rm .loki/autonomy-state.json"
|
|
@@ -6508,6 +6606,57 @@ if __name__ == "__main__":
|
|
|
6508
6606
|
# Auto-track iteration completion (for dashboard task queue)
|
|
6509
6607
|
track_iteration_complete "$ITERATION_COUNT" "$exit_code"
|
|
6510
6608
|
|
|
6609
|
+
# PRD Checklist verification on interval (v5.44.0)
|
|
6610
|
+
if type checklist_should_verify &>/dev/null && checklist_should_verify; then
|
|
6611
|
+
checklist_verify
|
|
6612
|
+
fi
|
|
6613
|
+
|
|
6614
|
+
# App Runner: init after first successful iteration (v5.45.0)
|
|
6615
|
+
if [ "${APP_RUNNER_INITIALIZED:-}" != "true" ] && [ $exit_code -eq 0 ] && \
|
|
6616
|
+
[ "${LOKI_APP_RUNNER:-true}" = "true" ] && type app_runner_init &>/dev/null; then
|
|
6617
|
+
if app_runner_init; then
|
|
6618
|
+
app_runner_start || log_warn "App runner: failed to start application"
|
|
6619
|
+
APP_RUNNER_INITIALIZED=true
|
|
6620
|
+
fi
|
|
6621
|
+
fi
|
|
6622
|
+
|
|
6623
|
+
# App Runner: restart on code changes (v5.45.0)
|
|
6624
|
+
if [ "${APP_RUNNER_INITIALIZED:-}" = "true" ] && type app_runner_should_restart &>/dev/null; then
|
|
6625
|
+
if app_runner_should_restart; then
|
|
6626
|
+
app_runner_restart || log_warn "App runner: failed to restart application"
|
|
6627
|
+
fi
|
|
6628
|
+
fi
|
|
6629
|
+
|
|
6630
|
+
# App Runner: watchdog check (v5.45.0)
|
|
6631
|
+
if [ "${APP_RUNNER_INITIALIZED:-}" = "true" ] && type app_runner_watchdog &>/dev/null; then
|
|
6632
|
+
app_runner_watchdog
|
|
6633
|
+
fi
|
|
6634
|
+
|
|
6635
|
+
# Playwright smoke test on interval (v5.46.0)
|
|
6636
|
+
if type playwright_verify_should_run &>/dev/null && playwright_verify_should_run; then
|
|
6637
|
+
if [ -f ".loki/app-runner/state.json" ]; then
|
|
6638
|
+
local app_url
|
|
6639
|
+
app_url=$(python3 -c "import json; d=json.load(open('.loki/app-runner/state.json')); print(d.get('url','') if d.get('status')=='running' else '')" 2>/dev/null || true)
|
|
6640
|
+
if [ -n "$app_url" ]; then
|
|
6641
|
+
playwright_verify_app "$app_url" || true
|
|
6642
|
+
fi
|
|
6643
|
+
fi
|
|
6644
|
+
fi
|
|
6645
|
+
|
|
6646
|
+
# App Runner: check for dashboard control signals (v5.45.0)
|
|
6647
|
+
if [ "${APP_RUNNER_INITIALIZED:-}" = "true" ]; then
|
|
6648
|
+
if [ -f ".loki/app-runner/restart-signal" ]; then
|
|
6649
|
+
rm -f ".loki/app-runner/restart-signal"
|
|
6650
|
+
log_info "App runner: restart signal received from dashboard"
|
|
6651
|
+
app_runner_restart || true
|
|
6652
|
+
fi
|
|
6653
|
+
if [ -f ".loki/app-runner/stop-signal" ]; then
|
|
6654
|
+
rm -f ".loki/app-runner/stop-signal"
|
|
6655
|
+
log_info "App runner: stop signal received from dashboard"
|
|
6656
|
+
app_runner_stop || true
|
|
6657
|
+
fi
|
|
6658
|
+
fi
|
|
6659
|
+
|
|
6511
6660
|
# Update session continuity file for next iteration / agent handoff
|
|
6512
6661
|
update_continuity
|
|
6513
6662
|
|
|
@@ -6799,6 +6948,9 @@ cleanup() {
|
|
|
6799
6948
|
echo ""
|
|
6800
6949
|
log_warn "Stop signal received - shutting down"
|
|
6801
6950
|
rm -f "$loki_dir/STOP" "$loki_dir/PAUSE" "$loki_dir/PAUSED.md" 2>/dev/null
|
|
6951
|
+
if type app_runner_cleanup &>/dev/null; then
|
|
6952
|
+
app_runner_cleanup
|
|
6953
|
+
fi
|
|
6802
6954
|
stop_dashboard
|
|
6803
6955
|
stop_status_monitor
|
|
6804
6956
|
rm -f "$loki_dir/loki.pid" 2>/dev/null
|
|
@@ -6823,6 +6975,9 @@ except (json.JSONDecodeError, OSError): pass
|
|
|
6823
6975
|
if [ "$time_diff" -lt 2 ] && [ "$INTERRUPT_COUNT" -gt 0 ]; then
|
|
6824
6976
|
echo ""
|
|
6825
6977
|
log_warn "Double interrupt - stopping immediately"
|
|
6978
|
+
if type app_runner_cleanup &>/dev/null; then
|
|
6979
|
+
app_runner_cleanup
|
|
6980
|
+
fi
|
|
6826
6981
|
stop_dashboard
|
|
6827
6982
|
stop_status_monitor
|
|
6828
6983
|
rm -f .loki/loki.pid .loki/PAUSE 2>/dev/null
|
|
@@ -6940,6 +7095,10 @@ main() {
|
|
|
6940
7095
|
BACKGROUND_MODE=true
|
|
6941
7096
|
shift
|
|
6942
7097
|
;;
|
|
7098
|
+
--interactive-prd|--interactive)
|
|
7099
|
+
LOKI_INTERACTIVE_PRD=true
|
|
7100
|
+
shift
|
|
7101
|
+
;;
|
|
6943
7102
|
--help|-h)
|
|
6944
7103
|
echo "Usage: ./autonomy/run.sh [OPTIONS] [PRD_PATH]"
|
|
6945
7104
|
echo ""
|
|
@@ -6948,6 +7107,7 @@ main() {
|
|
|
6948
7107
|
echo " --allow-haiku Enable Haiku model for fast tier (default: disabled)"
|
|
6949
7108
|
echo " --provider <name> Provider: claude (default), codex, gemini"
|
|
6950
7109
|
echo " --bg, --background Run in background mode"
|
|
7110
|
+
echo " --interactive-prd Interactive PRD pre-flight analysis"
|
|
6951
7111
|
echo " --help, -h Show this help message"
|
|
6952
7112
|
echo ""
|
|
6953
7113
|
echo "Environment variables: See header comments in this script"
|
package/completions/loki.bash
CHANGED
|
@@ -5,7 +5,7 @@ _loki_completion() {
|
|
|
5
5
|
_init_completion || return
|
|
6
6
|
|
|
7
7
|
# Main subcommands (must match autonomy/loki main case statement)
|
|
8
|
-
local main_commands="start quick demo init stop pause resume status dashboard logs serve api sandbox notify import issue config provider reset memory compound council dogfood projects enterprise voice version completions
|
|
8
|
+
local main_commands="start quick demo init stop pause resume status dashboard logs serve api sandbox notify import github issue config provider reset memory compound checkpoint council dogfood projects enterprise voice secrets doctor watchdog audit metrics syslog version completions help"
|
|
9
9
|
|
|
10
10
|
# 1. If we are on the first argument (subcommand)
|
|
11
11
|
if [[ $cword -eq 1 ]]; then
|
|
@@ -88,6 +88,11 @@ _loki_completion() {
|
|
|
88
88
|
COMPREPLY=( $(compgen -W "${voice_cmds}" -- "$cur") )
|
|
89
89
|
;;
|
|
90
90
|
|
|
91
|
+
syslog)
|
|
92
|
+
local syslog_cmds="test status help"
|
|
93
|
+
COMPREPLY=( $(compgen -W "${syslog_cmds}" -- "$cur") )
|
|
94
|
+
;;
|
|
95
|
+
|
|
91
96
|
status)
|
|
92
97
|
if [[ "$cur" == -* ]]; then
|
|
93
98
|
COMPREPLY=( $(compgen -W "--json --help" -- "$cur") )
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -48,6 +48,7 @@ from . import auth
|
|
|
48
48
|
from . import audit
|
|
49
49
|
from . import secrets as secrets_mod
|
|
50
50
|
from . import telemetry as _telemetry
|
|
51
|
+
from .control import atomic_write_json
|
|
51
52
|
|
|
52
53
|
try:
|
|
53
54
|
from . import __version__ as _version
|
|
@@ -2036,7 +2037,7 @@ async def stop_session(request: Request):
|
|
|
2036
2037
|
try:
|
|
2037
2038
|
sd = json.loads(session_file.read_text())
|
|
2038
2039
|
sd["status"] = "stopped"
|
|
2039
|
-
session_file
|
|
2040
|
+
atomic_write_json(session_file, sd, use_lock=True)
|
|
2040
2041
|
except Exception:
|
|
2041
2042
|
pass
|
|
2042
2043
|
|
|
@@ -3357,6 +3358,138 @@ async def prometheus_metrics():
|
|
|
3357
3358
|
return _build_metrics_text()
|
|
3358
3359
|
|
|
3359
3360
|
|
|
3361
|
+
# =============================================================================
|
|
3362
|
+
# PRD Checklist Endpoints (v5.44.0)
|
|
3363
|
+
# =============================================================================
|
|
3364
|
+
|
|
3365
|
+
@app.get("/api/checklist")
|
|
3366
|
+
async def get_checklist():
|
|
3367
|
+
"""Get full PRD checklist with verification status."""
|
|
3368
|
+
loki_dir = _get_loki_dir()
|
|
3369
|
+
checklist_file = loki_dir / "checklist" / "checklist.json"
|
|
3370
|
+
if not checklist_file.exists():
|
|
3371
|
+
return {"status": "not_initialized", "categories": [], "summary": {"total": 0, "verified": 0, "failing": 0, "pending": 0}}
|
|
3372
|
+
try:
|
|
3373
|
+
return json.loads(checklist_file.read_text())
|
|
3374
|
+
except (json.JSONDecodeError, OSError):
|
|
3375
|
+
return {"status": "error", "categories": [], "summary": {"total": 0, "verified": 0, "failing": 0, "pending": 0}}
|
|
3376
|
+
|
|
3377
|
+
|
|
3378
|
+
@app.get("/api/checklist/summary")
|
|
3379
|
+
async def get_checklist_summary():
|
|
3380
|
+
"""Get checklist verification summary."""
|
|
3381
|
+
loki_dir = _get_loki_dir()
|
|
3382
|
+
results_file = loki_dir / "checklist" / "verification-results.json"
|
|
3383
|
+
if not results_file.exists():
|
|
3384
|
+
return {"status": "not_initialized", "summary": {"total": 0, "verified": 0, "failing": 0, "pending": 0}}
|
|
3385
|
+
try:
|
|
3386
|
+
return json.loads(results_file.read_text())
|
|
3387
|
+
except (json.JSONDecodeError, OSError):
|
|
3388
|
+
return {"status": "error", "summary": {"total": 0, "verified": 0, "failing": 0, "pending": 0}}
|
|
3389
|
+
|
|
3390
|
+
|
|
3391
|
+
@app.get("/api/prd-observations")
|
|
3392
|
+
async def get_prd_observations():
|
|
3393
|
+
"""Get PRD quality analysis observations."""
|
|
3394
|
+
loki_dir = _get_loki_dir()
|
|
3395
|
+
obs_file = loki_dir / "prd-observations.md"
|
|
3396
|
+
if not obs_file.exists():
|
|
3397
|
+
return PlainTextResponse("No PRD observations available yet.", status_code=200)
|
|
3398
|
+
try:
|
|
3399
|
+
content = obs_file.read_text()
|
|
3400
|
+
return PlainTextResponse(content, status_code=200)
|
|
3401
|
+
except OSError:
|
|
3402
|
+
return PlainTextResponse("Error reading PRD observations.", status_code=500)
|
|
3403
|
+
|
|
3404
|
+
|
|
3405
|
+
# =============================================================================
|
|
3406
|
+
# App Runner Endpoints (v5.45.0)
|
|
3407
|
+
# =============================================================================
|
|
3408
|
+
|
|
3409
|
+
@app.get("/api/app-runner/status")
|
|
3410
|
+
async def get_app_runner_status():
|
|
3411
|
+
"""Get app runner current status."""
|
|
3412
|
+
loki_dir = _get_loki_dir()
|
|
3413
|
+
state_file = loki_dir / "app-runner" / "state.json"
|
|
3414
|
+
if not state_file.exists():
|
|
3415
|
+
return {"status": "not_initialized"}
|
|
3416
|
+
try:
|
|
3417
|
+
return json.loads(state_file.read_text())
|
|
3418
|
+
except (json.JSONDecodeError, OSError):
|
|
3419
|
+
return {"status": "error"}
|
|
3420
|
+
|
|
3421
|
+
|
|
3422
|
+
@app.get("/api/app-runner/logs")
|
|
3423
|
+
async def get_app_runner_logs(lines: int = Query(default=100, ge=1, le=1000)):
|
|
3424
|
+
"""Get last N lines of app runner logs."""
|
|
3425
|
+
loki_dir = _get_loki_dir()
|
|
3426
|
+
log_file = loki_dir / "app-runner" / "app.log"
|
|
3427
|
+
if not log_file.exists():
|
|
3428
|
+
return {"lines": []}
|
|
3429
|
+
try:
|
|
3430
|
+
all_lines = log_file.read_text().splitlines()
|
|
3431
|
+
return {"lines": all_lines[-lines:]}
|
|
3432
|
+
except OSError:
|
|
3433
|
+
return {"lines": []}
|
|
3434
|
+
|
|
3435
|
+
|
|
3436
|
+
@app.post("/api/control/app-restart", dependencies=[Depends(auth.require_scope("control"))])
|
|
3437
|
+
async def control_app_restart(request: Request):
|
|
3438
|
+
"""Signal app runner to restart the application."""
|
|
3439
|
+
if not _control_limiter.check(str(request.client.host)):
|
|
3440
|
+
raise HTTPException(status_code=429, detail="Rate limit exceeded")
|
|
3441
|
+
loki_dir = _get_loki_dir()
|
|
3442
|
+
signal_dir = loki_dir / "app-runner"
|
|
3443
|
+
signal_dir.mkdir(parents=True, exist_ok=True)
|
|
3444
|
+
signal_file = signal_dir / "restart-signal"
|
|
3445
|
+
signal_file.write_text(datetime.now(timezone.utc).isoformat())
|
|
3446
|
+
return {"status": "restart_signaled"}
|
|
3447
|
+
|
|
3448
|
+
|
|
3449
|
+
@app.post("/api/control/app-stop", dependencies=[Depends(auth.require_scope("control"))])
|
|
3450
|
+
async def control_app_stop(request: Request):
|
|
3451
|
+
"""Signal app runner to stop the application."""
|
|
3452
|
+
if not _control_limiter.check(str(request.client.host)):
|
|
3453
|
+
raise HTTPException(status_code=429, detail="Rate limit exceeded")
|
|
3454
|
+
loki_dir = _get_loki_dir()
|
|
3455
|
+
signal_dir = loki_dir / "app-runner"
|
|
3456
|
+
signal_dir.mkdir(parents=True, exist_ok=True)
|
|
3457
|
+
signal_file = signal_dir / "stop-signal"
|
|
3458
|
+
signal_file.write_text(datetime.now(timezone.utc).isoformat())
|
|
3459
|
+
return {"status": "stop_signaled"}
|
|
3460
|
+
|
|
3461
|
+
|
|
3462
|
+
# =============================================================================
|
|
3463
|
+
# Playwright Verification Endpoints (v5.46.0)
|
|
3464
|
+
# =============================================================================
|
|
3465
|
+
|
|
3466
|
+
@app.get("/api/playwright/results")
|
|
3467
|
+
async def get_playwright_results():
|
|
3468
|
+
"""Get latest Playwright smoke test results."""
|
|
3469
|
+
loki_dir = _get_loki_dir()
|
|
3470
|
+
results_file = loki_dir / "verification" / "playwright-results.json"
|
|
3471
|
+
if not results_file.exists():
|
|
3472
|
+
return {"status": "not_run"}
|
|
3473
|
+
try:
|
|
3474
|
+
return json.loads(results_file.read_text())
|
|
3475
|
+
except (json.JSONDecodeError, OSError):
|
|
3476
|
+
return {"status": "error"}
|
|
3477
|
+
|
|
3478
|
+
|
|
3479
|
+
@app.get("/api/playwright/screenshot")
|
|
3480
|
+
async def get_playwright_screenshot():
|
|
3481
|
+
"""Get path to latest Playwright screenshot."""
|
|
3482
|
+
loki_dir = _get_loki_dir()
|
|
3483
|
+
screenshots_dir = loki_dir / "verification" / "screenshots"
|
|
3484
|
+
if not screenshots_dir.exists():
|
|
3485
|
+
return {"screenshot": None}
|
|
3486
|
+
# Get most recent screenshot
|
|
3487
|
+
screenshots = sorted(screenshots_dir.glob("*.png"), key=lambda p: p.stat().st_mtime, reverse=True)
|
|
3488
|
+
if not screenshots:
|
|
3489
|
+
return {"screenshot": None}
|
|
3490
|
+
return {"screenshot": str(screenshots[0])}
|
|
3491
|
+
|
|
3492
|
+
|
|
3360
3493
|
# =============================================================================
|
|
3361
3494
|
# Static File Serving (Production/Docker)
|
|
3362
3495
|
# =============================================================================
|