Repo ini adalah UI + service manajemen kecil. Tidak ada logika agent, LLM, atau SSH ke router di sini. Agent dijalankan oleh dua backend yang dipanggil lewat API:
- NetOps Agent (repo saat ini masih
palapa-agent,/home/llmnetops/palapa-agent): disebut NetOps Agent di seluruh NetOpsUI (UI, id backendnetops, envNETOPS_AGENT_*). Namapalapahanya dipakai saat merujuk ke hal nyata di sisi Palapa: perintahuvicorn palapa.gateway.api,PALAPA_CONFIG_PATH, folder~/.palapa, dan namespacemetadata.palapadiSKILL.md. - Hermes (
~/.hermes/hermes-agent): gateway platformapi_server.
Hanya satu backend aktif pada satu waktu (localStorage['llmnetops-backend'], dipilih di Settings).
Scope kerja: hanya repo NetOpsUI. Jangan mengubah repo palapa-agent, ~/.palapa, atau ~/.hermes, dan jangan menjalankan/menghentikan backend tanpa diminta. Pengujian memakai mock dan salinan home backend, bukan yang asli. (Pengecualian yang disengaja: fitur Publish skill dan "Terapkan ke SOUL.md" di manager menulis ke folder backend atas aksi operator.)
cp .env.example .env # isi HOST_UID/GID, NETOPS_AGENT_HOME/HERMES_HOME (tanpa credential)
docker compose up -d --build # produksi
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d # dev (hot reload)UI di :3000, manager di 127.0.0.1:8200. Backend harus sudah jalan di host: NetOps Agent di 127.0.0.1:8100 (port 8000 dipakai container mikrotik-mcp; API-nya butuh PALAPA_CONFIG_PATH), Hermes di 127.0.0.1:8642 (butuh API_SERVER_KEY di ~/.hermes/.env). Detail di README.
Pengembangan dilakukan langsung di branch ui.
screens/*.js → backends/index.js activeBackend() → backends/{netops,hermes}.js → /backend/<id>/… → nginx → backend
screens/{skills,agents}.js, settings (tab file) → manager.js → /api/… → nginx → server/ (FastAPI + SQLite) → ~/.palapa, ~/.hermes
frontend/js/backends/index.js: registry dan kontrak adapter (komentar di atas file). Screen tidak bolehfetchlangsung ke backend.backends/common.js:http(),readSSE()(mendukungevent:dan multi-linedata:),toDate(),contentText().backends/netops.js:/chatstateless, jadi thread + pesan disimpan di database manager (/api/threads, tabelchat_threads/chat_messages) dan dibagi ke semua browser/operator; seluruh history dikirim tiap giliran.owner= label per-browser (llmnetops-client-name) + IP dariX-Real-IPnginx. Thread lama dilocalStorage['llmnetops-netops-threads-v1'](atau key…palapa…) diimpor sekali lewatPOST /api/threads/import. Layar chat mem-poll daftar thread tiap 5 dtk. Manager yang memanggil/chat(server/runs.py:POST /api/threads/{id}/run, streamGET …/events?after=N,POST …/stop; butuhNETOPS_AGENT_URLdi container manager), jadi jawaban tersimpan walau browser refresh/ditutup dan halaman bisa tersambung lagi ke run yang berjalan (resume()). Run hanya di memori satu proses (restart manager memutus run; flagrunningdibersihkan saat start). Stop hanya memutus stream manager (run di agent tidak berhenti). Tidak ada approval.backends/hermes.js: thread = Hermes session. Giliran chat memakaiPOST /v1/runs+GET /v1/runs/{id}/events, bukan/api/sessions/{id}/chat/stream, karena hanya runs yang mengirimapproval.requestdan menerima/approvaldan/stop. Profil agent aktif dikirim sebagaiinstructions.frontend/nginx.conf.template: proxy/backend/netops/,/backend/hermes/,/api/(manager). MenyisipkanAuthorizationHermes lewatauth_requestke/internal/hermes-authmilik manager (key dari database, bukan env) dan mengosongkanOrigin(Hermes menolak origin di luarAPI_SERVER_CORS_ORIGINS).server/(manager):backends.py(akses filesystem terbatas, render/parseSKILL.md, masking config),db.py(SQLite:skills,skill_publications,profiles; barisbackend='palapa'dimigrasi kenetopssaat start),main.py(API). Skill = folder<slug>/SKILL.mddengan frontmatter YAML di kedua backend; Hermes menaruh skill NetOpsUI diskills/netopsui/.
delta · tool_start · tool_end · note · thinking · approval · error · stopped. Backend baru = adapter yang memetakan event-nya ke daftar ini, lalu didaftarkan di BACKENDS.
NAV_ITEMS[].unavailable = true di config.js → screens/unavailable.js. Saat ini: Backups, Metrics. (Nodes aktif: hanya tambah node lewat POST /devices NetOps Agent; belum ada list/update/delete.) Aktifkan kembali dengan menghapus flag, menambah screen, dan method adapter/manager setelah datanya ada.
- Credential hanya di database manager (tabel
secrets,llm_providers; DB 0600), tidak pernah di environment/.env/compose dan tidak di JS. Jangan menambah env var berisi rahasia. API key Hermes:PUT /api/backends/hermes/credential(write-only, hanyahas_keyyang dikembalikan), divalidasi ASCII tercetak tanpa spasi (anti header injection); nginx mengambilnya per request dari/internal/hermes-auth(di luar/api/, jadi tidak ter-proxy ke browser; location/_hermes_authbersifatinternal). Tanpa key: header kosong → Hermes 401. Manager tidak pernah mengembalikan nilai.env;config.yamlselalu dimasking. - Di nginx,
proxy_set_headertidak diwarisi kelocationyang punyaproxy_set_headersendiri. Setiap location mengulang header lengkap. - Manager: slug divalidasi
^[a-z0-9][a-z0-9-]{0,63}$dan path skill dicek tetap di dalam folder skills (anti path traversal).SOUL.mdditulis in place (bisa berupa bind mount satu file yang tidak bisa di-rename atomik). - LLM provider (
server/llm.py,screens/settings-llm.js): API key write-only (tidak pernah dikembalikan API;has_keysaja);/api/llm/testdijalankan server-side, tidak mengikuti redirect, dan key tersimpan hanya dipakai bila Base URL sama; mengubah Base URL tanpa memasukkan key ulang ditolak. "Terapkan" mengedit baris demi baris blokmodel:diconfig.yaml(netops:name/base_url/api_key, opsionaldelegation.agents.*.model; hermes:default/provider=custom/base_url/api_key), tidak men-dump ulang YAML.edit_config()mem-parse hasil dan membandingkannya dengan aslinya (hanya key target yang boleh beda) sebelum menulis; backup keDATA_DIR/backups/(0600). Provider tanpa key menghapusapi_keylama. Hanya YAML block-style yang didukung (flow-style → 422). Backend tidak di-restart oleh NetOpsUI. - Publish skill tidak menimpa file yang bukan hasil publish NetOpsUI tanpa
force. Menghapus skill di library tidak menghapus file yang sudah dipublish. - File tunggal yang di-mount di compose harus sudah ada di host, dan
data/harus ada (data/.gitkeep) agar tidak dibuatrootoleh Docker. - Pengujian harus memakai
DATA_DIRterpisah (dan salinan home backend)../databerisi data asli operator; stack uji yang memakai./datayang sama akan bercampur dengannya. - Jangan mematikan proses dengan
pkill -f/pgrep -fberdasarkan nama (mis.uvicorn main:app): proses di dalam container terlihat di host dan ikut terbunuh. Gunakan PID atau port uji yang unik. - Tidak ada build step: ES modules langsung, Tailwind via CDN (config di
index.html), markdown renderer sendiri diutils.js(escape-first).localStoragebisa kosong/throw, selalutry/catch. - Autentikasi (
server/auth.py,screens/login.js): tabelusers(hash scrypt) dansessions(hanya SHA-256 token; cookienetopsui_sessionHttpOnly, SameSite=Strict, 7 hari). Guard: middleware manager untuk semua/api/*(kecualilogin,logout,health) danauth_request /_session_checknginx untuk/backend/netops/dan/backend/hermes/(yang tidak lewat manager);/internal/hermes-authjuga mensyaratkan sesi. User dibuat lewat CLI, tidak ada endpoint pendaftaran:docker compose exec manager python create_user.py <username>(juga untuk reset password). Login dibatasi 5 gagal / 5 menit per IP+username (di memori). Route baru di luar/api/yang dilewatkan nginx harus diberi guard sendiri. Manager di127.0.0.1:8200tetap menolak/api/*tanpa sesi.
- Kode: Bahasa Inggris (nama variabel, komentar). Teks UI untuk operator: Bahasa Indonesia.
.env # URL/port/path backend, HOST_UID/GID (TANPA credential)
data/* # database manager + backup SOUL.md (kecuali data/.gitkeep); lokasi bisa diganti via DATA_DIR
docs/ berisi dokumen desain era monolit LangGraph (tidak lagi mencerminkan kode) dan mockup Stitch di docs/stitch_llmnetops_enterprise_console/, yang masih menjadi acuan visual.
Issues dikelola sebagai file Markdown di .scratch/. Lihat docs/agents/issue-tracker.md.
Label dalam Bahasa Indonesia: perlu-triage, perlu-info, siap-agent, siap-manusia, tidak-dikerjakan. Lihat docs/agents/triage-labels.md.
Single-context: satu CONTEXT.md di root + docs/adr/. Lihat docs/agents/domain.md.