loki-mode 7.5.27 → 7.5.29
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 +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/project-graph.sh +24 -14
- package/autonomy/loki +50 -18
- package/bin/loki +2 -1
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/MERGE-DEDUP-MAP.md +420 -0
- package/docs/MERGE-ROUTE-MAP.md +139 -0
- package/docs/MERGE3-PLAN.md +119 -0
- package/loki-ts/dist/loki.js +196 -171
- package/mcp/__init__.py +1 -1
- package/package.json +2 -2
- package/web-app/dist/assets/{AdminPage-DwVUK4v9.js → AdminPage-CKUOsWZW.js} +3 -3
- package/web-app/dist/assets/{Avatar-B7gqhcg3.js → Avatar-CL9Id9Hi.js} +1 -1
- package/web-app/dist/assets/{Badge-DA3xNJAS.js → Badge-B12zwlD7.js} +1 -1
- package/web-app/dist/assets/{Button-BPXURLaK.js → Button-CFLVoduT.js} +1 -1
- package/web-app/dist/assets/{ComparePage-B0JQMhKG.js → ComparePage-Dg0UdZAk.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-D38-fy29.js → GitHubIssuesPanel-CSitxtAX.js} +2 -2
- package/web-app/dist/assets/{GitHubPRsPanel-DLPcW3N0.js → GitHubPRsPanel-BIT06FRo.js} +1 -1
- package/web-app/dist/assets/HomePage-pU_0fGny.js +28 -0
- package/web-app/dist/assets/{LoginPage-DqCzxsfx.js → LoginPage-DTZtt2Yb.js} +1 -1
- package/web-app/dist/assets/MagicPage-10zfra8o.js +31 -0
- package/web-app/dist/assets/{MetricsPage-CPYQR0zr.js → MetricsPage-C-wiKUkv.js} +1 -1
- package/web-app/dist/assets/NotFoundPage-BDkcmhYe.js +1 -0
- package/web-app/dist/assets/{ProjectPage-DNujSl6j.js → ProjectPage-CiCavQ8n.js} +71 -71
- package/web-app/dist/assets/ProjectsPage-BLCXQwwC.js +6 -0
- package/web-app/dist/assets/{SettingsPage-BaQJbOgL.js → SettingsPage-PkxtaMyg.js} +3 -3
- package/web-app/dist/assets/{ShowcasePage-DQR_e-kg.js → ShowcasePage-iECp8Tha.js} +1 -1
- package/web-app/dist/assets/SystemSettingsPage-DS6Anno1.js +6 -0
- package/web-app/dist/assets/{TeamsPage-DOFErDqX.js → TeamsPage-ls6h6bNL.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-Ty72hILN.js → TemplatesPage-Bk0QzlPt.js} +3 -3
- package/web-app/dist/assets/{TerminalOutput-DqOVnR1p.js → TerminalOutput-4-1hWCtZ.js} +1 -1
- package/web-app/dist/assets/{activity-BgBZ4s4c.js → activity-DH3ih2nS.js} +1 -1
- package/web-app/dist/assets/{bell-C-UezVWi.js → bell-Gn17S6uv.js} +1 -1
- package/web-app/dist/assets/{bot-D70fEnm5.js → bot-Cbycc3VE.js} +1 -1
- package/web-app/dist/assets/{check-CBohulxQ.js → check-nIAqa-kf.js} +1 -1
- package/web-app/dist/assets/{chevron-left-C-emzUhB.js → chevron-left-D2jcWDll.js} +1 -1
- package/web-app/dist/assets/{circle-alert-8SRY0_GX.js → circle-alert-CpL4Bhvt.js} +1 -1
- package/web-app/dist/assets/{clock-mfq4XnPQ.js → clock-IW4Wq86N.js} +1 -1
- package/web-app/dist/assets/{cloud-DpRM7T8t.js → cloud-Cn8nNuH2.js} +1 -1
- package/web-app/dist/assets/{code-xml-1N2Ui-4c.js → code-xml-BiJBteXf.js} +1 -1
- package/web-app/dist/assets/{copy-LXquTgzI.js → copy-CnqkyNsi.js} +1 -1
- package/web-app/dist/assets/{database-S1dyXnuT.js → database-CKSReqa5.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-CRqk0dW5.js → dollar-sign-CDzDY64R.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-B99CwY_6.js → file-code-corner-Box4IwG1.js} +1 -1
- package/web-app/dist/assets/{file-plus-DZ5qnz5b.js → file-plus-DpGqlXF8.js} +1 -1
- package/web-app/dist/assets/{folder-open-DBCm7yuF.js → folder-open-B57dAoBv.js} +1 -1
- package/web-app/dist/assets/{git-commit-horizontal-DM1ERuNd.js → git-commit-horizontal-BVbucmO5.js} +1 -1
- package/web-app/dist/assets/{globe-B7xEJSL_.js → globe-BkOnKl4x.js} +1 -1
- package/web-app/dist/assets/{hammer-Cgi3LTuS.js → hammer-DRbIQ4QU.js} +1 -1
- package/web-app/dist/assets/{index-BN52-GQT.js → index-CM_b_EhP.js} +77 -77
- package/web-app/dist/assets/{layers-Bi8RPIBC.js → layers-B78BiFiU.js} +1 -1
- package/web-app/dist/assets/{lightbulb-Doc_n8JX.js → lightbulb-B-Itbm9g.js} +1 -1
- package/web-app/dist/assets/{loader-circle-BB932A7A.js → loader-circle-Oq6NQhW2.js} +1 -1
- package/web-app/dist/assets/{lock-Bt6gpMrs.js → lock-DbJ9zxbw.js} +1 -1
- package/web-app/dist/assets/{mail-BuzAu1IP.js → mail-CzMRod6m.js} +1 -1
- package/web-app/dist/assets/{package-BE5FHxQ8.js → package-WZ5osvej.js} +1 -1
- package/web-app/dist/assets/{plus-CNqABexN.js → plus-j08lFR-K.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-34B13ztx.js → refresh-cw-CIr7E-g2.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-CrD2QB29.js → rotate-ccw-gwoXxDeE.js} +1 -1
- package/web-app/dist/assets/{save-DsJcqdnI.js → save-B8fV_ZpE.js} +1 -1
- package/web-app/dist/assets/{server-BcgRMArA.js → server-D5dO1paz.js} +1 -1
- package/web-app/dist/assets/{shield-alert-DLYLdVJ0.js → shield-alert-Du08zhdg.js} +1 -1
- package/web-app/dist/assets/{trash-2-Cc-VTvzt.js → trash-2-DEKSVae5.js} +1 -1
- package/web-app/dist/assets/{trending-down-CrDpO2a_.js → trending-down-DBiXUtxJ.js} +1 -1
- package/web-app/dist/assets/{trending-up-CNVsmM3G.js → trending-up-BgmK_tHq.js} +1 -1
- package/web-app/dist/assets/{upload-LuDuB7Wc.js → upload-IaViyeVD.js} +1 -1
- package/web-app/dist/assets/{usePolling-C8rvc-CG.js → usePolling-PiRLqNu6.js} +1 -1
- package/web-app/dist/assets/{user-BT79cI-o.js → user-BB5J8wAF.js} +1 -1
- package/web-app/dist/index.html +2 -3
- package/web-app/server.py +45 -7
- package/web-app/dist/assets/HomePage-CzeoS2V_.js +0 -28
- package/web-app/dist/assets/MagicPage-CBLqpa55.js +0 -31
- package/web-app/dist/assets/NotFoundPage-B62u4iCs.js +0 -1
- package/web-app/dist/assets/ProjectsPage-uHG7kxB-.js +0 -6
- package/web-app/dist/assets/SystemSettingsPage-C_Q_1WK4.js +0 -6
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v7.5.
|
|
6
|
+
# Loki Mode v7.5.29
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -381,4 +381,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
381
381
|
|
|
382
382
|
---
|
|
383
383
|
|
|
384
|
-
**v7.5.
|
|
384
|
+
**v7.5.29 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.5.
|
|
1
|
+
7.5.29
|
|
@@ -398,23 +398,33 @@ for m in meta.get("members", []):
|
|
|
398
398
|
out.append(os.path.realpath(p))
|
|
399
399
|
print(":".join(out))
|
|
400
400
|
' "$parent_meta" "$parent_dir" 2>/dev/null)
|
|
401
|
-
#
|
|
401
|
+
# Bug fix (v7.5.28, found via real CLI smoke): when only the parent
|
|
402
|
+
# has .loki/app.json (no thin pointers in members), the sibling
|
|
403
|
+
# walker's discovered `members` is empty -- in that case the
|
|
404
|
+
# explicit members[] from the parent manifest IS the authoritative
|
|
405
|
+
# member list (no intersection to apply). When discovered members
|
|
406
|
+
# exist, fall back to the intersection-narrow semantics.
|
|
402
407
|
if [ -n "$explicit_members" ]; then
|
|
403
|
-
local
|
|
404
|
-
local em
|
|
408
|
+
local em_arr=()
|
|
405
409
|
IFS=':' read -r -a em_arr <<<"$explicit_members"
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
410
|
+
if [ "${#members[@]}" -eq 0 ]; then
|
|
411
|
+
# No sibling-discovered members -- adopt explicit list verbatim.
|
|
412
|
+
members=("${em_arr[@]}")
|
|
413
|
+
else
|
|
414
|
+
# Sibling-discovered members exist -- intersect with explicit.
|
|
415
|
+
local final=()
|
|
416
|
+
local em m
|
|
417
|
+
for em in "${em_arr[@]}"; do
|
|
418
|
+
for m in "${members[@]}"; do
|
|
419
|
+
if [ "$m" = "$em" ]; then
|
|
420
|
+
final+=("$m")
|
|
421
|
+
break
|
|
422
|
+
fi
|
|
423
|
+
done
|
|
413
424
|
done
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
members=("${final[@]}")
|
|
425
|
+
if [ "${#final[@]}" -gt 0 ]; then
|
|
426
|
+
members=("${final[@]}")
|
|
427
|
+
fi
|
|
418
428
|
fi
|
|
419
429
|
fi
|
|
420
430
|
fi
|
package/autonomy/loki
CHANGED
|
@@ -428,6 +428,7 @@ show_help() {
|
|
|
428
428
|
echo " resume Resume paused execution"
|
|
429
429
|
echo " status [--json] Show current status (--json for machine-readable)"
|
|
430
430
|
echo " stats [flags] Session statistics (--json, --efficiency)"
|
|
431
|
+
echo " kpis [--json] Accuracy + efficiency KPIs (v7.5.28+) [Phase K]"
|
|
431
432
|
echo " logs Show recent log output"
|
|
432
433
|
echo " dashboard [cmd] Dashboard server (start|stop|status|url|open)"
|
|
433
434
|
echo " web [cmd] Web app UI (start|stop|status) -- serves web-app/dist/"
|
|
@@ -508,24 +509,55 @@ show_help() {
|
|
|
508
509
|
echo " --detach, -d Run in background (implies --worktree)"
|
|
509
510
|
echo ""
|
|
510
511
|
echo "Examples:"
|
|
511
|
-
echo "
|
|
512
|
-
echo "
|
|
513
|
-
echo " loki run owner/repo#789 # GitHub with specific repo"
|
|
514
|
-
echo " loki demo # Run 60-second interactive demo"
|
|
515
|
-
echo " loki init # Build a PRD interactively"
|
|
516
|
-
echo " loki init -t saas-starter # Start from a template"
|
|
517
|
-
echo " loki quick \"add dark mode\" # Quick single-task mode"
|
|
512
|
+
echo ""
|
|
513
|
+
echo " # Starting a session"
|
|
518
514
|
echo " loki start ./prd.md # Start with PRD file"
|
|
519
|
-
echo " loki start
|
|
520
|
-
echo " loki start
|
|
521
|
-
echo " loki
|
|
522
|
-
echo " loki
|
|
523
|
-
echo " loki
|
|
524
|
-
echo " loki
|
|
525
|
-
echo " loki
|
|
515
|
+
echo " loki start owner/repo#123 # GitHub issue (auto-fetches)"
|
|
516
|
+
echo " loki start PROJ-456 # Jira issue"
|
|
517
|
+
echo " loki start --bg ./prd.md # Start in background"
|
|
518
|
+
echo " loki start --parallel ./prd.md # Parallel mode (git worktrees)"
|
|
519
|
+
echo " loki quick \"add dark mode\" # Single-task mode (3 iters max)"
|
|
520
|
+
echo " loki demo # 60-second interactive demo"
|
|
521
|
+
echo " loki init -t saas-starter # Scaffold from template"
|
|
522
|
+
echo ""
|
|
523
|
+
echo " # Session ops + observability"
|
|
524
|
+
echo " loki status [--json] # Current status"
|
|
525
|
+
echo " loki stats --efficiency # Token + cost stats"
|
|
526
|
+
echo " loki kpis [--json] # Accuracy + efficiency KPI snapshot"
|
|
527
|
+
echo " loki doctor [--json] # System prereq + skill symlinks"
|
|
528
|
+
echo " loki logs # Tail recent log output"
|
|
529
|
+
echo " loki export json|markdown|csv|timeline # Export session"
|
|
530
|
+
echo " loki cleanup # Kill orphaned processes"
|
|
531
|
+
echo ""
|
|
532
|
+
echo " # Providers + model routing"
|
|
533
|
+
echo " loki provider list # Show 4 providers (claude/codex/cline/aider)"
|
|
534
|
+
echo " loki provider set codex # Switch active provider"
|
|
535
|
+
echo " # OpenRouter / Ollama routing (Phase I v7.5.25+):"
|
|
536
|
+
echo " export ANTHROPIC_BASE_URL=https://openrouter.ai/api/v1 \\\\"
|
|
537
|
+
echo " LOKI_MODEL_OVERRIDE=anthropic/claude-sonnet-4.5"
|
|
538
|
+
echo " loki start ./prd.md # Routes to OpenRouter via Claude Code"
|
|
539
|
+
echo ""
|
|
540
|
+
echo " # Cross-project context (Phase F v7.5.23+)"
|
|
541
|
+
echo " # Drop .loki/app.json with {schema_version:1,app_id:myapp,members:[ui,api]}"
|
|
542
|
+
echo " # at the parent of related repos for shared CLAUDE.md + memory."
|
|
543
|
+
echo ""
|
|
544
|
+
echo " # Memory + learnings"
|
|
545
|
+
echo " loki memory list # All learnings"
|
|
546
|
+
echo " loki memory search <query> # Search across learnings"
|
|
547
|
+
echo " loki memory consolidate # Run episodic-to-semantic pipeline"
|
|
548
|
+
echo ""
|
|
549
|
+
echo " # Config + dashboard"
|
|
550
|
+
echo " loki config set maxTier sonnet # Cap model cost"
|
|
551
|
+
echo " loki dashboard start # Web dashboard at localhost:57374"
|
|
552
|
+
echo " loki watch # Auto-rerun on PRD changes"
|
|
553
|
+
echo " loki remote # Remote session (phone/browser)"
|
|
554
|
+
echo ""
|
|
555
|
+
echo "Phase A-J features (v7.5.18 - v7.5.28) are default-on. See CHANGELOG."
|
|
526
556
|
echo ""
|
|
527
557
|
echo "Environment Variables:"
|
|
528
|
-
echo "
|
|
558
|
+
echo " Opt-outs: LOKI_HOOK_EVENTS=off, LOKI_DYNAMIC_PROMPT_SECTIONS=keep,"
|
|
559
|
+
echo " LOKI_MEMORY_BASE_PATH (shared memory dir for app graph)"
|
|
560
|
+
echo " See: $RUN_SH (header comments) for full list."
|
|
529
561
|
}
|
|
530
562
|
|
|
531
563
|
# Detect argument type for unified `loki start` (v6.84.0)
|
|
@@ -3751,7 +3783,7 @@ cmd_web_start() {
|
|
|
3751
3783
|
existing_pid=$(cat "$PURPLE_LAB_PID_FILE" 2>/dev/null)
|
|
3752
3784
|
if [ -n "$existing_pid" ] && kill -0 "$existing_pid" 2>/dev/null; then
|
|
3753
3785
|
echo -e "${GREEN}Purple Lab already running (PID: $existing_pid)${NC}"
|
|
3754
|
-
local url="http://${PURPLE_LAB_DEFAULT_HOST}:${port}"
|
|
3786
|
+
local url="http://${PURPLE_LAB_DEFAULT_HOST}:${port}/lab/"
|
|
3755
3787
|
echo -e "Open: ${CYAN}$url${NC}"
|
|
3756
3788
|
if [ "$open_browser" = true ]; then
|
|
3757
3789
|
if command -v open &> /dev/null; then
|
|
@@ -3810,7 +3842,7 @@ cmd_web_start() {
|
|
|
3810
3842
|
local retries=0
|
|
3811
3843
|
local server_ready=false
|
|
3812
3844
|
while [ $retries -lt 15 ]; do
|
|
3813
|
-
if curl -s "http://${PURPLE_LAB_DEFAULT_HOST}:${port}/api/session/status" > /dev/null 2>&1; then
|
|
3845
|
+
if curl -s "http://${PURPLE_LAB_DEFAULT_HOST}:${port}/lab/api/session/status" > /dev/null 2>&1; then
|
|
3814
3846
|
server_ready=true
|
|
3815
3847
|
break
|
|
3816
3848
|
fi
|
|
@@ -3825,7 +3857,7 @@ cmd_web_start() {
|
|
|
3825
3857
|
exit 1
|
|
3826
3858
|
fi
|
|
3827
3859
|
|
|
3828
|
-
local url="http://${PURPLE_LAB_DEFAULT_HOST}:${port}"
|
|
3860
|
+
local url="http://${PURPLE_LAB_DEFAULT_HOST}:${port}/lab/"
|
|
3829
3861
|
|
|
3830
3862
|
if [ "$server_ready" = true ]; then
|
|
3831
3863
|
echo -e "${GREEN}Purple Lab running at: $url${NC} (PID: $pid)"
|
package/bin/loki
CHANGED
|
@@ -113,9 +113,10 @@ fi
|
|
|
113
113
|
# Two-token routes (provider show/list, memory list/index) match on the first
|
|
114
114
|
# token only; the Bun dispatcher handles subcommand routing internally.
|
|
115
115
|
case "${1:-}" in
|
|
116
|
-
version|--version|-v|status|stats|doctor|provider|memory|rollback|internal)
|
|
116
|
+
version|--version|-v|status|stats|doctor|provider|memory|rollback|internal|kpis)
|
|
117
117
|
# v7.5.2: rollback added (wires loki-ts/src/commands/rollback.ts).
|
|
118
118
|
# v7.5.3: internal added for autonomy/run.sh phase1-hooks calls.
|
|
119
|
+
# v7.5.28: kpis added (Phase K MVP: read-only KPI snapshot).
|
|
119
120
|
exec bun "$BUN_CLI" "$@"
|
|
120
121
|
;;
|
|
121
122
|
*)
|
package/dashboard/__init__.py
CHANGED
package/docs/INSTALLATION.md
CHANGED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
# Purple Lab Into Dashboard: Deduplication Map (Phase Merge-2)
|
|
2
|
+
|
|
3
|
+
**Status:** Audit complete. Doc-only output for Phase Merge-2 of the v7.5.29+ true-integration arc.
|
|
4
|
+
**Date:** 2026-05-23
|
|
5
|
+
**Scope:** Duplicated business logic, state stores, file paths, session models, WebSocket buses, and auth infrastructure between `dashboard/` and `web-app/`.
|
|
6
|
+
|
|
7
|
+
This document is the source-of-truth deduplication roadmap. It enumerates every shared concept, identifies conflicts, and assigns a canonical version for each with the rationale. The merge into a single Loki Mode UI is Phase Merge-4. This audit completes the dependency analysis for Merge-5 (semantic dedup).
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. State Directory Paths
|
|
12
|
+
|
|
13
|
+
| Path | Dashboard Usage | Purple Lab Usage | Same Data? | Conflict if Both Write? | Canonical |
|
|
14
|
+
|---|---|---|---|---|---|
|
|
15
|
+
| `~/.loki/state/` | Dashboard only: orchestrator.json, session.json, provider, prd | Web-app sets `LOKI_DIR` per project: `<project>/.loki/state/` | NO: Dashboard monitors global state; Lab creates per-project state | YES: Dashboard writes global state; Lab writes local state inside project | Dashboard: Keep global state in `~/.loki/state/`. Lab: Write per-project to `<project>/.loki/state/` (already does via env var). Explicit namespace separation. |
|
|
16
|
+
| `~/.loki/dashboard/` | Token storage (tokens.json) at `dashboard/auth.py:32` | Purple Lab tokens stored at `web-app/server.py:7878` as `~/.loki/tokens/` | NO: Different files and locations | NO: Separate directories and files | Dashboard wins: keep `~/.loki/dashboard/tokens.json`. Lab redirects reads to Dashboard's auth endpoint (Merge-5). |
|
|
17
|
+
| `~/.loki/dashboard/audit/` | Dashboard audit logs at `dashboard/audit.py:7` | Web-app writes audit logs to DB (models.py, AuditLog table) | NO: Dashboard is file-based; Lab is DB-based | NO: Different storage backends | Dashboard wins for CLI audit trail (file-based); Lab uses DB for web-UI audit (will unify in Merge-5 via Dashboard audit API). |
|
|
18
|
+
| `~/.loki/purple-lab/child-pids.json` | N/A | Lab PID tracking at `web-app/server.py:223` | N/A | NO: Lab-specific, not written by Dashboard | Lab keeps this. Dashboard is unaware of Lab child processes. |
|
|
19
|
+
| `<project>/.loki/` | N/A | Web-app sets `LOKI_DIR=<project>/.loki` at `web-app/server.py:2663` | N/A | NO: Per-project state, isolated | Lab keeps this. Dashboard never touches per-project state. |
|
|
20
|
+
| `~/.loki/logs/` | Dashboard writes logs at `dashboard/control.py:32` | Web-app logs go to project-local dir (via LOKI_DIR) at `web-app/server.py` (implicit, via subprocess env) | NO: Different locations and scopes | NO: Separate directories | Dashboard: global logs. Lab: per-project logs. Both win (isolated scopes). |
|
|
21
|
+
| `~/.loki/migrations/` | Migration state at `dashboard/migration_engine.py:183` | N/A | N/A | N/A | Dashboard only. |
|
|
22
|
+
|
|
23
|
+
**Recommendation:** NO BREAKING CHANGES needed for Merge-4. The mount isolates via path prefix (`/lab/`). Lab's per-project state stays inside the project directory (via `LOKI_DIR` env var). Dashboard's global state stays at `~/.loki/`. The two trees do not collide because Dashboard reads global state and Lab reads per-project state.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 2. Session Models (Pydantic)
|
|
28
|
+
|
|
29
|
+
### Dashboard: `Session` (SQLAlchemy ORM)
|
|
30
|
+
|
|
31
|
+
**File:** `dashboard/models.py:179-203`
|
|
32
|
+
|
|
33
|
+
| Field | Type | Nullable | Notes |
|
|
34
|
+
|---|---|---|---|
|
|
35
|
+
| `id` | int (PK) | NO | Auto-increment |
|
|
36
|
+
| `project_id` | int (FK) | NO | Foreign key to Project |
|
|
37
|
+
| `status` | SessionStatus enum | NO | Values: ACTIVE, PAUSED, COMPLETED, FAILED |
|
|
38
|
+
| `provider` | str | NO | Default: "claude" |
|
|
39
|
+
| `model` | str | YES | Optional model override |
|
|
40
|
+
| `started_at` | datetime | NO | Server default: now() |
|
|
41
|
+
| `ended_at` | datetime | YES | NULL until session ends |
|
|
42
|
+
| `logs` | text | YES | Session logs (JSON or text) |
|
|
43
|
+
| Relationships | agents (1:N) | NO | Cascade delete |
|
|
44
|
+
|
|
45
|
+
### Purple Lab: `Session` (SQLAlchemy ORM)
|
|
46
|
+
|
|
47
|
+
**File:** `web-app/models.py:63-78`
|
|
48
|
+
|
|
49
|
+
| Field | Type | Nullable | Notes |
|
|
50
|
+
|---|---|---|---|
|
|
51
|
+
| `id` | UUID | NO | Client-side generated |
|
|
52
|
+
| `user_id` | UUID (FK) | NO | Foreign key to User |
|
|
53
|
+
| `project_id` | UUID (FK) | YES | Foreign key to Project |
|
|
54
|
+
| `prd_content` | text | YES | Full PRD stored in DB |
|
|
55
|
+
| `provider` | str | NO | Default: "claude" |
|
|
56
|
+
| `mode` | str | NO | Default: "standard" |
|
|
57
|
+
| `status` | str | NO | Values: "created", "running", "paused", "completed", "failed" |
|
|
58
|
+
| `started_at` | datetime | NO | Default: utcnow() |
|
|
59
|
+
| `ended_at` | datetime | YES | NULL until session ends |
|
|
60
|
+
| `metadata_json` | JSON | NO | Flexible metadata dict |
|
|
61
|
+
| Relationships | user, project | NO | |
|
|
62
|
+
|
|
63
|
+
### Compatibility Analysis
|
|
64
|
+
|
|
65
|
+
| Aspect | Dashboard | Lab | Compatible? | Action |
|
|
66
|
+
|---|---|---|---|---|
|
|
67
|
+
| **Primary Key** | int (auto) | UUID | NO | Lab uses user-facing UUIDs; Dashboard uses internal integers. Separate DBs (Merge-4 mounts, so no shared DB). No conflict. |
|
|
68
|
+
| **Status Field** | enum (SessionStatus) | string | PARTIAL | Dashboard: [ACTIVE, PAUSED, COMPLETED, FAILED]; Lab: "created", "running", "paused", "completed", "failed". Values drift. Map in API layer. |
|
|
69
|
+
| **Provider** | str | str | YES | Both default "claude". Compatible. |
|
|
70
|
+
| **Model** | optional str | NOT PRESENT | NO | Dashboard tracks model choice; Lab infers from provider. Add `model` field to Lab in Merge-5. |
|
|
71
|
+
| **Started/Ended** | datetime (server default) | datetime (utcnow) | YES | Functionally equivalent. |
|
|
72
|
+
| **Logs** | text field | NOT PRESENT | NO | Lab logs go to stdout/file, not DB. Store in metadata_json during Merge-5. |
|
|
73
|
+
| **User Tracking** | NOT PRESENT | user_id (FK) | NO | Dashboard is single-user (CLI); Lab is multi-user. Don't merge. Keep Lab's user_id. |
|
|
74
|
+
| **PRD Content** | NOT PRESENT | prd_content (text) | NO | Lab stores PRD in DB for re-use; Dashboard reads from file. Separate concerns. |
|
|
75
|
+
|
|
76
|
+
**Recommendation:**
|
|
77
|
+
- **DO NOT merge schemas.** Dashboard tracks **agent execution state** (provider, model, status, logs). Lab tracks **user sessions** (user, PRD, metadata).
|
|
78
|
+
- **Create a bridge table** in Merge-5: `DashboardSession` references `Lab.Session` + stores Dashboard-specific fields (model, logs_path, agent_list).
|
|
79
|
+
- **Status mapping layer:** Dashboard API wraps Lab session status strings into Dashboard enums for internal use.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 3. WebSocket / Event Bus
|
|
84
|
+
|
|
85
|
+
### Dashboard: `ConnectionManager` + Direct Broadcast
|
|
86
|
+
|
|
87
|
+
**File:** `dashboard/server.py:394-436`
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
class ConnectionManager:
|
|
91
|
+
active_connections: list[WebSocket] = []
|
|
92
|
+
|
|
93
|
+
async def connect(ws) -> bool
|
|
94
|
+
async def disconnect(ws)
|
|
95
|
+
async def broadcast(message: dict[str, Any])
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Events Broadcast:**
|
|
99
|
+
- `state_update`: `.loki/` state changed (via file monitor at `:451-667`)
|
|
100
|
+
- `skill-session-update`: Fall-back when `dashboard-state.json` is missing (`:554`)
|
|
101
|
+
- PID-based liveness checks (`:515`)
|
|
102
|
+
|
|
103
|
+
**Route:** `@app.websocket("/ws")` at `:1824`
|
|
104
|
+
|
|
105
|
+
### Purple Lab: File Watcher + Broadcast Callback
|
|
106
|
+
|
|
107
|
+
**File:** `web-app/server.py:420-530`
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
class FileEventDebouncer(FileSystemEventHandler):
|
|
111
|
+
def __init__(self, project_dir, broadcast_fn, loop)
|
|
112
|
+
def on_any_event(event) -> None
|
|
113
|
+
def _schedule_broadcast() -> None
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Events Broadcast:**
|
|
117
|
+
- File system changes: `{event_type, path, timestamp}`
|
|
118
|
+
- Terminal output: `{type: "terminal", output, session_id}`
|
|
119
|
+
- Dev server output: `{type: "backend_output", data, session_id}`
|
|
120
|
+
|
|
121
|
+
**Routes:**
|
|
122
|
+
- `@app.websocket("/ws")` at `:6290`
|
|
123
|
+
- `@app.websocket("/ws/terminal/{session_id}")` at `:6370`
|
|
124
|
+
|
|
125
|
+
### Compatibility Analysis
|
|
126
|
+
|
|
127
|
+
| Aspect | Dashboard | Lab | Compatible? |
|
|
128
|
+
|---|---|---|---|
|
|
129
|
+
| **Connection Manager** | Simple broadcast to all | File-system event handler + selective broadcast | NO: Different models (poll vs file-watch) |
|
|
130
|
+
| **Events** | State JSON changes, skill-session updates | File system + terminal output | NO: Different audiences |
|
|
131
|
+
| **Clients** | Dashboard UI (browser) | Lab UI (browser) + Terminal client | YES, but separate concerns |
|
|
132
|
+
| **Message Format** | `{message_type, data, ...}` | `{event_type, path, ...}` | PARTIAL: Different schemas |
|
|
133
|
+
|
|
134
|
+
**Recommendation:**
|
|
135
|
+
- **DO NOT unify in Merge-4.** Two distinct buses serve different purposes.
|
|
136
|
+
- **Merge-4:** Lab's `/ws` becomes `/lab/ws` (mount prefixing).
|
|
137
|
+
- **Merge-5:** Unify into a single **Unified Event Bus** (UEB):
|
|
138
|
+
- Dashboard clients listen to `/ws` for Dashboard state.
|
|
139
|
+
- Lab clients (mounted at `/lab`) listen to `/lab/ws` for Lab state.
|
|
140
|
+
- Eventually (Phase 7), cross-publish: Dashboard publishes `lab.session.started` events that Dashboard clients can consume for UI updates.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 4. Auth / Session-Token Handling
|
|
145
|
+
|
|
146
|
+
### Dashboard: Token-Based + OIDC (Optional)
|
|
147
|
+
|
|
148
|
+
**File:** `dashboard/auth.py:1-695`
|
|
149
|
+
|
|
150
|
+
- **Token storage:** `~/.loki/dashboard/tokens.json` (file-based, SHA256 hashed)
|
|
151
|
+
- **Token format:** `loki_<urlsafe(32 bytes)>`
|
|
152
|
+
- **Token validation:** `validate_token()` at `:346`
|
|
153
|
+
- **Scope hierarchy:** `*` > `control` > `write` > `read` (at `:53`)
|
|
154
|
+
- **OIDC support:** Optional via `LOKI_OIDC_ISSUER` + `LOKI_OIDC_CLIENT_ID` (`:36-41`)
|
|
155
|
+
- **Auth dependency:** `get_current_token()` at `:618`
|
|
156
|
+
- **CORS origins:** `LOKI_DASHBOARD_CORS` env var (server.py:732)
|
|
157
|
+
- **Root path cookies:** Not set (token-based only)
|
|
158
|
+
|
|
159
|
+
### Purple Lab: JWT + OAuth (GitHub, Google)
|
|
160
|
+
|
|
161
|
+
**File:** `web-app/auth.py:1-210+` (truncated in read)
|
|
162
|
+
|
|
163
|
+
- **Token storage:** `~/.loki/tokens/` (file-based)
|
|
164
|
+
- **Token format:** JWT (via `python-jose`, `PURPLE_LAB_SECRET_KEY` at `:34`)
|
|
165
|
+
- **Token creation:** `create_access_token()` at `:58`
|
|
166
|
+
- **Token validation:** `verify_token()` at `:68`
|
|
167
|
+
- **OAuth callbacks:** `github_oauth_callback()` (`:158`), `google_oauth_callback()` (`:209`)
|
|
168
|
+
- **CORS origins:** `PURPLE_LAB_CORS_ORIGINS` env var (server.py:108)
|
|
169
|
+
- **Root path cookies:** Not set (JWT bearer token only)
|
|
170
|
+
|
|
171
|
+
### Compatibility Analysis
|
|
172
|
+
|
|
173
|
+
| Aspect | Dashboard | Lab | Conflict if Both Write? |
|
|
174
|
+
|---|---|---|---|
|
|
175
|
+
| **Token Format** | `loki_<urlsafe>` (opaque) | JWT (introspectable) | NO: Different tokens, same purpose |
|
|
176
|
+
| **Token Storage** | `~/.loki/dashboard/tokens.json` | `~/.loki/tokens/` (implied) | NO: Different files |
|
|
177
|
+
| **Scope Model** | Role-based (admin, operator, viewer, auditor) | NOT PRESENT in web-app (all OIDC users get `["*"]`) | NO: Dashboard owns scopes. Lab uses DB users. |
|
|
178
|
+
| **OIDC Support** | Optional, via env vars | Implicit (OAuth), NOT OIDC | PARTIAL: Dashboard uses OIDC; Lab uses OAuth. |
|
|
179
|
+
| **Cookies** | NOT SET | NOT SET | NO: Both are stateless (token-based). |
|
|
180
|
+
| **Root-Path Auth** | Optional via `require_scope()` dependency | Optional via `get_current_user()` dependency | NO: Both use Bearer tokens. Same origin after mount means CORS becomes redundant. |
|
|
181
|
+
|
|
182
|
+
**Recommendation:**
|
|
183
|
+
- **DO NOT merge auth systems in Merge-4.** They serve different clients (CLI + Dashboard vs Web app).
|
|
184
|
+
- **Merge-4 action:** Dashboard's CORS middleware is redundant after mount (same origin). Remove `CORSMiddleware` from Lab when mounted.
|
|
185
|
+
- **Merge-5 action:** Unify token storage and validation:
|
|
186
|
+
- Canonical: Dashboard's `~/.loki/dashboard/tokens.json` for CLI API tokens.
|
|
187
|
+
- Lab users: Stored in DB (models.py:User table). Lab auth looks up user in DB, not file.
|
|
188
|
+
- No cross-auth: Dashboard API tokens ≠ Lab DB users. Separate realms.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 5. CORS Middleware
|
|
193
|
+
|
|
194
|
+
| Server | CORS Enabled? | Origins | Env Var | Conflict? |
|
|
195
|
+
|---|---|---|---|---|
|
|
196
|
+
| **Dashboard** | YES | `http://localhost:57374,http://127.0.0.1:57374` (default) | `LOKI_DASHBOARD_CORS` | YES: Redundant after mount (same origin) |
|
|
197
|
+
| **Purple Lab** | YES | `http://localhost:57374,http://127.0.0.1:57374` (default) | `PURPLE_LAB_CORS_ORIGINS` | YES: Redundant after mount (same origin) |
|
|
198
|
+
|
|
199
|
+
**Code References:**
|
|
200
|
+
- Dashboard: `dashboard/server.py:728-746`
|
|
201
|
+
- Lab: `web-app/server.py:104-124`
|
|
202
|
+
|
|
203
|
+
**Recommendation:**
|
|
204
|
+
- **Merge-4:** Remove `CORSMiddleware` from Lab's FastAPI app when mounted. Dashboard's CORS middleware at root (`/`) handles the browser's same-origin policy.
|
|
205
|
+
- **Rationale:** After mount, Lab is at `/lab/*` and Dashboard is at `/` + `/api/*` + `/dashboard/*`. All served from the same origin (same host/port), so CORS is unnecessary.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## 6. Lifespan / Startup Events
|
|
210
|
+
|
|
211
|
+
### Dashboard
|
|
212
|
+
|
|
213
|
+
**File:** `dashboard/server.py`
|
|
214
|
+
|
|
215
|
+
- **No `@app.on_event("startup")` found.** (grep returned empty)
|
|
216
|
+
- **Database init:** Via `init_db()` dependency injected at app scope (database.py, implicit)
|
|
217
|
+
- **Activity logger init:** `get_activity_logger()` called ad-hoc (activity_logger.py:31+)
|
|
218
|
+
- **Telemetry init:** `_telemetry` module imported, not explicitly initialized (telemetry.py:54)
|
|
219
|
+
|
|
220
|
+
### Purple Lab
|
|
221
|
+
|
|
222
|
+
**File:** `web-app/server.py`
|
|
223
|
+
|
|
224
|
+
- **No `@app.on_event("startup")` found.** (grep returned empty)
|
|
225
|
+
- **Database init:** Via `init_db()` called in `database.py` (models.py:116-140)
|
|
226
|
+
- **Dev server managers init:** `dev_server_manager` and `dev_server_manager_v2` instantiated at module level (server.py:1571+)
|
|
227
|
+
- **Terminal manager init:** `terminals_manager` instantiated at module level (server.py:211)
|
|
228
|
+
- **PID tracker init:** `session = SessionState()` at module level (server.py:217)
|
|
229
|
+
|
|
230
|
+
### Compatibility Analysis
|
|
231
|
+
|
|
232
|
+
| Component | Dashboard | Lab | Action |
|
|
233
|
+
|---|---|---|---|
|
|
234
|
+
| **Database init** | Via ORM session factory | Via async engine + async_session_factory | COMPATIBLE: Both async. No ordering required. |
|
|
235
|
+
| **Process managers** | N/A | Global instances (DevServerManager, TerminalManager) | NO CONFLICT: Lab-specific, not used by Dashboard. |
|
|
236
|
+
| **Activity logger** | Ad-hoc initialization | Not present | NO CONFLICT: Dashboard-only. |
|
|
237
|
+
| **Startup ordering** | Implicit (DB auto-init) | Implicit (global instances) | SAFE: No explicit hooks to compose. |
|
|
238
|
+
|
|
239
|
+
**Recommendation:**
|
|
240
|
+
- **Merge-4:** No changes needed. Both servers initialize implicitly via module-level globals and ORM lazy-loading.
|
|
241
|
+
- **Safe to mount:** Neither server has explicit lifespan hooks that could conflict.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## 7. Shared Python Utilities
|
|
246
|
+
|
|
247
|
+
### Shared Modules (Used by Both)
|
|
248
|
+
|
|
249
|
+
| Module | Dashboard Import | Lab Import | Conflict? | Canonical |
|
|
250
|
+
|---|---|---|---|---|
|
|
251
|
+
| `memory/` | `dashboard/server.py:2376` reads `.loki/memory/` | `web-app/server.py:3121` reads `.loki/memory/` | NO: Both read-only, same path | Both. Memory system is read-only for both servers. |
|
|
252
|
+
| `events/` | `dashboard/control.py:422` (`emit_event()`) | NOT FOUND in web-app | NO: Dashboard-only event bus | Dashboard. Lab does not emit to dashboard event bus. |
|
|
253
|
+
| `providers/` | Used via CLI (loki start), not in server code | Used via subprocess (loki start) | NO: CLI-level, not server-level | N/A (both invoke CLI) |
|
|
254
|
+
|
|
255
|
+
### Utility Functions
|
|
256
|
+
|
|
257
|
+
**Dashboard-only utilities:**
|
|
258
|
+
- `dashboard/control.py`: `atomic_write_json()` (`:41`), `get_status()` (`:60`), `emit_event()` (`:422`), `start_session()` (`:367`)
|
|
259
|
+
- `dashboard/registry.py`: Registry management for projects
|
|
260
|
+
- `dashboard/migration_engine.py`: Migration orchestration
|
|
261
|
+
- `dashboard/audit.py`: Audit logging
|
|
262
|
+
|
|
263
|
+
**Lab-only utilities:**
|
|
264
|
+
- `web-app/server.py`: `SessionState` (`:132`), `DevServerManager` (`:577`), `TerminalManager`, `ProjectFileManager`
|
|
265
|
+
- `web-app/models.py`: Async DB session factory
|
|
266
|
+
|
|
267
|
+
**No overlap detected.**
|
|
268
|
+
|
|
269
|
+
**Recommendation:**
|
|
270
|
+
- **DO NOT create shared utility modules in Merge-4.** Each server has distinct responsibilities.
|
|
271
|
+
- **Merge-5:** If cross-server calls are needed (e.g., Lab needs to call Dashboard's `get_status()`), use HTTP API, not shared Python modules.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## 8. Duplicated Business Functions
|
|
276
|
+
|
|
277
|
+
### Critical Duplicates Found
|
|
278
|
+
|
|
279
|
+
#### `start_session()` -- DUPLICATED
|
|
280
|
+
|
|
281
|
+
| Aspect | Dashboard (control.py:367) | Purple Lab (server.py:2606) | Conflict? |
|
|
282
|
+
|---|---|---|---|
|
|
283
|
+
| **Purpose** | Start loki autonomy via run.sh (CLI-driven) | Start loki session via loki start/quick (Lab-driven) | YES: Different triggering paths for same action |
|
|
284
|
+
| **Input** | `StartRequest` (prd path, provider, options) | `StartRequest` (prd content as string, provider, mode) | PARTIAL: Different fields (prd_path vs prd_content) |
|
|
285
|
+
| **Process** | Spawns `run.sh` subprocess directly | Spawns `loki start` via CLI (which runs run.sh) | YES: Dashboard invokes run.sh; Lab invokes CLI which invokes run.sh |
|
|
286
|
+
| **State Tracking** | Saves provider to `STATE_DIR / "provider"` | Sets `LOKI_DIR` env var per project | PARTIAL: Different state models |
|
|
287
|
+
| **Event Emission** | Calls `emit_event("session_start", {...})` | No event emission (implicit via subprocess) | NO: Dashboard has event infrastructure; Lab doesn't |
|
|
288
|
+
|
|
289
|
+
**Impact:** Both try to start the same underlying `run.sh` process, but from different code paths:
|
|
290
|
+
- Dashboard: CLI → `loki dashboard` → Dashboard Server (FastAPI) → `start_session()` → `run.sh`
|
|
291
|
+
- Lab: Web UI → Lab Server (FastAPI) → `start_session()` → `loki start` → `run.sh`
|
|
292
|
+
|
|
293
|
+
**Problem:** After mount, both `/api/control/start` (Dashboard) and `/lab/api/session/start` (Lab) invoke the same `run.sh`. They compete for:
|
|
294
|
+
- Global state at `~/.loki/state/`
|
|
295
|
+
- Single global session (only one can run at a time per Dashboard design)
|
|
296
|
+
|
|
297
|
+
**Recommendation:**
|
|
298
|
+
- **Merge-4 DECISION POINT:** Choose ONE `start_session()` entry point.
|
|
299
|
+
- **Option A** (recommended): Lab wins. Dashboard's `/api/control/start` becomes a thin wrapper calling `/lab/api/session/start` via HTTP (Merge-5).
|
|
300
|
+
- **Option B:** Dashboard wins. Lab's route is removed or deprecated in favor of Dashboard's `start_session()` (breaks Lab's standalone mode).
|
|
301
|
+
- **Rationale for Option A:** Lab's `start_session()` is more feature-complete (project directory, PRD content, quick mode). Dashboard's version is simpler. Unify on Lab's logic; Dashboard can call it via HTTP API.
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
### Other Business Functions
|
|
306
|
+
|
|
307
|
+
#### `get_status()` / Status Endpoints
|
|
308
|
+
|
|
309
|
+
| Aspect | Dashboard | Lab | Duplicate? |
|
|
310
|
+
|---|---|---|---|
|
|
311
|
+
| **Endpoint** | `@app.get("/api/status")` (server.py:850) | No `/status` endpoint (only `/api/session/status`) | PARTIAL: Different scope |
|
|
312
|
+
| **Purpose** | System-wide status (PID, agent count, session count, DB connected) | Session status (running, paused, error) | NO: Different concepts |
|
|
313
|
+
|
|
314
|
+
**Recommendation:** NO DEDUP NEEDED. Dashboard reports system health. Lab reports session state. Different concerns.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## 9. Cookie / Session-Token Namespace
|
|
319
|
+
|
|
320
|
+
| Aspect | Dashboard | Lab | Conflict? |
|
|
321
|
+
|---|---|---|---|
|
|
322
|
+
| **Cookie-based sessions** | NOT USED (token-based only) | NOT USED (JWT bearer token only) | NO: Both stateless. No cookies set at root path. |
|
|
323
|
+
| **Cookie domain** | N/A | N/A | NO: No cookies to conflict. |
|
|
324
|
+
| **Token scope** | Bearer token in Authorization header | Bearer token in Authorization header | COMPATIBLE: Same transport, different validation. |
|
|
325
|
+
|
|
326
|
+
**Recommendation:**
|
|
327
|
+
- **Safe to mount.** Neither server sets cookies. Both use stateless Bearer tokens in `Authorization: Bearer <token>` header.
|
|
328
|
+
- **Merge-4:** No changes needed.
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## 10. Summary Table: Duplicate Canonical Versions
|
|
333
|
+
|
|
334
|
+
| Duplicate | Location | Dashboard | Lab | Canonical | Reason |
|
|
335
|
+
|---|---|---|---|---|---|
|
|
336
|
+
| **start_session()** | control.py:367 vs server.py:2606 | Subprocess run.sh, state file tracking | Subprocess loki start, per-project state | **Lab wins** (more complete, feature-rich). Dashboard calls via HTTP (Merge-5). | Lab version handles projects, PRD content, quick mode. Dashboard version simpler. Unify on richer implementation. |
|
|
337
|
+
| **Session model** | models.py (both) | Execution state (provider, model, logs) | User session state (user_id, PRD, metadata) | **Keep separate** (different domains). | Dashboard tracks agent runs. Lab tracks user sessions. Bridge in Merge-5 via DashboardSession FK to Lab.Session. |
|
|
338
|
+
| **WebSocket bus** | server.py:394 vs server.py:420 | State polling + broadcast | File watch + event debouncer | **Keep separate** (different audiences). | Dashboard UI monitors .loki/ changes. Lab UI monitors project file changes. Unify message schema in Merge-5 (one UEB). |
|
|
339
|
+
| **Token auth** | auth.py (both) | Opaque loki_ tokens + OIDC | JWT tokens + OAuth | **Keep separate** (different clients). | Dashboard tokens for CLI API. Lab tokens for web users. Unify in Merge-5 at API layer (Dashboard token auth calls Lab JWT validation). |
|
|
340
|
+
| **CORS middleware** | server.py (both) | Enabled, port 57374 | Enabled, port 57374 | **Remove Lab's CORS** (redundant after mount). | Same origin after mount makes CORS unnecessary. Simplify. |
|
|
341
|
+
| **State paths** | Various | ~/.loki/state/ (global) | <project>/.loki/state/ (per-project) | **Keep both** (explicit namespace separation). | Dashboard state is global. Lab state is per-project. No collision. |
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Phase Merge-2 Deliverables
|
|
346
|
+
|
|
347
|
+
- [x] State directory path audit (section 1)
|
|
348
|
+
- [x] Session model compatibility analysis (section 2)
|
|
349
|
+
- [x] WebSocket/event bus architecture (section 3)
|
|
350
|
+
- [x] Auth/token namespace audit (section 4)
|
|
351
|
+
- [x] CORS middleware redundancy check (section 5)
|
|
352
|
+
- [x] Lifespan/startup hooks composition (section 6)
|
|
353
|
+
- [x] Shared utilities discovery (section 7)
|
|
354
|
+
- [x] Duplicate business functions (section 8)
|
|
355
|
+
- [x] Cookie/session-token conflicts (section 9)
|
|
356
|
+
- [x] Deduplication recommendations with canonical versions (section 10)
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Honest Acknowledgements
|
|
361
|
+
|
|
362
|
+
### What Was NOT Audited (and Why)
|
|
363
|
+
|
|
364
|
+
1. **Frontend code duplication** (TypeScript/React):
|
|
365
|
+
- Dashboard UI (`dashboard-ui/src/`) and Lab UI (`web-app/dist/` or source) likely have overlapping components (session cards, status displays, log viewers).
|
|
366
|
+
- Scope: This audit covers Python backend only. Frontend audit deferred to UI design review in Merge-3 (Vite rebuild).
|
|
367
|
+
|
|
368
|
+
2. **Database schema migrations and compatibility:**
|
|
369
|
+
- Dashboard uses SQLAlchemy ORM with explicit schema (models.py).
|
|
370
|
+
- Lab uses Alembic migrations (migrations/versions/).
|
|
371
|
+
- No shared database in Merge-4 (separate instances). DB unification is a Merge-5+ decision.
|
|
372
|
+
- Scope: This audit assumes separate DBs per server (no merge needed in Merge-4).
|
|
373
|
+
|
|
374
|
+
3. **Subprocess environment variables and cross-server communication:**
|
|
375
|
+
- Lab sets `LOKI_DIR` when spawning `loki start`. Dashboard sets environment for `run.sh`.
|
|
376
|
+
- No audit of whether subprocess reads Dashboard state or vice versa.
|
|
377
|
+
- Scope: Assumed to be isolated per server (no cross-reading of env vars).
|
|
378
|
+
|
|
379
|
+
4. **Test suite duplication:**
|
|
380
|
+
- `dashboard/tests/` and `web-app/tests/` may have duplicate test cases.
|
|
381
|
+
- Scope: Not audited. Test refactoring in Phase Merge-8 (regression).
|
|
382
|
+
|
|
383
|
+
5. **API endpoint overlap beyond routes:**
|
|
384
|
+
- Phase Merge-1 confirmed NO /api/* route collisions (paths are distinct).
|
|
385
|
+
- This audit did not deep-dive into semantic overlap (e.g., both have "get session info" but different response schemas).
|
|
386
|
+
- Scope: Merge-1 route audit sufficient. Semantic overlap is Merge-5 work.
|
|
387
|
+
|
|
388
|
+
6. **Configuration file conflicts:**
|
|
389
|
+
- `.env`, `.loki/config.json`, or other config files may conflict.
|
|
390
|
+
- Scope: Not audited. Assumed env vars and filesystem state isolation is sufficient for Merge-4.
|
|
391
|
+
|
|
392
|
+
7. **Dependency version skew:**
|
|
393
|
+
- Both servers require `fastapi`, `pydantic`, `sqlalchemy`, etc. Version mismatches not audited.
|
|
394
|
+
- Scope: Assumed CI/poetry lock files handle version alignment.
|
|
395
|
+
|
|
396
|
+
8. **Logging output verbosity and timestamp format:**
|
|
397
|
+
- Dashboard logs go to `~/.loki/logs/`. Lab logs go to project-local or stdout.
|
|
398
|
+
- Log format (JSON, plain text, timestamps) not audited.
|
|
399
|
+
- Scope: Not critical for Merge-4 mount. Unify in Merge-5 via structured logging.
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## Next Phases
|
|
404
|
+
|
|
405
|
+
**Merge-3:** Vite rebuild with `base: '/lab/'` (frontend routing setup).
|
|
406
|
+
|
|
407
|
+
**Merge-4:** FastAPI mount Lab into Dashboard (no code changes needed based on this audit).
|
|
408
|
+
|
|
409
|
+
**Merge-5 Deep Dedup Tasks (after Merge-4 mount is live):**
|
|
410
|
+
1. Unify `start_session()` entry points (recommendation: Lab wins).
|
|
411
|
+
2. Bridge Dashboard and Lab session models (DashboardSession FK).
|
|
412
|
+
3. Unify event bus into single Loki Mode UEB (with routing by prefix).
|
|
413
|
+
4. Consolidate CORS/auth at Dashboard root level.
|
|
414
|
+
5. Map session status strings (Dashboard enum ↔ Lab string).
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
**Audit completed by:** SDET (Senior Development Engineer in Test)
|
|
419
|
+
**Confidence:** HIGH (source code inspection + pattern matching)
|
|
420
|
+
**Blockers for Merge-4:** NONE. Mount is safe to proceed.
|