loki-mode 7.5.28 → 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/loki +3 -3
- 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 +2 -2
- 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
|
package/autonomy/loki
CHANGED
|
@@ -3783,7 +3783,7 @@ cmd_web_start() {
|
|
|
3783
3783
|
existing_pid=$(cat "$PURPLE_LAB_PID_FILE" 2>/dev/null)
|
|
3784
3784
|
if [ -n "$existing_pid" ] && kill -0 "$existing_pid" 2>/dev/null; then
|
|
3785
3785
|
echo -e "${GREEN}Purple Lab already running (PID: $existing_pid)${NC}"
|
|
3786
|
-
local url="http://${PURPLE_LAB_DEFAULT_HOST}:${port}"
|
|
3786
|
+
local url="http://${PURPLE_LAB_DEFAULT_HOST}:${port}/lab/"
|
|
3787
3787
|
echo -e "Open: ${CYAN}$url${NC}"
|
|
3788
3788
|
if [ "$open_browser" = true ]; then
|
|
3789
3789
|
if command -v open &> /dev/null; then
|
|
@@ -3842,7 +3842,7 @@ cmd_web_start() {
|
|
|
3842
3842
|
local retries=0
|
|
3843
3843
|
local server_ready=false
|
|
3844
3844
|
while [ $retries -lt 15 ]; do
|
|
3845
|
-
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
|
|
3846
3846
|
server_ready=true
|
|
3847
3847
|
break
|
|
3848
3848
|
fi
|
|
@@ -3857,7 +3857,7 @@ cmd_web_start() {
|
|
|
3857
3857
|
exit 1
|
|
3858
3858
|
fi
|
|
3859
3859
|
|
|
3860
|
-
local url="http://${PURPLE_LAB_DEFAULT_HOST}:${port}"
|
|
3860
|
+
local url="http://${PURPLE_LAB_DEFAULT_HOST}:${port}/lab/"
|
|
3861
3861
|
|
|
3862
3862
|
if [ "$server_ready" = true ]; then
|
|
3863
3863
|
echo -e "${GREEN}Purple Lab running at: $url${NC} (PID: $pid)"
|
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.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Purple Lab Into Dashboard: Route Map (Phase Merge-1)
|
|
2
|
+
|
|
3
|
+
**Status:** Audit complete. Doc-only output for Phase Merge-1 of the v7.5.29+ true-integration arc.
|
|
4
|
+
**Date:** 2026-05-23
|
|
5
|
+
**Source:** `dashboard/server.py` (133 routes) and `web-app/server.py` (112 routes), audited from HEAD.
|
|
6
|
+
|
|
7
|
+
This document is the source-of-truth route-collision analysis for the Purple Lab merge into Dashboard. It enumerates every route in both servers, identifies collisions, and defines the namespace strategy for Phases Merge-2 through Merge-7.
|
|
8
|
+
|
|
9
|
+
## Top-Level Summary
|
|
10
|
+
|
|
11
|
+
| Metric | Value |
|
|
12
|
+
|---|---|
|
|
13
|
+
| Dashboard routes -- main `app` decorators | 133 |
|
|
14
|
+
| Dashboard routes -- `api_v2_router` (mounted at `/api/v2/*` via `include_router`) | 24 |
|
|
15
|
+
| Dashboard routes -- total | **157** |
|
|
16
|
+
| Dashboard `/api/*` routes | 135 |
|
|
17
|
+
| Purple Lab routes (total) | 112 |
|
|
18
|
+
| Purple Lab `/api/*` routes | 100 |
|
|
19
|
+
| Purple Lab routers (`APIRouter` / `include_router`) | 0 |
|
|
20
|
+
| Exact collisions (method + path) | **3** |
|
|
21
|
+
| Path-only collisions | **3** |
|
|
22
|
+
| `/api/*` collisions (any method) | **0** |
|
|
23
|
+
| `/api/v2/*` collisions vs Purple Lab | **0** (Purple Lab has no `/api/v2/*` routes) |
|
|
24
|
+
| Surviving uncollided Purple Lab routes | **109** |
|
|
25
|
+
|
|
26
|
+
**Result:** The merge is structurally clean. Only 3 infrastructure paths collide; all 100 of Purple Lab's `/api/*` routes have distinct paths from Dashboard's 135 `/api/*` routes. The `api_v2_router` (24 routes at `/api/v2/tenants`, `/api/v2/runs`, `/api/v2/api-keys`, `/api/v2/policies`, `/api/v2/audit`) is enterprise multi-tenant surface that Purple Lab does not currently expose.
|
|
27
|
+
|
|
28
|
+
## The 3 Collisions
|
|
29
|
+
|
|
30
|
+
| Method | Path | Dashboard purpose | Purple Lab purpose | Resolution |
|
|
31
|
+
|---|---|---|---|---|
|
|
32
|
+
| `GET` | `/health` | Dashboard health check | Purple Lab health check | Single `/health` on Dashboard wins. Lab's becomes `/lab/health`. |
|
|
33
|
+
| `GET` | `/{full_path:path}` | Dashboard SPA catch-all (serves `dashboard/static/index.html`) | Purple Lab SPA catch-all (serves `web-app/dist/index.html`) | Dashboard catch-all wins at root. Lab's becomes `/lab/{full_path:path}` -- triggered only inside the mount. |
|
|
34
|
+
| `WS` | `/ws` | Dashboard WebSocket bus | Purple Lab WebSocket bus | Dashboard `/ws` wins. Lab's becomes `/lab/ws`. Long term: unify into a single bus (Phase Merge-5). |
|
|
35
|
+
|
|
36
|
+
All three collisions resolve automatically when Lab's FastAPI app is mounted at `/lab` via Starlette `app.mount("/lab", purple_lab_app)`. No code dedup needed in Merge-1 -- the mount provides path isolation.
|
|
37
|
+
|
|
38
|
+
## Namespace Strategy
|
|
39
|
+
|
|
40
|
+
Single rule: **Purple Lab's entire FastAPI app mounts under `/lab/`**. No route renames inside the Lab app itself -- the mount handles prefixing.
|
|
41
|
+
|
|
42
|
+
- Lab route `/api/sessions/{id}/chat` becomes externally visible as `/lab/api/sessions/{id}/chat`.
|
|
43
|
+
- Lab static asset `/assets/index-BN52-GQT.js` becomes `/lab/assets/index-BN52-GQT.js`.
|
|
44
|
+
- Lab WebSocket `/ws` becomes `/lab/ws`.
|
|
45
|
+
|
|
46
|
+
The Vite build (Phase Merge-3) is rebuilt with `base: '/lab/'` so the bundled JS naturally calls `/lab/api/*` and the HTML loads `/lab/assets/*`. No runtime base-href shimming needed.
|
|
47
|
+
|
|
48
|
+
## Web-App Route Inventory (by category)
|
|
49
|
+
|
|
50
|
+
| Category | Count | Sample paths |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `/api/sessions/*` | 61 | `/api/sessions/{session_id}/chat/{task_id}/stream`, `/api/sessions/{session_id}/checkpoints` |
|
|
53
|
+
| `/api/session/*` | 18 | `/api/session/start`, `/api/session/quick-start`, `/api/session/status` |
|
|
54
|
+
| `/api/deploy/*` | 6 | `/api/deploy/{provider}` |
|
|
55
|
+
| `/api/magic/*` | 5 | `/api/magic/components`, `/api/magic/generate` |
|
|
56
|
+
| `/api/auth/*` | 5 | (auth endpoints) |
|
|
57
|
+
| `/api/teams/*` | 4 | (team management) |
|
|
58
|
+
| `/api/secrets/*` | 3 | (secrets management) |
|
|
59
|
+
| `/api/templates/*` | 2 | `/api/templates`, `/api/templates/{filename}` |
|
|
60
|
+
| `/api/provider/*` | 2 | `/api/provider/current`, `/api/provider/set` |
|
|
61
|
+
| `/api/audit-log` | 1 | (audit log endpoint) |
|
|
62
|
+
| `/proxy/{session_id}` | 1 | (legacy proxy) |
|
|
63
|
+
| `/ws/*` | 2 | `/ws`, `/ws/terminal` |
|
|
64
|
+
| `/health` | 1 | (health check) |
|
|
65
|
+
| `/{full_path:path}` | 1 | (SPA catch-all) |
|
|
66
|
+
| **Total** | **112** | |
|
|
67
|
+
|
|
68
|
+
## Dashboard Route Inventory (by category, top 20)
|
|
69
|
+
|
|
70
|
+
| Category | Count |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `/api/memory/*` | 14 |
|
|
73
|
+
| `/api/registry/*` | 10 |
|
|
74
|
+
| `/api/learning/*` | 9 |
|
|
75
|
+
| `/api/migration/*` | 8 |
|
|
76
|
+
| `/api/council/*` | 8 |
|
|
77
|
+
| `/api/tasks/*` | 6 |
|
|
78
|
+
| `/api/enterprise/*` | 6 |
|
|
79
|
+
| `/api/projects/*` | 5 |
|
|
80
|
+
| `/api/control/*` | 5 |
|
|
81
|
+
| `/api/checklist/*` | 5 |
|
|
82
|
+
| `/api/notifications/*` | 4 |
|
|
83
|
+
| `/api/agents/*` | 4 |
|
|
84
|
+
| `/api/managed/*` | 3 |
|
|
85
|
+
| `/api/github/*` | 3 |
|
|
86
|
+
| `/api/focus/*` | 3 |
|
|
87
|
+
| `/api/checkpoints/*` | 3 |
|
|
88
|
+
| (etc., 89 more under /api/) | -- |
|
|
89
|
+
| **Total** | **133** |
|
|
90
|
+
|
|
91
|
+
## Semantic Overlap (Candidates for Dedup in Phase Merge-5)
|
|
92
|
+
|
|
93
|
+
After path-suffix analysis, only one path-suffix is genuinely shared between the two servers:
|
|
94
|
+
|
|
95
|
+
- `github/status` -- appears as `/api/github/status` (Dashboard) and inside Lab's session endpoints. Not a collision (different paths), but functional overlap worth investigation in Phase Merge-5.
|
|
96
|
+
|
|
97
|
+
The bigger semantic overlap is at the conceptual level:
|
|
98
|
+
|
|
99
|
+
| Concept | Dashboard endpoint | Purple Lab endpoint | Merge-5 decision needed |
|
|
100
|
+
|---|---|---|---|
|
|
101
|
+
| Session lifecycle (start/stop/status) | `/api/control/*` (5 routes) | `/api/session/*` (18 routes) + `/api/sessions/{id}/*` (61 routes) | Likely: Lab keeps richer session CRUD; Dashboard's `/api/control/*` becomes thin wrappers calling Lab's. Or: unify on Lab's surface and retire Dashboard's. Decide in Merge-5. |
|
|
102
|
+
| Memory access | `/api/memory/*` (14 routes) | `/api/session/{id}/memory` (1 route) | Dashboard owns the rich surface; Lab's becomes a thin wrapper. |
|
|
103
|
+
| Checkpoints | `/api/checkpoints/*` (3 routes) | `/api/sessions/{id}/checkpoints` (2 routes) | Dashboard owns the global view; Lab's per-session view consumes Dashboard's. |
|
|
104
|
+
| Council / quality | `/api/council/*` (8) + `/api/quality-score/*` (2) | -- | Dashboard-only. Lab needs to surface this via UI, not duplicate endpoints. |
|
|
105
|
+
| Provider routing | `/api/provider/*` (?) | `/api/provider/current`, `/api/provider/set` | Likely same routes already; verify in Merge-2. |
|
|
106
|
+
|
|
107
|
+
None of these block the mount in Merge-4. They become refactor targets in Merge-5.
|
|
108
|
+
|
|
109
|
+
## Phase Dependencies (Confirmed by This Audit)
|
|
110
|
+
|
|
111
|
+
- **Merge-2** (state/business-logic dedup audit): Doable in parallel with Merge-3. Operates on `.loki/state/` and Python modules, not routes.
|
|
112
|
+
- **Merge-3** (Vite rebuild with `base: '/lab/'`): Single-file config change in `web-app/vite.config.ts` + `npm run build`. No route changes needed because the mount handles prefixing.
|
|
113
|
+
- **Merge-4** (FastAPI mount): The 3 infrastructure collisions resolve naturally via mount path isolation. No route renames inside `web-app/server.py`.
|
|
114
|
+
- **Merge-5** (deep dedup): Only required for the 4 conceptual overlaps above. Each becomes a separate sub-task.
|
|
115
|
+
- **Merge-6** (sidebar entry): Pure dashboard-UI work, no backend route changes.
|
|
116
|
+
- **Merge-7** (deprecate `loki web` standalone): Keep `loki web` working for 2 minor versions per user-safety Rule 0. Standalone uvicorn entrypoint is preserved; only `loki dashboard` gains the mount.
|
|
117
|
+
|
|
118
|
+
## NOT Decided In This Phase
|
|
119
|
+
|
|
120
|
+
1. **Cookie / session-token namespace.** If Lab and Dashboard both set cookies at root path, mount may cause conflicts. Audit needed in Merge-2.
|
|
121
|
+
2. **CORS middleware.** Lab has CORS middleware at line 119 of `web-app/server.py`. After mount, this becomes redundant (same origin). Remove in Merge-4.
|
|
122
|
+
3. **Lifespan / startup events.** Lab's startup hooks (if any) must compose with Dashboard's. Audit in Merge-4.
|
|
123
|
+
4. **Auth headers.** If Lab requires its own auth token and Dashboard requires another, the mount may double-auth or break. Audit in Merge-2.
|
|
124
|
+
|
|
125
|
+
These are not blockers for Merge-1 sign-off; they are explicit followups.
|
|
126
|
+
|
|
127
|
+
## Cleanup
|
|
128
|
+
|
|
129
|
+
Audit artifacts in `/tmp/loki-merge-audit/` may be removed:
|
|
130
|
+
```bash
|
|
131
|
+
rm -rf /tmp/loki-merge-audit
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Honest Acknowledgements
|
|
135
|
+
|
|
136
|
+
- The "0 /api/* collisions" finding holds for the route-table inspection only. Two routes with distinct paths can still share a backing function or `.loki/` file, and that semantic overlap is what Phase Merge-2 must catalog.
|
|
137
|
+
- Initial audit used `grep -nE '^@app\.(get|post|put|delete|patch|websocket)\(' ...` which missed `include_router` mounts. Followup audit found Dashboard's `api_v2_router` at `dashboard/api_v2.py` (24 routes, prefix `/api/v2`) and confirmed Purple Lab has zero `APIRouter` / `include_router` registrations. The summary table reflects the corrected total of 157 Dashboard routes.
|
|
138
|
+
- `app.add_api_route(...)` is also a dynamic registration pattern. `grep -n "add_api_route" web-app/server.py dashboard/server.py` returns nothing -- both servers use decorator-style registration exclusively.
|
|
139
|
+
- No fixture-based runtime collision test was performed. Phase Merge-4 must add a route-table dump test that runs the mounted app and asserts the surviving route set against this doc's expected output.
|