loki-mode 10.8.0 → 10.9.1

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.
Files changed (147) hide show
  1. package/README.md +2 -2
  2. package/SKILL.md +2 -2
  3. package/VERSION +1 -1
  4. package/autonomy/loki +16 -31
  5. package/autonomy/tui.sh +5 -1
  6. package/dashboard/__init__.py +1 -1
  7. package/dashboard/api_phases.py +1 -1
  8. package/dashboard/api_start.py +0 -1
  9. package/dashboard/server.py +22 -138
  10. package/docs/COCKPIT-SPEC.md +3 -1
  11. package/docs/DASHBOARD-9.12-EVIDENCE.md +1 -1
  12. package/docs/DASHBOARD-ARCHITECTURE.md +13 -11
  13. package/docs/DASHBOARD_V2_CHECKLIST.md +2 -0
  14. package/docs/INSTALLATION.md +12 -11
  15. package/docs/MERGE-DEDUP-MAP.md +1 -1
  16. package/docs/MERGE-ROUTE-MAP.md +1 -1
  17. package/docs/MERGE3-PLAN.md +2 -2
  18. package/docs/R3-COST-OBSERVABILITY-DESIGN.md +2 -2
  19. package/docs/R4-TRUST-TRAJECTORY-DESIGN.md +1 -1
  20. package/docs/R5-AUTO-WIKI-DESIGN.md +1 -1
  21. package/docs/R6-ROLLBACK-CHECKPOINT-PLAN.md +1 -1
  22. package/docs/RARV-C-100X-PLAN.md +3 -3
  23. package/docs/SONNET5-DEFAULT-PLAN.md +2 -2
  24. package/docs/SYNERGY-TASKS.md +1 -1
  25. package/docs/TOOL-INTEGRATION.md +1 -1
  26. package/docs/TOP-100-BACKLOG.md +1 -1
  27. package/docs/alternative-installations.md +1 -1
  28. package/docs/architecture/ADR-001-runtime-migration.md +1 -1
  29. package/docs/architecture/DASHBOARD_V2_ARCHITECTURE.md +2 -0
  30. package/docs/audit-logging.md +2 -0
  31. package/docs/authentication.md +2 -0
  32. package/docs/authorization.md +2 -0
  33. package/docs/certification/02-enterprise-features/lab.md +2 -0
  34. package/docs/certification/02-enterprise-features/lesson.md +2 -0
  35. package/docs/certification/04-production-deployment/lab.md +2 -0
  36. package/docs/certification/04-production-deployment/lesson.md +2 -0
  37. package/docs/control-plane-migration.md +97 -0
  38. package/docs/dashboard-guide.md +6 -7
  39. package/docs/dev/release-checklist.md +9 -11
  40. package/docs/enterprise/integration-cookbook.md +2 -0
  41. package/docs/enterprise/migration.md +1 -1
  42. package/docs/enterprise/performance.md +3 -1
  43. package/docs/enterprise/sdk-guide.md +2 -0
  44. package/docs/enterprise/security.md +2 -0
  45. package/docs/plans/FORGE-AUTONOMOUS-QUEUE.md +2 -2
  46. package/docs/research-2026-07/OBSERVABILITY-TOOL-SPEC.md +1 -1
  47. package/docs/retrospectives/v7.5.15-honesty-audit.md +2 -2
  48. package/docs/test-scenarios/enterprise-scenarios.md +2 -2
  49. package/docs/v10/ARCHITECT-CUTS.md +9 -9
  50. package/docs/v10/BACKLOG.md +7 -7
  51. package/docs/v10/BOARD.md +25 -25
  52. package/docs/v10/CONTROL-PLANE.md +4 -4
  53. package/docs/v10/CP-ENTERPRISE-UI.md +66 -66
  54. package/docs/v10/D51-PHASE-B.md +1 -1
  55. package/docs/v10/DECISIONS.md +3 -3
  56. package/docs/v10/DEPS.md +10 -10
  57. package/docs/v10/FAILURE-CLASSES.md +45 -1
  58. package/docs/v10/LEGACY-REMOVAL.md +2 -2
  59. package/docs/v10/SWARM.md +2 -2
  60. package/loki-ts/dist/cockpit.js +11 -11
  61. package/loki-ts/dist/loki.js +596 -579
  62. package/magic/core/design_tokens.py +1 -2
  63. package/magic/core/generator.py +2 -2
  64. package/magic/tokens/README.md +1 -3
  65. package/mcp/__init__.py +1 -1
  66. package/package.json +2 -7
  67. package/packages/control-plane/dist/server.js +1683 -843
  68. package/packages/control-plane/src/server/legacy/routes-audit.ts +50 -0
  69. package/packages/control-plane/src/server/legacy/routes-control.ts +26 -0
  70. package/packages/control-plane/src/server/legacy/routes-memory.ts +30 -0
  71. package/packages/control-plane/src/server/legacy/routes.ts +58 -58
  72. package/packages/control-plane/src/server/legacy/shim.ts +10 -4
  73. package/packages/control-plane/src/server/routes/checkpoints.ts +171 -0
  74. package/packages/control-plane/src/server/routes/index.ts +4 -1
  75. package/packages/control-plane/src/server/routes/memory.ts +219 -0
  76. package/packages/control-plane/src/server/routes/session_control.ts +155 -0
  77. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  78. package/references/magic-modules-patterns.md +2 -2
  79. package/skills/factory-operations.md +3 -3
  80. package/skills/magic-modules.md +1 -1
  81. package/skills/release-cadence.md +1 -1
  82. package/tools/audit-docs.py +1 -1
  83. package/tools/index-codebase.py +1 -1
  84. package/web-app/dist/assets/{AdminPage-DDDvUz0N.js → AdminPage-DqMnfZrH.js} +1 -1
  85. package/web-app/dist/assets/{Avatar-Dcdz46i6.js → Avatar-BEof29du.js} +1 -1
  86. package/web-app/dist/assets/{Badge-BB24cCpK.js → Badge-WD53OL5m.js} +1 -1
  87. package/web-app/dist/assets/{Button-D0-DmPZL.js → Button-Rzk5flhU.js} +1 -1
  88. package/web-app/dist/assets/{CockpitPage-CdjpU2JW.js → CockpitPage-Bqz3PCMh.js} +1 -1
  89. package/web-app/dist/assets/{ComparePage-DT5U0y0d.js → ComparePage-Byo2NzAZ.js} +1 -1
  90. package/web-app/dist/assets/{ErrorBoundary-lEHcl0EO.js → ErrorBoundary-BuE0kH3W.js} +1 -1
  91. package/web-app/dist/assets/{EvidenceReceiptPanel-GpnWvdCr.js → EvidenceReceiptPanel-CKFHOPBx.js} +1 -1
  92. package/web-app/dist/assets/{GitHubIssuesPanel-C7VOh1B7.js → GitHubIssuesPanel-Df_kTMqI.js} +1 -1
  93. package/web-app/dist/assets/{GitHubPRsPanel-G_kOuINv.js → GitHubPRsPanel-CouvcKrZ.js} +1 -1
  94. package/web-app/dist/assets/{HomePage-PqRisOny.js → HomePage-dEpLzj0x.js} +2 -2
  95. package/web-app/dist/assets/{LoginPage-DU7pFAkf.js → LoginPage-6DVjUND5.js} +1 -1
  96. package/web-app/dist/assets/{MagicPage-C5JWIWMk.js → MagicPage-BDQ9MOfY.js} +1 -1
  97. package/web-app/dist/assets/{MetricsPage-CbIGS_qo.js → MetricsPage-CXiJ04G_.js} +1 -1
  98. package/web-app/dist/assets/{NotFoundPage-vU1pvdSt.js → NotFoundPage-BaXhZvyj.js} +1 -1
  99. package/web-app/dist/assets/{ProjectPage-SsixIPPv.js → ProjectPage-BzE4V7Ti.js} +3 -3
  100. package/web-app/dist/assets/{ProjectsPage-BfQKSR9k.js → ProjectsPage-ipf6TLMX.js} +1 -1
  101. package/web-app/dist/assets/{SettingsPage-B8YfXAom.js → SettingsPage-BDxkUKN6.js} +1 -1
  102. package/web-app/dist/assets/{ShowcasePage-Ba407f2-.js → ShowcasePage-YQXD72SM.js} +1 -1
  103. package/web-app/dist/assets/{SystemSettingsPage-DzNQhhBS.js → SystemSettingsPage-Dou7zHS0.js} +1 -1
  104. package/web-app/dist/assets/{TeamsPage-tqUnpZ15.js → TeamsPage-CQarfcYr.js} +1 -1
  105. package/web-app/dist/assets/{TemplatesPage-ClB5EpI3.js → TemplatesPage-CPi1R8Xj.js} +1 -1
  106. package/web-app/dist/assets/{TerminalOutput-CBvpYfEu.js → TerminalOutput-WiGhWdYD.js} +1 -1
  107. package/web-app/dist/assets/{activity-C1s0L_X9.js → activity-DOa7ejQA.js} +1 -1
  108. package/web-app/dist/assets/{bell-DMEiXr73.js → bell-CX-VAlYN.js} +1 -1
  109. package/web-app/dist/assets/{bot-BF_xIOxu.js → bot-EDXaZmHc.js} +1 -1
  110. package/web-app/dist/assets/{check-Dv1Ynvrs.js → check-CnBdKk3L.js} +1 -1
  111. package/web-app/dist/assets/{chevron-left-Ddhsod7S.js → chevron-left-BJ_ydRQW.js} +1 -1
  112. package/web-app/dist/assets/{circle-alert-OATHUkBZ.js → circle-alert-tz8ufhc_.js} +1 -1
  113. package/web-app/dist/assets/{clock-C1Ltva07.js → clock-DFBZPSsH.js} +1 -1
  114. package/web-app/dist/assets/{cloud-B-S4oOy9.js → cloud-uy3p_5Q7.js} +1 -1
  115. package/web-app/dist/assets/{code-xml-D3Femn_F.js → code-xml-3CbYD9YL.js} +1 -1
  116. package/web-app/dist/assets/{database-DBSeFgO3.js → database-YiNBXc9c.js} +1 -1
  117. package/web-app/dist/assets/{dollar-sign-sgDgNuGE.js → dollar-sign-D0wBa4Cv.js} +1 -1
  118. package/web-app/dist/assets/{file-code-corner-C-MbdZEc.js → file-code-corner-DDdZVZ5v.js} +1 -1
  119. package/web-app/dist/assets/{file-plus-CCB6yqXv.js → file-plus-D09LTBqt.js} +1 -1
  120. package/web-app/dist/assets/{globe-DlbrNAj1.js → globe-BirTPwff.js} +1 -1
  121. package/web-app/dist/assets/{hammer-B-DWm695.js → hammer-DaE5qgnd.js} +1 -1
  122. package/web-app/dist/assets/{index-Dvny-1SU.js → index-D8t7KnQf.js} +3 -3
  123. package/web-app/dist/assets/{layers-99qsKpOT.js → layers-DVVrckRY.js} +1 -1
  124. package/web-app/dist/assets/{loader-circle-ZvZyZl3J.js → loader-circle-2-nMGmcb.js} +1 -1
  125. package/web-app/dist/assets/{lock-DosGjcxP.js → lock-D0Xj43pC.js} +1 -1
  126. package/web-app/dist/assets/{package-C-PumesN.js → package-BB7S3egm.js} +1 -1
  127. package/web-app/dist/assets/{plus-CTYYskTB.js → plus-BLhoMrn3.js} +1 -1
  128. package/web-app/dist/assets/{refresh-cw-qW2ztTFS.js → refresh-cw-DMc2uPLw.js} +1 -1
  129. package/web-app/dist/assets/{rotate-ccw-ZIKrf-vO.js → rotate-ccw-DM3IP9x7.js} +1 -1
  130. package/web-app/dist/assets/{scroll-text-BamlXjAT.js → scroll-text-lH2uW7rB.js} +1 -1
  131. package/web-app/dist/assets/{server-BljXb79I.js → server-BkerZcdR.js} +1 -1
  132. package/web-app/dist/assets/{shield-alert-Dr6eT9c_.js → shield-alert-B8ohcUfu.js} +1 -1
  133. package/web-app/dist/assets/{thumbs-up-v3oBXZgU.js → thumbs-up-CTIxLzPD.js} +1 -1
  134. package/web-app/dist/assets/{trash-2-BOuIrCpi.js → trash-2-C3zIpsH5.js} +1 -1
  135. package/web-app/dist/assets/{trending-down-Dp7jMCgH.js → trending-down-BTT7wV4Y.js} +1 -1
  136. package/web-app/dist/assets/{trending-up-CDiur-nP.js → trending-up-BzHdCDvF.js} +1 -1
  137. package/web-app/dist/assets/{upload-DxIHXhNE.js → upload-HxR34-A8.js} +1 -1
  138. package/web-app/dist/assets/{usePolling-D2Uu-YZb.js → usePolling-DDj6Fupy.js} +1 -1
  139. package/web-app/dist/assets/{user-BVWCUAfF.js → user-qH5y9yNy.js} +1 -1
  140. package/web-app/dist/index.html +1 -1
  141. package/dashboard/static/assets/mermaid.min.js +0 -2030
  142. package/dashboard/static/cost.html +0 -344
  143. package/dashboard/static/favicon.svg +0 -5
  144. package/dashboard/static/index.html +0 -15577
  145. package/dashboard/static/proofs.html +0 -194
  146. package/dashboard/static/start.html +0 -131
  147. package/dashboard/static/trust.html +0 -325
package/README.md CHANGED
@@ -310,7 +310,7 @@ The URL comes from `LOKI_CONTROL_URL`, else `http://127.0.0.1:${LOKI_CONTROL_POR
310
310
 
311
311
  Answering a BLOCKED run: when a run stops on a spec conflict, its run page shows the question and an answer box. Submitting posts to `POST /v1/runs/<source>/<run>/answer` (JSON `{"answer": "..."}`, up to 4000 characters, loopback only) and writes `~/.loki/control/answers/<source>/<run>.answer.txt` (override the directory with `LOKI_CONTROL_ANSWER_DIR`). The page prints the resume command, `loki answer <run>`, which starts a fresh run carrying the task, the question and that file's text (or pass `--text "..."` yourself; with no run id it picks the newest BLOCKED run). A run that is not BLOCKED exits 2.
312
312
 
313
- Set `LOKI_CONTROL_DEFAULT=1` (off by default) to make `loki dashboard`, `loki dashboard start` and `loki dashboard open` start the Control Plane instead of the old dashboard. The old dashboard is unchanged when the flag is unset.
313
+ The Control Plane is the only UI. `loki dashboard`, `loki dashboard start` and `loki dashboard open` open it (starting `loki control serve` if none is running). The classic dashboard UI and its port 57374 are removed in 10.8; see [docs/control-plane-migration.md](docs/control-plane-migration.md) for the route mapping and what is not yet available.
314
314
 
315
315
  Container, Helm and ECS deployment: [docs/control-plane-container.md](docs/control-plane-container.md).
316
316
 
@@ -588,7 +588,7 @@ Legacy engine, being removed (these run on the previous engine; `loki --help` st
588
588
  | `loki stop`, `pause`, `resume` | Control a legacy run |
589
589
  | `loki steer "<note>"` | Nudge a legacy run (needs `LOKI_PROMPT_INJECTION=1`) |
590
590
  | `loki why`, `loki next` | Explain or continue a legacy run |
591
- | `loki dashboard` | Operations UI server (`start\|stop\|status\|url\|open`) |
591
+ | `loki dashboard` | Open the Control Plane UI (see [docs/control-plane-migration.md](docs/control-plane-migration.md)) |
592
592
  | `loki review`, `loki test`, `loki analyze`, `loki memory`, `loki failover`, `loki enterprise`, `loki import`, `loki ci` | Review, test generation, codebase analysis, memory, failover, enterprise, issue import, CI gate |
593
593
  | `loki modernize heal <path>` | Legacy system healing |
594
594
 
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: An autonomous software factory that knows what it is supposed to deliver, and proves it did. Use when the user says Loki Mode or asks to build, fix or verify software autonomously.
4
4
  ---
5
5
 
6
- # Loki Mode v10.8.0
6
+ # Loki Mode v10.9.1
7
7
 
8
8
  **Current entry points (Loki 10 engine):** `loki "<task>"`, `loki owner/repo#N` and `loki quick "<task>"` run the Loki 10 engine and end with a signed receipt (`loki verify`). `loki backlog owner/repo --all` runs many issues in parallel. Guide: `docs/v10/GUIDE.md`. The skill instructions below, and `loki start`, describe the legacy engine, which still ships and is being removed (`loki start owner/repo#N` still routes to it; prefer `loki owner/repo#N`).
9
9
 
@@ -473,4 +473,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
473
473
 
474
474
  ---
475
475
 
476
- **v10.8.0 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
476
+ **v10.9.1 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 10.8.0
1
+ 10.9.1
package/autonomy/loki CHANGED
@@ -2032,12 +2032,11 @@ cmd_ui() {
2032
2032
  LOKI_DASHBOARD_CORS="http://localhost:$port,http://127.0.0.1:$port" \
2033
2033
  cmd_dashboard_start --host 127.0.0.1 --port "$port" >&2 || return 1
2034
2034
  fi
2035
- local url="http://127.0.0.1:${port}/start"
2036
- if [ "${LOKI_HEADLESS:-0}" = "1" ] || [ "${1:-}" = "--no-open" ]; then
2037
- echo "$url"
2038
- else
2039
- loki_open_url "$url" || echo "$url"
2040
- fi
2035
+ # The classic server is API-only; the UI is the Control Plane.
2036
+ local url="http://127.0.0.1:${port}"
2037
+ echo "loki: the classic dashboard at $url serves the API only (the browser UI moved to the Control Plane)." >&2
2038
+ echo "loki: start the Control Plane UI with: loki ui (needs bun)" >&2
2039
+ echo "$url"
2041
2040
  }
2042
2041
 
2043
2042
  # Tight newcomer landing for the bare `loki` invocation (no args).
@@ -3915,18 +3914,9 @@ cmd_start() {
3915
3914
  # + cockpit, honoring the card's "open dashboard" promise. The
3916
3915
  # cockpit hint is printed by _loki_start_handoff for the Both path.
3917
3916
  args+=("--bg")
3918
- # Open the URL the runner PUBLISHES for the port it actually bound
3919
- # or reused (it walks past refused ports); never guess the default
3920
- # 57374, which may be a foreign or stale dashboard. stdout must
3921
- # stay unredirected: loki_open_url checks the TTY.
3922
- local _dfile="${PWD}/.loki/dashboard/url"
3923
- rm -f "$_dfile" 2>/dev/null
3924
- (
3925
- for _ in $(seq 1 120); do
3926
- if [ -s "$_dfile" ]; then loki_open_url "$(head -1 "$_dfile")" 2>/dev/null; exit 0; fi
3927
- sleep 0.5
3928
- done
3929
- ) &
3917
+ # The browser UI is the Control Plane; open it (guarded by
3918
+ # loki_open_url: no-op headless and in tests).
3919
+ ( LOKI_UI_NO_CLASSIC=1 cmd_ui 2>/dev/null || true ) &
3930
3920
  ;;
3931
3921
  launch-bg-watch)
3932
3922
  # Watch (cockpit only) detaches without opening the browser.
@@ -7561,7 +7551,7 @@ cmd_dashboard_help() {
7561
7551
  echo " loki dashboard start --tls-cert cert.pem --tls-key key.pem"
7562
7552
  echo " loki dashboard status # Check if running"
7563
7553
  echo " loki dashboard url --format json # Get URL as JSON"
7564
- echo " loki dashboard open # Open in browser"
7554
+ echo " loki dashboard open # Print the Control Plane hint (no browser)"
7565
7555
  echo " loki dashboard stop # Stop server"
7566
7556
  }
7567
7557
 
@@ -7789,7 +7779,7 @@ cmd_dashboard_start() {
7789
7779
  # a second port.
7790
7780
  echo -e "${DIM}Want to submit a spec in the browser? Open the 'Lab' tab in this dashboard.${NC}"
7791
7781
  echo ""
7792
- echo -e "Open in browser: ${CYAN}loki dashboard open${NC}"
7782
+ echo -e "Control Plane UI: ${CYAN}loki ui${NC}"
7793
7783
  echo -e "Check status: ${CYAN}loki dashboard status${NC}"
7794
7784
  echo -e "Stop server: ${CYAN}loki dashboard stop${NC}"
7795
7785
  else
@@ -7907,7 +7897,7 @@ cmd_dashboard_status() {
7907
7897
  fi
7908
7898
 
7909
7899
  echo ""
7910
- echo -e "Open in browser: ${CYAN}loki dashboard open${NC}"
7900
+ echo -e "Control Plane UI: ${CYAN}loki ui${NC}"
7911
7901
  echo -e "Stop server: ${CYAN}loki dashboard stop${NC}"
7912
7902
  else
7913
7903
  echo -e "${YELLOW}Status: Not running (stale PID file)${NC}"
@@ -8040,14 +8030,10 @@ cmd_dashboard_open() {
8040
8030
 
8041
8031
  local url="${scheme}://${host}:${port}"
8042
8032
 
8043
- echo -e "${GREEN}Opening dashboard: $url${NC}"
8044
-
8045
- # Open in browser (cross-platform, guarded: no-op in tests/CI/non-TTY)
8046
- if ! loki_open_url "$url" 2>/dev/null; then
8047
- echo ""
8048
- echo "Browser not opened (headless, test, or no opener)."
8049
- echo "Please open in browser: $url"
8050
- fi
8033
+ # The legacy browser UI was removed; this server is API-only. Never open a
8034
+ # browser here: point at the Control Plane instead.
8035
+ echo -e "${YELLOW}The dashboard server at $url serves the API only; the browser UI is now the Control Plane.${NC}"
8036
+ echo -e "Open the Control Plane with: ${CYAN}loki ui${NC}"
8051
8037
  }
8052
8038
 
8053
8039
  # Welcome opener (the "magic opener"). Shows a branded welcome page in the
@@ -18401,8 +18387,7 @@ print(json.dumps({'id': manifest.id, 'dir': str(pipeline.migration_dir)}))
18401
18387
 
18402
18388
  sleep 2
18403
18389
  if kill -0 "$migrate_dashboard_pid" 2>/dev/null; then
18404
- echo -e " Dashboard: ${CYAN}${url_scheme}://127.0.0.1:${dashboard_port}/${NC}"
18405
- loki_open_url "${url_scheme}://127.0.0.1:${dashboard_port}/" 2>/dev/null || true
18390
+ echo -e " Dashboard API: ${CYAN}${url_scheme}://127.0.0.1:${dashboard_port}/${NC} (UI: loki ui)"
18406
18391
  echo ""
18407
18392
  else
18408
18393
  echo -e " ${YELLOW}Dashboard failed to start (check logs)${NC}"
package/autonomy/tui.sh CHANGED
@@ -24,7 +24,11 @@ TUI_HAS_COLOR=false
24
24
  TUI_COLS=80
25
25
  if [ -t 1 ]; then
26
26
  TUI_HAS_COLOR=true
27
- TUI_COLS=$(tput cols 2>/dev/null || echo 80)
27
+ # FC-10: tput can print 0 or nothing on a pty with no size; fall back to COLUMNS, then 80
28
+ TUI_COLS=$(tput cols 2>/dev/null || true)
29
+ case "$TUI_COLS" in '' | *[!0-9]*) TUI_COLS="${COLUMNS:-80}" ;; esac
30
+ case "$TUI_COLS" in '' | *[!0-9]*) TUI_COLS=80 ;; esac
31
+ [ "$TUI_COLS" -ge 40 ] || TUI_COLS=80
28
32
  fi
29
33
 
30
34
  # Unicode box drawing (degrade to ASCII if terminal doesn't support)
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "10.8.0"
10
+ __version__ = "10.9.1"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -1,6 +1,6 @@
1
1
  """Read-only phase history for a run, derived from real `phase_change` events.
2
2
 
3
- WHY THIS EXISTS. dashboard-ui/components/loki-session-timeline.js SYNTHESIZED
3
+ WHY THIS EXISTS. The legacy dashboard session timeline (removed in CPE-24) SYNTHESIZED
4
4
  its timeline. It took a scalar status (uptime, current phase, iteration count),
5
5
  rotated a fixed list `['planning','building','testing','reviewing']`, and gave
6
6
  each invented segment a randomized duration:
@@ -37,7 +37,6 @@ from . import auth
37
37
  logger = logging.getLogger(__name__)
38
38
  router = APIRouter(tags=["start"])
39
39
 
40
- START_HTML = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static", "start.html")
41
40
  _LOKI_BIN = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "bin", "loki")
42
41
  _REPO_RE = re.compile(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$")
43
42
  _TOKEN_RE = re.compile(r"^[A-Za-z0-9_\-]{10,255}$")
@@ -1149,14 +1149,8 @@ app.add_middleware(
1149
1149
  allow_headers=["Content-Type", "Authorization", "X-Requested-With"],
1150
1150
  )
1151
1151
 
1152
- # RESPONSE COMPRESSION. Measured, not assumed: the served dashboard bundle
1153
- # (dashboard/static/index.html) is 779,725 bytes raw and 150,341 gzipped --
1154
- # an 81% reduction. Until now only CORS and the collab WS auth middleware were
1155
- # registered, so every dashboard load shipped the full 780KB.
1156
- #
1157
- # That single fact is the most plausible cause of "the dashboard feels slow":
1158
- # it is not a rendering problem, it is 630KB of avoidable transfer on first
1159
- # paint, and it costs one middleware to fix.
1152
+ # RESPONSE COMPRESSION. Large JSON API payloads compress well, and it costs one
1153
+ # middleware. (The legacy single-file UI bundle that motivated it was removed.)
1160
1154
  #
1161
1155
  # minimum_size=1024 leaves small JSON responses uncompressed, where the CPU
1162
1156
  # round-trip outweighs the saving. GZipMiddleware is stdlib-backed and does
@@ -1360,16 +1354,12 @@ else:
1360
1354
  app.include_router(api_operator_router)
1361
1355
 
1362
1356
  # D51 Phase A: first-run onboarding + backlog API and its /start page.
1363
- from .api_start import router as api_start_router, START_HTML as _START_HTML
1357
+ from .api_start import router as api_start_router
1364
1358
  app.include_router(api_start_router)
1365
1359
  from .api_runs_v1 import router as api_runs_v1_router
1366
1360
  app.include_router(api_runs_v1_router)
1367
1361
 
1368
1362
 
1369
- @app.get("/start", include_in_schema=False, dependencies=[Depends(auth.require_scope("read"))])
1370
- async def serve_start_page():
1371
- return FileResponse(_START_HTML, media_type="text/html")
1372
-
1373
1363
 
1374
1364
  # Phase Merge-4: Mount Purple Lab FastAPI app under /lab/ so it appears as a
1375
1365
  # sidebar entry in Dashboard. Same `app` is also wrapped by `standalone_app`
@@ -11579,49 +11569,11 @@ def optimize_prompts(sessions: int = 10, dry_run: bool = True):
11579
11569
 
11580
11570
  from fastapi.responses import FileResponse, HTMLResponse
11581
11571
 
11582
- # Find static files in multiple possible locations
11583
11572
  DASHBOARD_DIR = os.path.dirname(os.path.abspath(__file__))
11584
11573
  PROJECT_ROOT = os.path.dirname(DASHBOARD_DIR)
11585
11574
 
11586
- # Possible static file locations (in order of preference)
11587
- # Resolves correctly regardless of PYTHONPATH, symlinks, or install method
11588
- def _static_candidates(skill_dir=None, home=None):
11589
- """Static dirs in preference order: package, LOKI_SKILL_DIR, ~/.claude skill, dev build."""
11590
- skill_dir = os.environ.get("LOKI_SKILL_DIR", "") if skill_dir is None else skill_dir
11591
- home_skill = os.path.join(home or os.path.expanduser("~"), ".claude", "skills", "loki-mode")
11592
- cands = [os.path.join(DASHBOARD_DIR, "static")]
11593
- if skill_dir:
11594
- cands.append(os.path.join(skill_dir, "dashboard", "static"))
11595
- cands.append(os.path.join(home_skill, "dashboard", "static"))
11596
- cands.append(os.path.join(PROJECT_ROOT, "dashboard-ui", "dist"))
11597
- if skill_dir:
11598
- cands.append(os.path.join(skill_dir, "dashboard-ui", "dist"))
11599
- cands.append(os.path.join(home_skill, "dashboard-ui", "dist"))
11600
- return cands
11601
-
11602
-
11603
- def _pick_static_dir(candidates):
11604
- """First candidate that CONTAINS index.html; an empty or partial dir must not win."""
11605
- for loc in candidates:
11606
- if os.path.isfile(os.path.join(loc, "index.html")):
11607
- return loc
11608
- return None
11609
-
11610
-
11611
- STATIC_LOCATIONS = _static_candidates()
11612
- STATIC_DIR = _pick_static_dir(STATIC_LOCATIONS)
11613
- if STATIC_DIR:
11614
- logger.info("Dashboard frontend served from: %s", STATIC_DIR)
11615
- else:
11616
- logger.warning("Dashboard frontend NOT found (looked in %s); serving built-in fallback page", STATIC_LOCATIONS)
11617
-
11618
- if STATIC_DIR:
11619
- from fastapi.staticfiles import StaticFiles
11620
-
11621
- # Check if assets directory exists (built frontend)
11622
- ASSETS_DIR = os.path.join(STATIC_DIR, "assets")
11623
- if os.path.isdir(ASSETS_DIR):
11624
- app.mount("/assets", StaticFiles(directory=ASSETS_DIR), name="assets")
11575
+ DASHBOARD_DIR = os.path.dirname(os.path.abspath(__file__))
11576
+ PROJECT_ROOT = os.path.dirname(DASHBOARD_DIR)
11625
11577
 
11626
11578
  # ---------------------------------------------------------------------------
11627
11579
  # Activity Logger & Session Diff
@@ -11682,75 +11634,29 @@ async def log_activity(entry: dict):
11682
11634
  return result
11683
11635
 
11684
11636
 
11685
- # Serve favicon.svg from static directory
11686
- @app.get("/favicon.svg", include_in_schema=False)
11687
- async def serve_favicon():
11688
- """Serve the dashboard favicon."""
11689
- if STATIC_DIR:
11690
- favicon_path = os.path.join(STATIC_DIR, "favicon.svg")
11691
- if os.path.isfile(favicon_path):
11692
- return FileResponse(favicon_path, media_type="image/svg+xml")
11693
- return Response(status_code=404)
11694
-
11695
-
11696
- # Serve the self-contained cost + observability panel (R3). Zero-build
11697
- # standalone page that fetches /api/cost/timeline. Mirrors the proofs.html
11698
- # pattern: works without the SPA build.
11699
- @app.get("/cost", include_in_schema=False, dependencies=[Depends(auth.require_scope("read"))])
11700
- async def serve_cost_panel():
11701
- """Serve the standalone cost + observability HTML panel."""
11702
- if STATIC_DIR:
11703
- cost_path = os.path.join(STATIC_DIR, "cost.html")
11704
- if os.path.isfile(cost_path):
11705
- return FileResponse(cost_path, media_type="text/html")
11706
- return Response(status_code=404)
11707
-
11708
-
11709
- # R4: standalone trust-trajectory page that fetches /api/trust/trajectory.
11710
- # Mirrors the cost.html / /cost pattern: works without the SPA build.
11711
- @app.get("/trust", include_in_schema=False, dependencies=[Depends(auth.require_scope("read"))])
11712
- async def serve_trust_panel():
11713
- """Serve the standalone trust-trajectory HTML panel."""
11714
- if STATIC_DIR:
11715
- trust_path = os.path.join(STATIC_DIR, "trust.html")
11716
- if os.path.isfile(trust_path):
11717
- return FileResponse(trust_path, media_type="text/html")
11718
- return Response(status_code=404)
11719
-
11720
-
11721
- # Serve index.html or standalone HTML for root
11637
+ # The legacy dashboard UI was removed (CPE-24). The Control Plane serves the UI;
11638
+ # this server keeps only the /api/* routes that have no Control Plane home yet.
11722
11639
  @app.get("/", include_in_schema=False, dependencies=[Depends(auth.require_scope("read"))])
11723
11640
  async def serve_index():
11724
- """Serve the frontend SPA or standalone HTML."""
11725
- # Try multiple index file locations
11726
- index_candidates = []
11727
- if STATIC_DIR:
11728
- index_candidates.append(os.path.join(STATIC_DIR, "index.html"))
11729
- index_candidates.append(os.path.join(STATIC_DIR, "loki-dashboard-standalone.html"))
11641
+ """Tell a browser where the UI moved; never serve developer JSON to it."""
11642
+ return HTMLResponse(content=_moved_page(), status_code=410)
11730
11643
 
11731
- # Also check dashboard-ui directly for standalone
11732
- standalone_path = os.path.join(PROJECT_ROOT, "dashboard-ui", "dist", "loki-dashboard-standalone.html")
11733
- if standalone_path not in index_candidates:
11734
- index_candidates.append(standalone_path)
11735
11644
 
11736
- for index_path in index_candidates:
11737
- if os.path.isfile(index_path):
11738
- return FileResponse(index_path, media_type="text/html")
11739
-
11740
- # No frontend anywhere: a browser must never see developer JSON.
11741
- return HTMLResponse(content=_fallback_page(), status_code=503)
11742
-
11743
-
11744
- def _fallback_page() -> str:
11645
+ def _moved_page() -> str:
11745
11646
  import html as _html
11647
+ cp = os.environ.get("LOKI_CONTROL_PLANE_URL", "").strip()
11648
+ where = (
11649
+ f'<p>Control Plane: <a href="{_html.escape(cp, quote=True)}">{_html.escape(cp)}</a></p>'
11650
+ if cp.startswith(("http://", "https://"))
11651
+ else "<p>Run <code>loki ui</code> to open the Control Plane.</p>"
11652
+ )
11746
11653
  return (
11747
11654
  '<!doctype html><html lang="en"><head><meta charset="utf-8">'
11748
- "<title>Loki Mode Dashboard</title></head>"
11655
+ "<title>Loki Mode API</title></head>"
11749
11656
  '<body style="font-family:system-ui,sans-serif;max-width:40rem;margin:4rem auto;padding:0 1rem">'
11750
- "<h1>Loki Mode dashboard UI is not installed</h1>"
11751
- f"<p>The dashboard API is running (version {_html.escape(str(_version))}) "
11752
- "but this install has no frontend files.</p>"
11753
- "<p>Reinstall to fix it:</p><pre>npm install -g loki-mode@latest</pre>"
11657
+ "<h1>The dashboard UI moved to the Control Plane</h1>"
11658
+ f"<p>This server (version {_html.escape(str(_version))}) now serves the /api routes only.</p>"
11659
+ f"{where}"
11754
11660
  '<p><a href="/docs">API docs</a> | <a href="/health">Health</a></p>'
11755
11661
  "</body></html>"
11756
11662
  )
@@ -13099,40 +13005,18 @@ async def post_wiki_ask(req: WikiAskRequest):
13099
13005
 
13100
13006
 
13101
13007
  # ---------------------------------------------------------------------------
13102
- # SPA catch-all: serve index.html for any path not matched by API routes
13103
- # or static asset mounts. This lets the dashboard UI handle client-side routing.
13008
+ # Catch-all: unknown paths are real 404s (the SPA was removed, CPE-24).
13104
13009
  # Must be registered LAST so it never shadows an API endpoint.
13105
13010
  # ---------------------------------------------------------------------------
13106
13011
  @app.get("/{full_path:path}", include_in_schema=False, dependencies=[Depends(auth.require_scope("read"))])
13107
13012
  async def serve_spa_catchall(full_path: str):
13108
- """Serve static files or fall back to index.html for SPA routing.
13109
-
13110
- v7.6.1 B-10 fix: requests under /api/, /lab/api/, or /ws/ that fall through
13111
- here are missing routes, not SPA navigation. Returning index.html (text/html)
13112
- silently masks 404s and breaks JSON clients (the dashboard UI's loki-memory-browser
13113
- pinged /api/learning/metrics expecting JSON and got an HTML SPA on prior
13114
- failures). Return a JSON 404 instead so clients fail loud.
13115
- """
13116
- # API paths that fell through are real 404s, not SPA routes.
13013
+ """Return a JSON 404 for API-like paths so JSON clients fail loud, else a bare 404."""
13117
13014
  api_like = full_path.startswith("api/") or full_path.startswith("lab/api/") or full_path.startswith("ws/")
13118
13015
  if api_like:
13119
13016
  return JSONResponse(
13120
13017
  status_code=404,
13121
13018
  content={"error": "Not Found", "path": f"/{full_path}"},
13122
13019
  )
13123
- if STATIC_DIR:
13124
- static_root = os.path.realpath(STATIC_DIR)
13125
- # Try to serve the exact file first (e.g. /vite.svg, /manifest.json)
13126
- # Use realpath to prevent path traversal attacks
13127
- candidate = os.path.realpath(os.path.join(STATIC_DIR, full_path))
13128
- if not candidate.startswith(static_root + os.sep) and candidate != static_root:
13129
- return Response(status_code=404)
13130
- if os.path.isfile(candidate):
13131
- return FileResponse(candidate)
13132
- # Fall back to index.html for client-side routes
13133
- index_path = os.path.join(STATIC_DIR, "index.html")
13134
- if os.path.isfile(index_path):
13135
- return FileResponse(index_path, media_type="text/html")
13136
13020
  return Response(status_code=404)
13137
13021
 
13138
13022
 
@@ -1,5 +1,7 @@
1
1
  # Loki Cockpit (rec #6) - build spec
2
2
 
3
+ > **Historical:** this describes the classic dashboard, removed in Loki Mode 10.8. The Control Plane is the only UI; see [control-plane-migration.md](control-plane-migration.md).
4
+
3
5
  Status: IN BUILD (v7.126.0 target). Design approved via mockup
4
6
  (scratchpad/loki-cockpit.html). Grounded in real engine state + Autonomi identity.
5
7
 
@@ -59,7 +61,7 @@ No chafa / no headless Chrome hard dependency. Pipeline:
59
61
  - S3 (render pipeline): SVG-from-state builder + SVG->PNG + terminal-image
60
62
  encoder + capability detection + fallback. Tests (encoder byte-shape, detection).
61
63
  - S4 (dashboard identity redesign): apply logo/purple/fonts/multi-repo switcher to
62
- dashboard-ui; rebuild dist. Lightweight gate.
64
+ legacy-ui; rebuild dist. Lightweight gate.
63
65
  - S5 (docs + wiki + release): help text, README, wiki, CHANGELOG, 14 version files.
64
66
 
65
67
  ## Constraints
@@ -28,7 +28,7 @@ carries a `reason`, an unmeasured number reads `None` rather than `0`, and
28
28
  | `source` | 10 of 43 | Most rows cannot be audited back to the file they came from |
29
29
 
30
30
  ```bash
31
- cd dashboard-ui
31
+ cd legacy-ui
32
32
  grep -l "freshness_s" components/*.js core/*.js | wc -l # 0
33
33
  grep -l "measured" components/*.js core/*.js | wc -l # 0
34
34
  ```
@@ -1,5 +1,7 @@
1
1
  # Dashboard Architecture: Real-Data, Near-Realtime
2
2
 
3
+ > **Historical:** this describes the classic dashboard, removed in Loki Mode 10.8. The Control Plane is the only UI; see [control-plane-migration.md](control-plane-migration.md).
4
+
3
5
  Design only. No implementation in this document.
4
6
 
5
7
  Status: proposal. Measured against the worktree at
@@ -41,7 +43,7 @@ brief's number and my measurement disagree, both are shown.
41
43
 
42
44
  One correction against myself: my first pass reported `0` aria attributes in the
43
45
  built artifact and I nearly wrote that up as a shipped accessibility regression.
44
- It was a measurement error. `dashboard/static/index.html` has lines long enough
46
+ It was a measurement error. `legacy-ui-static/index.html` has lines long enough
45
47
  (577 chars plus minified runs) that `grep` treated the file as binary and
46
48
  suppressed output. With `grep -a` the brief's numbers are confirmed. This is the
47
49
  `grep-absence false-green` failure mode: an empty result was an absent
@@ -178,7 +180,7 @@ machine-checkable instead of a naming habit.
178
180
  `/api/health/processes`.
179
181
 
180
182
  Built artifact realtime constructs: 9
181
- (`grep -oaE "new WebSocket|EventSource|setInterval" dashboard/static/index.html | wc -l`).
183
+ (`grep -oaE "new WebSocket|EventSource|setInterval" legacy-ui-static/index.html | wc -l`).
182
184
  The brief's "34 realtime constructs" is not reproducible by any pattern I tried;
183
185
  I mark that figure **not independently verified** rather than repeat it.
184
186
 
@@ -234,8 +236,8 @@ The bundle is 779,725 bytes raw, 150,341 gzipped
234
236
  gzip stays; nothing in this design touches it.
235
237
 
236
238
  **Progressive disclosure here cannot mean code-splitting.**
237
- `dashboard-ui/scripts/build-standalone.js` produces a single self-contained
238
- file with zero runtime dependencies, written to both `dashboard/static/` and
239
+ `legacy-ui/scripts/build-standalone.js` produces a single self-contained
240
+ file with zero runtime dependencies, written to both `legacy-ui-static/` and
239
241
  `dist/`. That property is why the dashboard works offline and inside Docker
240
242
  without a CDN. Splitting the bundle would trade a real capability for a
241
243
  transfer-size win that gzip has already mostly taken (5.2x). So disclosure is
@@ -265,12 +267,12 @@ Measured on the shipped artifact with `grep -a`:
265
267
 
266
268
  | Property | Count | Command |
267
269
  |---|---|---|
268
- | Custom elements registered | 39 | `grep -oa "customElements.define" dashboard/static/index.html \| wc -l` |
270
+ | Custom elements registered | 39 | `grep -oa "customElements.define" legacy-ui-static/index.html \| wc -l` |
269
271
  | `aria-*` attributes | 100 | `grep -oa "aria-[a-z]*=" ... \| wc -l` |
270
272
  | `aria-` occurrences | 113 | `grep -oa "aria-" ... \| wc -l` |
271
273
  | `role="` | 38 | `grep -oa 'role="' ... \| wc -l` |
272
274
  | `@media` breakpoints | 13 | `grep -oa "@media" ... \| wc -l` |
273
- | Component source files | 43 | `ls dashboard-ui/components \| grep -v vendor \| wc -l` |
275
+ | Component source files | 43 | `ls legacy-ui/components \| grep -v vendor \| wc -l` |
274
276
  | Registrations in source | 44 | `grep -roha "customElements.define" components core \| wc -l` |
275
277
 
276
278
  The brief's 113 aria and 41 role are consistent with mine within pattern choice
@@ -278,7 +280,7 @@ The brief's 113 aria and 41 role are consistent with mine within pattern choice
278
280
  figure is 43 files carrying 44 registrations, not 46.
279
281
 
280
282
  **One gap worth noting:** source registers 44 custom elements, the shipped
281
- artifact registers 39. Five components exist in `dashboard-ui/` and do not reach
283
+ artifact registers 39. Five components exist in `legacy-ui/` and do not reach
282
284
  the browser. This is out of scope for this document and I did not chase it, but
283
285
  it is recorded here because it means the source tree is not a reliable proxy for
284
286
  what ships. All acceptance criteria in section 5 therefore measure the **built
@@ -369,10 +371,10 @@ Every row is measurable by a command. No aspirational entries.
369
371
  |---|---|---|---|
370
372
  | A1 | No route regression | `grep -cE '^@app\.(get\|post\|put\|delete\|patch\|websocket)' dashboard/server.py` | >= 165 |
371
373
  | A2 | api_v2 routes intact | `grep -cE '@router\.(get\|post\|put\|delete)' dashboard/api_v2.py` | >= 24 |
372
- | A3 | Accessibility not regressed | `grep -oa "aria-" dashboard/static/index.html \| wc -l` | >= 113 |
373
- | A4 | Roles not regressed | `grep -oa 'role="' dashboard/static/index.html \| wc -l` | >= 38 |
374
- | A5 | Custom elements not regressed | `grep -oa "customElements.define" dashboard/static/index.html \| wc -l` | >= 39 |
375
- | A6 | Breakpoints not regressed | `grep -oa "@media" dashboard/static/index.html \| wc -l` | >= 13 |
374
+ | A3 | Accessibility not regressed | `grep -oa "aria-" legacy-ui-static/index.html \| wc -l` | >= 113 |
375
+ | A4 | Roles not regressed | `grep -oa 'role="' legacy-ui-static/index.html \| wc -l` | >= 38 |
376
+ | A5 | Custom elements not regressed | `grep -oa "customElements.define" legacy-ui-static/index.html \| wc -l` | >= 39 |
377
+ | A6 | Breakpoints not regressed | `grep -oa "@media" legacy-ui-static/index.html \| wc -l` | >= 13 |
376
378
  | A7 | gzip still active | `grep -c "GZipMiddleware" dashboard/server.py` | >= 1 |
377
379
  | A8 | Zero runtime dependencies | Built artifact contains no external script/CDN src | 0 external hosts |
378
380
  | A9 | Runs store is written by the engine | Drive the lifecycle writer directly (source `run.sh`, call the phase-boundary function), then `GET /api/v2/runs`. No provider call, no spend | Returns >= 1 record with a real run id |
@@ -1,5 +1,7 @@
1
1
  # Loki Mode Dashboard V2 - Implementation Checklist
2
2
 
3
+ > **Historical:** this describes the classic dashboard, removed in Loki Mode 10.8. The Control Plane is the only UI; see [control-plane-migration.md](control-plane-migration.md).
4
+
3
5
  ## Overview
4
6
  Enterprise-grade Kanban dashboard with FastAPI backend, cross-project support, and CLI integration.
5
7
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
4
4
 
5
- **Version:** v10.8.0
5
+ **Version:** v10.9.1
6
6
 
7
7
  **Engine note:** `loki "<task>"`, `loki owner/repo#N` and `loki quick` run the Loki 10 engine. `loki start` still routes to the legacy engine, which is being removed (planned work resumes 2026-10-07); prefer `loki owner/repo#N`. See [docs/v10/GUIDE.md](v10/GUIDE.md).
8
8
 
@@ -294,7 +294,7 @@ trade-offs.
294
294
  ## VS Code Extension (Deprecated)
295
295
 
296
296
  > **DEPRECATED as of v7.2.0.** The Loki Mode VS Code extension is no longer
297
- > maintained. Use the dashboard at `loki dashboard start` instead.
297
+ > maintained. Use the Control Plane (`loki dashboard`) instead.
298
298
  >
299
299
  > The marketplace listing remains for users on v7.2.0 and earlier but will
300
300
  > not receive further updates. The `vscode-extension/` source remains in the
@@ -305,8 +305,8 @@ trade-offs.
305
305
  ### Recommended replacement: the dashboard
306
306
 
307
307
  ```bash
308
- loki dashboard start
309
- # Then open http://localhost:57374 in your browser.
308
+ loki dashboard
309
+ # Opens the Control Plane (default http://127.0.0.1:47821).
310
310
  ```
311
311
 
312
312
  The dashboard provides session monitoring, task views, the completion
@@ -450,7 +450,7 @@ behave exactly like the local `loki` CLI. Auth is auto-detected:
450
450
  (Max/Pro subscribers need no API key), else an honest error with guidance. It
451
451
  also forwards `~/.gitconfig` and `~/.config/gh` (read-only) plus
452
452
  `GITHUB_TOKEN`/`GH_TOKEN` if set, so commits and PRs work like local, and
453
- exposes the dashboard on port 57374. Use `--image IMG` to override the image.
453
+ exposes the API on port 57374. Use `--image IMG` to override the image.
454
454
 
455
455
  `loki docker` is a thin host wrapper around the image; it requires loki and
456
456
  Docker installed on the host.
@@ -459,7 +459,7 @@ Multi-repo + unified dashboard: you can run `loki docker start` in several
459
459
  different repos at once, exactly like the host CLI. Each repo gets its own
460
460
  container (deterministic name `loki-<hash-of-path>`) and its own bind-mounted
461
461
  `.loki/` state. Every `loki docker` project registers with the host dashboard,
462
- so running `loki dashboard` on the host shows ALL your projects in one unified
462
+ so running `loki dashboard` on the host (the Control Plane) shows ALL your projects in one unified
463
463
  view, whether they run via local `loki start` or via `loki docker start`. Builds
464
464
  run with the dashboard off by default (so concurrent runs do not collide on port
465
465
  57374); use the host `loki dashboard`, or `loki docker start --api` for a single
@@ -775,11 +775,12 @@ Loki Mode uses two network ports for different services:
775
775
 
776
776
  | Port | Service | Description |
777
777
  |------|---------|-------------|
778
- | **57374** | Dashboard + API (FastAPI) | Unified server serving both the web dashboard UI (real-time monitoring, task board, Completion Council, memory browser, log streaming) and the REST API (used by CLI tools, programmatic access, and the deprecated VS Code extension). Served by `dashboard/server.py`. |
778
+ | **47821** | Control Plane UI + API | The only UI, plus the `/v1/*` API. Started by `loki control serve` or `loki dashboard`. See [control-plane-migration.md](control-plane-migration.md). |
779
+ | **57374** | Legacy API (FastAPI) | The classic dashboard UI is removed in 10.8; this port no longer serves a UI. |
779
780
 
780
781
  ### When to Use Which Port
781
782
 
782
- - **Browser access** (dashboard, monitoring): Use port **57374** -- `http://localhost:57374`
783
+ - **Browser access** (UI, monitoring): Use the Control Plane, port **47821** -- `http://127.0.0.1:47821`
783
784
  - **API calls** (REST, programmatic): Use port **57374** -- `http://localhost:57374`
784
785
  - **VS Code extension** (deprecated as of v7.2.0): Connects to API on port **57374** automatically (configurable via `loki.apiPort` setting). No new releases will be published; see the deprecation notice above.
785
786
  - The server is started automatically when you run `loki start` or `./autonomy/run.sh`. No manual configuration is needed.
@@ -787,8 +788,8 @@ Loki Mode uses two network ports for different services:
787
788
  ### Port Configuration
788
789
 
789
790
  ```bash
790
- # Dashboard port (default: 57374)
791
- LOKI_DASHBOARD_PORT=57374 loki dashboard start
791
+ # Control Plane port (default: 47821)
792
+ loki control serve --port 47821
792
793
 
793
794
  # API port (default: 57374)
794
795
  loki api start --port 57374 # was: loki serve
@@ -796,7 +797,7 @@ loki api start --port 57374 # was: loki serve
796
797
 
797
798
  ### CORS Configuration
798
799
 
799
- For remote or cross-origin access to the dashboard, configure allowed origins via the `LOKI_DASHBOARD_CORS` environment variable:
800
+ The Control Plane has no CORS setting and requires a loopback Host header unless it is bound to a non-loopback address with `LOKI_CONTROL_TOKEN` set (see [control-plane-migration.md](control-plane-migration.md)). The `LOKI_DASHBOARD_CORS` variable below applies only to the legacy API server:
800
801
 
801
802
  ```bash
802
803
  # Allow specific origins
@@ -362,7 +362,7 @@ class FileEventDebouncer(FileSystemEventHandler):
362
362
  ### What Was NOT Audited (and Why)
363
363
 
364
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).
365
+ - Dashboard UI (`legacy-ui/src/`) and Lab UI (`web-app/dist/` or source) likely have overlapping components (session cards, status displays, log viewers).
366
366
  - Scope: This audit covers Python backend only. Frontend audit deferred to UI design review in Merge-3 (Vite rebuild).
367
367
 
368
368
  2. **Database schema migrations and compatibility:**
@@ -30,7 +30,7 @@ This document is the source-of-truth route-collision analysis for the Purple Lab
30
30
  | Method | Path | Dashboard purpose | Purple Lab purpose | Resolution |
31
31
  |---|---|---|---|---|
32
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. |
33
+ | `GET` | `/{full_path:path}` | Dashboard SPA catch-all (serves `legacy-ui-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
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
35
 
36
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.